Linux-2.6.20的cs8900驱动分析(一)

来源:互联网 发布:mac系统没了怎么办 编辑:程序博客网 时间:2024/05/17 03:51

几经波折,在开发板上终于可以使用网络了。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 的代码可以在<top_dir>/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 函数,顾名思义该函数的功能就是“做基本设置”,它的实现代码也在<top_dir>/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 函数将会调用内核中所有的初始化函数,它的代码同样在<top_dir>/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 宏,该宏的定义在<top_dir>/ 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 函数中被调用并执行,那么它到底要做什么呢?看看实现代码就知道了,它的实现代码可以在<top_dir>/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 函数
       先看看该函数的实现代码,该代码也在<top_dir>/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 的定义也在<top_dir>/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 域指向的函数。他的实现代码同样在<top_dir>/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 驱动初始化程序,该函数在<top_dir>/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 设备驱动程序》一书中的描述,也可以参见源代码(在<top_dir>/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 < 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 can't 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 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 < 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 函数只正常执行一次,这样处理以后就没有提示失败的信息。
                                                                                          To be continued……
                                                                                           ------ anmnmnly
                                                                                           ------ 2007.11.30

原创粉丝点击