Linux网络设备驱动架構學習(二)
接下來會從以下幾個方面介紹網絡設備驅動的編寫流程:
1、網絡設備的註冊與註銷
2、網絡設備的初始化
3、網絡設備的打開與釋放
4、網絡數據發送流程
5、網絡數據接收流程
6、網絡連接狀態
7、網絡參數設置和統計數據
瞭解了這幾部份內容,網絡設備驅動的編寫方法也就基本明白了
網絡設備的註冊與註銷
网络设备驱动的注册与注销使用成对出现的register_netdev()和unregister_netdev()函数完成,这两个函数的原型为:
int register_netdev(struct net_device *dev);
void unregister_netdev(struct net_device *dev);
这两个函数都接收一个 net_device 结构体指针为参数,可见 net_device 数据结构在网络设备驱动中的核心地位。
net_device 的生成和成员的赋值并非一定要由工程师逐个亲自动手完成,可以利用下面的函数帮助我们填充:
struct net_device *alloc_netdev(int sizeof_priv, const char *name, void(*setup) (struct net_device*));
struct net_device *alloc_etherdev(int sizeof_priv);
alloc_netdev()函数生成一个 net_device 结构体,对其成员赋值并返回该结构体的指针。第一个参数为设备私有成员的大小,第二个参数为设备名,第三个参数为net_device 的 setup()函数指针。setup()函数接收的参数也为 struct net_device 指针,用
于预置 net_device 成员的值。
alloc_etherdev()是 alloc_netdev()针对以太网的“快捷”函数。
完成与 alloc_enetdev()和 alloc_etherdev()函数相反功能,即释放 net_device 结构体的函数为:
void free_netdev(struct net_device *dev);
net_device 结构体的分配和网络设备驱动注册需在网络设备驱动程序的模块加载函数中进行,而 net_device 结构体的释放和网络设备驱动的注销则需在模块卸载函数中完成,如代码清单所示。
1 int xxx_init_module(void) 2 { 3 ... 4 /* 分配 net_device 结构体并对其成员赋值 */ 5 xxx_dev = alloc_netdev(sizeof(struct xxx_priv), "sn%d", xxx_init); 6 if (xxx_dev == NULL) 7 ... /* 分配 net_device 失败 */ 8 9 /* 注册 net_device 结构体 */ 10 if ((result = register_netdev(xxx_dev))) 11 ... 12 } 13 14 void xxx_cleanup(void) 15 { 16 ... 17 /* 注销 net_device 结构体 */ 18 unregister_netdev(xxx_dev); 19 /* 释放 net_device 结构体 */ 20 free_netdev(xxx_dev); 21 }
網絡設備的初始化
网络设备的初始化主要需要完成如下几个方面的工作。
1、进行硬件上的准备工作,检查网络设备是否存在,如果存在,则检测设备所使用的硬件资源。
2、进行软件接口上的准备工作,分配 net_device 结构体并对其数据和函数指针成员赋值。
3、获得设备的私有信息指针并初始化其各成员的值。如果私有信息中包括自旋锁或信号量等并发或同步机制,则需对其进行初始化。
对 net_device 结构体成员及私有数据的赋值都可能需要与硬件初始化工作协同进行,即硬件检测出了相应的资源,需要根据检测结果填充 net_device 结构体成员和私有数据。 一个网络设备驱动初始化函数的模板如代码清单所示,具体的设备驱动初始化函数并不一定完全和本模板一样,但是其本质过程是一致的。
1 void xxx_init(struct net_device *dev) 2 { 3 /*设备的私有信息结构体*/ 4 struct xxx_priv *priv; 5 6 /* 检查设备是否存在和设备所使用的硬件资源 */ 7 xxx_hw_init(); 8 9 /* 初始化以太网设备的公用成员 */ 10 ether_setup(dev); 11 12 /*设置设备的成员函数指针*/ 13 dev->open = xxx_open; 14 dev->stop = xxx_release; 15 dev->set_config = xxx_config; 16 dev->hard_start_xmit = xxx_tx; 17 dev->do_ioctl = xxx_ioctl; 18 dev->get_stats = xxx_stats; 19 dev->change_mtu = xxx_change_mtu; 20 dev->rebuild_header = xxx_rebuild_header; 21 dev->hard_header = xxx_header; 22 dev->tx_timeout = xxx_tx_timeout; 23 dev->watchdog_timeo = timeout; 24 25 /*如果使用 NAPI,设置 pool 函数*/ 26 if (use_napi) 27 { 28 dev->poll = xxx_poll; 29 } 30 31 /* 取得私有信息,并初始化它*/ 32 priv = netdev_priv(dev); 33 ... /* 初始化设备私有数据区 */ 34 }
上述代码第 7 行的 xxx_hw_init()函数完成硬件相关的初始化操作,如下所示。
1、探测 xxx 网络设备是否存在。探测的方法类似于数学上的“反证法”,即先假设存在设备 xxx,访问该设备,如果设备的表现与预期的一致,就确定设备存在;否则,假设错误,设备 xxx 不存在。
2、探测设备的具体硬件配置。一些设备驱动编写得非常通用,对于同类的设备使用统一的驱动,我们需要在初始化时探测设备的具体型号。另外,即便是同一设备,在硬件上的配置也可能不一样,我们也可以探测设备所使用的硬件资源。
3、申请设备所需要的硬件资源,如用 request_region()函数进行 I/O 端口的申请等,但是这个过程可以放在设备的打开函数 xxx_open()中完成。
針對Mini2440 DM9000 驱动probe方法做一個分析就可以對上面兩個過程有一個更清晰的認識
網絡設備的打開與釋放
网络设备的打开函数需要完成如下工作。
1、使能设备使用的硬件资源,申请 I/O 区域、中断和 DMA 通道等。
2、调用 Linux 内核提供的 netif_start_queue()函数,激活设备发送队列。
网络设备的关闭函数需要完成如下工作。
1、调用 Linux 内核提供的 netif_stop_queue()函数,停止设备传输包。
2、释放设备所使用的 I/O 区域、中断和 DMA 资源。
Linux 内核提供的 netif_start_queue()和 netif_stop_queue()两个函数的原型为:
void netif_start_queue(struct net_device *dev);
void netif_stop_queue (struct net_device *dev);
根据以上分析,可得出如代码清单所示的网络设备打开和释放函数的模板。
1 int xxx_open(struct net_device *dev) 2 { 3 /* 申请端口、IRQ 等,类似于 fops->open */ 4 ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev); 5 ... 6 netif_start_queue(dev); 7 ... 8 } 9 10 int xxx_release(struct net_device *dev) 11 { 12 /* 释放端口、IRQ 等,类似于 fops->close */ 13 free_irq(dev->irq, dev); 14 ... 15 netif_stop_queue(dev); /* can't transmit any more */ 16 ... 17 }
針對Mini2440 DM9000 驱动open,stop方法做一個分析就可以對上面兩個過程有一個更清晰的認識