您好,登錄后才能下訂單哦!
Preface
內核源碼版本:linux-2.6.18
網卡驅動·linux內核網絡分層結構:http://infohacker.blog.51cto.com/6751239/1221140
DM9000芯片
DM9000是一款高度集成低功耗快速以太網處理器,該芯片集成了MAC和PHY。DM9000可以和CPU直接連接,支持8位、16位和32位數據總線寬度。該芯片支持10M和100M自適應以太網接口,內部有16K的FIFO以及4K雙字節SRAM,支持全雙工工作。
DM9000內部還集成了接收緩沖區,可以在接收到數據的時候把數據存放到緩沖區中,鏈路層可以直接把數據從緩沖區取走。
網卡驅動程序框架
在一個網絡驅動程序中,一般都提供了一個platform_driver結構變量。
platform_driver結構包括了網卡驅動的相關操作函數,通過platform_driver_register()函數注冊到內核設備驅動列表。
內核會根據驅動程序中設備描述設置網卡的中斷和定時器,并且在網絡數據包到來的時候調用網卡對應的處理函數。
通常,網卡需要向內核提供下面幾個接口函數:
probe:加載網卡驅動的時候執行,主要用于初始化網卡硬件接口,設置網絡接口函數;
remove:卸載網卡驅動的時候執行該函數,用于從系統中注銷網絡接口函數;
suspend:在掛起網絡設備的時候被調用;
resume:在恢復網絡設備的時候被調用。
網絡設備驅動主要是按照內核網絡數據包處理流程中用到的數據結構,設置對應的處理函數供內核使用。
DM9000網卡驅動主要數據結構
DM9000網卡驅動位于driver/net/dm9000.c文件,有兩個主要的數據結構dm9000_driver和board_info。其中,dm9000_driver是platform_driver結構。
static struct platform_driver dm9000_driver = { .driver = { .name = "dm9000", //網卡名稱 .owner = THIS_MODULE, }, .probe = dm9000_probe, //加載驅動函數 .remove = dm9000_drv_remove, //刪除驅動函數 .suspend = dm9000_drv_suspend, //掛起驅動函數 .resume = dm9000_drv_resume, //恢復驅動函數 };
dm9000_probe()函數在加載驅動的時候被內核調用,用于檢測mqh上設備并且分配資源,設置網絡接口控制器;
dm9000_drv_remove()函數在卸載驅動的時候被調用,用于釋放網卡驅動占用的資源;
dm9000_drv_suspend()函數在掛起網卡的時候被調用,該函數會暫時刪除網絡口;
dm9000_drv_resume()函數在恢復網卡接口時被調用,該函數重新加載網絡接口。
DM9000網卡驅動還設置了供DM9000網絡控制芯片使用的 board_info結構
/* Structure/enum declaration ------------------------------- */ typedef struct board_info { void __iomem *io_addr;/* Register I/O base address *///控制寄存器地址 void __iomem *io_data;/* Data I/O address *///數據寄存器地址 u16 irq;/* IRQ *///中斷號,在嵌入式系統常常無效 u16 tx_pkt_cnt;//已發送數據包個數 u16 queue_pkt_len;//數據包發送隊列中的數據包個數 u16 queue_start_addr;//數據包發送隊列的起始地址 u16 dbug_cnt; u8 io_mode;/* 0:word, 2:byte */ u8 phy_addr;//網卡物理地址 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 resource*addr_res; /* resources found */ struct resource *data_res; struct resource*addr_req; /* resources requested */ struct resource *data_req; struct resource *irq_res; struct timer_list timer; struct net_device_stats stats; unsigned char srom[128];//網絡控制器內部EEPROM內容 spinlock_t lock; struct mii_if_info mii; u32 msg_enable; } board_info_t;
board_info結構存放在 net_device結構的私有數據部分,DM9000驅動的接口處理函數會使用該結構訪問網絡控制芯片。
在 board_info結構中,
io_addr和 io_data成員變量存放了控制寄存器和數據寄存器地址;
tx_pkt_cnt記錄了發送數據包個數;
queue_pkt_len記錄了發送隊列中數據包個數;
queue_start_addr記錄了數據包發送隊列的起始地址;
phy_addr是網卡的物理地址;
srom是一個組,記錄了DM9000網絡控制芯片內部EEPROM的內容。
加載驅動程序
在dm9000.c文件中使用模塊加載宏和卸載宏設置了模塊的初始化函數dm9000_init()和卸載函數dm9000_cleanup()。
static int __init dm9000_init(void) { printk(KERN_INFO "%s Ethernet Driver\n", CARDNAME); //打印模塊啟動信息 return platform_driver_register(&dm9000_driver); /* search board and register */ //調用驅動注冊函數 } static void __exit dm9000_cleanup(void) { platform_driver_unregister(&dm9000_driver); //調用驅動卸載函數 } module_init(dm9000_init); //設置模塊啟動函數 module_exit(dm9000_cleanup); //設置模塊卸載函數
設置好驅動函數的初始化后,在啟動的時候會注冊 dm9000_driver結構到內核,內核會調用 dm9000 driver結構中的 probe函數成員,也就是調用 dm9000_probe()函數設置網卡驅動。
函數執行過程如下。
步驟一
函數首先是分配 board_info結構占用的私有資源,在程序中使用 alloc_etherdev()函數分配網卡驅動使用的私有資源。
如果分配資源失敗,提示出錯wyth 并且退出函數,設置返回值為 -ENOMEM,表示沒有內存。
static int dm9000_probe(struct platform_device *pdev) { struct dm9000_plat_data *pdata = pdev->dev.platform_data; struct board_info *db; /* Point a board information structure */ struct net_device *ndev; unsigned long base; int ret = 0; int iosize; int i; u32 id_val; /* Init network device */ ndev = alloc_etherdev(sizeof (struct board_info)); //分配資源,在私有數據區保存 board_info內容 if (!ndev) { printk("%s: could not allocate device.\n", CARDNAME); return -ENOMEM; } SET_MODULE_OWNER(ndev); //該宏是空定義,被忽略,下面一行相同 SET_NETDEV_DEV(ndev, &pdev->dev); PRINTK2("dm9000_probe()");
步驟二
分配資源成功后,開始初始化 board_info結構,設置結構的值為0,然后初始化 spin_lock,spin_lock稱做自旋鎖,是內核中用于臨界資源的一種結構。
初始化自旋鎖以后,程序分配網絡適配器 I/O地址寄存器、數據寄存器用到的內存,并且映射到內核空間。
/* setup board info structure */ db = (struct board_info *) ndev->priv; memset(db, 0, sizeof (*db)); //初始化 board_info結構為0 spin_lock_init(&db->lock); //初始化 spin_lock if (pdev->num_resources < 2) { //檢查是否安裝多個網絡適配器 ret = -ENODEV; goto out; } else if (pdev->num_resources == 2) { base = pdev->resource[0].start; if (!request_mem_region(base, 4, ndev->name)) { //分配網絡適配器結構占用的內存 ret = -EBUSY; goto out; } ndev->base_addr = base; //設置網絡適配器 I/O基址 ndev->irq = pdev->resource[1].start; //設置網絡適配器中斷地址 db->io_addr = (void __iomem *)base; //設置網絡適配器地址寄存器基址 db->io_data = (void __iomem *)(base + 4); //設置網絡適配器數據寄存器基址 } else { db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //獲取 I/O地址 db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //獲取 IRQ地址 if (db->addr_res == NULL || db->data_res == NULL || db->irq_res == NULL) { //檢查網絡適配器用到的內存地址是否有效 printk(KERN_ERR PFX "insufficient resources\n"); ret = -ENOENT; goto out; } i = res_size(db->addr_res); //計算地址寄存器空間 db->addr_req = request_mem_region(db->addr_res->start, i, pdev->name); //請求內存地址 if (db->addr_req == NULL) { //檢查地址寄存器是否有效 printk(KERN_ERR PFX "cannot claim address reg area\n"); ret = -EIO; goto out; } db->io_addr = ioremap(db->addr_res->start, i); //映射網絡適配器 I/O地址 if (db->io_addr == NULL) { //檢查網絡適配器 I/O地址是否有效 printk(KERN_ERR "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) { //檢查數據寄存器地址是否有效 printk(KERN_ERR PFX "cannot claim data reg area\n"); ret = -EIO; goto out; } db->io_data = ioremap(db->data_res->start, iosize); //映射網絡適配器數據寄存器地址 if (db->io_data == NULL) { //檢查 I/O地址是否有效 printk(KERN_ERR "failed to ioremap data reg\n"); ret = -EINVAL; goto out; } /* fill in parameters for net-dev structure */ //填充網絡設備數據結構 ndev->base_addr = (unsigned long)db->io_addr; //設置網絡設備 I/O地址 ndev->irq = db->irq_res->start; //設置IRQ /* ensure at least we have a default set of IO routines */ dm9000_set_io(db, iosize); //設置DM9000寄存器地址 }
步驟三
檢查是否需要繼承系統提供的函數。
初始化網絡適配器數據結構后,需要設置網絡設備用到的回調函數。
/* check to see if anything is being over-ridden */ //檢查是否有繼承的數據 if (pdata != NULL) { /* check to see if the driver wants to over-ride the * default IO width */ //檢查是否繼承默認 I/O寬度 if (pdata->flags & DM9000_PLATF_8BITONLY) // 8位 I/O位寬 dm9000_set_io(db, 1); if (pdata->flags & DM9000_PLATF_16BITONLY) // 16位 I/O位寬 dm9000_set_io(db, 2); if (pdata->flags & DM9000_PLATF_32BITONLY) // 32位 I/O位寬 dm9000_set_io(db, 4); /* check to see if there are any IO routine * over-rides */ //檢查是否有繼承的函數 if (pdata->inblk != NULL) db->inblk = pdata->inblk; //入鏈路函數 if (pdata->outblk != NULL) db->outblk = pdata->outblk; //出鏈路 if (pdata->dumpblk != NULL) db->dumpblk = pdata->dumpblk; // dump()函數 }
步驟四
設置DM9000網絡適配器芯片ID。
dm9000_reset(db); //復位DM9000網絡控制芯片 /* try two times, DM9000 sometimes gets the first read wrong */ for (i = 0; i < 2; i++) { //讀取芯片 ID,需要讀兩次,這是芯片的一個Bug 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; printk("%s: read wrong id 0x%08x\n", CARDNAME, id_val); } if (id_val != DM9000_ID) { //檢查芯片 ID是否正確 printk("%s: wrong id: 0x%08x\n", CARDNAME, id_val); goto release; }
這里的for循環從DM9000網絡控制器的寄存器兩次讀取芯片ID,這樣做的目的是因為芯片的Bug,第一次讀取的芯片ID是錯誤的,需要再讀取一次。
讀取芯片ID結束后,程序驗證芯片ID是否正確,如果不正確,跳轉到release標簽,清空分配的內存,退出函數。
步驟五
設置網絡適配器用到的回調函數。
/* from this point we assume that we have found a DM9000 */ /* driver system function */ ether_setup(ndev);//初始化網絡控制芯片內部結構 ndev->open = &dm9000_open;//設置打開網卡驅動函數 ndev->hard_start_xmit = &dm9000_start_xmit;//設置發送數據包函數 ndev->tx_timeout = &dm9000_timeout;//設置超時處理函數 ndev->watchdog_timeo = msecs_to_jiffies(watchdog);//設置超時時間 ndev->stop = &dm9000_stop;//設置停止網卡回調函數 ndev->get_stats = &dm9000_get_stats;//設置獲取網卡信息函數 ndev->set_multicast_list = &dm9000_hash_table; #ifdef CONFIG_NET_POLL_CONTROLLER ndev->poll_controller = &dm9000_poll_controller; #endif
程序中使用 ether_setup()函數設置了網卡的驅動描述結構,然后設置網卡打開、接收數據包、發送數據包超時處理函數。
步驟六
設置回調函數后,設置MII接口,MII接口不是所有的處理器都支持,設置的目的是供支持MII接口的處理器使用。
#ifdef DM9000_PROGRAM_EEPROM program_eeprom(db); //更新網絡控制器內部EEPROM #endif db->msg_enable = NETIF_MSG_LINK; //設置MII接口 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; // MII方式讀函數 db->mii.mdio_write = dm9000_phy_write; // MII方式寫函數 /* Read SROM content */ for (i = 0; i < 64; i++) //讀取 EEPROM內容,每次讀 16b ((u16 *) db->srom)[i] = read_srom_word(db, i); /* Set Node Address */ for (i = 0; i < 6; i++) // EEPROM的前6個字節是MAC地址,存放到 board_info結構內 ndev->dev_addr[i] = db->srom[i]; if (!is_valid_ether_addr(ndev->dev_addr)) { //驗證MAC地址是否合法 /* try reading from mac */ for (i = 0; i < 6; i++) //如果是不合法地址,則從DM9000內部寄存器中重新讀取 MAC地址 ndev->dev_addr[i] = ior(db, i+DM9000_PAR); } if (!is_valid_ether_addr(ndev->dev_addr)) //兩次驗證MAC地址 printk("%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("%s: dm9000 at %p,%p IRQ %d MAC: ", ndev->name, db->io_addr, db->io_data, ndev->irq); for (i = 0; i < 5; i++) printk("%02x:", ndev->dev_addr[i]); printk("%02x\n", ndev->dev_addr[5]); } return 0;
程序讀取 EEPROM的內容到 board_info結構,每次讀取16b數據。讀取結束后,再讀取 EEPROM的前6個字節,該處存放了網卡的 MAC地址。
讀取 MAC地址后,使用 is_valid_ether_addr()函數驗證 MAC地址是否正確,如果不正確,則從DM9000網絡控制器的寄存器中重新讀取 MAC地址,然后兩次比較,如果 MAC地址還是錯誤,提示用戶使用 ifconfig命令設置 MAC地址。
驗證 MAC地址結束后,程序使用 register_netdev()函數注冊網絡驅動到內核。如果注冊成功,則打印網卡的基本信息。
步驟七
出錯處理。
函數的最后是出錯處理,使用 release和out標號標記,對應不同的處理流程。函數在執行過程中,可以使用 goto語言直接轉到出錯處理代碼。
出錯處理的主要功能是釋放網絡適配器用到的數據結構,然后返回出錯值。
release: out: printk("%s: not found (%d).\n", CARDNAME, ret); dm9000_release_board(pdev, db); kfree(ndev); return ret; }
停止網卡
停止網卡是用戶使用 ifdown命令設置網卡暫時停止,用戶的命令通過系統調用最終會調用網卡驅動的停止函數,對于 DM9000網卡驅動來說 dm9000_stop()函數
static void dm9000_shutdown(struct net_device *dev) { board_info_t *db = (board_info_t *) dev->priv; /* RESET device */ dm9000_phy_write(dev, 0, MII_BMCR, BMCR_RESET); /* PHY RESET */ //重啟 PHY iow(db, DM9000_GPR, 0x01); /* Power-Down PHY */ //關閉 PHY iow(db, DM9000_IMR, IMR_PAR); /* Disable all interrupt */ //屏蔽所有中斷 iow(db, DM9000_RCR, 0x00); /* Disable RX */ //停止接收數據包 } /* * Stop the interface. * The interface is stopped when it is brought. */ static int dm9000_stop(struct net_device *ndev) { board_info_t *db = (board_info_t *) ndev->priv; PRINTK1("entering %s\n",__FUNCTION__); /* deleted timer */ del_timer(&db->timer); //刪除定時器 netif_stop_queue(ndev); //停止數據包發送隊列 netif_carrier_off(ndev); /* free interrupt */ free_irq(ndev->irq, ndev); //釋放所有中斷請求 dm9000_shutdown(ndev); return 0; }
函數首先刪除網絡驅動的定時器,然后停止數據包發送隊列工作、釋放中斷請求,最后調用 dm9000_shutdown()函數。
dm9000_shutdown()函數的作用是重啟設備,然后通過設置網絡控制器的控制寄存器關閉芯片的部分電源和中斷,并且停止接收數據包。
啟動網卡
與關閉網卡相反,用戶使用 ifup命令可以啟動一個網卡,內核會調用一個網卡的啟動函數。
DM9000網卡的 dm9000_open()函數供內核在啟動網卡時調用。
/* * Open the interface. * The interface is opened whenever "ifconfig" actives it. */ static int dm9000_open(struct net_device *dev) { board_info_t *db = (board_info_t *) dev->priv; PRINTK2("entering dm9000_open\n"); if (request_irq(dev->irq, &dm9000_interrupt, IRQF_SHARED, dev->name, dev)) return -EAGAIN; /* Initialize DM9000 board */ dm9000_reset(db); //重啟網絡控制芯片 dm9000_init_dm9000(dev); //初始化網絡控制芯片 /* Init driver variable */ db->dbug_cnt = 0; /* set and active a timer process */ init_timer(&db->timer); //初始化定時器 db->timer.expires = DM9000_TIMER_WUT; //設置超時值 db->timer.data = (unsigned long) dev; db->timer.function = &dm9000_timer; //設置超時處理函數 add_timer(&db->timer); //添加定時器 mii_check_media(&db->mii, netif_msg_link(db), 1); //檢查 MII接口 netif_start_queue(dev); //啟動包發送隊列 return 0; }
程序中使用 request_irq()函數申請中斷請求,然后重啟網絡控制芯片,然后調用 dm9000_init_dm9000()函數初始化網絡控制芯片。
網絡控制芯片設置完畢后,程序初始化定時器,然后設置定時器超時值,添加定時器超時處理函數。
程序最后檢查MII接口,然后調用 netif_start_queue()函數啟動包發送隊列。
發送數據包
網卡驅動程序需要向內核提供兩個發送數據包的回調函數,一個用于發送數據包,一個用于數據包發送完畢后的處理。
DM9000向內核提供 dm9000_start_xmit()函數用于發送數據包
/* * Hardware start transmission. * Send a packet to media from the upper layer. */ static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev) { board_info_t *db = (board_info_t *) dev->priv; PRINTK3("dm9000_start_xmit\n"); if (db->tx_pkt_cnt > 1) return 1; netif_stop_queue(dev); //停止接收隊列 /* Disable all interrupts */ iow(db, DM9000_IMR, IMR_PAR); //關閉所有中斷 /* Move data to DM9000 TX RAM */ writeb(DM9000_MWCMD, db->io_addr); //設置網卡控制器的控制寄存器 (db->outblk)(db->io_data, skb->data, skb->len); //復制 sk_buff的數據到網卡控制器的 SRAM db->stats.tx_bytes += skb->len; //發送字節數統計加上當前數據包長度 /* TX control: First packet immediately send, second packet queue */ if (db->tx_pkt_cnt == 0) { //判斷是否第一次發送數據包 /* First Packet */ db->tx_pkt_cnt++; //發送數據包總結加1 /* Set TX length to DM9000 */ iow(db, DM9000_TXPLL, skb->len & 0xff); //設置 DM9000的發送數據長度寄存器 iow(db, DM9000_TXPLH, (skb->len >> 8) & 0xff); /* Issue TX polling command */ iow(db, DM9000_TCR, TCR_TXREQ); /* Cleared after TX complete */ //設定發送請求 dev->trans_start = jiffies; /* save the time stamp */ //寫入發送數據包的時間戳 } else { /* Second packet */ db->tx_pkt_cnt++; db->queue_pkt_len = skb->len; } /* free this SKB */ dev_kfree_skb(skb); //釋放數據包緩存 /* Re-enable resource check */ if (db->tx_pkt_cnt == 1) //檢查第一個包是否發送完畢 netif_wake_queue(dev); //如果發送完畢可以重啟接收隊列 /* Re-enable interrupt */ iow(db, DM9000_IMR, IMR_PAR | IMR_PTM | IMR_PRM); //打開 DM9000中斷,如果此時數據包成功發送, //會收到 DM9000發送的中斷,轉到驅動的中斷處理函數進行處理 return 0; }
發送數據包的流程需要考慮到內核數據包隊列和中斷控制器。
程序首先使用 netif_stop_queue()函數停止接收隊列,該隊列是內核與網卡驅動之間的數據包隊列,內核把發送的數據包放到隊列中,網卡驅動從隊列中取出數據包進行發送。
關閉隊列后,程序操作 DM9000的控制寄存器,關閉中斷請求,目的是防止在發送數據包的過程中被打斷,因為內核的代碼都是可重入的,這點需要注意。
程序第16行設置 DM9000的控制寄存器通知 DM9000開始內存復制,然后把發適的數據包 sk_buff中的內容復制到 DM9000內部的SRAM,然后更新網卡發送字節統計。
程序進行發送流程時,首先通過 tx_pkt_cnt變量判斷是否發送第一個數據包,DM9000的驅動設計第一個數據包可以被發送,第二個數據包通過 dm9000_tx_done()函數發送。如果發送的是第一數據包,則程序把發送數據包個數加1,通過設置 DM9000控制寄存器,通知發送數據包長度,然后向 DM9000寫入發送命令。
設置發送數據包后,可以認為數據包已經發送出去,而發送的狀態需要通過中斷得到。接下來,程序釋放已經發送數據包的 sk_buff,然后檢查 tx_pkt_cnt,判斷數據包是否已經發送。如果數據包已經發送,則通過 netif_wake_queue()函數重新開啟接收隊列。
最后,在程序中寫入 DM9000的命令打開中斷響應,如果數據包已經發送,驅動程序會收到 DM9000控制器發送的中斷。
數據包發送完畢后,內核會調用后續的處理函數,DM9000驅動程序提供了 dm9000_tx_done()函數
/* * DM9000 interrupt handler * receive the packet to upper layer, free the transmitted packet */ static void dm9000_tx_done(struct net_device *dev, board_info_t * db) { int tx_status = ior(db, DM9000_NSR); /* Got TX status */ if (tx_status & (NSR_TX2END | NSR_TX1END)) { //判斷是否已經有一個數據包發送完畢 /* One packet sent complete */ db->tx_pkt_cnt--; db->stats.tx_packets++; /* Queue packet check & send */ if (db->tx_pkt_cnt > 0) { //判斷緩沖區是否有未發送的數據包 iow(db, DM9000_TXPLL, db->queue_pkt_len & 0xff); //設置發送數據包長度 iow(db, DM9000_TXPLH, (db->queue_pkt_len >> 8) & 0xff); iow(db, DM9000_TCR, TCR_TXREQ); //啟動數據包發送 dev->trans_start = jiffies; } netif_wake_queue(dev); //通知內核開啟接收隊列 } }
程序首先判斷是否已經有一個數據包被成功發送,如果已經有數據包功能發送,則進入第二個數據包處理。程序通過判斷緩沖區是否有未發送的數據包,如果有,則通知 DM9000控制器數據包的長度,然后寫入命令發送數據包。
數據包發送完畢后,程序開啟內核接收數據包隊列。
接收數據包
DM9000向內核提供了dm9000_rx()函數,在內核收到DM9000網絡控制器的接收數據包中斷后被內核調用。dm9000_rx()函數使用了一個自定義的dm9000_rxhdr結構,該結構與DM9000網絡控制器提供的數據包接收信息對應。
struct dm9000_rxhdr { u16 RxStatus; u16 RxLen; } __attribute__((__packed__)); /* * Received a packet and pass to upper layer */ static void dm9000_rx(struct net_device *dev) { board_info_t *db = (board_info_t *) dev->priv; struct dm9000_rxhdr rxhdr; struct sk_buff *skb; u8 rxbyte, *rdptr; int GoodPacket; int RxLen; /* Check packet ready or not */ do { ior(db, DM9000_MRCMDX); /* Dummy read */ /* Get most updated data */ rxbyte = readb(db->io_data); //讀取網絡控制器狀態 /* Status check: this byte must be 0 or 1 */ if (rxbyte > DM9000_PKT_RDY) { //判斷狀態是否正確 printk("status check failed: %d\n", rxbyte); iow(db, DM9000_RCR, 0x00); /* Stop Device */ //停止網絡控制器 iow(db, DM9000_ISR, IMR_PAR); /* Stop INT request */ //停止中斷請求 return; } if (rxbyte != DM9000_PKT_RDY) return; /* A packet ready now & Get status/length */ GoodPacket = TRUE; writeb(DM9000_MRCMD, db->io_addr); //向控制器發起讀命令 (db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr)); //讀取包頭 RxLen = rxhdr.RxLen; //讀取包長度 /* Packet Status check */ if (RxLen < 0x40) { //判斷數據包是否小于64字節 GoodPacket = FALSE; PRINTK1("Bad Packet received (runt)\n"); } if (RxLen > DM9000_PKT_MAX) { //判斷數據包是否超過 1536字節 PRINTK1("RST: RX Len:%x\n", RxLen); } if (rxhdr.RxStatus & 0xbf00) { //檢查接收狀態是否出錯 GoodPacket = FALSE; if (rxhdr.RxStatus & 0x100) { // FIFO 錯誤 PRINTK1("fifo error\n"); db->stats.rx_fifo_errors++; } if (rxhdr.RxStatus & 0x200) { // CRC 錯誤 PRINTK1("crc error\n"); db->stats.rx_crc_errors++; } if (rxhdr.RxStatus & 0x8000) { // 包長度錯誤 PRINTK1("length error\n"); db->stats.rx_length_errors++; } } /* Move data from DM9000 */ if (GoodPacket && ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) { //分配 sk_buff skb->dev = dev; skb_reserve(skb, 2); rdptr = (u8 *) skb_put(skb, RxLen - 4); /* Read received packet from RX SRAM */ (db->inblk)(db->io_data, rdptr, RxLen); //所數據包從 DM9000控制器復制到 sk_buff db->stats.rx_bytes += RxLen; //更新包計數器 /* Pass to upper layer */ skb->protocol = eth_type_trans(skb, dev); netif_rx(skb); db->stats.rx_packets++; } else { /* need to dump the packet's data */ (db->dumpblk)(db->io_data, RxLen); } } while (rxbyte == DM9000_PKT_RDY); //判斷網絡控制器處理準備好狀態 }
程序中 dm9000_rxhdr結構的 RxStatus成員變量存放接收數據包的狀態,RxLen存放接收到的數據包長度。dm9000_rx()函數內部是一個大的 do……while{}循環。
從18行開始,首先獲取網絡控制器狀態,然后判斷網絡控制器狀態是否正確。如果網絡控制器狀態不正確,則停止網絡控制器,并且屏蔽中斷請求。
如果網絡控制器處理"準備好"的狀態,則向網絡控制器發起讀數據包命令。
程序從34行讀取數據包頭,然后取出包長。再判斷數據包長度是否小于64字節,因為以太網協議規定,小于64字節的數據包是錯誤的。
在從網絡控制器接收數據包內容之前,程序首先在使用 dev_alloc_skb()函數分配了一個 sk_buff緩沖區,用于存放數據包,然后把數據包從網絡控制器的 SRAM復制到 sk_buff,再更新字節計數器。
新的數據包收到后,就可以通知上層協議棧處理了,程序使用 eth_type_trans()函數把數據包丟給協議棧,然后更新包計數器。
中斷和定時器處理
網絡設備驅動需要提供中斷處理函數和定時處理函數供內核使用。
中斷處理函數當網絡控制器向CPU發出中斷后,由內核中斷處理函數調用。
定時器處理函數是由內核的一個定時器周期的調用。
DM9000網卡驅動設計了 dm9000_interrupt()函數響應網絡控制發送的中斷請求。
static irqreturn_t dm9000_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct net_device *dev = dev_id; board_info_t *db; int int_status; u8 reg_save; PRINTK3("entering %s\n",__FUNCTION__); if (!dev) { //檢查網絡設備是否存在 PRINTK1("dm9000_interrupt() without DEVICE arg\n"); return IRQ_HANDLED; } /* A real interrupt coming */ db = (board_info_t *) dev->priv; spin_lock(&db->lock); //對臨界資源加鎖 /* Save previous register address */ reg_save = readb(db->io_addr); //保存當前中斷寄存器的值 /* Disable all interrupts */ iow(db, DM9000_IMR, IMR_PAR); //關閉所有中斷請求 /* Got DM9000 interrupt status */ int_status = ior(db, DM9000_ISR); /* Got ISR */ //獲取 ISR iow(db, DM9000_ISR, int_status); /* Clear ISR status */ //清除 ISR狀態 /* Received the coming packet */ if (int_status & ISR_PRS) //判斷是否收到數據包中斷 dm9000_rx(dev); //調用接收數據包函數處理 /* Trnasmit Interrupt check */ if (int_status & ISR_PTS) //判斷是否發送數據包中斷 dm9000_tx_done(dev, db); //調用發送數據包函數處理 /* Re-enable interrupt mask */ iow(db, DM9000_IMR, IMR_PAR | IMR_PTM | IMR_PRM); //打開中斷請求 /* Restore previous register address */ writeb(reg_save, db->io_addr); //恢復中斷處理前中斷寄存器的值 spin_unlock(&db->lock); //對臨界資源解鎖 return IRQ_HANDLED; }
DM9000的中斷處理函數只處理網絡控制器發送的接收數據包和發送數據包請求。
進入函數后,程序街道檢查內核傳入的網絡設備句柄是否合法,不合法則直接退出函數。如果是合法的網絡設備句柄,則對網絡設備加鎖,防止其它例程處理。
然后取出當前中斷寄存器的值保存,關閉中斷請求,并處理 DM9000的ISR。
前面的工作都是建立中斷處理的環境,接下在程序判斷是否是接收到數據包中斷,如果是則調用 dm9000_rx()函數接收數據包,再判斷是否發送數據包中斷,如果是則調用 dm9000_tx_done()函數進行處理。
處理完所有的中斷以后,程序重新打開中斷請求,然后恢復中斷處理之前中斷寄存器的值。最后對臨界資源解鎖,整個中斷處理流程結束。
小結
在網絡通信中,計算機通過網卡(包括網絡控制器和網絡接口)與其它網絡節點通信。由于不同的網絡有不同協議,網卡的設計不僅需要兼顧到網絡上數據包的處理,還涉及主機網絡協議棧的接口。
網卡驅動在linux內核是一類復雜的設備驅動,學習的時候要立足從網絡協議入手,需要了解網絡協議和內核協議棧工作流程。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。