Linux-2.6.20的cs8900驅(qū)動(dòng)分析(一)http://blog.chinaunix.net/u1/49924/showart_488174.html 幾經(jīng)波折,在開(kāi)發(fā)板上終于可以使用網(wǎng)絡(luò)了。Linux內(nèi)核可以通過(guò)網(wǎng)絡(luò)掛接網(wǎng)絡(luò)文件系統(tǒng)了。首先感謝Internet,Google等幫助過(guò)我的工具,還要感謝各位嵌友的無(wú)私奉獻(xiàn)。在移植的過(guò)程中尤其感激weibing的博客文章cs8900移植linux-2.6.19.2,根據(jù)他的文章使cs8900成功跑起來(lái)。此文章可以在http://weibing.blogbus.com/logs/4467465.html找到。 在解釋網(wǎng)絡(luò)驅(qū)動(dòng)前,先說(shuō)說(shuō)自己的硬件配置: 1. 處理器為s3c2410 2. 網(wǎng)絡(luò)芯片cs8900a 3. cs8900a映射到s3c2410的bank3空間 4. cs8900a占用int9號(hào)中斷 5. Linux內(nèi)核版本為2.6.20一、初始化階段 網(wǎng)絡(luò)初始化被調(diào)用的路徑為:init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2->cs89x0_probe->cs89x0_probe1真是不容易啊,終于進(jìn)到cs89x0_probe1了,在這里開(kāi)始探測(cè)和初始化cs8900了。下面就按照這個(gè)順序來(lái)說(shuō)明網(wǎng)絡(luò)驅(qū)動(dòng)第一階段的工作。注意:這里的調(diào)用順序是將cs8900驅(qū)動(dòng)編入內(nèi)核所產(chǎn)生的,如果將cs8900驅(qū)動(dòng)選為模塊,這個(gè)路徑:init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2也會(huì)執(zhí)行。1.1 init函數(shù)我們知道當(dāng)start_kernel函數(shù)完成后就會(huì)啟動(dòng)init進(jìn)程執(zhí)行,在真正的應(yīng)用程序init進(jìn)程(如busybox的/sbin/init)之前,Linux還需要執(zhí)行一些初始化操作。init的代碼可以在<top_dir>\init\main.c中找到,它的代碼如下:static int init(void * unused){ lock_kernel();…… //省略多cpu的初始化代碼先 do_basic_setup(); //我們所關(guān)注的初始化函數(shù)…… 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(); //掛接根文件系統(tǒng) }…… free_initmem(); //釋放初始化代碼的空間 unlock_kernel();…… //這幾段沒(méi)看懂 if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) //檢查控制臺(tái) //console是否存在 printk(KERN_WARNING "Warning: unable to open an initial console.\n");……//這幾段沒(méi)看懂 if (ramdisk_execute_command) { //運(yùn)行ramdisk_execute_command指定的init用戶進(jìn)程 run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s\n", ramdisk_execute_command); } …… if (execute_command) { //判斷在啟動(dòng)時(shí)是否指定了init參數(shù),如果指定, //此值將賦給execute_command run_init_process(execute_command); //開(kāi)始執(zhí)行用戶init進(jìn)程,如果成功將不會(huì) //返回。 printk(KERN_WARNING "Failed to execute %s. Attempting " "defaults...\n", execute_command); }//如果沒(méi)有指定init啟動(dòng)參數(shù),則查找下面的目錄init進(jìn)程,如果找到則不會(huì)返回 run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh"); //如果上面的程序都出錯(cuò),則打印下面的信息,如果內(nèi)核找到init進(jìn)程, //則程序不會(huì)指向到此處 panic("No init found. Try passing init= option to kernel.");}1.2 do_basic_setup函數(shù)在這里我們最關(guān)心的是do_basic_setup函數(shù),顧名思義該函數(shù)的功能就是“做基本設(shè)置”,它的實(shí)現(xiàn)代碼也在<top_dir>\init\main.c中。do_basic_setup()完成外設(shè)及其驅(qū)動(dòng)程序的加載和初始化。該函數(shù)代碼如下所示: static void __init do_basic_setup(void){ /* drivers will send hotplug events */ init_workqueues(); //初始化工作隊(duì)列 usermodehelper_init(); //初始化khelper內(nèi)核線程,還沒(méi)弄清楚 driver_init(); //初始化內(nèi)核的設(shè)備管理架構(gòu)需要的數(shù)據(jù)結(jié)構(gòu), //很復(fù)雜,以后在談這部分。 #ifdef CONFIG_SYSCTL sysctl_init(); //沒(méi)搞懂#endif do_initcalls(); //重點(diǎn)函數(shù),初始化的主要工作就靠它了}1.3 do_ initcalls函數(shù) do_initcalls函數(shù)將會(huì)調(diào)用內(nèi)核中所有的初始化函數(shù),它的代碼同樣在<top_dir>\init\main.c中。do_initcalls函數(shù)調(diào)用其他初始化函數(shù)相當(dāng)簡(jiǎn)潔,它的關(guān)鍵代碼如下所示:initcall_t *call;for (call = __initcall_start; call < __initcall_end; call++) {…… result = (*call)();……} 簡(jiǎn)潔歸簡(jiǎn)潔,但這段代碼是什么意思呢?這說(shuō)來(lái)就話長(zhǎng)了,最重要的應(yīng)該是先了解Linux處理初始化的大體思想,由于Linux有很多部分需要初始化,每個(gè)部分都有自己的初始化函數(shù),如果按照常理一個(gè)一個(gè)的調(diào)用未免顯得冗長(zhǎng),而且也不便于擴(kuò)展。那么Linux是怎么處理的呢?首先,Linux將各個(gè)部分的初始化函數(shù)編譯到一個(gè)塊內(nèi)存區(qū)中,當(dāng)初始化完了以后釋放這塊內(nèi)存區(qū),這就是init函數(shù)中free_initmem所要做的事。然后,再在另外的內(nèi)存區(qū)中存放一些函數(shù)指針,讓每個(gè)指針指向一個(gè)初始化函數(shù)。然后在do_initcalls中依次根據(jù)這些指針調(diào)用初始化函數(shù)。上面一段就是Linux實(shí)現(xiàn)初始化的大體思想,下面我們看看它最終是怎么實(shí)現(xiàn)的。首先要了解的是__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宏有三個(gè)參數(shù),level表示初始化函數(shù)的級(jí)別,level值的大小覺(jué)得了調(diào)用順序,level越小越先被調(diào)用,fn就是具體的初始化函數(shù),id簡(jiǎn)單標(biāo)識(shí)初始化函數(shù),現(xiàn)在還沒(méi)找到有什么用^_^。__define_initcall的功能為,首先聲明一個(gè)initcall_t類型的函數(shù)指針__initcall_##fn##id,initcall_t的原型為:typedef int (*initcall_t)(void);該類型可簡(jiǎn)單理解為函數(shù)指針類型^_^。然后,讓該函數(shù)指針指向fn。最后,通過(guò)編譯器的編譯參數(shù)將此指針?lè)诺街付ǖ目臻g".initcall" level ".init"中,__attribute_used向編譯器說(shuō)明這段代碼有用,即使在沒(méi)用到的時(shí)候,編譯器也不會(huì)警告。__attribute__的__section__參數(shù)表示該段代碼放入什么內(nèi)存區(qū)域中,也即指定編譯到什么地方,編譯參數(shù)更詳細(xì)的地方可以查閱GCC文檔,在gcc官方網(wǎng)站http://gcc.gnu.org/onlinedocs/中能找到各個(gè)版本的手冊(cè)。這樣說(shuō)來(lái)還是比較抽象,下面舉個(gè)例子來(lái)說(shuō)明: 假如有初始化函數(shù)init_foolish函數(shù),現(xiàn)在使用__define_initcall宏向內(nèi)核加入該函數(shù)。假如調(diào)用方式如下:__define_initcall("0",init_foolish,1);那么,__define_initcall宏首先申請(qǐng)一個(gè)initcall_t類型的函數(shù)指針__initcall_init_foolish1(注意替換關(guān)系),且使該指針指向了init_foolish,函數(shù)指針__initcall_init_foolish1被放到.initcall.0.init內(nèi)存區(qū)域中,這個(gè)標(biāo)志在連接時(shí)會(huì)用到。 有了上面的基礎(chǔ)知識(shí),現(xiàn)在回到do_initcalls函數(shù)中,首先注意到是__initcall_start和__initcall_end,它們的作用就是界定了存放初始化函數(shù)指針區(qū)域的起始地址,也即從__initcall_start開(kāi)始到__initcall_end結(jié)束的區(qū)域中存放了指向各個(gè)初始化函數(shù)的函數(shù)指針。換句話說(shuō),只要某段程序代碼從__initcall_start開(kāi)始依次調(diào)用函數(shù)指針,那么就可以完成各個(gè)部分的初始化工作,這顯得十分優(yōu)雅而且便于擴(kuò)充,再看看do_initcalls,它何嘗不是如此呢。這里還有一個(gè)有用的技巧就是__initcall_start和__initcall_end的原型是initcall_t型的數(shù)組,以后可以使用這種技巧^_^。 現(xiàn)在我們知道了do_initcalls函數(shù)的實(shí)現(xiàn)原理,那么到底它調(diào)用了多少初始化函數(shù)呢?我們?cè)鯓硬拍苤滥兀扛鶕?jù)上面的分析,我們知道所有的初始化函數(shù)的指針都放在__initcall_start和__initcall_end區(qū)域期間,而函數(shù)指針與它指向的函數(shù)之間又有固定的關(guān)系,如上面的例子,初始化函數(shù)名為init_foolish,指向它的函數(shù)指針就是__initcall_init_foolish1,即在此函數(shù)加上前綴__initcall_和一個(gè)數(shù)字后綴,反之,從函數(shù)指針也可推出初始化函數(shù)名。有了這兩個(gè)信息,我們就可以很方便的找個(gè)初始化函數(shù)。怎么找呢??首先打開(kāi)Linux完后產(chǎn)生的System.map文件,然后找到__initcall_start和__initcall_end字符串,你會(huì)發(fā)現(xiàn)它們之間有很多類似于__initcall_xxx1這樣的符號(hào),這些符號(hào)就是我們需要的函數(shù)指針了,這樣就可推出初始化函數(shù)的名字。比如,我們這里需要的函數(shù)指針__initcall_net_olddevs_init6,按照上面的名字規(guī)則,很容易推出它所指向的初始化函數(shù)名字是net_olddevs_init。 得到了初始化函數(shù)的名字又怎么樣呢?又不知道它在哪個(gè)文件里,不要著急!請(qǐng)打開(kāi)你的瀏覽器登陸http://lxr.linux.no/ident網(wǎng)站,然后選擇Linux版本和架構(gòu),然后可以搜索我們想要的信息。比如我輸入net_olddevs_init,然后我就會(huì)得到該函數(shù)所在文件的相關(guān)信息。1.4 net_olddevs_init函數(shù) 我們知道net_olddevs_init函數(shù)在do_initcalls函數(shù)中被調(diào)用并執(zhí)行,那么它到底要做什么呢?看看實(shí)現(xiàn)代碼就知道了,它的實(shí)現(xiàn)代碼可以在<top_dir>\drivers\net\Space.c中找到。對(duì)于網(wǎng)絡(luò)驅(qū)動(dòng)部分的主要實(shí)現(xiàn)代碼如下:static int __init net_olddevs_init(void){ …… int num; for (num = 0; num < 8; ++num) ethif_probe2(num); ……}這段代碼就不用講解了吧,嘿嘿!就是調(diào)用了8次ethif_probe2,趕快去看看ethif_probe2長(zhǎng)什么樣子。1.5 ethif_probe2函數(shù) 先看看該函數(shù)的實(shí)現(xiàn)代碼,該代碼也在<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調(diào)用了8次, // 所以unit的值為0~7,也即在這里可以注冊(cè)eth0~eth7八個(gè)網(wǎng)絡(luò)設(shè)備 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));} 該函數(shù)首先調(diào)用netdev_boot_base所給的設(shè)備是否已經(jīng)向內(nèi)核注冊(cè),如果已注冊(cè)netdev_boot_base返回1,隨后推出ethif_probe2。如果設(shè)備沒(méi)注冊(cè),則又調(diào)用函數(shù)probe_list2四次,每次傳遞的傳輸不同,注意到每次傳遞的第二個(gè)參數(shù)不同,這個(gè)參數(shù)也是相當(dāng)重要的,這里拿isa_probes參數(shù)為例說(shuō)明,因?yàn)檫@個(gè)參數(shù)與cs89x0_probe有關(guān),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的驅(qū)動(dòng)選為非編譯進(jìn)內(nèi)核,那么它的探測(cè)函數(shù)cs89x0_probe就不會(huì)存在于isa_probes數(shù)組中,所以在初始階段就不能被調(diào)用。從上面的代碼可以知道devprobe2類型至少包括兩個(gè)域,至少一個(gè)域?yàn)楹瘮?shù)指針,看看它的原型如下:struct devprobe2 { struct net_device *(*probe)(int unit); //函數(shù)指針,指向探測(cè)函數(shù) int status; /* non-zero if autoprobe has failed */};下面看看probe_list2函數(shù)是怎么表演的。1.6 ethif_probe2函數(shù) 對(duì)于ethif_probe2函數(shù)也沒(méi)有什么需要說(shuō)明的,它的主要任務(wù)是依次調(diào)用devprobe2類型的probe域指向的函數(shù)。他的實(shí)現(xiàn)代碼同樣在<top_dir>\drivers\net\Space.c中,它的關(guān)鍵代碼如下: 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函數(shù) 從該函數(shù)起,真正開(kāi)始執(zhí)行與cs8900驅(qū)動(dòng)初始化程序,該函數(shù)在<top_dir>\drivers\net\cs89x0.c文件實(shí)現(xiàn)。下面依次解釋該函數(shù)。 struct net_device * __init cs89x0_probe(int unit){ struct net_device *dev = alloc_etherdev(sizeof(struct net_local)); //該函數(shù)申請(qǐng)一個(gè)net_device+//sizeof(struct net_local)的空間,net_local是cs8900驅(qū)動(dòng)的私有數(shù)據(jù)空間。 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); //檢查是否給定了啟動(dòng)參數(shù),如果給定了 //啟動(dòng)參數(shù),此函數(shù)將初始化dev的irq、 //base_addr、mem_start和mem_end域。 io = dev->base_addr; //io實(shí)際實(shí)質(zhì)cs8900所占地址空間的起始地址, //此地址為虛擬地址 irq = dev->irq; if (net_debug) printk("cs89x0:cs89x0_probe(0x%x)\n", io);//下面根據(jù)io的值調(diào)用cs89x0_probe1函數(shù) if (io > 0x1ff) {/* Check a single specified location. */ //此段沒(méi)搞懂,由于沒(méi)給 //啟動(dòng)參數(shù),這里也不會(huì)執(zhí)行 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型數(shù)組,在cs89x0.c文件中定//義,里面列出了cs8900可能占用空間的起始地址,這些地址//將在cs89x0_probe1函數(shù)中用于向內(nèi)核申請(qǐng)。 if (cs89x0_probe1(dev, *port, 0) == 0) // cs89x0_probe1探測(cè)成功就返回0 break; dev->irq = irq; } if (!*port) err = -ENODEV; } if (err) goto out; return dev;out: free_netdev(dev); //表示探測(cè)失敗,這里就釋放dev的空間,隨后打印些消息 printk(KERN_WARNING "cs89x0: no cs8900 or cs8920 detected. Be sure to disable PnP with SETUP\n"); return ERR_PTR(err);} 從上面的程序清單可以看到該函數(shù)還沒(méi)有真正的開(kāi)始探測(cè)cs8900,實(shí)質(zhì)的探測(cè)工作是讓cs89x0_probe1完成的。在解釋cs89x0_probe1之前先提一下網(wǎng)絡(luò)驅(qū)動(dòng)程序中非常重要的一些函數(shù)。內(nèi)核需要一個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)管理或者描述每個(gè)網(wǎng)絡(luò)驅(qū)動(dòng)程序,這個(gè)數(shù)據(jù)類型就是struct net_device,該數(shù)據(jù)類型包括很多域,詳細(xì)的解釋可以參見(jiàn)《Linux 設(shè)備驅(qū)動(dòng)程序》一書(shū)中的描述,也可以參見(jiàn)源代碼(在<top_dir>\include\linux\netdevice.h中,源碼中也有詳細(xì)的注解)。內(nèi)核為了編程方便特地實(shí)現(xiàn)了函數(shù)alloc_netdev來(lái)完成對(duì)net_device的空間分配。那么alloc_etherdev函數(shù)主要針對(duì)以太網(wǎng)在alloc_netdev基礎(chǔ)上封裝的一個(gè)函數(shù),它除了申請(qǐng)net_device空間外,還會(huì)初始化net_device的相關(guān)域。1.8 cs89x0_probe1函數(shù) 對(duì)于該函數(shù)完成了最終的網(wǎng)絡(luò)芯片cs8900的探測(cè)工作,里面涉及了一些芯片硬件的操作,看這個(gè)源碼之前應(yīng)該對(duì)cs8900a芯片比較熟悉,或者在讀的時(shí)候把它的芯片manual打開(kāi)。這函數(shù)的代碼很長(zhǎng),大約有300多行,但是它沒(méi)有什么特別的技巧,只要認(rèn)真閱讀,最多半天就能搞明了^_^,下面給出該函數(shù)在ARM架構(gòu)下,且沒(méi)開(kāi)DMA情況下的注解。static int __init cs89x0_probe1(struct net_device *dev, int ioaddr, int modular){ struct net_local *lp = netdev_priv(dev); //dev 空間已經(jīng)在cs89x0_probe中申請(qǐng)成功, //這里lp從dev中得到自己的私有數(shù)據(jù),也即net_local數(shù)據(jù)域的起始地址, //netdev_priv 函數(shù)為網(wǎng)絡(luò)驅(qū)動(dòng)中得到私有數(shù)據(jù)的標(biāo)準(zhǔn)函數(shù),當(dāng)然也可以直接 //使用dev->priv,但不鼓勵(lì)這種做法。 //下面申請(qǐng)些局部變量 static unsigned version_printed; int i; int tmp; unsigned rev_type = 0; int eeprom_buff[CHKSUM_LEN]; int retval; SET_MODULE_OWNER(dev); // 設(shè)置模塊的屬于者,該宏定義在 //include\linux\netdevice文件中,實(shí)際為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); //初始化自旋鎖,自旋鎖用于保護(hù)dev結(jié)構(gòu)的互斥訪問(wèn)#ifndef MODULE //在make menuconfig時(shí)確定,表示是否將網(wǎng)絡(luò)驅(qū)動(dòng)編譯為模塊。#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函數(shù)向內(nèi)核注冊(cè)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. */ //下面這段代碼比較費(fèi)解,不是說(shuō)代碼的意思不好解釋,而是為什么只在寄地址 //才檢查呢?根據(jù)數(shù)據(jù)手冊(cè)的說(shuō)明“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存在部分的依據(jù); //從后面的代碼中可以看到,還需要確定cs8900的ID號(hào)后才能真正確保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, //按照數(shù)據(jù)手冊(cè)寫(xiě)0 //下面這段代碼確定cs8900的EISA ID號(hào)是否為0x630E。這里DATA_PORT=0x0C是//cs8900的數(shù)據(jù)口,CHIP_EISA_ID_SIG=0x630E,0x630E為Crystal公司在EISA的注冊(cè)//號(hào)。通過(guò)下面的檢查以后就真正確定了cs8900存在,硬件電路ok。//以及向內(nèi)核注冊(cè)的端口地址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,這個(gè)值是實(shí)際 //測(cè)試出來(lái)的,但根據(jù)cs8900A的數(shù)據(jù)手冊(cè),該值應(yīng)該是0x0700。??? lp->chip_type = rev_type &~ REVISON_BITS; lp->chip_revision = ((rev_type & REVISON_BITS) >> 8) + 'A'; //執(zhí)行上面賦值后lp->chip_type=0x0,lp->chip_revision=0x4b。注意這里的加法運(yùn)算 //rev_type & REVISON_BITS)>>8=0x0a,這個(gè)0x0a是數(shù)字,'A'轉(zhuǎn)換成十 //六進(jìn)制后為0x41,所以,0x41+0x0a=0x4b,0x4b在ascii碼中對(duì)應(yīng)的字母為'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; //默認(rèn)每次傳輸381字節(jié), //根據(jù)數(shù)據(jù)手冊(cè)可以傳輸?shù)淖止?jié) //選項(xiàng)有5、381、1021、all四個(gè),但這里的驅(qū)動(dòng)不支持1021字節(jié)的選項(xiàng)。 if (lp->chip_type == CS8900 && lp->chip_revision >= 'F')//此條件滿足 lp->send_cmd = TX_NOW;//選擇每次傳輸5字節(jié) 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);//按照上面的分析,這里打印的應(yīng)該形如: //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); //重新復(fù)位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結(jié)構(gòu)的相關(guān)域。 if ((readreg(dev, PP_SelfST) & (EEPROM_OK | EEPROM_PRESENT)) == (EEPROM_OK|EEPROM_PRESENT)) {//讀取SelfST寄存器,并判斷EEPROM //是否存在若存在,則判斷是否讀取操作成功。上述條件滿足, //則讀出EEPROM中的配置信息填充dev相關(guān)域。 /* Load the MAC. */ for (i=0; i < ETH_ALEN/2; i++) {//讀取以太網(wǎng)地址 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的相關(guān)信息 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. 擴(kuò)展配置*/ /* 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",//如果沒(méi)有EEPROM,將打印單個(gè)空格 (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;//由于沒(méi)有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數(shù)據(jù)元個(gè)數(shù) printk("\ncs89x0: invalid ISA interrupt number %d\n", i); else i = cs8900_irq_map[i];//i保存了中斷號(hào) 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]); } //指定相關(guān)cs8900支持的相關(guān)操作 dev->open = net_open; //打開(kāi)接口,該函數(shù)應(yīng)該注冊(cè)所有的系統(tǒng)資源 dev->stop = net_close; //停止接口,該函數(shù)執(zhí)行的操作與open相反 dev->tx_timeout = net_timeout; //傳輸超時(shí)時(shí),將調(diào)用此函數(shù) dev->watchdog_timeo= HZ; //在網(wǎng)絡(luò)層確定傳輸超時(shí),調(diào)用tx_timeout前的最小延時(shí) dev->hard_start_xmit = net_send_packet; //該方法初始化數(shù)據(jù)包傳輸。完整的數(shù)據(jù)包在sk_buffer中 dev->get_stats = net_get_stats; //獲得接口的統(tǒng)計(jì)信息 dev->set_multicast_list = set_multicast_list; //當(dāng)組播列表發(fā)生改變,或者設(shè)備標(biāo)志發(fā) //生改變時(shí),將調(diào)用該方法 dev->set_mac_address = set_mac_address; //設(shè)置硬件的地址#ifdef CONFIG_NET_POLL_CONTROLLER dev->poll_controller = net_poll_controller; //該方法在進(jìn)制中斷的情況下,//要求驅(qū)動(dòng)程序在接口上檢查事件。它被用于特定的內(nèi)核網(wǎng)絡(luò)中,比如遠(yuǎn)程控制臺(tái)//和內(nèi)核網(wǎng)絡(luò)調(diào)試。#endif printk("\n"); if (net_debug) printk("cs89x0_probe1() successful\n"); retval = register_netdev(dev);//向內(nèi)核注冊(cè)cs8900驅(qū)動(dòng)程序 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 一些問(wèn)題總結(jié) 這里沒(méi)有講解cs8900驅(qū)動(dòng)的移植過(guò)程,需要移植的朋友可以參見(jiàn)前面提到的weibing的博客文章。這里需要補(bǔ)充的是很多朋友在移植成功了以后,發(fā)現(xiàn)內(nèi)核會(huì)打印出如下的消息:cs89x0_probe1() successfulcs89x0:cs89x0_probe(0x0)cs8900a: request_region(0xf4000300, 0x10) failedcs89x0: no cs8900 or cs8920 detected. Be sure to disable PnP with SETUP該消息的很奇怪,先是說(shuō)cs89x0_peobe1成功,后面又提示說(shuō)失敗,而且沒(méi)有影響網(wǎng)絡(luò)驅(qū)動(dòng)的功能,這時(shí)為什么呢?回憶在net_olddevs_init函數(shù)時(shí),它調(diào)用了8次ethif_probe2函數(shù),也就是說(shuō)cs89x0_peobe1不被調(diào)用了一次,第一次成功了,后面的肯定會(huì)失敗,如果按照這種思路,那應(yīng)該會(huì)打印7次失敗信息,而這里只有一次,不解ing!這個(gè)問(wèn)題也可以簡(jiǎn)單的解決,我采用了下面的方法解決此問(wèn)題,判斷cs89x0_probe的參數(shù)是否大于0,如果大于0就直接退出,這使得cs89x0_probe函數(shù)只正常執(zhí)行一次,這樣處理以后就沒(méi)有提示失敗的信息。 To be continued…… ------ anmnmnly ------ 2007.11.30
|
|