CS8900A网卡设备驱动程序分析基于ARM平台(上)

网络驱动程序主要完成系统的初始化、数据包的发送和接收。在以前的内核版本中,网络设备的初始化主要由net_device数据结构中的init函数指针所指向的初始化函数来完成。在现在较新的2.6内核中,网络设备的初始化主要由device_driver数据结构中的probe函数指针所指向的函数来完成。数据包的发送和接收是实现Linux网络驱动程序中两个最关键的过程,对这两个过程处理的好坏将直接影响到驱动程序的整体运行质量。首先来分析CS8900A网卡设备驱动的初始化。
1初始化
CS8900A网卡设备驱动的初始化主要由device_driver数据结构中的probe函数指针所指向的初始化函数来完成,当内核启动或加载网络驱动模块的时候,就会调用这个初始化函数。该模块加载函数实现如下:
1 static int __init cirrus_init(void)
2 {
3 return driver_register(&cirrus_driver);
4 }
模块加载函数cirrus_init通过调用内核函数driver_register来注册CS8900A网卡设备驱动,driver_register函数的实现在内核<drivers/base/driver.c>文件中。对设备驱动程序进行注册和初始化是两件不同的事情。设备驱动程序应当尽快被注册,以便用户应用程序通过相应的设备文件使用它。通常设备驱动程序在最后可能的时刻才被初始化。事实上,初始化驱动程序意味着分配系统宝贵的资源,这些被分配的资源因此就对其他驱动程序不能用。关于注册的网络设备驱动结构cirrus_driver的定义如下:
1 static struct device_driver cirrus_driver = {
2 .name = "cirrus-cs89x0",
3 .bus = &platform_bus_type,
4 .probe = cirrus_drv_probe,
5 .remove = cirrus_remove,
6 .suspend = cirrus_suspend,
7 .resume = cirrus_resume,
8 };
第1行,定义变量cirrus_driver为device_driver结构类型,关于device_driver结构的定义在<include/linux/device.h>文件中。第2行,定义设备驱动名称为cirrus-cs89x0。第3行,定义bus类型为platform_bus_type。第4行,定义probe函数为cirrus_drv_probe,也就是说该网络设备的初始化是由cirrus_drv_probe函数来完成的,下面会具体讲述这个函数。第5行,定义remove函数为cirrus_remove,该函数主要完成网络设备的退出功能。第6行,定义suspend函数为cirrus_suspend,用来实现设备驱动的挂起操作,一般不用实现。第7行,定义resume函数为cirrus_resume,该函数用来实现从挂起状态返回到继续执行状态,一般也不用实现。
现在来分析一下初始化函数cirrus_drv_probe的具体实现。在初始化函数中通过检测物理设备的硬件特征来侦测网络物理设备是否存在,然后再对设备进行资源配置,以及内存映射,接下来构造设备的net_device数据结构,并用检测到的数据对net_device中的变量初始化,最后向Linux内核注册该设备并申请内存空间。
1 int __init cirrus_drv_probe (struct device *dev)
2 {
3 struct platform_device *pdev = to_platform_device(dev);
4 struct resource *res;
5 unsigned int *addr;
6 int ret;
7
8 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
9 if (!res) {
10 ret = -ENODEV;
11 goto out;
12 }
13
14 /* Request the regions. */
15 if (!request_mem_region(res->start, 16, "cirrus-cs89x0")) {
16 ret = -EBUSY;
17 goto out;
18 }
19
20 /* remap it. */
21 addr = ioremap(res->start, res->end - res->start);
22 if (!addr) {
23 ret = -ENOMEM;
24 goto release_1;
25 }
26
27 ndev = alloc_etherdev(sizeof(cirrus_t));
28 if (!ndev) {
29 printk("cirrus-cs89x0: could not allocate device.n");
30 ret = -ENOMEM;
31 goto release_2;
32 }
33
34 SET_NETDEV_DEV(ndev, dev);
35
36 ndev->irq = platform_get_irq(pdev, 0);
37 printk(KERN_DEBUG "cirrus: irq:%dn",ndev->irq);
38
39 dev_set_drvdata(dev, ndev);
40
41 ret = cirrus_probe(ndev, (unsigned long)addr);
42 if (ret != 0)
43 goto release_3;
44 return 0;
45
46 release_3:
47 dev_set_drvdata(dev, NULL);
48 free_netdev(ndev);
49 release_2:
50 iounmap(addr);
51 release_1:
52 release_mem_region(res->start, res->end - res->start);
53 out:
54 printk("cirrus-cs89x0: not found (%d).n", ret);
55 return ret;
56 }
        现在来分析上述代码。第3行,调用to_platform_device宏将device结构转化为platform_device结构的指针。第4-6行,定义一些该函数内部将用到的局部变量。第8-12行,调用platform_get_resource内核函数来为该平台设备申请内存资源,当该函数返回值为0时,表示申请内存资源失败,否则表示成功,返回申请的资源地址。第15-18行,调用request_mem_region宏来请求分配指定的I/O内存资源,如果返回值为0表示设备或资源被占用。第21-25行,调用ioremap函数将该设备的物理地址转化为内核地址,如果返回值为0,表示失败,此时需要释放指定的I/O内存资源。第27-32行,调用alloc_etherdev函数分配和设置一个以太网设备,如果返回值为0,表示分配失败,然后调用iounmap函数取消之前的内存映射。第34行,调用SET_NETDEV_DEV宏实现为系统文件系统中物理设备创建一个与网络类逻辑设备的链接,也就是说将物理设备与网络设备联系起来。第36行,调用platform_get_irq函数获得一个设备IRQ,并将获得结果传递给ndev->irq。第39行,调用dev_set_drvdata函数将网络设备与驱动具体的数据关联起来。第41-44行,调用cirrus_probe函数实现对网络设备的初始化工作,如果返回0,表示初始化失败,然后调用dev_set_drvdata将驱动设备的具体数据设为空,最后调用free_netdev内核函数释放之前注册的网络设备。关于cirrus_probe函数的具体实现,下面将具体介绍。在这里为止,对cirrus_drv_probe函数的介绍基本完毕。
cirrus_probe函数主要用来初始化网络设备,包括数据包发送、接收函数的定义,网络设备的注册等。下面将具体分析该函数的实现:
1 int __init cirrus_probe (struct net_device *dev, unsigned long ioaddr)
2 {
3 cirrus_t *priv = netdev_priv(dev);
4 int i;
5 u16 value;
6
7 ether_setup (dev);
8
9 dev->open = cirrus_start;
10 dev->stop = cirrus_stop;
11 dev->hard_start_xmit = cirrus_send_start;
12 dev->get_stats = cirrus_get_stats;
13 dev->set_multicast_list = cirrus_set_receive_mode;
14 dev->set_mac_address = cirrus_set_mac_address;
15 dev->tx_timeout = cirrus_transmit_timeout;
16 dev->watchdog_timeo = HZ;
17
18 dev->if_port = IF_PORT_10BASET;
19 dev->priv = (void *)priv;
20
21 spin_lock_init(&priv->lock);
22
23 SET_MODULE_OWNER (dev);
24
25 dev->base_addr = ioaddr;
26
27 /* if an EEPROM is present, use it's MAC address */
28 if (!cirrus_eeprom(dev,&priv->eeprom))
29 for (i = 0; i < 6; i++)
30 dev->dev_addr[i] = priv->eeprom.mac[i];
31 else
32 cirrus_parse_mac(cirrus_mac, dev->dev_addr);
33
34 /* verify EISA registration number for Cirrus Logic */
35 if ((value = cirrus_read (dev,PP_ProductID)) != EISA_REG_CODE) {
36 printk (KERN_ERR "%s: incorrect signature 0x%.4xn",dev->name,value);
37 return (-ENXIO);
38 }
39
40 /* verify chip version */
41 value = cirrus_read (dev,PP_ProductID + 2);
42 if (VERSION (value) != CS8900A) {
43 printk (KERN_ERR "%s: unknown chip version 0x%08xn",dev->name,VERSION (value));
44 return (-ENXIO);
45 }
46 printk (KERN_INFO "%s: CS8900A rev %c detectedn",dev->name,'B' + REVISION (value) - REV_B);
47
48 /* setup interrupt number */
49 cirrus_write (dev,PP_IntNum,0);
50
51 /* configure MAC address */
52 for (i = 0; i < ETH_ALEN; i += 2)
53 cirrus_write (dev,PP_IA + i,dev->dev_addr[i] | (dev->dev_addr[i + 1] << 8));
54
55 return register_netdev(dev);
56 }
现在来分析上述代码。第3行,调用netdev_priv函数获得net_device结构末端地址,也就是网卡私有数据结构的起始地址。关于netdev_priv函数的内核实现如下:
static inline void *netdev_priv(struct net_device *dev)
{
return (char *)dev + ((sizeof(struct net_device)
+ NETDEV_ALIGN_CONST)
& ~NETDEV_ALIGN_CONST);
}
第4-5行,定义两个局部变量供函数内部使用。第7行,调用ether_setup函数设置以太网相关的网络设备属性。第9-10行,定义网络设备的open方法和stop方法分别由cirrus_start和cirrus_stop函数实现,这两个函数会在后面具体讲解。第11行,定义网络设备的数据包发送函数hard_start_xmit由cirrus_send_start函数具体实现。该函数是网络设备驱动中非常重要的一个函数,后面将会具体介绍该函数的实现。第12行,定义网络设备的get_stats方法由cirrus_get_stats函数实现,主要用来获得网络设备的状态信息。第13行,设备网络设备的set_multicast_list方法由cirrus_set_receive_mode函数完成,主要实现设置接收数据包的类型。第14行,定义网络设备的set_mac_address方法由cirrus_set_mac_address函数实现,该函数用来设备网络设备的MAC地址。第15行,定义网络设备的tx_timeout方法由cirrus_transmit_timeout函数完成,该函数的用于当传输数据包超时时告诉内核来调度网络处理事件队列。第16行,初始化网络设备watchdog_timeo的值为HZ。第18行,初始化网络设备的if_port属性为IF_PORT_10BASET,即选择10M以太网。第19行,初始化网络设备的私有数据。第21行,调用spin_lock_init宏来初始化网络设备的自旋锁。第23行,调用SET_MODULE_OWNER宏设置网络设备的模块拥有属性。第25行,初始化网络设备的base_addr为ioremap映射后的地址。第28-32行,检查是否有EEPROM(Electrically Erasable Programmable Read-Only Memory,电可擦写可编程只读存储器),如果有的话,设置网络设备的MAC地址为EEPROM的MAC地址,否则,调用cirrus_parse_mac函数来获得网络设备的MAC地址。第35-38行,通过读CS8900A的产品标识寄存器来检查是否是已定义的产品,如果不是,返回无此设备的错误标志。第41-45行,验证芯片的版本是否与定义的版本相同,如果不同,返回无此设备的错误标志。第49行,调用cirrus_write函数来设置网络设备中断的个数在相应的寄存器中。第52-53行,调用cirrus_write函数将MAC地址写到相应的寄存器。第55行,调用register_netdev函数来注册网络设备到内核中去,根据该函数的返回值可以判断是否注册成功,返回值为0表示成功,否则表示错误。
关于CS8900A网卡设备驱动的初始化过程已经介绍完毕,下面将接着介绍网络设备的打开和关闭函数的实现。
2打开和关闭
网络设备的打开(open)和关闭(stop)是非常重要的网络设备驱动的两个实现函数。打开方法的作用就是激活网络接口,使它能接收来自网络的数据并且传递到网络协议栈的上层,也可以将数据发送到网络上。先来分析CS8900A网卡设备的打开实现函数cirrus_start:
1 static int cirrus_start (struct net_device *dev)
2 {
3 int result;
4
5 /* valid ethernet address? */
6 if (!is_valid_ether_addr(dev->dev_addr)) {
7 printk(KERN_ERR "%s: invalid ethernet MAC addressn",dev->name);
8 return (-EINVAL);
9 }
10
11 /* install interrupt handler */
12 if ((result = request_irq (dev->irq,&cirrus_interrupt,0,dev->name,dev)) < 0) {
13 printk (KERN_ERR "%s: could not register interrupt %dn",dev->name,dev->irq);
14 return (result);
15 }
16
17 set_irq_type(dev->irq, IRQT_RISING);
18
19 /* enable the ethernet controller */
20 cirrus_set (dev,PP_RxCFG,RxOKiE | BufferCRC | CRCerroriE | RuntiE | ExtradataiE);
21 cirrus_set (dev,PP_RxCTL,RxOKA | IndividualA | BroadcastA);
22 cirrus_set (dev,PP_TxCFG,TxOKiE | Out_of_windowiE | JabberiE);
23 cirrus_set (dev,PP_BufCFG,Rdy4TxiE | RxMissiE | TxUnderruniE | TxColOvfiE | MissOvfloiE);
24 cirrus_set (dev,PP_LineCTL,SerRxON | SerTxON);
25 cirrus_set (dev,PP_BusCTL,EnableRQ);
26
27 #ifdef FULL_DUPLEX
28 cirrus_set (dev,PP_TestCTL,FDX);
29 #endif /* #ifdef FULL_DUPLEX */
30
31 /* start the queue */
32 netif_start_queue (dev);
33
34 return (0);
35 }
现在来分析上述代码。第6-9行,调用is_valid_ether_addr函数来确认已知的以太网地址是否有效,如果不是返回无效错误标志。第12-15行,调用request_irq函数来申请一个IRQ资源,并且定义该中断处理函数为cirrus_interrupt,这个函数在后面会专门介绍。第17行,调用set_irq_type函数来设置刚才申请的IRQ类型,设置类型为上升沿触发(IRQT_RISING)。第20-25行,通过调用cirrus_set函数来设置CS8900A芯片的相关寄存器,来启动CS8900A以太网控制器。关于需要设置哪些寄存器需要参考CS8900A芯片的用户手册。第27-29行,如果支持全双工传输模式,需要调用cirrus_set函数来设置相应的寄存器使该功能启动。第32行,调用netif_start_queue函数用来告诉上层网络协议该驱动程序有空的缓冲区可用,请开始送数据包。第34行,返回0。
关闭方法用于停止网络设备,它的作用与open方法相反,CS8900A网卡设备的关闭方法由cirrus_stop函数实现,具体实现如下:
1 static int cirrus_stop (struct net_device *dev)
2 {
3 /* disable ethernet controller */
4 cirrus_write (dev,PP_BusCTL,0);
5 cirrus_write (dev,PP_TestCTL,0);
6 cirrus_write (dev,PP_SelfCTL,0);
7 cirrus_write (dev,PP_LineCTL,0);
8 cirrus_write (dev,PP_BufCFG,0);
9 cirrus_write (dev,PP_TxCFG,0);
10 cirrus_write (dev,PP_RxCTL,0);
11 cirrus_write (dev,PP_RxCFG,0);
12
13 /* uninstall interrupt handler */
14 free_irq (dev->irq,dev);
15
16 /* stop the queue */
17 netif_stop_queue (dev);
18
19 return (0);
20 }
        现在来分析上述代码。第4-11行,调用cirrus_write函数写CS8900A芯片相应的寄存器,从而关闭CS8900A以太网控制器。第14行,调用free_irq函数释放之前申请的IRQ资源。第17行,调用netif_stop_queue函数告诉上层网络请不要再送数据包。
关于CS8900A网络设备的打开和关闭方法已经分析完毕,相对来说这两个方法的实现比较简单,但是非常重要。下面将介绍网卡驱动的中断处理函数。

你可能感兴趣的:(CS8900A网卡设备驱动程序分析基于ARM平台(上))