Linux-2.6.20的cs8900驅動分析(一)

幾經波折,在開發板上終於可以使用網絡了。Linux 內核可以通過網絡掛接網絡文件系統了。首先感謝InternetGoogle 等幫助過我的工具,還要感謝各位嵌友的無私奉獻。在移植的過程中尤其感激weibing 的博客文章cs8900 移植linux-2.6.19.2 ,根據他的文章使cs8900
成功跑起來。此文章可以在http://weibing.blogbus.com/logs/4467465.html 找到。
   在解釋網絡驅動前,先說說自己的硬件配置:
    1. 處理器爲s3c2410
    2. 網絡芯片cs8900a
    3. cs8900a 映射到s3c2410bank3 空間
    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##idinitcall_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);
       ……
}
這段代碼就不用講解了吧,嘿嘿!就是調用了8ethif_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 的值爲07 ,也即在這裏可以註冊eth0eth7 八個網絡設備
       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_localcs8900 驅動的私有數據空間。
       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);                   // 檢查是否給定了啓動參數,如果給定了
                                                                           // 啓動參數,此函數將初始 devirq
                                                                          //base_addrmem_startmem_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_portlistunsigned 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 中申請成功,
              // 這裏lpdev 中得到自己的私有數據,也即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) { // 這裏的modular0,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_EXTENT16
       // 所以可以看出cs8900 工作在I/O 模式。cs8900memory 模式需要映射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 存在部分的依據;
 // 從後面的代碼中可以看到,還需要確定cs8900ID 號後才能真正確保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
 
// 下面這段代碼確定cs8900EISA ID 號是否爲0x630E 。這裏DATA_PORT=0x0C
//cs8900 的數據口, CHIP_EISA_ID_SIG=0x630E0x630ECrystal 公司在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=0x0lp->chip_revision=0x4b 。注意這裏的加法運算
  //rev_type & REVISON_BITS)>>8=0x0a ,這個0x0a 是數字,'A' 轉換成十
  // 六進制後爲0x41 ,所以,0x41+0x0a=0x4b0x4bascii 碼中對應的字母爲'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 字節,
                                                                     // 根據數據手冊可以傳輸的字節
       // 選項有53811021all 四個,但這裏的驅動不支持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 <[email protected]>,
              //Andrew Morton <[email protected]> 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 函數時,它調用了8ethif_probe2 函數,也就是說cs89x0_peobe1 不被調用了一次,第一次成功了,後面的肯定會失敗,如果按照這種思路,那應該會打印7 次失敗信息,而這裏只有一次,不解ing !這個問題也可以簡單的解決,我採用了下面的方法解決此問題,判斷cs89x0_probe 的參數是否大於0 ,如果大於0 就直接退出,這使得cs89x0_probe 函數只正常執行一次,這樣處理以後就沒有提示失敗的信息。
                                                                                          To be continued……
                                                                                           ------ anmnmnly
                                                                                           ------ 2007.11.30
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章