51/AVR单片机技术驿站!  <在线翻译>     便利工具    特色网址   无弹窗、无插件的绿色站点...  英才招聘   学历查询  喜欢>>收藏我站 

当前位置:首页 > ARM/CPLD/综合 > 详细内容
Linux-2.6.20的cs8900驱动分析经验总结
发布时间:2009/7/23  阅读次数:1185  字体大小: 【】 【】【

几经波折,在开发板上终于可以使用网络了。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) < 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 < __initcall_end; 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 < 8; ++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) { /* Dont 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 ,
                        //Andrew Morton 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 < ETH_ALEN/2; 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) < 0) {
               //读取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) < 0) {
                        /* 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] << 8;

                          /* eeprom_buff has 32-bit ints, so we cant just memcpy it */
                          /* store the initial memory base address */
                          for (i = 0; i < ETH_ALEN/2; 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 dont let you set dc-dc polarity or low RX squelch from the command line: add it here */

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

              /* FIXME: we dont 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 < CS8920_NO_INTS))) {//非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] << 8);
                                   }

                        }
                        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 < ETH_ALEN; 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芯片内部的中断线,

二、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);    //该函数在kernelirqchip实现,

              //可选择的中断类型有includelinuxinterrupt.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
, dev->irq);

                                   goto bad_out;

                        }

……



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

           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));



           /* while were 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
, 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
, dev->name);

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

                                                      result = DETECTED_RJ45H; /* Yes! I dont 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
, dev->name);

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

                                                      result = DETECTED_AUI; /* Yes! I dont 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
, dev->name);

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

                                                      result = DETECTED_BNC; /* Yes! I dont 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
, 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
, dev->name);

                          goto release_irq;

           case DETECTED_RJ45H:

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

                        break;

           case DETECTED_RJ45F:

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

                        break;

           case DETECTED_AUI:

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

                        break;

           case DETECTED_BNC:

                        printk(KERN_INFO %s: using 10Base-2 (BNC)
, 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 weve 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
);

           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 well never

                   get interrupted again.    As a consequence, we cant have any limit

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

                   hardware guarantees that eventually well run out of events.    Of

                   course, if youre on a slow machine, and packets are arriving

                   faster than you can read them off, youre 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
, 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
, 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 wasnt 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);
}

, 以太网地址,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芯片内部的中断线,

二、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);    //该函数在kernelirqchip实现,

              //可选择的中断类型有includelinuxinterrupt.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
, dev->irq);

                                   goto bad_out;

                        }

……



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

           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));



           /* while were 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
, 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
, dev->name);

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

                                                      result = DETECTED_RJ45H; /* Yes! I dont 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
, dev->name);

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

                                                      result = DETECTED_AUI; /* Yes! I dont 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
, dev->name);

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

                                                      result = DETECTED_BNC; /* Yes! I dont 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
, 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
, dev->name);

                          goto release_irq;

           case DETECTED_RJ45H:

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

                        break;

           case DETECTED_RJ45F:

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

                        break;

           case DETECTED_AUI:

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

                        break;

           case DETECTED_BNC:

                        printk(KERN_INFO %s: using 10Base-2 (BNC)
, 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 weve 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
);

           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 well never

                   get interrupted again.    As a consequence, we cant have any limit

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

                   hardware guarantees that eventually well run out of events.    Of

                   course, if youre on a slow machine, and packets are arriving

                   faster than you can read them off, youre 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
, 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
, 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 wasnt 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);
}

我要评论
  • 匿名发表
  • [添加到收藏夹]
  • 发表评论:(匿名发表无需登录,已登录用户可直接发表。) 登录状态:未登录
最新评论
所有评论[0]
    暂无已审核评论!

网站导航 管理登陆 ┊ 免责声明 问题反馈  友链说明
本站部分内容来自网络共享资源,如有冒犯您的权利请来信告之删除或纠正!
不得对本站进行复制、盗链或镜像,转载内容须获得同意或授权;欢迎友情链接、站务合作!

    我要报警 Alexa
 mcusy_cn#126.com (请把#改成@) 交流:522422171
本站学习交流群:138..158(高级群1-)、77930286(高级群2)、61804809(群3)
Copyright© MCUSY All Rights Reserved
本站网警备案号: WZ36040002485
  ICP备案证书号:粤ICP备09034963号