文章轉自 --小雷總,具體點這裡
說明1:本文分析基于核心源碼版本為linux-2.6.31
說明2:本文在了解了linux中總線、裝置和驅動模型的基礎上加以分析代碼
雖然Linux驅動程式應該是和具體的硬體平台分離的,但是為了更好的了解DM9000的驅動程式,這裡還是結合一下Mini2440開發闆,這樣也可以更好的體會如何實作驅動和平台分離。
本文分成以下幾個部分:
一、Mini2440開發闆上DM9000的電氣連接配接和Mach-mini2440.c檔案的關系。
二、兩個重要的結構體介紹:sk_buff和net_device
三、具體代碼分析
一、Mini2440開發闆上DM9000的電氣連接配接和Mach-mini2440.c檔案的關系
Mini2440開發闆上DM9000與S3C2440的連接配接關系如下:
圖一
圖二
圖三
其中片選信号AEN使用了nGCS4,是以網卡的記憶體區域在BANK4,也就是從位址0x20000000開始。而為什麼MACH_MINI2440_DM9K_BASE = 0x20000300,看下面分析
首先,Mini2440隻有27根位址線,addr0---addr26。可尋址範圍是128M。
有八個bank用做為記憶體晶片的選通腳,可以尋址128M*8=1G.
BANK4 可尋址範圍是:0x20000000-------0x20000000+128M(即:0x27FFFFFF)
是以位址寫成 0x20000000-------0x20000000+128M 之間都是可以的,但是注意: addr2 必須為 0 ,為什麼呢?
這些内容在Mach檔案中有如下展現:
- #define S3C2410_CS4 (0x20000000)
- #define MACH_MINI2440_DM9K_BASE (S3C2410_CS4 + 0x300)
- static struct resource mini2440_dm9k_resource[] __initdata = {
- [0] = {
- .start = MACH_MINI2440_DM9K_BASE,
- .end = MACH_MINI2440_DM9K_BASE + 3,
- .flags = IORESOURCE_MEM
- },
- [1] = {
- .start = MACH_MINI2440_DM9K_BASE + 4,
- .end = MACH_MINI2440_DM9K_BASE + 7,
- .flags = IORESOURCE_MEM
- },
- [2] = {
- .start = IRQ_EINT7,
- .end = IRQ_EINT7,
- .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE,
- }
- };
另外在Mach檔案中還定義了DM9000平台裝置,裝置名稱為“dm9000”,裝置資源就是上面定義的IO和中斷資源。代碼清單如下:
- static struct dm9000_plat_data mini2440_dm9k_pdata __initdata = {
- .flags = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM),
- };
- static struct platform_device mini2440_device_eth __initdata = {
- .name = "dm9000",
- .id = -1,
- .num_resources = ARRAY_SIZE(mini2440_dm9k_resource),
- .resource = mini2440_dm9k_resource,
- .dev = {
- .platform_data = &mini2440_dm9k_pdata,
- },
- };
這樣我們把要發送的dm9000的位址資訊固定放在了0x20000300,把要存放在該位址的資料存放在0x2000 0304。(因為位址和資料線複用,是以位址和資料需要分兩次傳送),即:數組【0】是dm9000要取的位址,數組【1】存放dm9000位址對應要存放的資料。數組[2]外部中斷号
問題三:.end= MACH_MINI2440_DM9K_BASE +3,為什麼尾位址是+3?
因為一個位址需要四個位元組。
問題四:數組【1】.start= MACH_MINI2440_DM9K_BASE +4,為什麼?
注意這個位址是不能随便選的,必須根據數組【0】.start位址和硬體連接配接情況進行對應的。現在來回答之前提到的問題,為什麼MACH_MINI2440_DM9K_BASE定義的時候addr2為能為0.?
請看原理圖二,或者圖三。
注意這裡非常重要,一語雙關。Addr2即是Cup的位址線addr0-----addr26中的一員:
1、它的任何一位變化都會使得目前CPU通路的位址發生變化。
2、他連接配接着dm9000的資料/位址選通引腳,即:addr2=0則寫的是位址,addr2=1則寫的是資料。
因為當我們的基址是:0x2000 0300的時候,我們現在要寫資料了,這時候把addr2置高電平也就是1就開始寫資料了,同時cup的通路位址發生改變,變成0x20000304了。(這樣做有一個好處,就是我們要寫位址了就寫到0x2000 0300,要寫資料了就寫到0x20000300+4,而不需要特意的去改變選通腳狀态。不需要寫位址的時候特意置0,在寫資料的時候特意置1,因為我們通路方式改變的時候選通也會随着改變。)
反過來,我們要設這個位址存放是dm9000的位址還是資料時要考慮addr2的情況。也就MACH_MINI2440_DM9K_BASEp這個位址可以設addr2=0的,0x20000000-------0x20000000+128M的任何一個位址。當然不同的開發闆可能會不一樣,我們硬體可以換成addr2----addr26中的任何一位。
(addr0,addr1,因為一個資料需要四個位元組存放,是以這兩位不能做為選通引腳,不然兩個存放位址就有重疊的地方了,如果一個資料是一個位元組大小則可以)
比如說:我們硬體設定addr3為dm9000讀寫選通引腳,MACH_MINI2440_DM9K_BASEp就設成0x20000000,那麼可以這樣寫:
[0] = {
.start = MACH_MINI2440_DM9K_BASE,//addr3=0,此空間存放的是要發送的位址
.end = MACH_MINI2440_DM9K_BASE +3,
.flags = IORESOURCE_MEM
},
[1] = {
.start = MACH_MINI2440_DM9K_BASE + 8,//addr3=1,此空間存放的是要發送的資料
.end = MACH_MINI2440_DM9K_BASE +11,
.flags = IORESOURCE_MEM
},
數組【2】則是跟中斷有關的硬體資源,這個檢視上面的原理圖與之對應就可以了。
問題六:.flags = (DM9000_PLATF_16BITONLY|DM9000_PLATF_NO_EEPROM),傳送資料總線為什麼選擇16位寬度?
S2c2440支援8位,16位,32位資料傳送。看上面的原理圖三可知:dm9000隻用了LDATA0----LDATA15即16位寬度。
這個DM9000平台裝置作為衆多平台裝置中的一個在扳子初始化的時候就被添加到了總線上。代碼清單如下:
- MACHINE_START(MINI2440, "MINI2440")
- .phys_io = S3C2410_PA_UART,
- .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
- .boot_params = S3C2410_SDRAM_PA + 0x100,
- .map_io = mini2440_map_io,
- .init_machine = mini2440_init,
- .init_irq = s3c24xx_init_irq,
- .timer = &s3c24xx_timer,
- MACHINE_END
- static void __init mini2440_init(void)
- {
- ...
- ...
- platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
- ...
- ...
- }
- static struct platform_device *mini2440_devices[] __initdata = {
- &s3c_device_usb,
- &s3c_device_wdt,
- &s3c_device_i2c0,
- &s3c_device_rtc,
- &s3c_device_usbgadget,
- &mini2440_device_eth,
- &mini2440_led1,
- &mini2440_led2,
- &mini2440_led3,
- &mini2440_led4,
- &mini2440_button_device,
- &s3c_device_nand,
- &s3c_device_sdi,
- &s3c_device_iis,
- &mini2440_audio,
- };
二、兩個重要的結構體簡單介紹:sk_buff和net_device(可看另外的部落格講解)
*sk_buff
如果把網絡傳輸看成是運送貨物的話,那麼sk_buff就是這個“貨物”了,所有經手這個貨物的人都要幹點什麼事兒,要麼加個包裝,要麼印個戳兒等等。收貨的時候就要拆掉這些包裝,得到我們需要的貨物(payload data)。沒有貨物你還運輸什麼呢?由此可見sk_buff的重要性了。關于sk_buff的詳細介紹和幾個操作它的函數,參考本部落格轉載的一篇文章:“linux核心sk_buff的結構分析”,寫得非常明白了。贊一個~
*net_device
又是一個龐大的結構體。好吧,我承認我從來就沒有看全過這個結構體。它在核心中就是指代了一個網絡裝置。驅動程式需要在探測的時候配置設定并初始化這個結構體,然後使用register_netdev來注冊它,這樣就可以把操作硬體的函數與核心挂接在一起。
三、具體代碼的分析
在順序分析之前先看三個結構體變量和一個自定義的結構體。
* dm9000_driver變量。是platform_driver結構體變量,其中包含了重要的:驅動的名字(用來match)和幾個重要操作函數。
[c-sharp] view plain copy
- static struct platform_driver dm9000_driver = {
- .driver = {
- .name = "dm9000",
- .owner = THIS_MODULE,
- },
- .probe = dm9000_probe,
- .remove = __devexit_p(dm9000_drv_remove),
- .suspend = dm9000_drv_suspend,
- .resume = dm9000_drv_resume,
- };
* dm9000_netdev_ops變量。是net_device_ops結構體變量, 其中定義了操作net_device的重要函數,我們在驅動程式中根據需要的操作要填充這些函數。代碼清單如下:
[c-sharp] view plain copy
- static const struct net_device_ops dm9000_netdev_ops = {
- .ndo_open = dm9000_open,
- .ndo_stop = dm9000_stop,
- .ndo_start_xmit = dm9000_start_xmit,
- .ndo_tx_timeout = dm9000_timeout,
- .ndo_set_multicast_list = dm9000_hash_table,
- .ndo_do_ioctl = dm9000_ioctl,
- .ndo_change_mtu = eth_change_mtu,
- .ndo_validate_addr = eth_validate_addr,
- .ndo_set_mac_address = eth_mac_addr,
- #ifdef CONFIG_NET_POLL_CONTROLLER
- .ndo_poll_controller = dm9000_poll_controller,
- #endif
- };
* dm9000_ethtool_ops變量。是ethtool_ops結構體變量,為了支援ethtool,其中的函數主要是用于查詢和設定網卡參數(當然也有的驅動程式可能不支援ethtool)。代碼清單如下:
[c-sharp] view plain copy
- static const struct ethtool_ops dm9000_ethtool_ops = {
- .get_drvinfo = dm9000_get_drvinfo,
- .get_settings = dm9000_get_settings,
- .set_settings = dm9000_set_settings,
- .get_msglevel = dm9000_get_msglevel,
- .set_msglevel = dm9000_set_msglevel,
- .nway_reset = dm9000_nway_reset,
- .get_link = dm9000_get_link,
- .get_eeprom_len = dm9000_get_eeprom_len,
- .get_eeprom = dm9000_get_eeprom,
- .set_eeprom = dm9000_set_eeprom,
- };
* board_info結構體。用來儲存晶片相關的一些私有資訊。具體在代碼中分析。下面是這個結構體的清單。
[c-sharp] view plain copy
- typedef struct board_info {
- void __iomem *io_addr;
- void __iomem *io_data;
- u16 irq;
- u16 tx_pkt_cnt;
- u16 queue_pkt_len;
- u16 queue_start_addr;
- u16 dbug_cnt;
- u8 io_mode;
- u8 phy_addr;
- u8 imr_all;
- unsigned int flags;
- unsigned int in_suspend :1;
- int debug_level;
- enum dm9000_type type;
- void (*inblk)(void __iomem *port, void *data, int length);
- void (*outblk)(void __iomem *port, void *data, int length);
- void (*dumpblk)(void __iomem *port, int length);
- struct device *dev;
- struct resource *addr_res;
- struct resource *data_res;
- struct resource *addr_req;
- struct resource *data_req;
- struct resource *irq_res;
- struct mutex addr_lock;
- struct delayed_work phy_poll;
- struct net_device *ndev;
- spinlock_t lock;
- struct mii_if_info mii;
- u32 msg_enable;
- } board_info_t;
下面看一下具體代碼。
分析代碼還是從init順序開始。
1. 注冊平台驅動。
主要完成的任務是:将驅動添加到總線上,完成驅動和裝置的match,并執行驅動的probe函數。代碼清單如下:
[c-sharp] view plain copy
- static struct platform_driver dm9000_driver = {
- .driver = {
- .name = "dm9000",
- .owner = THIS_MODULE,
- },
- .probe = dm9000_probe,
- .remove = __devexit_p(dm9000_drv_remove),
- .suspend = dm9000_drv_suspend,
- .resume = dm9000_drv_resume,
- };
- static int __init
- dm9000_init(void)
- {
- printk(KERN_INFO "%s Ethernet Driver, V%s/n", CARDNAME, DRV_VERSION);
- return platform_driver_register(&dm9000_driver);
- }
2. probe函數。
主要完成的任務是:探測裝置獲得并儲存資源資訊,根據這些資訊申請記憶體和中斷,最後調用register_netdev注冊這個網絡裝置。以下是代碼清單,可以分成幾個部分來看:
1) 首先定義了幾個局部變量:
struct dm9000_plat_data *pdata = pdev->dev.platform_data;
struct board_info *db;
struct net_device *ndev;
2) 初始化一個網絡裝置。關鍵系統函數:alloc_etherdev()
3) 獲得資源資訊并将其儲存在board_info變量db中。關鍵系統函數:netdev_priv(), platform_get_resource()
4) 根據資源資訊配置設定記憶體,申請中斷等等, 并将申請後的資源資訊也儲存到db中,并且填充ndev中的參數。 關鍵系統函數:request_mem_region(), ioremap()。 自定義函數:dm9000_set_io()
5) 完成了第4步以後,回顧一下db和ndev中都有了什麼:
struct board_info *db:
addr_res -- 位址資源
data_res -- 資料資源
irq_res -- 中斷資源
addr_req -- 配置設定的位址記憶體資源
io_addr -- 寄存器I/O基位址
data_req -- 配置設定的資料記憶體資源
io_data -- 資料I/O基位址
dumpblk -- IO模式
outblk -- IO模式
inblk -- IO模式
lock -- 自旋鎖(已經被初始化)
addr_lock -- 互斥鎖(已經被初始化)
struct net_device *ndev:
base_addr -- 裝置IO位址
irq -- 裝置IRQ号
6) 裝置複位。硬體操作函數dm9000_reset()
7) 讀一下生産商和制造商的ID,應該是0x9000 0A46。 關鍵函數:ior()
8) 讀一下晶片類型。
========以上步驟結束後我們可以認為已經找到了DM9000========
9) 借助ether_setup()函數來部分初始化ndev。因為對以太網裝置來講,很多操作與屬性是固定的,核心可以幫助完成。
10) 手動初始化ndev的ops和db的mii部分。
11) (如果有的話)從EEPROM中讀取節點位址。這裡可以看到mini2440這個闆子上沒有為DM9000外挂EEPROM,是以讀取出來的全部是0xff。見函數dm9000_read_eeprom。 關于外挂EEPROM,可以參考datasheet上的7.EEPROM Format一節。
12) 很顯然ndev是我們在probe函數中定義的局部變量,如果我想在其他地方使用它怎麼辦呢? 這就需要把它儲存起來。核心提供了這個方法,使用函數platform_set_drvdata()可以将ndev儲存成平台總線裝置的私有資料。以後再要使用它時隻需調用platform_get_drvdata()就可以了。
13) 使用register_netdev()注冊ndev。
下面是代碼清單:
[c-sharp] view plain copy
- static int __devinit
- dm9000_probe(struct platform_device *pdev)
- {
- struct dm9000_plat_data *pdata = pdev->dev.platform_data;
- struct board_info *db;
- struct net_device *ndev;
- const unsigned char *mac_src;
- int ret = 0;
- int iosize;
- int i;
- u32 id_val;
- ndev = alloc_etherdev(sizeof(struct board_info));
- if (!ndev) {
- dev_err(&pdev->dev, "could not allocate device./n");
- return -ENOMEM;
- }
- SET_NETDEV_DEV(ndev, &pdev->dev);
- dev_dbg(&pdev->dev, "dm9000_probe()/n");
- db = netdev_priv(ndev);
- memset(db, 0, sizeof(*db));
- db->dev = &pdev->dev;
- db->ndev = ndev;
- spin_lock_init(&db->lock);
- mutex_init(&db->addr_lock);
- INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);
- db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
- db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (db->addr_res == NULL || db->data_res == NULL ||
- db->irq_res == NULL) {
- dev_err(db->dev, "insufficient resources/n");
- ret = -ENOENT;
- goto out;
- }
- iosize = res_size(db->addr_res);
- db->addr_req = request_mem_region(db->addr_res->start, iosize,
- pdev->name);
- if (db->addr_req == NULL) {
- dev_err(db->dev, "cannot claim address reg area/n");
- ret = -EIO;
- goto out;
- }
- db->io_addr = ioremap(db->addr_res->start, iosize);
- if (db->io_addr == NULL) {
- dev_err(db->dev, "failed to ioremap address reg/n");
- ret = -EINVAL;
- goto out;
- }
- iosize = res_size(db->data_res);
- db->data_req = request_mem_region(db->data_res->start, iosize,
- pdev->name);
- if (db->data_req == NULL) {
- dev_err(db->dev, "cannot claim data reg area/n");
- ret = -EIO;
- goto out;
- }
- db->io_data = ioremap(db->data_res->start, iosize);
- if (db->io_data == NULL) {
- dev_err(db->dev, "failed to ioremap data reg/n");
- ret = -EINVAL;
- goto out;
- }
- ndev->base_addr = (unsigned long)db->io_addr;
- ndev->irq = db->irq_res->start;
- dm9000_set_io(db, iosize);
- if (pdata != NULL) {
- if (pdata->flags & DM9000_PLATF_8BITONLY)
- dm9000_set_io(db, 1);
- if (pdata->flags & DM9000_PLATF_16BITONLY)
- dm9000_set_io(db, 2);
- if (pdata->flags & DM9000_PLATF_32BITONLY)
- dm9000_set_io(db, 4);
- if (pdata->inblk != NULL)
- db->inblk = pdata->inblk;
- if (pdata->outblk != NULL)
- db->outblk = pdata->outblk;
- if (pdata->dumpblk != NULL)
- db->dumpblk = pdata->dumpblk;
- db->flags = pdata->flags;
- }
- #ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
- db->flags |= DM9000_PLATF_SIMPLE_PHY;
- #endif
- dm9000_reset(db);
- for (i = 0; i < 8; i++) {
- id_val = ior(db, DM9000_VIDL);
- id_val |= (u32)ior(db, DM9000_VIDH) << 8;
- id_val |= (u32)ior(db, DM9000_PIDL) << 16;
- id_val |= (u32)ior(db, DM9000_PIDH) << 24;
- if (id_val == DM9000_ID)
- break;
- dev_err(db->dev, "read wrong id 0x%08x/n", id_val);
- }
- if (id_val != DM9000_ID) {
- dev_err(db->dev, "wrong id: 0x%08x/n", id_val);
- ret = -ENODEV;
- goto out;
- }
- id_val = ior(db, DM9000_CHIPR);
- dev_dbg(db->dev, "dm9000 revision 0x%02x/n", id_val);
- switch (id_val) {
- case CHIPR_DM9000A:
- db->type = TYPE_DM9000A;
- break;
- case CHIPR_DM9000B:
- db->type = TYPE_DM9000B;
- break;
- default:
- dev_dbg(db->dev, "ID %02x => defaulting to DM9000E/n", id_val);
- db->type = TYPE_DM9000E;
- }
- ether_setup(ndev);
- ndev->netdev_ops = &dm9000_netdev_ops;
- ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
- ndev->ethtool_ops = &dm9000_ethtool_ops;
- db->msg_enable = NETIF_MSG_LINK;
- db->mii.phy_id_mask = 0x1f;
- db->mii.reg_num_mask = 0x1f;
- db->mii.force_media = 0;
- db->mii.full_duplex = 0;
- db->mii.dev = ndev;
- db->mii.mdio_read = dm9000_phy_read;
- db->mii.mdio_write = dm9000_phy_write;
- mac_src = "eeprom";
- for (i = 0; i < 6; i += 2)
- dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i);
- if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {
- mac_src = "platform data";
- memcpy(ndev->dev_addr, pdata->dev_addr, 6);
- }
- if (!is_valid_ether_addr(ndev->dev_addr)) {
- mac_src = "chip";
- for (i = 0; i < 6; i++)
- ndev->dev_addr[i] = ior(db, i+DM9000_PAR);
- }
- if (!is_valid_ether_addr(ndev->dev_addr))
- dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
- "set using ifconfig/n", ndev->name);
- platform_set_drvdata(pdev, ndev);
- ret = register_netdev(ndev);
- if (ret == 0)
- printk(KERN_INFO "%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)/n",
- ndev->name, dm9000_type_to_char(db->type),
- db->io_addr, db->io_data, ndev->irq,
- ndev->dev_addr, mac_src);
- return 0;
- out:
- dev_err(db->dev, "not found (%d)./n", ret);
- dm9000_release_board(pdev, db);
- free_netdev(ndev);
- return ret;
- }
3. platform_driver的remove, suspend和resume的實作
remove函數的功能是把裝置從核心中移除,釋放記憶體區域。該函數在解除安裝子產品時被調用。代碼清單如下:
[c-sharp] view plain copy
- static int __devexit
- dm9000_drv_remove(struct platform_device *pdev)
- {
- struct net_device *ndev = platform_get_drvdata(pdev);
- platform_set_drvdata(pdev, NULL);
- unregister_netdev(ndev);
- dm9000_release_board(pdev, (board_info_t *) netdev_priv(ndev));
- free_netdev(ndev);
- dev_dbg(&pdev->dev, "released and freed device/n");
- return 0;
- }
suspend函數并不真正把裝置從核心中移除,而隻是标志裝置為removed狀态,并設定挂起标志位,最後關閉裝置。代碼清單如下:
[c-sharp] view plain copy
- static int
- dm9000_drv_suspend(struct platform_device *dev, pm_message_t state)
- {
- struct net_device *ndev = platform_get_drvdata(dev);
- board_info_t *db;
- if (ndev) {
- db = netdev_priv(ndev);
- db->in_suspend = 1;
- if (netif_running(ndev)) {
- netif_device_detach(ndev);
- dm9000_shutdown(ndev);
- }
- }
- return 0;
- }
resume函數将挂起的裝置複位并初始化,軟後将裝置标志為attached狀态,并設定挂起标志位。代碼清單如下:
[c-sharp] view plain copy
- static int
- dm9000_drv_resume(struct platform_device *dev)
- {
- struct net_device *ndev = platform_get_drvdata(dev);
- board_info_t *db = netdev_priv(ndev);
- if (ndev) {
- if (netif_running(ndev)) {
- dm9000_reset(db);
- dm9000_init_dm9000(ndev);
- netif_device_attach(ndev);
- }
- db->in_suspend = 0;
- }
- return 0;
- }
4. 下面看一下用于填充net_device中netdev_ops和ethtool_ops的一些函數。
代碼在上面已經寫出來了,為了看着友善在下面再寫一遍,可以看出雖然mini2440的闆子上沒有為DM9000挂EEPROM,但這裡還是定義了操作EEPROM的函數。就是說寫驅動的時候是不考慮具體的闆子的,你闆子用不用是你的事,但是我們的驅動應該所有的功能都考慮進去。這也展現了驅動和平台分離的設計思想。
[c-sharp] view plain copy
- static const struct net_device_ops dm9000_netdev_ops = {
- .ndo_open = dm9000_open,
- .ndo_stop = dm9000_stop,
- .ndo_start_xmit = dm9000_start_xmit,
- .ndo_tx_timeout = dm9000_timeout,
- .ndo_set_multicast_list = dm9000_hash_table,
- .ndo_do_ioctl = dm9000_ioctl,
- .ndo_change_mtu = eth_change_mtu,
- .ndo_validate_addr = eth_validate_addr,
- .ndo_set_mac_address = eth_mac_addr,
- #ifdef CONFIG_NET_POLL_CONTROLLER
- .ndo_poll_controller = dm9000_poll_controller,
- #endif
- };
[c-sharp] view plain copy
- static const struct ethtool_ops dm9000_ethtool_ops = {
- .get_drvinfo = dm9000_get_drvinfo,
- .get_settings = dm9000_get_settings,
- .set_settings = dm9000_set_settings,
- .get_msglevel = dm9000_get_msglevel,
- .set_msglevel = dm9000_set_msglevel,
- .nway_reset = dm9000_nway_reset,
- .get_link = dm9000_get_link,
- .get_eeprom_len = dm9000_get_eeprom_len,
- .get_eeprom = dm9000_get_eeprom,
- .set_eeprom = dm9000_set_eeprom,
- };
*dm9000_open()
進行的工作有 向核心注冊中斷,複位并初始化dm9000,檢查MII接口,使能傳輸等。代碼清單如下:
[c-sharp] view plain copy
- static int
- dm9000_open(struct net_device *dev)
- {
- board_info_t *db = netdev_priv(dev);
- unsigned long irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK;
- if (netif_msg_ifup(db))
- dev_dbg(db->dev, "enabling %s/n", dev->name);
- if (irqflags == IRQF_TRIGGER_NONE)
- dev_warn(db->dev, "WARNING: no IRQ resource flags set./n");
- irqflags |= IRQF_SHARED;
- if (request_irq(dev->irq, &dm9000_interrupt, irqflags, dev->name, dev))
- return -EAGAIN;
- dm9000_reset(db);
- dm9000_init_dm9000(dev);
- db->dbug_cnt = 0;
- mii_check_media(&db->mii, netif_msg_link(db), 1);
- netif_start_queue(dev);
- dm9000_schedule_poll(db);
- return 0;
- }
*dm9000_stop()
做的工作基本上和open相反。代碼清單如下:
[c-sharp] view plain copy
- static int
- dm9000_stop(struct net_device *ndev)
- {
- board_info_t *db = netdev_priv(ndev);
- if (netif_msg_ifdown(db))
- dev_dbg(db->dev, "shutting down %s/n", ndev->name);
- cancel_delayed_work_sync(&db->phy_poll);
- netif_stop_queue(ndev);
- netif_carrier_off(ndev);
- free_irq(ndev->irq, ndev);
- dm9000_shutdown(ndev);
- return 0;
- }
*dm9000_start_xmit()
重要的發送資料包函數。從上層發送sk_buff包。在看代碼之前先來看一下DM9000是如何發送資料包的。
如上圖所示,在DM9000内部SRAM中,位址0x0000~0x0BFF是TX Buffer, 位址0x0C00~0x3FFF是RX Buffer。在發送一個包之前,包中的有效資料必須先被存儲到TX Buffer中并且使用輸出端口指令來選擇MWCMD寄存器。包的長度定義在TXPLL和TXPLH中。最後設定TXCR寄存器的bit[0] TXREQ來自動發送包。如果設定了IMR寄存器的PTM位,則DM9000會産生一個中斷觸發在ISR寄存器的bit[1]=PTS=1, 同時設定一個完成标志在NSR寄存器的bit[2]=TX1END或者 bit[3]=TX2END,表示包已經發送完了。發送一個包的具體步驟如下:
Step 1: 檢查存儲資料寬度。通過讀取中斷狀态寄存器(ISR)的bit[7:6]來确定是8bit,16bit還是32bit。
Step 2: 寫資料到TX SRAM中。
Step 3: 寫傳輸長度到TXPLL和TXPLH寄存器中。
Step 4: 設定TXCR寄存器的bit[0]TXREQ來開始發送一個包。
代碼清單如下,讓我們看看在獲得自旋鎖這段期間都幹了些什麼:
[c-sharp] view plain copy
- static int
- dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
- {
- unsigned long flags;
- board_info_t *db = netdev_priv(dev);
- dm9000_dbg(db, 3, "%s:/n", __func__);
- if (db->tx_pkt_cnt > 1)
- return NETDEV_TX_BUSY;
- spin_lock_irqsave(&db->lock, flags);
- writeb(DM9000_MWCMD, db->io_addr);
- (db->outblk)(db->io_data, skb->data, skb->len);
- dev->stats.tx_bytes += skb->len;
- db->tx_pkt_cnt++;
- if (db->tx_pkt_cnt == 1) {
- iow(db, DM9000_TXPLL, skb->len);
- iow(db, DM9000_TXPLH, skb->len >> 8);
- iow(db, DM9000_TCR, TCR_TXREQ);
- dev->trans_start = jiffies;
- } else {
- db->queue_pkt_len = skb->len;
- netif_stop_queue(dev);
- }
- spin_unlock_irqrestore(&db->lock, flags);
- dev_kfree_skb(skb);
- return 0;
- }
*dm9000_timeout()
當watchdog逾時時調用該函數。主要的功能是儲存寄存器位址,停止隊列,重新開機并初始化DM9000,喚醒隊列,恢複寄存器位址。
代碼清單如下:
[c-sharp] view plain copy
- static void dm9000_timeout(struct net_device *dev)
- {
- board_info_t *db = netdev_priv(dev);
- u8 reg_save;
- unsigned long flags;
- reg_save = readb(db->io_addr);
- spin_lock_irqsave(&db->lock, flags);
- netif_stop_queue(dev);
- dm9000_reset(db);
- dm9000_init_dm9000(dev);
- dev->trans_start = jiffies;
- netif_wake_queue(dev);
- writeb(reg_save, db->io_addr);
- spin_unlock_irqrestore(&db->lock, flags);
- }
*dm9000_hash_table()
該函數用來設定DM9000的多點傳播位址。代碼清單如下:
[c-sharp] view plain copy
- static void
- dm9000_hash_table(struct net_device *dev)
- {
- board_info_t *db = netdev_priv(dev);
- struct dev_mc_list *mcptr = dev->mc_list;
- int mc_cnt = dev->mc_count;
- int i, oft;
- u32 hash_val;
- u16 hash_table[4];
- u8 rcr = RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN;
- unsigned long flags;
- dm9000_dbg(db, 1, "entering %s/n", __func__);
- spin_lock_irqsave(&db->lock, flags);
- for (i = 0, oft = DM9000_PAR; i < 6; i++, oft++)
- iow(db, oft, dev->dev_addr[i]);
- for (i = 0; i < 4; i++)
- hash_table[i] = 0x0;
- hash_table[3] = 0x8000;
- if (dev->flags & IFF_PROMISC)
- rcr |= RCR_PRMSC;
- if (dev->flags & IFF_ALLMULTI)
- rcr |= RCR_ALL;
- for (i = 0; i < mc_cnt; i++, mcptr = mcptr->next) {
- hash_val = ether_crc_le(6, mcptr->dmi_addr) & 0x3f;
- hash_table[hash_val / 16] |= (u16) 1 << (hash_val % 16);
- }
- for (i = 0, oft = DM9000_MAR; i < 4; i++) {
- iow(db, oft++, hash_table[i]);
- iow(db, oft++, hash_table[i] >> 8);
- }
- iow(db, DM9000_RCR, rcr);
- spin_unlock_irqrestore(&db->lock, flags);
- }
*dm9000_ioctl()
從源碼可以看出,dm9000的ioctl實際上是使用了mii的ioctl。代碼清單如下:
[c-sharp] view plain copy
- static int dm9000_ioctl(struct net_device *dev, struct ifreq *req, int cmd)
- {
- board_info_t *dm = to_dm9000_board(dev);
- if (!netif_running(dev))
- return -EINVAL;
- return generic_mii_ioctl(&dm->mii, if_mii(req), cmd, NULL);
- }
*dm9000_poll_controller()
當核心配置Netconsole時該函數生效。代碼清單如下:
[c-sharp] view plain copy
- #ifdef CONFIG_NET_POLL_CONTROLLER
- static void dm9000_poll_controller(struct net_device *dev)
- {
- disable_irq(dev->irq);
- dm9000_interrupt(dev->irq, dev);
- enable_irq(dev->irq);
- }
- #endif
*dm9000_get_drvinfo()
該函數去的裝置的基本資訊(裝置名,版本,總線名)傳給ethtool_drvinfo結構體變量。代碼清單如下:
[c-sharp] view plain copy
- static void dm9000_get_drvinfo(struct net_device *dev,
- struct ethtool_drvinfo *info)
- {
- board_info_t *dm = to_dm9000_board(dev);
- strcpy(info->driver, CARDNAME);
- strcpy(info->version, DRV_VERSION);
- strcpy(info->bus_info, to_platform_device(dm->dev)->name);
- }
*dm9000_get_settings()
該函數得到由參數cmd指定的設定資訊。
*dm9000_set_settings()
該函數設定由參數cmd指定的資訊。
*dm9000_get_msglevel()
*dm9000_set_msglevel()
這兩個函數設定和取得message level,實際是設定和取得board_info中的msg_enable資訊。
*dm9000_nway_reset()
重新開機mii的自動協商
*dm9000_get_link()
該函數的到link狀态。如果帶外部PHY,則傳回mii連結狀态。 否則傳回DM9000 NSR寄存器數值。
*dm9000_get_eeprom_len()
dm9000_get_eeprom()
dm9000_set_eeprom()
這三個函數用來讀寫eeprom。
5. 與資料傳輸有關的函數。
上面已經分析了一個與資料傳輸有關的函數,那就是發送資料的函數dm9000_start_xmit()。這裡再來分析資料的接收。再看具體代碼之前還是來看看DM9000的資料接收的過程。
接收的資料存儲在RX SRAM中,位址是0C00h~3FFFh。存儲在RX_SRAM中的每個包都有4個位元組的資訊頭。可以使用MRCMDX和MRCMD寄存器來得到這些資訊。第一個位元組用來檢查資料包是否接收到了RX_SRAM中,如果這個位元組是"01",意味着一個包已經接收。如果是"00",則還沒有資料包被接收到RX_SRAM中。第二個位元組儲存接收到的資料包的資訊,格式和RSR寄存器一樣。根據這個格式,接收到的包能被校驗是正确的還是錯誤的包。第三和第四位元組儲存了接收的資料包的長度。這四個位元組以外的其他位元組就是接收包的資料。看下圖可以更好的了解這種格式。
根據包的結構可以知道接收一個包應該按照下面的步驟來進行:
第一步:判斷包是否已經接收過來了。需要用到MRCMDX寄存器。MRCMDX寄存器是存儲資料讀指令寄存器(位址不增加)。 這個寄存器隻是用來讀接收包标志位"01"。下面這段代碼是一個例子,用來判斷RX ready:
[c-sharp] view plain copy
- u8 RX_ready = ior( IOaddr, 0xF0 );
- RX_ready = (u8) inp( IOaddr + 4 );
- if ( RX_ready == 1 ) {
- } else if ( RX_ready != 0 ) {
- iow( IOaddr, 0xFF, 0x80 );
- iow( IOaddr, 0xFE, 0x0F );
- iow( IOaddr, 0x05, 0x00 );
- u8 device_wait_reset = TRUE;
- }
第二步:檢查包的狀态和長度。需要用到MRCMD寄存器(存儲資料讀指令,讀指針自動增加)。下面這段例子代碼用來讀RX狀态和長度。
[c-sharp] view plain copy
- u8 io_mode = ior( IOaddr, 0xFE ) >> 6;
- outp( IOaddr, 0xF2 );
- if ( io_mode == 2 ) {
- RX_status = inp( IOaddr + 4 ) + ( inp( IOaddr + 4 ) << 8 );
- RX_length = inp( IOaddr + 4 ) + ( inp( IOaddr + 4 ) << 8 ); }
- else if ( io_mode == 0 ) {
- RX_status = inpw( IOaddr + 4 );
- RX_length = inpw( IOaddr + 4 ); }
- else if ( io_mode == 1 ) {
- (u32) status_tmp = inpl( IOaddr + 4 );
- RX_status = (u16)( status_tmp & 0xFFFF );
- RX_length = (u16)( ( status_tmp >> 16 ) & 0xFFFF ); }
第三步:讀包的資料。也需要MRCMD寄存器。例子代碼如下:
[c-sharp] view plain copy
- if ( io_mode == 2 ) {
- for ( i = 0 ; i < RX_length ; i++ )
- RX_data[ i ] = (u8) inp( IOaddr + 4 ); }
- else if ( io_mode == 0 ) {
- int length_tmp = ( RX_length + 1 ) / 2;
- for ( i = 0 ; i < length_tmp ; i++ )
- ( (u16 *)RX_data)[ i ] = inpw( IOaddr + 4 ); }
- else if ( io_mode == 1 ) {
- int length_tmp = ( RX_length + 3 ) / 4;
- for ( i = 0 ; i < length_tmp ; i++ )
- ( (u32 *)RX_data)[ i ] = inpl( IOaddr + 4 ); }
下面的dm9000_rx()函數實際上是按照上面這三個步驟來實作的,具體實作并不一定是要參照例子代碼。 注意這裡按照DM9000接收包的格式定義了一個結構體dm9000_rxhdr用來表示頭部的四個位元組。代碼清單如下:
[c-sharp] view plain copy
- struct dm9000_rxhdr {
- u8 RxPktReady;
- u8 RxStatus;
- __le16 RxLen;
- } __attribute__((__packed__));
接收函數代碼如下:
[c-sharp] view plain copy
- static void
- dm9000_rx(struct net_device *dev)
- {
- board_info_t *db = netdev_priv(dev);
- struct dm9000_rxhdr rxhdr;
- struct sk_buff *skb;
- u8 rxbyte, *rdptr;
- bool GoodPacket;
- int RxLen;
- do {
- ior(db, DM9000_MRCMDX);
- rxbyte = readb(db->io_data);
- if (rxbyte > DM9000_PKT_RDY) {
- dev_warn(db->dev, "status check fail: %d/n", rxbyte);
- iow(db, DM9000_RCR, 0x00);
- iow(db, DM9000_ISR, IMR_PAR);
- return;
- }
- if (rxbyte != DM9000_PKT_RDY)
- return;
- GoodPacket = true;
- writeb(DM9000_MRCMD, db->io_addr);
- (db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr));
- RxLen = le16_to_cpu(rxhdr.RxLen);
- if (netif_msg_rx_status(db))
- dev_dbg(db->dev, "RX: status %02x, length %04x/n",
- rxhdr.RxStatus, RxLen);
- if (RxLen < 0x40) {
- GoodPacket = false;
- if (netif_msg_rx_err(db))
- dev_dbg(db->dev, "RX: Bad Packet (runt)/n");
- }
- if (RxLen > DM9000_PKT_MAX) {
- dev_dbg(db->dev, "RST: RX Len:%x/n", RxLen);
- }
- if (rxhdr.RxStatus & (RSR_FOE | RSR_CE | RSR_AE |
- RSR_PLE | RSR_RWTO |
- RSR_LCS | RSR_RF)) {
- GoodPacket = false;
- if (rxhdr.RxStatus & RSR_FOE) {
- if (netif_msg_rx_err(db))
- dev_dbg(db->dev, "fifo error/n");
- dev->stats.rx_fifo_errors++;
- }
- if (rxhdr.RxStatus & RSR_CE) {
- if (netif_msg_rx_err(db))
- dev_dbg(db->dev, "crc error/n");
- dev->stats.rx_crc_errors++;
- }
- if (rxhdr.RxStatus & RSR_RF) {
- if (netif_msg_rx_err(db))
- dev_dbg(db->dev, "length error/n");
- dev->stats.rx_length_errors++;
- }
- }
- if (GoodPacket
- && ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) {
- skb_reserve(skb, 2);
- rdptr = (u8 *) skb_put(skb, RxLen - 4);
- (db->inblk)(db->io_data, rdptr, RxLen);
- dev->stats.rx_bytes += RxLen;
- skb->protocol = eth_type_trans(skb, dev);
- netif_rx(skb);
- dev->stats.rx_packets++;
- } else {
- (db->dumpblk)(db->io_data, RxLen);
- }
- } while (rxbyte == DM9000_PKT_RDY);
- }
6. 中斷處理相關函數
DM9000的驅動程式采用了中斷方式而非輪詢方式。觸發中斷的時機發生在:1)DM9000接收到一個包以後。2)DM9000發送完了一個包以後。
中斷處理函數在open的時候被注冊進核心。代碼清單如下:
[c-sharp] view plain copy
- static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
- {
- struct net_device *dev = dev_id;
- board_info_t *db = netdev_priv(dev);
- int int_status;
- unsigned long flags;
- u8 reg_save;
- dm9000_dbg(db, 3, "entering %s/n", __func__);
- spin_lock_irqsave(&db->lock, flags);
- reg_save = readb(db->io_addr);
- iow(db, DM9000_IMR, IMR_PAR);
- int_status = ior(db, DM9000_ISR);
- iow(db, DM9000_ISR, int_status);
- if (netif_msg_intr(db))
- dev_dbg(db->dev, "interrupt status %02x/n", int_status);
- if (int_status & ISR_PRS)
- dm9000_rx(dev);
- if (int_status & ISR_PTS)
- dm9000_tx_done(dev, db);
- if (db->type != TYPE_DM9000E) {
- if (int_status & ISR_LNKCHNG) {
- schedule_delayed_work(&db->phy_poll, 1);
- }
- }
- iow(db, DM9000_IMR, db->imr_all);
- writeb(reg_save, db->io_addr);
- spin_unlock_irqrestore(&db->lock, flags);
- return IRQ_HANDLED;
- }
*dm9000_tx_done()
注:dm9000可以發送兩個資料包,當發送一個資料包産生中斷後,要确認一下隊列中有沒有第2個包需要發送。
(1)讀取dm9000寄存器NSR(Network Status Register)擷取發送的狀态,存在變量tx_status中;
(2)如果發送狀态為NSR_TX2END(第2個包發送完畢)或者NSR_TX1END(第1個包發送完畢),則将待發送的資料包數量(db->tx_pkt_cnt)減1,已發送的資料包數量(dev->stats.tx_packets)加1;
(3)檢查變量db->tx_pkt_cnt(待發送的資料包)是否大于0(表明還有資料包要發送),則調用函數dm9000_send_packet發送隊列中的資料包;
(4)調用函數netif_wake_queue(dev)通知核心可以将待發送的資料包進入發送隊列。
[c-sharp] view plain copy
- static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
- {
- int tx_status = ior(db, DM9000_NSR);
- if (tx_status & (NSR_TX2END | NSR_TX1END)) {
- db->tx_pkt_cnt--;
- dev->stats.tx_packets++;
- if (netif_msg_tx_done(db))
- dev_dbg(db->dev, "tx done, NSR %02x/n", tx_status);
- if (db->tx_pkt_cnt > 0) {
- iow(db, DM9000_TXPLL, db->queue_pkt_len);
- iow(db, DM9000_TXPLH, db->queue_pkt_len >> 8);
- iow(db, DM9000_TCR, TCR_TXREQ);
- dev->trans_start = jiffies;
- }
- netif_wake_queue(dev);
- }
- }
7. 一些操作硬體細節的函數。
在看函數之前還是先來看一下DM9000 CMD Pin 和Processor并行總線的連接配接關系。CMD管腳用來設定指令類型。當CMD管腳拉高時,這個指令周期通路DATA_PORT。 如果拉低, 則這個指令周期通路ADDR_PORT。見下圖:
當然,記憶體映射的I/O空間讀寫還是采用最基本的readb(), readw(), readl(), writeb(), writew(), writel() , readsb(), readsw(), readsl(), writesb(), writesw(), writesl() 。
在DM9000的驅動中還自定義了幾個函數,友善操作。
* ior()
從IO端口讀一個位元組。代碼清單如下:
[c-sharp] view plain copy
- static u8
- ior(board_info_t * db, int reg)
- {
- writeb(reg, db->io_addr);
- return readb(db->io_data);
- }
* iow()
向IO端口寫一個位元組。代碼清單如下:
[c-sharp] view plain copy
- static void
- iow(board_info_t * db, int reg, int value)
- {
- writeb(reg, db->io_addr);
- writeb(value, db->io_data);
- }
此外還有dm9000_outblk_8bit(), dm9000_outblk_16bit(), dm9000_outblk_32bit(), dm9000_inblk_8bit(), dm9000_inblk_16bit(), dm9000_inblk_32bit()等等。不一一解釋。