cs8900的网卡驱动终于看的告一段落了。背景:cs8900网卡驱动代码。

          目测这套代码不是很完整,但是个人学习是足够的,因为模块和模型都是很清晰的。

自己在这几天的学习过程中查了很多的资料,最后在CSDN上找了一位大神写的,所以这篇文章的一部分内容和驱动代码的介绍顺序也是来自于这位大神。原文地址:点击打开链接

先来附图一张--------linux网卡驱动结构示意图。

首先来说一下初始化,这是从51到linux从来都不会变的东西。

和所有其他模块一样,函数init_module是cs8900的入口:


module_init(init_cs8900a_s3c2410);
module_exit(cleanup_cs8900a_s3c2410);

init_cs8900a_s3c2410函数填充net_device结构体。net_device是网络驱动中最为重要的一个结构,需要认真阅读,其原型在 include/linux/netdevice.h中。为了填充如此复杂的结构,内核提供了ether_setup函数作为辅助。关于ether_setup函数,你只要知道它的功能就可以了。在我手上的这套代码中ether_setup的实现是在cs89x0_probe1这个函数中实现的。(ps:probe1这个函数是在probe这个函数中调用的,而probe这个函数是init_cs8900a_s3c2410这个初始化函数定义的结构体成员。)

下面是一点具体的代码:

ether_setup(dev);

同时还要进行其他的填充(这写填充是通过init_cs8900a_s3c2410,probe,probe1三个函数完成的。)这几个函数的本质作用都是对net_device这个结构体进行初始化 但是这几个函数里面对net_device这个结构体初始化的过程可以相互调换,不拘一格。

而下面语句的含义也非常明确:

    cs8900_dev->init = cs8900_probe

cs8900_probe是初始化函数(Driver initialization routines),主要完成一些初始化操作。cs8900_init中使用了另外一个函数还没有说-->alloc_etherdev。alloc_etherdev是alloc_netdev的封装函数,它负责在内核空间为 net_device结构体分配内存(kmalloc)。alloc_etherdev的原型在include/linux/etherdevice.h中。

在函数最后,通过register_netdev完成设备注册。register_netdev函数原型在net/core/dev.c中,但是如果你就是为了写驱动,你可以不往下看了。

/*网卡设备驱动的注册*/
    if (register_netdev(&dev_cs89x0) != 0) {
      printk(KERN_ERR "cs89x0.c: No card found at 0x%x\n", io);
      ret = -ENXIO;
      goto out;
    }

同时在init函数中还实现了申请IO端口的操作

//申请适用IO端口。同时,这个操作也可以放在设备打开函数中。
    request_region(dev_cs89x0.base_addr, NETCARD_IO_EXTENT, "cs8900a");

下面是在probe1函数中实现的

//为私有数据结构体分配内存。
      dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL);

需要多说一句的是priv是私有数据结构体,同时这个私有数据也是net_device结构体的成员。这个priv的作用和字符设备驱动中的private_data是一个性质的。

用大神的一句话来总结:init,cs8900_probe和cs8900_probe在使用各种手段填充了net_device结构体后,通过register_netdev向系统注册了一个网络设备。

 

 

cs8900_probe和cs89x0_probe1函数本身并不难,但是你必须清楚probe函数的重要职能。因为你很有可能要为属于你的设备编写一个probe函数。先来看一行最易懂的代码:printk(VERSION_STRING"/n"); 显然,内核启动时显示的相关信息就是这句话打印出来的。

net_device中的dev_addr就是网卡的MAC地址,你应该提供:

//定义MAC的地址。
	//dev_addr存在于net_dev这个结构体中。
    dev->dev_addr[0] = 0x00;
    dev->dev_addr[1] = 0x00;
    dev->dev_addr[2] = 0xc0;
    dev->dev_addr[3] = 0xff;
    dev->dev_addr[4] = 0xee;
    dev->dev_addr[5] = 0x08;
    set_mac_address(dev, dev->dev_addr);

具体的set_mac_address实现在驱动程序中也是有的:


/*设置以太网的MAC地址函数。*/
static int set_mac_address(struct net_device *dev, void *addr)
{
    int i;
	
	//设备忙的情况。
    if (netif_running(dev))
      return -EBUSY;

    DPRINTK(1, "%s: Setting MAC address to ", dev->name);
    for (i = 0; i < 6; i++) {
	//dev的值是从init函数中传来的。
      dev->dev_addr[i] = ((unsigned char *)addr)[i];
      DPRINTK(1, " %2.2x", dev->dev_addr[i]);
    }
    DPRINTK(1, ".\n");

    /* set the Ethernet address */
	//这个函数涉及到了PP_IA这个寄存器,所以这一部分也不是框架之内的。
    for (i=0; i < ETH_ALEN/2; i++)
      writereg(dev, PP_IA+i*2, dev->dev_addr[i*2] | (dev->dev_addr[i*2+1] << 8));
    return 0;
}

以下是大神关于IO端口初始化的一段话(我的代码中实现的很简单):



使用类似的代码,为我们的网络芯片提供  dev->if_port   = IF_PORT_10BASET; (支持的多种接口)  dev->priv      = (void *) &priv; (设备私有数据结构) 

哦,还要多说一句,IF_PORT_10BASET在include/linux/netdevice.h中定义,你通过这个枚举类型,可以了解更多的接口信息:

enum {         
IF_PORT_UNKNOWN = 0,         
IF_PORT_10BASE2,         
IF_PORT_10BASET,         
IF_PORT_AUI,         
IF_PORT_100BASET,         
IF_PORT_100BASETX,         
IF_PORT_100BASEFX };

下面的代码对于移植是非常关键的。vSMDK2410_ETH_IO是网卡的虚拟地址,IRQ_EINT9是硬件中断。这两个值和另外一个地址(网卡的物理地址)必须在头文件中定义,而且要正确的定义。

#if defined(CONFIG_ARCH_SMDK2410)     dev->base_addr = vSMDK2410_ETH_IO + 0x300;     dev->irq = IRQ_EINT9; #endif /* #if defined(CONFIG_ARCH_SMDK2410) */ 

接下来分配I/O端口资源。使用的函数是check_mem_region和request_mem_region。LDD中的理论结合这里的实践,相信你很快就掌握驱动编程中的这个API了。

下面的代码实现了硬件的读写。使用的函数是cs8900_read和cs8900_write。虽然只需很短的篇幅就可以解释这对读写函数,但是为了不分散你对cs8900_probe函数的注意力,我还是留到下一节中说,不要着急:)哦,差点忘了,这里使用了内核的自旋锁(spin_lock_init),不过关于自旋锁的机制已经超出本文的范围了。你现在只需要知道自旋锁用于并发控制,以及如何动态初始化自旋锁就可以了。如果你还想继续了解,请自行学习与自旋锁相关的API,它们是:

spinlock_t spin; //定义自旋锁

spin_lock(lock); //获得自旋锁

spin_trylock(lock); //尝试获得自旋锁

spin_unlock(lock); //释放自旋锁

大神一句话总结:cs8900_probe和cs8900_probe1函数继续为设备进行初始化,并申请各种资源。

Logo

更多推荐