网络接口卡(NIC)驱动开发指南
1. 驱动加载与接口创建
当模块加载且设备匹配成功后,系统会创建一个以太网接口。以下是具体操作及示例:
-
查看 dmesg 信息
:使用
dmesg
命令可查看相关信息,例如:
# dmesg
[...]
[146698.060074] fake eth device initialized
[146698.087297] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready
-
查看接口信息
:运行
ifconfig -a命令,接口信息将显示在屏幕上:
# ifconfig -a
[...]
eth0 Link encap:Ethernet HWaddr 00:00:00:00:00:00
BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
-
配置接口 IP 地址
:使用
ifconfig命令为接口分配 IP 地址:
# ifconfig eth0 192.168.1.45
# ifconfig
[...]
eth0 Link encap:Ethernet HWaddr 00:00:00:00:00:00
inet addr:192.168.1.45 Bcast:192.168.1.255 Mask:255.255.255.0
BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
2. 设备状态与控制
设备控制指内核主动或响应用户操作改变接口属性的情况。可通过
struct net_device_ops
结构的操作,或使用
ethtool
工具。而状态则反映接口的当前状态。
3. 中断处理程序
如今的硬件接口更智能,能报告状态,网络接口可生成中断以指示错误、链路状态变化等,这些都需在中断处理程序中处理。以下是中断处理程序的示例代码:
static irqreturn_t my_netdev_irq(int irq, void *dev_id)
{
struct priv_net_struct *priv = dev_id;
/*
* Can't do anything in interrupt context because we need to
* block (spi_sync() is blocking) so fire of the interrupt
* handling workqueue.
* Remember, we access our netdev registers through SPI bus
* via spi_sync() call.
*/
schedule_work(&priv->irq_work);
return IRQ_HANDLED;
}
static void my_netdev_irq_work_handler(struct work_struct *work)
{
struct priv_net_struct *priv =
container_of(work, struct priv_net_struct, irq_work);
struct net_device *ndev = priv->netdev;
int intflags, loop;
/* disable further interrupts */
my_netdev_reg_bitclear(priv, EIE, EIE_INTIE);
do {
loop = 0;
intflags = my_netdev_regb_read(priv, EIR);
/* DMA interrupt handler (not currently used) */
if ((intflags & EIR_DMAIF) != 0) {
loop++;
handle_dma_complete();
clear_dma_interrupt_flag();
}
/* LINK changed handler */
if ((intflags & EIR_LINKIF) != 0) {
loop++;
my_netdev_check_link_status(ndev);
clear_link_interrupt_flag();
}
/* TX complete handler */
if ((intflags & EIR_TXIF) != 0) {
bool err = false;
loop++;
priv->tx_retry_count = 0;
if (locked_regb_read(priv, ESTAT) & ESTAT_TXABRT)
clear_tx_interrupt_flag();
}
/* TX Error handler */
if ((intflags & EIR_TXERIF) != 0) {
loop++;
/*
* Reset TX logic by setting/clearing appropriate
* bit in the right register
*/
[...]
/* Transmit Late collision check for retransmit */
if (my_netdev_cpllision_bit_set())
/* Handlecollision */
[...]
}
/* RX Error handler */
if ((intflags & EIR_RXERIF) != 0) {
loop++;
/* Check free FIFO space to flag RX overrun */
[...]
}
/* RX handler */
if (my_rx_interrupt(ndev))
loop++;
} while (loop);
/* re-enable interrupts */
my_netdev_reg_bitset(priv, EIE, EIE_INTIE);
}
4. ethtool 支持
ethtool
是用于检查和调整以太网网络接口设置的小工具。借助它可控制多种参数,如速度、媒体类型、双工操作等。需要
ethtool
支持的驱动应包含
<linux/ethtool.h>
,并填充
ethtool_ops
结构,将其分配给
struct net_device
结构的
.ethtool_ops
字段,示例如下:
my_netdev->ethtool_ops = &my_ethtool_ops;
也可使用
SET_ETHTOOL_OPS
宏。部分实现
ethtool
支持的驱动示例:
-
drivers/net/ethernet/microchip/enc28j60.c
-
drivers/net/ethernet/freescale/fec.c
-
drivers/net/usb/rtl8150.c
5. 驱动方法
驱动方法主要是
probe()
和
remove()
函数,负责向内核注册和注销网络设备。下面是网络接口可执行的操作:
static const struct net_device_ops my_netdev_ops = {
.ndo_open = my_netdev_open,
.ndo_stop = my_netdev_close,
.ndo_start_xmit = my_netdev_start_xmit,
.ndo_set_rx_mode = my_netdev_set_multicast_list,
.ndo_set_mac_address = my_netdev_set_mac_address,
.ndo_tx_timeout = my_netdev_tx_timeout,
.ndo_change_mtu = eth_change_mtu,
.ndo_validate_addr = eth_validate_addr,
};
6. 探测函数
探测函数需完成设备的早期初始化,并向内核注册网络设备,具体步骤如下:
1. 使用
alloc_etherdev()
函数(借助
netdev_priv()
)分配网络设备及其私有数据。
2. 初始化私有数据字段(如互斥锁、自旋锁、工作队列等)。若设备位于可能睡眠的总线(如 SPI),应使用工作队列;若为 MMIO 设备,可使用自旋锁。
3. 初始化特定总线的参数和功能(如 SPI、USB、PCI 等)。
4. 请求并映射资源(I/O 内存、DMA 通道和 IRQ)。
5. 必要时,生成随机 MAC 地址并分配给设备。
6. 填充必要的
netdev
属性(如
if_port
、
irq
、
netdev_ops
、
ethtool_ops
等)。
7. 将设备置于低功耗状态(
open()
函数会将其移除该模式)。
8. 调用
register_netdev()
注册设备。
以下是 SPI 网络设备的探测函数示例:
static int my_netdev_probe(struct spi_device *spi)
{
struct net_device *dev;
struct priv_net_struct *priv;
int ret = 0;
/* Allocate network interface */
dev = alloc_etherdev(sizeof(struct priv_net_struct));
if (!dev)
[...] /* handle -ENOMEM error */
/* Private data */
priv = netdev_priv(dev);
/* set private data and bus-specific parameter */
[...]
/* Initialize some works */
INIT_WORK(&priv->tx_work, data_tx_work_handler);
[...]
/* Devicerealy init, only few things */
if (!my_netdev_chipset_init(dev))
[...] /* handle -EIO error */
/* Generate and assign random MAC address to the device */
eth_hw_addr_random(dev);
my_netdev_set_hw_macaddr(dev);
/* Board setup must set the relevant edge trigger type;
* level triggers won't currently work.
*/
ret = request_irq(spi->irq, my_netdev_irq, 0, DRV_NAME, priv);
if (ret < 0)
[...]; /* Handle irq request failure */
/* Fill some netdev mandatory or useful properties */
dev->if_port = IF_PORT_10BASET;
dev->irq = spi->irq;
dev->netdev_ops = &my_netdev_ops;
dev->ethtool_ops = &my_ethtool_ops;
/* Put device into sleep mode */
My_netdev_lowpower(priv, true);
/* Register our device with the kernel */
if (register_netdev(dev))
[...]; /* Handle registration failure error */
dev_info(&dev->dev, DRV_NAME " driver registered\n");
return 0;
}
7. 模块卸载
模块卸载是清理函数,依赖
unregister_netdev()
和
free_netdev()
函数,示例代码如下:
static int my_netdev_remove(struct spi_device *spi)
{
struct priv_net_struct *priv = spi_get_drvdata(spi);
unregister_netdev(priv->netdev);
free_irq(spi->irq, priv);
free_netdev(priv->netdev);
return 0;
}
8. 总结
本文详细介绍了编写 NIC 设备驱动所需的知识,虽以 SPI 总线的网络接口为例,但 USB 或 PCI 网络接口原理相同。通过阅读本文,NIC 驱动开发应不再神秘。
以下是探测函数步骤的 mermaid 流程图:
graph TD
A[开始] --> B[分配网络设备及私有数据]
B --> C[初始化私有数据字段]
C --> D[初始化总线参数和功能]
D --> E[请求并映射资源]
E --> F[生成并分配 MAC 地址]
F --> G[填充 netdev 属性]
G --> H[将设备置于低功耗状态]
H --> I[注册设备]
I --> J[结束]
以下是中断处理流程的表格:
| 中断类型 | 处理操作 |
| ---- | ---- |
| DMA 中断 | 调用
handle_dma_complete()
并清除标志 |
| 链路状态改变中断 | 检查链路状态并清除标志 |
| 发送完成中断 | 重置重试计数,必要时清除标志 |
| 发送错误中断 | 重置发送逻辑,处理碰撞 |
| 接收错误中断 | 检查 FIFO 空间 |
| 接收中断 | 处理接收操作 |
网络接口卡(NIC)驱动开发指南
9. 设备加载与配置操作总结
在网络接口卡(NIC)驱动开发中,设备加载与配置是基础且关键的环节。以下是具体操作的总结:
-
驱动加载与接口创建
:
1. 使用
dmesg
命令查看模块加载和设备匹配后的信息,确认以太网接口是否成功初始化。
2. 运行
ifconfig -a
命令查看接口信息。
3. 使用
ifconfig
命令为接口分配 IP 地址,使其可正常工作。
-
设备状态与控制
:内核可通过
struct net_device_ops
结构操作或
ethtool
工具改变接口属性,状态则反映接口当前情况。
10. 中断处理与 ethtool 支持要点
- 中断处理 :现代硬件接口能报告多种状态,网络接口产生的中断(如错误、链路状态变化等)需在中断处理程序中处理。示例代码展示了中断处理的基本流程,将操作延迟到工作队列中执行,避免在中断上下文进行阻塞操作。
-
ethtool 支持
:
ethtool工具可控制以太网网络接口的多种参数。驱动需包含<linux/ethtool.h>,填充ethtool_ops结构并分配给struct net_device结构的.ethtool_ops字段,以实现ethtool支持。
11. 驱动方法及模块管理
-
驱动方法
:
probe()和remove()函数分别负责网络设备的注册和注销。网络接口的操作通过struct net_device_ops结构定义,包含打开、停止、发送、设置接收模式等操作。 -
模块管理
:
- 探测函数 :按特定步骤完成设备早期初始化和注册,包括分配设备和私有数据、初始化字段、映射资源、生成 MAC 地址等。SPI 网络设备的探测函数示例展示了具体实现。
-
模块卸载
:使用
unregister_netdev()移除接口,free_netdev()释放相关内存,确保资源正确清理。
12. 开发注意事项
- 设备命名 :避免使用重复的设备名称,否则可能导致注册失败。
- 资源管理 :在驱动开发过程中,要正确管理资源,如请求和释放 IRQ、分配和释放内存等,防止资源泄漏。
- 中断处理 :由于中断处理程序运行在中断上下文,应避免进行阻塞操作,可将耗时操作延迟到工作队列中执行。
13. 开发建议
-
参考示例代码
:可参考
drivers/net/ethernet/microchip/enc28j60.c等示例代码,学习成熟的驱动开发实现。 - 测试驱动 :使用提供的虚拟驱动进行测试,确保驱动在不同场景下的稳定性和正确性。
14. 总结回顾
本文全面介绍了网络接口卡(NIC)驱动开发的各个方面,包括设备加载与配置、中断处理、
ethtool
支持、驱动方法和模块管理等。通过详细的代码示例和操作步骤,帮助开发者理解和掌握 NIC 驱动开发的关键技术。
以下是设备加载与配置操作的列表:
1. 加载模块并匹配设备。
2. 使用
dmesg
查看初始化信息。
3. 用
ifconfig -a
查看接口信息。
4. 用
ifconfig
配置接口 IP 地址。
以下是开发注意事项的 mermaid 流程图:
graph TD
A[开始] --> B[检查设备命名]
B --> C{是否重复?}
C -- 是 --> D[修改名称]
C -- 否 --> E[管理资源]
D --> E
E --> F[处理中断]
F --> G[结束]
通过以上内容,开发者可以系统地学习和实践 NIC 驱动开发,为网络设备的稳定运行提供支持。


驱动开发指南&spm=1001.2101.3001.5002&articleId=150248254&d=1&t=3&u=52c5966695044aac9b017a82bb831e04)
1135

被折叠的 条评论
为什么被折叠?



