Linux2.6.2的cs8900驱动分析

简介: Linux-2.6.20的cs8900驱动分析 几经波折,在开发板上终于可以使用网络了。Linux 内核可以通过网络挂接网络文件系统了。首先感谢Internet ,Google 等帮助过我的工具,还要感谢各位嵌友的无私奉献。

Linux-2.6.20cs8900驱动分析

几经波折,在开发板上终于可以使用网络了。Linux 内核可以通过网络挂接网络文件系统了。首先感谢Internet Google 等帮助过我的工具,还要感谢各位嵌友的无私奉献。在移植的过程中尤其感激weibing 的博客文章cs8900 移植linux-2.6.19.2 ,根据他的文章使cs8900

成功跑起来。此文章可以在http://weibing.blogbus.com/logs/4467465.html 找到。

   在解释网络驱动前,先说说自己的硬件配置:

    1. 处理器为s3c2410

    2. 网络芯片cs8900a

    3. cs8900a 映射到s3c2410 bank3 空间

    4. cs8900a 占用int9 号中断

    5. Linux 内核版本为2.6.20

一、初始化阶段

    网络初始化被调用的路径为:

init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2->cs89x0_probe->cs89x0_probe1

真是不容易啊,终于进到cs89x0_probe1 了,在这里开始探测和初始化cs8900 了。下面就按照这个顺序来说明网络驱动第一阶段的工作。注意:这里的调用顺序是将cs8900 驱动编入内核所产生的,如果将cs8900 驱动选为模块,这个路径:init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2 也会执行。

1.1 init 函数

我们知道当start_kernel 函数完成后就会启动init 进程执行,在真正的应用程序init 进程(如busybox /sbin/init )之前,Linux 还需要执行一些初始化操作。init 的代码可以在\init\main.c 中找到,它的代码如下:

static int init(void * unused)

{

       lock_kernel();

……                                                         // 省略多cpu 的初始化代码先

       do_basic_setup();                                // 我们所关注的初始化函数

……

       if (!ramdisk_execute_command)

              ramdisk_execute_command = "/init";

       if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {

              ramdisk_execute_command = NULL;

              prepare_namespace();                           // 挂接根文件系统

       }

……

       free_initmem();                                             // 释放初始化代码的空间

       unlock_kernel();

……                                                                 // 这几段没看懂

 

       if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) 检查控制台

                                             //console 是否存在

              printk(KERN_WARNING "Warning: unable to open an initial console.\n");

……// 这几段没看懂

       if (ramdisk_execute_command) {  // 运行ramdisk_execute_command 指定的init 用户进程

              run_init_process(ramdisk_execute_command);

              printk(KERN_WARNING "Failed to execute %s\n",

              ramdisk_execute_command);

       }

       ……

       if (execute_command) {      // 判断在启动时是否指定了init 参数,如果指定,

                    // 此值将赋给execute_command

              run_init_process(execute_command); // 开始执行用户init 进程,如果成功将不会

                                                                        // 返回。

              printk(KERN_WARNING "Failed to execute %s.  Attempting "

                                   "defaults...\n", execute_command);

       }

// 如果没有指定init 启动参数,则查找下面的目录init 进程,如果找到则不会返回

       run_init_process("/sbin/init");

       run_init_process("/etc/init");

       run_init_process("/bin/init");

       run_init_process("/bin/sh");

    // 如果上面的程序都出错,则打印下面的信息,如果内核找到init 进程,

  // 则程序不会指向到此处

       panic("No init found.  Try passing init= option to kernel.");

}

1.2 do_basic_setup 函数

在这里我们最关心的是do_basic_setup 函数,顾名思义该函数的功能就是做基本设置,它的实现代码也在\init\main.c 中。do_basic_setup() 完成外设及其驱动程序的加载和初始化。该函数代码如下所示:

 

static void __init do_basic_setup(void)

{

       /* drivers will send hotplug events */

       init_workqueues();     // 初始化工作队列

       usermodehelper_init();  // 初始化khelper 内核线程,还没弄清楚

 

       driver_init();   // 初始化内核的设备管理架构需要的数据结构,

                            // 很复杂,以后在谈这部分。

 

#ifdef CONFIG_SYSCTL

       sysctl_init();          // 没搞懂

#endif

       do_initcalls();         // 重点函数,初始化的主要工作就靠它了

}

1.3 do_ initcalls 函数

       do_initcalls 函数将会调用内核中所有的初始化函数,它的代码同样在\init\main.c 中。do_initcalls 函数调用其他初始化函数相当简洁,它的关键代码如下所示:

initcall_t *call;

for (call = __initcall_start; call

……

       result = (*call)();

……

       简洁归简洁,但这段代码是什么意思呢?这说来就话长了,最重要的应该是先了解Linux 处理初始化的大体思想,由于Linux 有很多部分需要初始化,每个部分都有自己的初始化函数,如果按照常理一个一个的调用未免显得冗长,而且也不便于扩展。那么Linux 是怎么处理的呢?首先,Linux 将各个部分的初始化函数编译到一个块内存区中,当初始化完了以后释放这块内存区,这就是init 函数中free_initmem 所要做的事。然后,再在另外的内存区中存放一些函数指针,让每个指针指向一个初始化函数。然后在do_initcalls 中依次根据这些指针调用初始化函数。

上面一段就是Linux 实现初始化的大体思想,下面我们看看它最终是怎么实现的。首先要了解的是__define_initcall 宏,该宏的定义在\ include\linux\init.h 中,它的原型如下所示:

 

#define __define_initcall(level,fn,id)  static initcall_t __initcall_##fn##id __attribute_used__ \

       __attribute__((__section__(".initcall" level ".init"))) = fn

 

__define_initcall 宏有三个参数,level 表示初始化函数的级别,level 值的大小觉得了调用顺序,level 越小越先被调用,fn 就是具体的初始化函数,id 简单标识初始化函数,现在还没找到有什么用^_^ __define_initcall 的功能为,首先声明一个initcall_t 类型的函数指针__initcall_##fn##id initcall_t 的原型为:

typedef int (*initcall_t)(void);

该类型可简单理解为函数指针类型^_^ 。然后,让该函数指针指向fn 。最后,通过编译器的编译参数将此指针放到指定的空间".initcall" level ".init" 中,__attribute_used 向编译器说明这段代码有用,即使在没用到的时候,编译器也不会警告。__attribute__ __section__ 参数表示该段代码放入什么内存区域中,也即指定编译到什么地方,编译参数更详细的地方可以查阅GCC 文档,在gcc 官方网站http://gcc.gnu.org/onlinedocs/ 中能找到各个版本的手册。这样说来还是比较抽象,下面举个例子来说明:

       假如有初始化函数init_foolish 函数,现在使用__define_initcall 宏向内核加入该函数。假如调用方式如下:

__define_initcall("0",init_foolish,1)

那么,__define_initcall 宏首先申请一个initcall_t 类型的函数指针__initcall_init_foolish1 (注意替换关系),且使该指针指向了init_foolish ,函数指针__initcall_init_foolish1 被放到.initcall.0.init 内存区域中,这个标志在连接时会用到。

       有了上面的基础知识,现在回到do_initcalls 函数中,首先注意到是__initcall_start __initcall_end ,它们的作用就是界定了存放初始化函数指针区域的起始地址,也即从__initcall_start 开始到__initcall_end 结束的区域中存放了指向各个初始化函数的函数指针。换句话说,只要某段程序代码从__initcall_start 开始依次调用函数指针,那么就可以完成各个部分的初始化工作,这显得十分优雅而且便于扩充,再看看do_initcalls ,它何尝不是如此呢。这里还有一个有用的技巧就是__initcall_start __initcall_end 的原型是initcall_t 型的数组,以后可以使用这种技巧^_^

       现在我们知道了do_initcalls 函数的实现原理,那么到底它调用了多少初始化函数呢?我们怎样才能知道呢?根据上面的分析,我们知道所有的初始化函数的指针都放在__initcall_start __initcall_end 区域期间,而函数指针与它指向的函数之间又有固定的关系,如上面的例子,初始化函数名为init_foolish ,指向它的函数指针就是__initcall_init_foolish1 ,即在此函数加上前缀__initcall_ 和一个数字后缀,反之,从函数指针也可推出初始化函数名。有了这两个信息,我们就可以很方便的找个初始化函数。怎么找呢??首先打开Linux 完后产生的System.map 文件,然后找到__initcall_start __initcall_end 字符串,你会发现它们之间有很多类似于__initcall_xxx1 这样的符号,这些符号就是我们需要的函数指针了,这样就可推出初始化函数的名字。比如,我们这里需要的函数指针__initcall_net_olddevs_init6 ,按照上面的名字规则,很容易推出它所指向的初始化函数名字是net_olddevs_init

       得到了初始化函数的名字又怎么样呢?又不知道它在哪个文件里,不要着急!请打开你的浏览器登陆http://lxr.linux.no/ident 网站,然后选择Linux 版本和架构,然后可以搜索我们想要的信息。比如我输入net_olddevs_init ,然后我就会得到该函数所在文件的相关信息。

1.4 net_olddevs_init 函数

       我们知道net_olddevs_init 函数在do_initcalls 函数中被调用并执行,那么它到底要做什么呢?看看实现代码就知道了,它的实现代码可以在\drivers\net\Space.c 中找到。对于网络驱动部分的主要实现代码如下:

static int __init net_olddevs_init(void){  

 ……

       int num;

       for (num = 0; num

              ethif_probe2(num);

       ……

}

这段代码就不用讲解了吧,嘿嘿!就是调用了8 ethif_probe2 ,赶快去看看ethif_probe2 长什么样子。

1.5 ethif_probe2 函数

       先看看该函数的实现代码,该代码也在\drivers\net\Space.c 文件中。

static void __init ethif_probe2(int unit)

{

       unsigned long base_addr = netdev_boot_base("eth", unit);   // 由于ethif_probe2

                                                                                                 //net_olddevs_init 调用了8 次,

                   // 所以unit 的值为0 7 ,也即在这里可以注册eth0 eth7 八个网络设备

       if (base_addr == 1)

              return;

 

       (void)(    probe_list2(unit, m68k_probes, base_addr == 0) &&

              probe_list2(unit, eisa_probes, base_addr == 0) &&

              probe_list2(unit, mca_probes, base_addr == 0) &&

              probe_list2(unit, isa_probes, base_addr == 0) &&

              probe_list2(unit, parport_probes, base_addr == 0));

}

       该函数首先调用netdev_boot_base 所给的设备是否已经向内核注册,如果已注册netdev_boot_base 返回1 ,随后推出ethif_probe2 。如果设备没注册,则又调用函数probe_list2 四次,每次传递的传输不同,注意到每次传递的第二个参数不同,这个参数也是相当重要的,这里拿isa_probes 参数为例说明,因为这个参数与cs89x0_probe 有关,isa_probes 的定义也在\drivers\net\Space.c 中,它的样子形如:

static struct devprobe2 isa_probes[] __initdata = {

……

#ifdef CONFIG_SEEQ8005

       {seeq8005_probe, 0},

#endif

#ifdef CONFIG_CS89x0

      {cs89x0_probe, 0},

#endif

#ifdef CONFIG_AT1700

       {at1700_probe, 0},

#endif

       {NULL, 0},

……

};

如果把cs8900 的驱动选为非编译进内核,那么它的探测函数cs89x0_probe 就不会存在于isa_probes 数组中,所以在初始阶段就不能被调用。从上面的代码可以知道devprobe2 类型至少包括两个域,至少一个域为函数指针,看看它的原型如下:

struct devprobe2 {

       struct net_device *(*probe)(int unit);                         // 函数指针,指向探测函数

       int status;       /* non-zero if autoprobe has failed */

};

下面看看probe_list2 函数是怎么表演的。

1.6 ethif_probe2 函数

       对于ethif_probe2 函数也没有什么需要说明的,它的主要任务是依次调用devprobe2 类型的probe 域指向的函数。他的实现代码同样在\drivers\net\Space.c 中,它的关键代码如下:

 

 

static int __init probe_list2(int unit, struct devprobe2 *p, int autoprobe)

{

       struct net_device *dev;

       for (; p->probe; p++) {

           ……

              dev = p->probe(unit);

              ……

       }

……

}

1.7 cs89x0_probe 函数

       从该函数起,真正开始执行与cs8900 驱动初始化程序,该函数在\drivers\net\cs89x0.c 文件实现。下面依次解释该函数。

 

struct net_device * __init cs89x0_probe(int unit)

{

       struct net_device *dev = alloc_etherdev(sizeof(struct net_local));  // 该函数申请一个net_device

//sizeof(struct net_local) 的空间,net_local cs8900 驱动的私有数据空间。

       unsigned *port;

       int err = 0;

       int irq;

       int io;

      

       if (!dev)

              return ERR_PTR(-ENODEV);

       sprintf(dev->name, "eth%d", unit);                 // 初始化dev->name

       netdev_boot_setup_check(dev);                   // 检查是否给定了启动参数,如果给定了

                                                                           // 启动参数,此函数将初始化dev irq

                                                                          //base_addr mem_start mem_end 域。

       io = dev->base_addr;        //io 实际实质cs8900 所占地址空间的起始地址,

                                                // 此地址为虚拟地址

       irq = dev->irq;

 

       if (net_debug)

              printk("cs89x0:cs89x0_probe(0x%x)\n", io);

// 下面根据io 的值调用cs89x0_probe1 函数

       if (io > 0x1ff) {/* Check a single specified location. */    // 此段没搞懂,由于没给

                                                                                            // 启动参数,这里也不会执行

         err = cs89x0_probe1(dev, io, 0);

       } else if (io != 0) { /* Don't probe at all. */

              err = -ENXIO;

       } else {

              for (port = netcard_portlist; *port; port++) {// netcard_portlist unsigned int 型数组,在cs89x0.c 文件中定

// 义,里面列出了cs8900 可能占用空间的起始地址,这些地址

// 将在cs89x0_probe1 函数中用于向内核申请。

                     if (cs89x0_probe1(dev, *port, 0) == 0) // cs89x0_probe1 探测成功就返回0

                            break;

                     dev->irq = irq;

              }

              if (!*port)

                     err = -ENODEV;

       }

       if (err)

              goto out;

       return dev;

out:

       free_netdev(dev);   // 表示探测失败,这里就释放dev 的空间,随后打印些消息

       printk(KERN_WARNING "cs89x0: no cs8900 or cs8920 detected.  Be sure to disable PnP with SETUP\n");

       return ERR_PTR(err);

}

       从上面的程序清单可以看到该函数还没有真正的开始探测cs8900 ,实质的探测工作是让cs89x0_probe1 完成的。在解释cs89x0_probe1 之前先提一下网络驱动程序中非常重要的一些函数。内核需要一个数据结构来管理或者描述每个网络驱动程序,这个数据类型就是struct net_device ,该数据类型包括很多域,详细的解释可以参见《Linux 设备驱动程序》一书中的描述,也可以参见源代码(在\include\linux\netdevice.h 中,源码中也有详细的注解)。内核为了编程方便特地实现了函数alloc_netdev 来完成对net_device 的空间分配。那么alloc_etherdev 函数主要针对以太网在alloc_netdev 基础上封装的一个函数,它除了申请net_device 空间外,还会初始化net_device 的相关域。

1.8 cs89x0_probe1 函数

       对于该函数完成了最终的网络芯片cs8900 的探测工作,里面涉及了一些芯片硬件的操作,看这个源码之前应该对cs8900a 芯片比较熟悉,或者在读的时候把它的芯片manual 打开。这函数的代码很长,大约有300 多行,但是它没有什么特别的技巧,只要认真阅读,最多半天就能搞明了^_^ ,下面给出该函数在ARM 架构下,且没开DMA 情况下的注解。

static int __init cs89x0_probe1(struct net_device *dev, int ioaddr, int modular)

{

       struct net_local *lp = netdev_priv(dev);  //dev 空间已经在cs89x0_probe 中申请成功,

              // 这里lp dev 中得到自己的私有数据,也即net_local 数据域的起始地址,

             //netdev_priv 函数为网络驱动中得到私有数据的标准函数,当然也可以直接

             // 使用dev->priv ,但不鼓励 这种做法。

 

  // 下面申请些局部变量

       static unsigned version_printed;

       int i;

       int tmp;

       unsigned rev_type = 0;

       int eeprom_buff[CHKSUM_LEN];

       int retval;

 

       SET_MODULE_OWNER(dev);  // 设置模块的属于者, 该宏定义在

                                                           //include\linux\netdevice 文件中, 实际为 do{}while(0)

      

       /* Initialize the device structure. */

       if (!modular) { // 这里的modular 0, cs89x0_probe 传入

              memset(lp, 0, sizeof(*lp));      // lp 填充为0

              spin_lock_init(&lp->lock);  // 初始化自旋锁, 自旋锁用于保护dev 结构的互斥访问

#ifndef MODULE      // make menuconfig 时确定, 表示是否将网络驱动编译为模块。

#if ALLOW_DMA    // 是否启用了DMA

              if (g_cs89x0_dma) {

                     lp->use_dma = 1;

                     lp->dma = g_cs89x0_dma;

                     lp->dmasize = 16;   /* Could make this an option... */

              }

 

#endif

              lp->force = g_cs89x0_media__force;

#endif

        }

 

  ......

 

       /* Grab the region so we can find another board if autoIRQ fails. */

       /* WTF is going on here? */

      

       // request_region 函数向内核注册io 地址空间,这里NETCARD_IO_EXTENT 16

       // 所以可以看出cs8900 工作在I/O 模式。cs8900 memory 模式需要映射4k 空间

       if (request_region(ioaddr & ~3, NETCARD_IO_EXTENT, DRV_NAME)==NULL) {

              printk(KERN_ERR "%s: request_region(0x%x, 0x%x) failed\n",

                            DRV_NAME, ioaddr, NETCARD_IO_EXTENT);

              retval = -EBUSY;

              goto out1;

       }

 

......

 

       /* if they give us an odd I/O address, then do ONE write to

           the address port, to get it back to address zero, where we

           expect to find the EISA signature word. An IO with a base of 0x3

          will skip the test for the ADD_PORT. */

         

  // 下面这段代码比较费解,不是说代码的意思不好解释,而是为什么只在寄地址

  // 才检查呢?根据数据手册的说明The CS8900A reads 3000h from IObase+0Ah after

  //the reset, until the software writes a non-zero value at IObase+0Ah. The

  //3000h value can be used as part of the CS8900A signature when the system

  //scans for the CS8900A.从这段话可知,这只能作为扫描到cd8900 存在部分的依据;

 // 从后面的代码中可以看到,还需要确定cs8900 ID 号后才能真正确保cs8900 存在。

       if (ioaddr & 1) {

              if (net_debug > 1)

                     printk(KERN_INFO "%s: odd ioaddr 0x%x\n", dev->name, ioaddr);

               if ((ioaddr & 2) != 2)

                      if ((readword(ioaddr & ~3, ADD_PORT) & ADD_MASK) != ADD_SIG) {

                            printk(KERN_ERR "%s: bad signature 0x%x\n",

                                   dev->name, readword(ioaddr & ~3, ADD_PORT));

                             retval = -ENODEV;

                            goto out2;

                     }

       }

 

       ioaddr &= ~3;

       printk(KERN_DEBUG "PP_addr at %x[%x]: 0x%x\n",

                     ioaddr, ADD_PORT, readword(ioaddr, ADD_PORT));

       writeword(ioaddr, ADD_PORT, PP_ChipID);             // 这里表示扫描到cs8900

                                                                                           // 按照数据手册写0

 

// 下面这段代码确定cs8900 EISA ID 号是否为0x630E 。这里DATA_PORT=0x0C

//cs8900 的数据口, CHIP_EISA_ID_SIG=0x630E 0x630E Crystal 公司在EISA 的注册

// 号。通过下面的检查以后就真正确定了cs8900 存在,硬件电路ok

// 以及向内核注册的端口地址ok

       tmp = readword(ioaddr, DATA_PORT);

       if (tmp != CHIP_EISA_ID_SIG) {

              printk(KERN_DEBUG "%s: incorrect signature at %x[%x]: 0x%x!="

                     CHIP_EISA_ID_SIG_STR "\n",

                     dev->name, ioaddr, DATA_PORT, tmp);

             retval = -ENODEV;

             goto out2;

       }

 

       /* Fill in the 'dev' fields. */

       dev->base_addr = ioaddr;

 

       /* get the chip type */

       rev_type = readreg(dev, PRODUCT_ID_ADD);//rev_type=0x0a00 ,这个值是实际

       // 测试出来的,但根据cs8900A 的数据手册,该值应该是0x0700 ???

       lp->chip_type = rev_type &~ REVISON_BITS;

       lp->chip_revision = ((rev_type & REVISON_BITS) >> 8) + 'A';

  // 执行上面赋值后lp->chip_type=0x0 lp->chip_revision=0x4b 。注意这里的加法运算

  //rev_type & REVISON_BITS)>>8=0x0a ,这个0x0a 是数字,'A' 转换成十

  // 六进制后为0x41 ,所以,0x41+0x0a=0x4b 0x4b ascii 码中对应的字母为'K'

 

       /* Check the chip type and revision in order to set the correct send command

       CS8920 revision C and CS8900 revision F can use the faster send. */

       lp->send_cmd = TX_AFTER_381;       // 默认每次传输381 字节,

                                                                     // 根据数据手册可以传输的字节

       // 选项有5 381 1021 all 四个,但这里的驱动不支持1021 字节的选项。

       if (lp->chip_type == CS8900 && lp->chip_revision >= 'F')// 此条件满足

              lp->send_cmd = TX_NOW;// 选择每次传输5 字节

       if (lp->chip_type != CS8900 && lp->chip_revision >= 'C')

              lp->send_cmd = TX_NOW;

 

       if (net_debug  &&  version_printed++ == 0)

              printk(version);

 

       printk(KERN_INFO "%s: cs89%c0%s rev %c found at %#3lx ",

              dev->name,

              lp->chip_type==CS8900?'0':'2',

              lp->chip_type==CS8920M?"M":"",

              lp->chip_revision,

              dev->base_addr);// 按照上面的分析,这里打印的应该形如:

              //cs89x0.c: v2.4.3-pre1 Russell Nelson nelson@crynwr.com>,

              //Andrew Morton andrewm@uow.edu.au> eth0: cs8900 rev K found at 0xf4000300

 

       reset_chip(dev);  // 重新复位cs8900a

 

     /* Here we read the current configuration of the chip. If there

          is no Extended EEPROM then the idea is to not disturb the chip

          configuration, it should have been correctly setup by automatic

          EEPROM read on reset. So, if the chip says it read the EEPROM

          the driver will always do *something* instead of complain that

          adapter_cnf is 0. */

 

 

......

// 以下代码一直到printk(KERN_INFO "cs89x0 media %s%s%s", 功能为

// EEPROM 中读出配置信息,并填充dev 结构的相关域。

        if ((readreg(dev, PP_SelfST) & (EEPROM_OK | EEPROM_PRESENT)) ==

             (EEPROM_OK|EEPROM_PRESENT)) {// 读取SelfST 寄存器,并判断EEPROM

                        // 是否存在 若存在,则判断是否读取操作成功。上述条件满足,

                        // 则读出EEPROM 中的配置 信息填充dev 相关域。

               /* Load the MAC. */

              for (i=0; i 读取以太网地址

                       unsigned int Addr;

                     Addr = readreg(dev, PP_IA+i*2);

                      dev->dev_addr[i*2] = Addr & 0xFF;

                      dev->dev_addr[i*2+1] = Addr >> 8;

              }

 

            /* Load the Adapter Configuration.

                 Note:  Barring any more specific information from some

                 other source (ie EEPROM+Schematics), we would not know

                 how to operate a 10Base2 interface on the AUI port.

                 However, since we  do read the status of HCB1 and use

                 settings that always result in calls to control_dc_dc(dev,0)

                 a BNC interface should work if the enable pin

                 (dc/dc converter) is on HCB1. It will be called AUI

                 however. */

 

              lp->adapter_cnf = 0;

              i = readreg(dev, PP_LineCTL);      // 读取LineCTL 寄存器,

                           // 确定MAC 配置和物理接口

              /* Preserve the setting of the HCB1 pin. */

              if ((i & (HCB1 | HCB1_ENBL)) ==  (HCB1 | HCB1_ENBL))

                     lp->adapter_cnf |= A_CNF_DC_DC_POLARITY;

              /* Save the sqelch bit */

              if ((i & LOW_RX_SQUELCH) == LOW_RX_SQUELCH)

                     lp->adapter_cnf |= A_CNF_EXTND_10B_2 | A_CNF_LOW_RX_SQUELCH;

              /* Check if the card is in 10Base-t only mode */

              if ((i & (AUI_ONLY | AUTO_AUI_10BASET)) == 0)

                     lp->adapter_cnf |=  A_CNF_10B_T | A_CNF_MEDIA_10B_T;

              /* Check if the card is in AUI only mode */

              if ((i & (AUI_ONLY | AUTO_AUI_10BASET)) == AUI_ONLY)

                     lp->adapter_cnf |=  A_CNF_AUI | A_CNF_MEDIA_AUI;

              /* Check if the card is in Auto mode. */

              if ((i & (AUI_ONLY | AUTO_AUI_10BASET)) == AUTO_AUI_10BASET)

                     lp->adapter_cnf |=  A_CNF_AUI | A_CNF_10B_T |

                     A_CNF_MEDIA_AUI | A_CNF_MEDIA_10B_T | A_CNF_MEDIA_AUTO;

 

              if (net_debug > 1)

                     printk(KERN_INFO "%s: PP_LineCTL=0x%x, adapter_cnf=0x%x\n",

                                   dev->name, i, lp->adapter_cnf);

 

              /* IRQ. Other chips already probe, see below. */

              if (lp->chip_type == CS8900)

                     lp->isa_config = readreg(dev, PP_CS8900_ISAINT) & INT_NO_MASK;

 

              printk( "[Cirrus EEPROM] ");

       }

 

        printk("\n");

 

       /* First check to see if an EEPROM is attached. */

......// 以下检查EEPROM 的相关信息

       if ((readreg(dev, PP_SelfST) & EEPROM_PRESENT) == 0)// 是否EEPROM 存在

              printk(KERN_WARNING "cs89x0: No EEPROM, relying on command line....\n");

       else if (get_eeprom_data(dev, START_EEPROM_DATA,CHKSUM_LEN,eeprom_buff)

         // 读取RRPROM 失败

              printk(KERN_WARNING "\ncs89x0: EEPROM read failed, relying on command line.\n");

        } else if (get_eeprom_cksum(START_EEPROM_DATA,CHKSUM_LEN,eeprom_buff)

              /* Check if the chip was able to read its own configuration starting

                 at 0 in the EEPROM*/

              if ((readreg(dev, PP_SelfST) & (EEPROM_OK | EEPROM_PRESENT)) !=

                  (EEPROM_OK|EEPROM_PRESENT))

                       printk(KERN_WARNING "cs89x0: Extended EEPROM checksum bad and no Cirrus EEPROM, relying on command line\n");

 

        } else {

              /* This reads an extended EEPROM that is not documented

                 in the CS8900 datasheet. 扩展配置*/

 

                /* get transmission control word  but keep the autonegotiation bits */

                if (!lp->auto_neg_cnf) lp->auto_neg_cnf = eeprom_buff[AUTO_NEG_CNF_OFFSET/2];

                /* Store adapter configuration */

                if (!lp->adapter_cnf) lp->adapter_cnf = eeprom_buff[ADAPTER_CNF_OFFSET/2];

                /* Store ISA configuration */

                lp->isa_config = eeprom_buff[ISA_CNF_OFFSET/2];

                dev->mem_start = eeprom_buff[PACKET_PAGE_OFFSET/2]

 

                /* eeprom_buff has 32-bit ints, so we can't just memcpy it */

                /* store the initial memory base address */

                for (i = 0; i

                        dev->dev_addr[i*2] = eeprom_buff[i];

                        dev->dev_addr[i*2+1] = eeprom_buff[i] >> 8;

                }

              if (net_debug > 1)

                     printk(KERN_DEBUG "%s: new adapter_cnf: 0x%x\n",

                            dev->name, lp->adapter_cnf);

        }

 

        /* allow them to force multiple transceivers.  If they force multiple, autosense */

        {

              int count = 0;

              if (lp->force & FORCE_RJ45)      {lp->adapter_cnf |= A_CNF_10B_T; count++; }

              if (lp->force & FORCE_AUI)      {lp->adapter_cnf |= A_CNF_AUI; count++; }

              if (lp->force & FORCE_BNC)      {lp->adapter_cnf |= A_CNF_10B_2; count++; }

              if (count > 1)                {lp->adapter_cnf |= A_CNF_MEDIA_AUTO; }

              else if (lp->force & FORCE_RJ45){lp->adapter_cnf |= A_CNF_MEDIA_10B_T; }

              else if (lp->force & FORCE_AUI) {lp->adapter_cnf |= A_CNF_MEDIA_AUI; }

              else if (lp->force & FORCE_BNC)       {lp->adapter_cnf |= A_CNF_MEDIA_10B_2; }

        }

 

       if (net_debug > 1)

              printk(KERN_DEBUG "%s: after force 0x%x, adapter_cnf=0x%x\n",

                     dev->name, lp->force, lp->adapter_cnf);

 

        /* FIXME: We don't let you set dc-dc polarity or low RX squelch from the command line: add it here */

 

        /* FIXME: We don't let you set the IMM bit from the command line: add it to lp->auto_neg_cnf here */

 

        /* FIXME: we don't set the Ethernet address on the command line.  Use

           ifconfig IFACE hw ether AABBCCDDEEFF */

 

       printk(KERN_INFO "cs89x0 media %s%s%s",// 如果没有EEPROM ,将打印单个空格

              (lp->adapter_cnf & A_CNF_10B_T)?"RJ-45,":"",

              (lp->adapter_cnf & A_CNF_AUI)?"AUI,":"",

              (lp->adapter_cnf & A_CNF_10B_2)?"BNC,":"");

 

       lp->irq_map = 0xffff;

 

       /* If this is a CS8900 then no pnp soft */

       if (lp->chip_type != CS8900 &&

           /* Check if the ISA IRQ has been set  */

              (i = readreg(dev, PP_CS8920_ISAINT) & 0xff,

               (i != 0 && i cs8900 芯片

              if (!dev->irq)

                     dev->irq = i;

       } else {

              i = lp->isa_config & INT_NO_MASK;// 由于没有EEPROM ,所以lp->isa_config=0

              if (lp->chip_type == CS8900) {

 

                     /* Translate the IRQ using the IRQ mapping table. */

                     if (i >= sizeof(cs8900_irq_map)/sizeof(cs8900_irq_map[0]))

             //sizeof(cs8900_irq_map)/sizeof(cs8900_irq_map[0]) cs8900_irq_map 数据元个数

                            printk("\ncs89x0: invalid ISA interrupt number %d\n", i);

                     else

                            i = cs8900_irq_map[i];//i 保存了中断号

 

                     lp->irq_map = CS8900_IRQ_MAP; /* fixed IRQ map for CS8900 */

              } else {

                     int irq_map_buff[IRQ_MAP_LEN/2];

 

                     if (get_eeprom_data(dev, IRQ_MAP_EEPROM_DATA,

                                       IRQ_MAP_LEN/2,

                                       irq_map_buff) >= 0) {

                            if ((irq_map_buff[0] & 0xff) == PNP_IRQ_FRMT)

                                   lp->irq_map = (irq_map_buff[0]>>8) | (irq_map_buff[1]

                     }

 

              }

              if (!dev->irq)

                     dev->irq = i;// 填充dev->irq ,按照前面的定义该值为53

       }

 

       printk(" IRQ %d", dev->irq);

 

#if ALLOW_DMA

       if (lp->use_dma) {

              get_dma_channel(dev);

              printk(", DMA %d", dev->dma);

       }

       else

#endif

       {

              printk(", programmed I/O");

       }

 

       /* print the ethernet address. */

       printk(", MAC");

       for (i = 0; i

       {

              printk("%c%02x", i ? ':' : ' ', dev->dev_addr[i]);

       }

 

 

// 指定相关cs8900 支持的相关操作

       dev->open             = net_open;      // 打开接口,该函数应该注册所有的系统资源

       dev->stop              = net_close;      // 停止接口,该函数执行的操作与open 相反

       dev->tx_timeout            = net_timeout;                     // 传输超时时,将调用此函数

       dev->watchdog_timeo= HZ; // 在网络层确定传输超时,调用tx_timeout 前的最小延时

       dev->hard_start_xmit    = net_send_packet;              // 该方法初始化数据包传输。完整的数据包在sk_buffer

       dev->get_stats              = net_get_stats;                          // 获得接口的统计信息

       dev->set_multicast_list = set_multicast_list; // 当组播列表发生改变,或者设备标志发

                                                                         // 生改变时,将调 用该方法

       dev->set_mac_address = set_mac_address;             // 设置硬件的地址

#ifdef CONFIG_NET_POLL_CONTROLLER

       dev->poll_controller       = net_poll_controller;           // 该方法在进制中断的情况下,

// 要求驱动程序在接口上检 查事件。它被用于特定的内核网络中,比如远程控制台

// 和内核网络调试。

#endif

 

       printk("\n");

       if (net_debug)

              printk("cs89x0_probe1() successful\n");

 

       retval = register_netdev(dev);// 向内核注册cs8900 驱动程序

       if (retval)

              goto out3;

       return 0;

out3:

       writeword(dev->base_addr, ADD_PORT, PP_ChipID);

out2:

       release_region(ioaddr & ~3, NETCARD_IO_EXTENT);

out1:

       return retval;

}

 

1.9 一些问题总结

       这里没有讲解cs8900 驱动的移植过程,需要移植的朋友可以参见前面提到的weibing 的博客文章。这里需要补充的是很多朋友在移植成功了以后,发现内核会打印出如下的消息:

cs89x0_probe1() successful

cs89x0:cs89x0_probe(0x0)

cs8900a: request_region(0xf4000300, 0x10) failed

cs89x0: no cs8900 or cs8920 detected.  Be sure to disable PnP with SETUP

该消息的很奇怪,先是说cs89x0_peobe1 成功,后面又提示说失败,而且没有影响网络驱动的功能,这时为什么呢?回忆在net_olddevs_init 函数时,它调用了8 ethif_probe2 函数,也就是说cs89x0_peobe1 不被调用了一次,第一次成功了,后面的肯定会失败,如果按照这种思路,那应该会打印7 次失败信息,而这里只有一次,不解ing !这个问题也可以简单的解决,我采用了下面的方法解决此问题,判断cs89x0_probe 的参数是否大于0 ,如果大于0 就直接退出,这使得cs89x0_probe 函数只正常执行一次,这样处理以后就没有提示失败的信息。

 

二、 net_open net_close net_interrupt

2.1 net_open net_close

net_open 函数主要完成的工作有:(这段 net_open 函数的概要内容总结来源于网络,网址: http://www.akae.cn/bbs/archiver/?tid-6657.html

A .获取私有数据指针存放于 lp

B .启动设备总线控制功能和启动存储器

C .调用 request_irq() 请求中断并注册 net_interrupt 为中断服务程序;

D .写中断号存于设备中 write_irq()

E .如果无法申请中断号,则返回错误

F .如果支持 DMA 则通过以下函数初始化 DMA

              _get_dma_pages();

              get_order();

               dma_page_eq();

G .申请 DMA requeset_dma()

H .初始化设备结构中关于 DMA 的参数,并使能 DMA

I .设置以太网地址, writereg()

J .检测链路,从而确定连接媒体类型

K .配置链路:

               a.10B_T:detect_tp()

              b.AUI:detect_aui()

              c.10B_2:detect_bnu()

               d.AUTO: 从头检测自动配置

L .输出信息

M .启动链路串行接收和发送功能

N .初始化 lp 相关参数

O .配置 DMA set_dma_cfg()

P .配置芯片相关寄存器

Q .配置 DMA 缓冲 dma_bufcfg()

R .使能芯片中断;

S .启动网络传输队列, netif_start_queue()

       这部分内容都与 cs8900 芯片具体操作相关,相对来说和比较简单,下面直接给出 net_open net_close 的相关注解

static int net_open(struct net_device *dev)

{

       struct net_local *lp = netdev_priv(dev);

       int result = 0;

       int i;

       int ret;

 

       ......// 省略一些信息

/* FIXME: Cirrus' release had this: */

       writereg(dev, PP_BusCTL, readreg(dev, PP_BusCTL)|ENABLE_IRQ );                                                           // 使能 cs8900 中断

             

     write_irq(dev, lp->chip_type, dev->irq);     // 该函数选择 cs8900 芯片内部的中断线,

                                                                       // 见本文件中的 write_irq 实现

//++++++++++++++++++++++ 这段代码为自己添加,内核原版中没有

#if defined(CONFIG_ARCH_S3C2410)

           set_irq_type(dev->irq, IRQT_RISING);  // 该函数在 kernel\irq\chip 实现,

        // 可选择的中断类型有 include\linux\interrupt.h 中定义,此处设置为上升沿触发中断

#endif

//++++++++++++++++++++++

              ret = request_irq(dev->irq, &net_interrupt, 0, dev->name, dev); // 注册中断

              if (ret) {

                     if (net_debug)

                            printk(KERN_DEBUG "cs89x0: request_irq(%d) failed\n", dev->irq);

                     goto bad_out;

              }

……

 

       /* set the Ethernet address */// MAC 地址设置到 cs8900 Individual Address 寄存器

       for (i=0; i

              writereg(dev, PP_IA+i*2, dev->dev_addr[i*2] | (dev->dev_addr[i*2+1]

 

       /* while we're testing the interface, leave interrupts disabled */

       writereg(dev, PP_BusCTL, MEMORY_ON);  // 使 cd8900 工作到 memory 模式,

                                                                               // 如果 dev->mem_start 域为 0

     // 将关闭该模式

 

 

       // 以下代码为选择 cs8900 的物理传输媒体的类型

       /* Set the LineCTL quintuplet based on adapter configuration read from EEPROM */

       // 由于没有 eeprom lp->adapter_cnf cs89x0_probe1 中未设置,此值为 0.

       if ((lp->adapter_cnf & A_CNF_EXTND_10B_2) && (lp->adapter_cnf & A_CNF_LOW_RX_SQUELCH))

                lp->linectl = LOW_RX_SQUELCH;

       else

                lp->linectl = 0;

 

        /* check to make sure that they have the "right" hardware available */

       switch(lp->adapter_cnf & A_CNF_MEDIA_TYPE) {

       case A_CNF_MEDIA_10B_T: result = lp->adapter_cnf & A_CNF_10B_T; break;

       case A_CNF_MEDIA_AUI:   result = lp->adapter_cnf & A_CNF_AUI; break;

       case A_CNF_MEDIA_10B_2: result = lp->adapter_cnf & A_CNF_10B_2; break;

        default: result = lp->adapter_cnf & (A_CNF_10B_T | A_CNF_AUI | A_CNF_10B_2);

        }

 

#if defined(CONFIG_ARCH_PNX0105) || defined(CONFIG_ARCH_S3C2410)//+++++++++

       result = A_CNF_10B_T;     // 上面由于 lp->adapter_cnf=0 ,导致 result=0

         // 这里额外设置 该值可以根据需要实际情况设置,可设置的值可在

        // cs89x0.h 中找到 当然这里也可以设置 lp->adapter_cnf 成想要的值

#endif

        if (!result) {//result==0 时执行此段代码

                printk(KERN_ERR "%s: EEPROM is configured for unavailable media\n", dev->name);

        release_irq:

        ......

                writereg(dev, PP_LineCTL, readreg(dev, PP_LineCTL) & ~(SERIAL_TX_ON | SERIAL_RX_ON));

                free_irq(dev->irq, dev);

              ret = -EAGAIN;

              goto bad_out;

       }

 

        /* set the hardware to the configured choice */

       switch(lp->adapter_cnf & A_CNF_MEDIA_TYPE) {//lp->adapter_cnf & A_CNF_MEDIA_TYPE==0,

                                                   // 不符合任何 case 情况,将执行 default ,但未实现 default

       case A_CNF_MEDIA_10B_T:

                result = detect_tp(dev);  //detect_tp 探测物理传输媒体类型是 RJ-45H,

                                                     // 还是 RJ-45F

                if (result==DETECTED_NONE) {

                        printk(KERN_WARNING "%s: 10Base-T (RJ-45) has no cable\n", dev->name);

                        if (lp->auto_neg_cnf & IMM_BIT) /* check "ignore missing media" bit */

                                 result = DETECTED_RJ45H; /* Yes! I don't care if I see a link pulse */

                }

              break;

       case A_CNF_MEDIA_AUI:

                result = detect_aui(dev);//detect_tp 探测物理传输媒体类型是否为 AUI

                if (result==DETECTED_NONE) {

                        printk(KERN_WARNING "%s: 10Base-5 (AUI) has no cable\n", dev->name);

                        if (lp->auto_neg_cnf & IMM_BIT) /* check "ignore missing media" bit */

                                result = DETECTED_AUI; /* Yes! I don't care if I see a carrrier */

                }

              break;

       case A_CNF_MEDIA_10B_2:

                result = detect_bnc(dev);  //detect_tp 探测物理传输媒体类型是否为 BNC

                if (result==DETECTED_NONE) {

                        printk(KERN_WARNING "%s: 10Base-2 (BNC) has no cable\n", dev->name);

                        if (lp->auto_neg_cnf & IMM_BIT) /* check "ignore missing media" bit */

                                result = DETECTED_BNC; /* Yes! I don't care if I can xmit a packet */

                }

              break;

       case A_CNF_MEDIA_AUTO:

              writereg(dev, PP_LineCTL, lp->linectl | AUTO_AUI_10BASET);

              if (lp->adapter_cnf & A_CNF_10B_T)

                     if ((result = detect_tp(dev)) != DETECTED_NONE)

                            break;

              if (lp->adapter_cnf & A_CNF_AUI)

                     if ((result = detect_aui(dev)) != DETECTED_NONE)

                            break;

              if (lp->adapter_cnf & A_CNF_10B_2)

                     if ((result = detect_bnc(dev)) != DETECTED_NONE)

                            break;

              printk(KERN_ERR "%s: no media detected\n", dev->name);

                goto release_irq;

       }

       switch(result) {     // 上面将 result 赋成了 A_CNF_10B_T ,该值为 1 ,刚好等于

                 // DETECTED_RJ45H 所以也可以在上面的 result 中直接赋成

                 // DETECTED_RJ45H 或者其他类型的接口

       case DETECTED_NONE:

              printk(KERN_ERR "%s: no network cable attached to configured media\n", dev->name);

                goto release_irq;

       case DETECTED_RJ45H:

              printk(KERN_INFO "%s: using half-duplex 10Base-T (RJ-45)\n", dev->name);

              break;

       case DETECTED_RJ45F:

              printk(KERN_INFO "%s: using full-duplex 10Base-T (RJ-45)\n", dev->name);

              break;

       case DETECTED_AUI:

              printk(KERN_INFO "%s: using 10Base-5 (AUI)\n", dev->name);

              break;

       case DETECTED_BNC:

              printk(KERN_INFO "%s: using 10Base-2 (BNC)\n", dev->name);

              break;

       }

 

       /* Turn on both receive and transmit operations */

       writereg(dev, PP_LineCTL, readreg(dev, PP_LineCTL) | SERIAL_RX_ON | SERIAL_TX_ON);

 

       /* Receive only error free packets addressed to this card */

       lp->rx_mode = 0;// 确定接收模式, 0 表示接收广播 , 0 表示全部接收

       writereg(dev, PP_RxCTL, DEF_RX_ACCEPT);    // 初始化接收控制器 RxCTL 为默认

                      // 接收模式。 该模式下,只接收 Broadcast Individual CRC

                                            // 正确的数据包, 具体可查看 cs8900 手册。

 

       lp->curr_rx_cfg = RX_OK_ENBL | RX_CRC_ERROR_ENBL;   // 接收 OK 产生中断,

                                                      // CRC 错产生中断

 

       if (lp->isa_config & STREAM_TRANSFER)// 判断是否打开 cs8900 stream 传输模式

              lp->curr_rx_cfg |= RX_STREAM_ENBL;// 使用 stream 模式 , 此处没有启用。

 

       writereg(dev, PP_RxCFG, lp->curr_rx_cfg);       // 初始化接收配置控制器 RxCFG

                                                                                  // 这里确定了接收中断源

 

  // 初始化发送配置控制器 TxCFG TxCFG 寄存器的全部有效位置为 1

  // 也确定了发送中断源

       writereg(dev, PP_TxCFG, TX_LOST_CRS_ENBL | TX_SQE_ERROR_ENBL | TX_OK_ENBL |

              TX_LATE_COL_ENBL | TX_JBR_ENBL | TX_ANY_COL_ENBL | TX_16_COL_ENBL);

 

       writereg(dev, PP_BufCFG, READY_FOR_TX_ENBL | RX_MISS_COUNT_OVRFLOW_ENBL |

 

              TX_COL_COUNT_OVRFLOW_ENBL | TX_UNDERRUN_ENBL);

 

       /* now that we've got our act together, enable everything */

       writereg(dev, PP_BusCTL, ENABLE_IRQ          // 开中断

                | (dev->mem_start?MEMORY_ON : 0)      // 没有设置共享内存空

                                                              // dev->mem_start 0 memory 模式将被关闭

 

                 );

        netif_start_queue(dev);  // 激活设备发送队列,以便内核可以开始发送数据

       if (net_debug > 1)

              printk("cs89x0: net_open() succeeded\n");

       return 0;

bad_out:

       return ret;

}

 

 

net_close(struct net_device *dev)

{

  ......// 略去 DMA 部分

 

       netif_stop_queue(dev);// 停止设备发送队列,通知内核不能使用该设备发送数据

 

       writereg(dev, PP_RxCFG, 0);// 禁用接收

       writereg(dev, PP_TxCFG, 0);// 禁用发送

       writereg(dev, PP_BufCFG, 0);// 关闭 cs8900 内部缓冲区

       writereg(dev, PP_BusCTL, 0);// 停止总线

 

       free_irq(dev->irq, dev);// 释放占用的中断线

 

  ......// 略去 DMA 部分

 

       /* Update the statistics here. */

       return 0;

}

2.2 net_interrupt

       该函数的大体流程如下:(此段总结来源同上)

A .获取设备私有数据 net_priv()

B .读取 CS8900 的中断端口状态 readword()

C .判断中断类型:

              a. 接收事件:调用 net_rx() 接收数据;

              b. 传输事件:调用 netif_wake_queue() 唤醒传输队列,进行异常处理;

              c. 缓冲区事件:可以发送数据,调用 netif_wake_queue() 唤醒传输队列;

              d. 接收包丢失事件:初始化相关 error ,错误计数

e. 传输冲突时间:初始化相关 error

D .返回中断句柄。

net_interrupt 中断处理函数的实现非常简单,它首先读出 cs8900 ISQ 寄存器的值,然后根据 ISQ 的值分别处理各种情况。当中断发生时,这些中断实际反映在相应的寄存器中, ISQ 寄存器用低 6 位记录了当前寄存器的编号,高 10 位记录了当前寄存器的实际内容。这些寄存器有: RxEvent(Register 4) TxEvent(Register 8) BufEvent(RegisterC) RxMISS(Register 10) TxCOL(Register 12) 。比如,传输成功后, cs8900 TxEvent 的第 0bit 置为 1 ,如果允许该事件中断,那么 ISQ 寄存器的低 6 位将记录 TxEvent 的编号 8 ,并且将 TxEvent 寄存器的高 10 copy 到它的高 10 位中。

       net_interrupt 注解如下:

 

static irqreturn_t net_interrupt(int irq, void *dev_id)

{

       struct net_device *dev = dev_id;

       struct net_local *lp;

       int ioaddr, status;

       int handled = 0;

 

       ioaddr = dev->base_addr;

       lp = netdev_priv(dev);

 

       /* we MUST read all the events out of the ISQ, otherwise we'll never

           get interrupted again.  As a consequence, we can't have any limit

           on the number of times we loop in the interrupt handler.  The

           hardware guarantees that eventually we'll run out of events.  Of

           course, if you're on a slow machine, and packets are arriving

           faster than you can read them off, you're screwed.  Hasta la

           vista, baby!  */

       while ((status = readword(dev->base_addr, ISQ_PORT))) {

   //ISQ_PORT=08h, 根据 cs8900 的用户手册, 这里再次说明了 cs8900 工作在 I/O 模式

              if (net_debug > 4)printk("%s: event=%04x\n", dev->name, status);

              handled = 1;

              switch(status & ISQ_EVENT_MASK) {    //ISQ_EVENT_MASK=0x3f

                                   // 确定 ISQ 的低 6 位, 该 6 位纪录了发生中断的寄存器

              case ISQ_RECEIVER_EVENT:     //ISQ_RECEIVER_EVENT=0x04,

                                         // 中断源来自 RxEvent , 表示接收到了数据包

                     /* Got a packet(s). */

                     net_rx(dev);

                     break;

              case ISQ_TRANSMITTER_EVENT:   //ISQ_RECEIVER_EVENT=0x08,

                       // 中断源来自 TxEvent ,根据 net_open 中设置,有很多发送事件

                     // 可以产生中断,需要分别处理

                     lp->stats.tx_packets++;    // 累加发送包的总数

                     netif_wake_queue(dev);  /* Inform upper layers. */ 

                     if ((status & ( TX_OK |    //ISQ 的高 10 位描述了 TxEvent 的实际内容,

                              // 也即实际传输的信息 这里似乎 status 应该右移 6 位?的确应该

                              // 这样,这里之所以没这样做, 是因为 TX_OK 等这些值,在设

                              // 计时已经左移了 6

                                   TX_LOST_CRS |

                                   TX_SQE_ERROR |

                                   TX_LATE_COL |

                                   TX_16_COL)) != TX_OK) {  // 做些错误统计工作

                            if ((status & TX_OK) == 0) lp->stats.tx_errors++;

                            if (status & TX_LOST_CRS) lp->stats.tx_carrier_errors++;

                            if (status & TX_SQE_ERROR) lp->stats.tx_heartbeat_errors++;

                            if (status & TX_LATE_COL) lp->stats.tx_window_errors++;

                            if (status & TX_16_COL) lp->stats.tx_aborted_errors++;

                     }

                     break;

              case ISQ_BUFFER_EVENT:  //ISQ_RECEIVER_EVENT=0x0c,

                                                              // 中断源来自 BufEvent

      if (status & TX_UNDERRUN) {    // 这里说明估计的发送长度过短,可能需要做调整

                            if (net_debug > 0) printk("%s: transmit underrun\n", dev->name);

                                lp->send_underrun++;

                                if (lp->send_underrun == 3) lp->send_cmd = TX_AFTER_381; // 此值 cs89x0_probe1 时初始化为 5 ,这里修正。

                                else if (lp->send_underrun == 6) lp->send_cmd = TX_AFTER_ALL;

                            /* transmit cycle is done, although

                               frame wasn't transmitted - this

                               avoids having to wait for the upper

                               layers to timeout on us, in the

                               event of a tx underrun */

                            netif_wake_queue(dev); /* Inform upper layers. */

                        }

      ......//DMA 部分

                     break;

              case ISQ_RX_MISS_EVENT:   //ISQ_RX_MISS_EVENT=0x10,

                                         // 中断来自于 RxMISS , 该寄存器的高 10 位记录丢失的数据包

                     lp->stats.rx_missed_errors += (status >>6);

                     break;

              case ISQ_TX_COL_EVENT:        //ISQ_TX_COL_EVENT=0x12, 中断来自于

                // TxCOL , 该寄存器的高 10 位记录发了生冲突的数据包

                     lp->stats.collisions += (status >>6);

                     break;

              }

       }

       return IRQ_RETVAL(handled);

}

 

三、 net_rx net_send_packet

3.1 net_rx

在这部分将介绍cs8900 驱动的两个最重要的函数,内核通过该两个函数实现了数据的收发。net_rx 函数的主要功能是从cs8900 的片上数据缓冲区中将数据传送给sk_buff 缓冲区,sk_buff 是网络驱动程序与Linux 内核通信的缓冲区。该结构可在\include\linux\skbuff.h 中找到。net_rx 函数的功能可总结如下:(该总结来源于:http://www.akae.cn/bbs/archiver/?tid-6657.html

A .获取私有数据存放于lp 中;

B .获取设备缓冲区状态和缓冲长度;

C .如果状态不为RX_OK 则计数接收数据错误次数count_rx_error()

D .分配一个sk_buf 区间

E .字对齐,skb_reserve()

F .插入数据到接收口,insw()

G .写入数据;

H .初始化sk_buff 结构,eth_type_trans()

I .进入上层接收函数netif_rx()

J .初始化设备的计数;

 

net_rx 函数的注解如下所示:

static void net_rx(struct net_device *dev)

{

       struct net_local *lp = netdev_priv(dev);             //lp 指向驱动程序的私有数据区

       struct sk_buff *skb;                             // 申请skb_buff 指针

       int status, length;

 

       int ioaddr = dev->base_addr;                       // 得到cs8900 的基地址

       status = readword(ioaddr, RX_FRAME_PORT);       // 获取cs8900 片上缓冲区的状态

       length = readword(ioaddr, RX_FRAME_PORT);       // 获取cs8900 片上缓冲区的长度

 

       if ((status & RX_OK) == 0) {    // 状态为接收错误,调用count_rx_errors 统计错误

              count_rx_errors(status, lp);

              return;

       }

 

       /* Malloc up new buffer. */

       skb = dev_alloc_skb(length + 2);             // 分配一个缓冲区,dev_alloc_skb 函数以

                   //GFP_ATOMIC 优先级调用alloc_skb alloc_skb 的功能为分配一个缓冲区

                   // 并初始化skb->data skb->tail skb_head 域。dev_alloc_skb alloc_skb 的区

                   // 别为,前者在skb->data skb_head 之间保留了一些空间,网络 层使用这

                   // 一数据空间进行优化工作,驱动程序不该访问该空间。

       if (skb == NULL) {    //skb 缓冲区分配失败?

……

              lp->stats.rx_dropped++;  // 直接将丢包数加1

              return;

       }

       skb_reserve(skb, 2);      /* longword align L3 header */    // 该函数增加skb data tail

               // 该函数可填充缓冲区之 前保留报文头空间,大多数以太网在数据包之前

               // 保留2 个字节,这样IP 头可在14 字节的以太网头之 后,在16 字节边界上对

               // 齐。这里也空了两个字节,这两个自己加上14 字节的以太网头刚好16

              // 节。 所以这里的主要作用是字对齐。

       skb->dev = dev;

 

       readwords(ioaddr, RX_FRAME_PORT, skb_put(skb, length), length >> 1);  //skb_put

            // 数的作用是更新skb tail len 成员,也即在缓冲区尾部添加数据,该函数返

            // skb->tail 的先前值。整句代码的含义为, 从cs8900 的数据缓冲区中读取

      //length 个字节数据到skb 缓冲区。由于readwords 是以读取字(两个字 节)为

            // 单位,所以length 应该保持字对齐,也即length 右移一位。

       if (length & 1)       // 因为前面length 以字对齐,如果length 为单字节,

                                   // 所以这里应该补上最后一个字节

              skb->data[length-1] = readword(ioaddr, RX_FRAME_PORT);

……

        skb->protocol=eth_type_trans(skb,dev);  // 该函数定义在 linux/net/ethernet/eth.c 中,

                                                 // 该处可参见linux 设备 驱动程序相关章节

       netif_rx(skb);       // 通知内核已经接收到一个数据包,并封装入一个套接字缓冲区

       dev->last_rx = jiffies;                    // 更新最后的接收包时间

       lp->stats.rx_packets++;                // 接收的总数据包数加1

       lp->stats.rx_bytes += length;         // 接收的字节数加上length

}

 

3.1 net_send_packet

       net_send_parcket 为内核提供了数据包发送功能,该函数在cs89x0_probe1 中被赋予了net_device hard_start_xmit 域,当内核需要发送数据包时,将调用dev-> hard_start_xmit 完成最后的数据包发送。该函数被调用的前提是,在调用该函数之前,内核已经将数据包放入了skb 缓冲区中。该函数的主要任务有:

A .获取设备私有数据指针

B .加环形锁,spin_lock_irq()

C .检测缓冲区是否为满,若满则调用netif_stop_queue() 暂停发送队列;

D .写发送命令和发送长度,writeword()

E .读取发送总线状态readreg()

F .解环形锁,spin_unlock_irq()

G .设置传输时钟计数;

H .释放相应sk_buff, dev_kfree_skb().

下面为此函数的简单注释:

static int net_send_packet(struct sk_buff *skb, struct net_device *dev)

{

       struct net_local *lp = netdev_priv(dev);            // 获得驱动程序的私有数据

 

……

 

       spin_lock_irq(&lp->lock);         // 获得自旋锁,以便进入临界区

       netif_stop_queue(dev);    // 通知内核暂停内核与驱动程序间的数据传递,也即告诉

                                              // 内核不要向skb 缓冲区填充数据。

 

       /* initiate a transmit sequence */  // 初始化cs8900 的发送对列,主要为写命令和数

                                                         // 据长度,为数据发送做准备

       writeword(dev->base_addr, TX_CMD_PORT, lp->send_cmd);

       writeword(dev->base_addr, TX_LEN_PORT, skb->len);

 

       /* Test to see if the chip has allocated memory for the packet * /       // 查看cs8900 是否为

                                                                                      // 发送分配了地址空间。

       if ((readreg(dev, PP_BusST) & READY_FOR_TX_NOW) == 0) {

           …….

              spin_unlock_irq(&lp->lock);

              if (net_debug) printk("cs89x0: Tx buffer not free!\n");

              return 1;

       }

       /* Write the contents of the packet */

       writewords(dev->base_addr, TX_FRAME_PORT,skb->data,(skb->len+1) >>1);   // 将数

                                                                                                               // 据交给cs8900 发送

       spin_unlock_irq(&lp->lock);         // 发送结束,释放自旋锁

       lp->stats.tx_bytes += skb->len;    // 累加发送的总字节数

       dev->trans_start = jiffies;             // 更新最后的传输时间

       dev_kfree_skb (skb);                  // 发送完毕,释放skb 缓冲区

    ……

       return 0;

}

总结:

       cs8900 驱动中,主要简解了驱动程序中的部分重要函数,包括初始化、打开/ 关闭网络驱动和发送/ 接收数据。对于其余的驱动程序代码,如超时处理、状态获取等函数没做解释,它们的实现也比较简单。由于自己板子上没有EEPROM ,所以也没有分析与EEPROM 相关部分的代码。DMA 部分好像编译进去会错,所以也没有去分析,以后有时间再去弄弄DMA 部分。

 

 

 

 

 

 

 

目录
相关文章
|
29天前
|
Linux API 调度
Linux系统驱动跟裸机驱动的区别
Linux系统驱动跟裸机驱动的区别
27 0
|
1月前
|
Linux Android开发
嵌入式linux中Framebuffer 驱动程序框架分析
嵌入式linux中Framebuffer 驱动程序框架分析
26 0
|
1月前
|
Linux C语言 SoC
嵌入式linux总线设备驱动模型分析
嵌入式linux总线设备驱动模型分析
32 1
|
27天前
|
监控 Shell Linux
【Shell 命令集合 网络通讯 】Linux 分析串口的状态 statserial命令 使用指南
【Shell 命令集合 网络通讯 】Linux 分析串口的状态 statserial命令 使用指南
32 0
|
27天前
|
存储 缓存 Linux
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
35 0
|
3天前
|
Linux Go
Linux命令Top 100驱动人生! 面试必备
探索Linux命令不再迷茫!本文分10部分详解20个基础命令,带你由浅入深掌握文件、目录管理和文本处理。 [1]: <https://cloud.tencent.com/developer/article/2396114> [2]: <https://pan.quark.cn/s/865a0bbd5720> [3]: <https://yv4kfv1n3j.feishu.cn/docx/MRyxdaqz8ow5RjxyL1ucrvOYnnH>
46 0
|
16天前
|
Prometheus 监控 数据可视化
linux分析方法与技巧
【4月更文挑战第3天】在Linux环境中,进行日志分析和系统性能分析的关键方法包括:使用`cat`, `less`, `tail`查看和过滤日志,`logrotate`管理日志文件,`rsyslog`或`syslog-ng`聚合日志,以及通过`top`, `mpstat`, `pidstat`, `free`, `iostat`, `netstat`, `strace`, `sar`, `dstat`等工具监控CPU、内存、磁盘I/O和网络。对于高级分析,可利用Brendan Gregg的性能工具,以及Grafana、Prometheus等可视化工具。
16 2
linux分析方法与技巧
|
17天前
|
Linux
Linux驱动运行灯 Heartbeat
Linux驱动运行灯 Heartbeat
8 0
|
23天前
|
监控 Linux Shell
Linux 进程问题调查探秘:分析和排查频繁创建进程问题
Linux 进程问题调查探秘:分析和排查频繁创建进程问题
39 0
|
23天前
|
消息中间件 存储 网络协议
Linux IPC 进程间通讯方式的深入对比与分析和权衡
Linux IPC 进程间通讯方式的深入对比与分析和权衡
65 0