Linux裝置驅動工程師之路——DM9000網路卡驅動程式分析
K-Style
轉載請註明來自于衡陽師範學院08電2 K-Style http://blog.csdn.net/ayangke,QQ:843308498 郵箱:yangkeemail@qq.com
DM9000是開發板經採用的網路晶片,是一種高度整合而且功耗很低的高速網路控制卡,可以和CPU直連,支援10/100M乙太網路串連,晶片內部內建16K SARM(3KB用來發送,13KB用來接收).
1.模組初始化
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 __initdm9000_init(void){printk(KERN_INFO "%s Ethernet Driver, V%s\n", CARDNAME, DRV_VERSION);return platform_driver_register(&dm9000_driver);}
模組初始化完成了基於platfrom平台的DM9000網卡驅動的註冊,當DM9000網卡找到其對應的能處理的platform裝置後調用probe函數。
2.DM9000網卡初始化
在probe函數中完成了對DM9000網卡的初始化
DM9000的特性:DM9000地址訊號和資料訊號複用使用CMD引腳區分它們(CMD為低是讀寫DM900地址寄存器,CMD為高時讀寫DM9000資料寄存器),訪問DM9000內部寄存器時,先將CMD置低,寫DM900地址寄存器,然後將CMD置高,讀寫DM9000資料寄存器。
static int __devinitdm9000_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;const unsigned char *mac_src;int ret = 0;int iosize;int i;u32 id_val;/* Init network device *///申請net_device結構ndev = alloc_etherdev(sizeof(struct board_info));if (!ndev) {dev_err(&pdev->dev, "could not allocate device.\n");return -ENOMEM;}//將net_device的parent指標指向platform_device對象,表示該裝置掛載platform裝置上。SET_NETDEV_DEV(ndev, &pdev->dev); dev_dbg(&pdev->dev, "dm9000_probe()\n");/* setup board info structure *///擷取net_device私人資料結構指標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);//擷取平台裝置資源。包括DM9000地址寄存器地址,DM9000資料寄存器地址,和DM900所佔用的中斷號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;}//申請地址寄存器IO記憶體地區並映射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;}//申請資料寄存器IO記憶體地區並映射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;}/* fill in parameters for net-dev structure */ndev->base_addr = (unsigned long)db->io_addr;ndev->irq= db->irq_res->start;//設定資料位元寬/* ensure at least we have a default set of IO routines */dm9000_set_io(db, iosize);/* 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 */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);/* 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;db->flags = pdata->flags;}#ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLLdb->flags |= DM9000_PLATF_SIMPLE_PHY;#endif//複位網卡晶片dm9000_reset(db);//讀取裝置ID,判斷是否是驅動能夠處理的網卡晶片/* try multiple times, DM9000 sometimes gets the read wrong */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;}/* Identify what type of DM9000 we are working on */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;}/* 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->set_multicast_list = &dm9000_hash_table;ndev->ethtool_ops = &dm9000_ethtool_ops;ndev->do_ioctl = &dm9000_ioctl;#ifdef CONFIG_NET_POLL_CONTROLLERndev->poll_controller = &dm9000_poll_controller;#endifdb->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";//從EEPROM中讀取MAC地址填充dev_addr/* try reading the node address from the attached 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)) {/* try reading from mac */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);//設定平台裝置驅動的dev成員為ndev。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;}
我們在來看看讀寫網卡寄存器所用的ior和iow
static u8ior(board_info_t * db, int reg){writeb(reg, db->io_addr);return readb(db->io_data);}static voidiow(board_info_t * db, int reg, int value){writeb(reg, db->io_addr);writeb(value, db->io_data);}
可以看得出是先將要訪問的寄存器地址寫入到地址寄存器,然後在將資料寫入到資料寄存器。地址。
3.開啟網卡
在linux終端下使用ifconfig命令時調用net_device的open函數開啟網卡裝置
static intdm9000_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 there is no IRQ type specified, default to something that * may work, and tell the user that this is a problem */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;/* Initialize DM9000 board *///複位網卡晶片dm9000_reset(db);//初始化網卡(相關寄存器設定)dm9000_init_dm9000(dev);/* Init driver variable */db->dbug_cnt = 0;mii_check_media(&db->mii, netif_msg_link(db), 1);//開啟發送隊列netif_start_queue(dev);//調度發送隊列開始工作dm9000_schedule_poll(db);return 0;}
4.資料發送
下面說一下DM9000A中的儲存部分,DM9000A內部有一個4K Dword SRAM,其中3KB是作為發送,16KB作為接收,如所示。其中0x0000~0x0BFF是傳說中的TX buffer(TX buffer中只能存放兩個包),0x0C00~0x3FFF是RX buffer。因此在寫記憶體操作時,當IMR的第7位被設定,如果到達了地址的結尾比如到了3KB,則回卷到0。相似的方式,在讀操作中,當IMR的第7位被設定如果到達了地址的結尾比如16K,則回卷到0x0C00。
DM9000的TX RAM可以同時放兩個包,可以第9行代碼中看出如果大於TXRAM中的包大於2則返回,DM9000會先發送第一個包,然後再發第二個包。
static intdm9000_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__);//如果TX RAM中的包大於2個包則返回if (db->tx_pkt_cnt > 1)return 1;spin_lock_irqsave(&db->lock, flags);*MWCMD是Memory data write command with address increment Register(F8H) *將要訪問的TXRAM地址寫入地址寄存器。/* Move data to DM9000 TX RAM */writeb(DM9000_MWCMD, db->io_addr);//拷貝資料到TXRAM(db->outblk)(db->io_data, skb->data, skb->len);dev->stats.tx_bytes += skb->len;db->tx_pkt_cnt++;//增加資料包計數,這個值會在發送完成中斷時進行自減如果是第一個包則直接發送/* TX control: First packet immediately send, second packet queue */if (db->tx_pkt_cnt == 1) {/* Set TX length to DM9000 */ /*把資料的長度填到TXPLL(發送包長度低位元組)和TXPLH(發送包長度高位元組)中*/ iow(db, DM9000_TXPLL, skb->len); iow(db, DM9000_TXPLH, skb->len >> 8); /*置發送控制寄存器(TX Control Register)的發送請求位TXREQ(Auto clears after sending completely),這樣就可以發送出去了*/ /* *記下此時的時間,這裡起一個時間戳記的作用,之後的逾時會用到。如果當前的系統時間超過裝置的trans_start時間 *至少一個逾時周期,網路層將最終調用驅動程式的tx_timeout。那個這個"一個逾時周期"又是什麼呢?這個是我們在 *probe函數中設定的,ndev->watchdog_timeo = msecs_to_jiffies(watchdog); */dev->trans_start = jiffies;/* save the time stamp */} else {//如果是第二個包,則暫時不發送,等待第一個包發送完成時tx_pkt_cnt減為1的時候再發送。/* Second packet */db->queue_pkt_len = skb->len;netif_stop_queue(dev);//停止發送隊列}spin_unlock_irqrestore(&db->lock, flags);/* free this SKB */dev_kfree_skb(skb);return 0;}
4.中斷
static irqreturn_t dm9000_interrupt(intirq, void *dev_id){ structnet_device *dev = dev_id; board_info_t*db = netdev_priv(dev); intint_status; unsignedlong flags; u8reg_save; dm9000_dbg(db,3, "entering %s\n", __func__); /*A real interrupt coming */ //禁止所用中斷 /*holders of db->lock must always block IRQs */ spin_lock_irqsave(&db->lock,flags); //儲存寄存器地址 /*Save previous register address */ reg_save= readb(db->io_addr); //禁止DM9000的所有中斷 /*Disable all interrupts */ iow(db,DM9000_IMR, IMR_PAR); /*Got DM9000 interrupt status */ //擷取中斷狀態寄存器的值 int_status= ior(db, DM9000_ISR); /* Got ISR */ iow(db,DM9000_ISR, int_status); /* Clear ISRstatus */ if(netif_msg_intr(db)) dev_dbg(db->dev,"interrupt status %02x\n", int_status); /*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); if(db->type != TYPE_DM9000E) { if(int_status & ISR_LNKCHNG) { /*fire a link-change request */ schedule_delayed_work(&db->phy_poll,1); } } /*Re-enable interrupt mask */ //重新開啟DM9000的內部中斷 iow(db,DM9000_IMR, db->imr_all); /*Restore previous register address */ //恢複寄存器的值 writeb(reg_save,db->io_addr); //重新允許所有中斷 spin_unlock_irqrestore(&db->lock,flags); returnIRQ_HANDLED;}
5.接收資料
static voiddm9000_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;/* 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) {dev_warn(db->dev, "status check fail: %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 = le16_to_cpu(rxhdr.RxLen);if (netif_msg_rx_status(db))dev_dbg(db->dev, "RX: status %02x, length %04x\n",rxhdr.RxStatus, RxLen);/* Packet Status check */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);}/* rxhdr.RxStatus is identical to RSR register. */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++;}}/* Move data from DM9000 *///如果接收正確,開始接收if (GoodPacket && ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) {skb_reserve(skb, 2);rdptr = (u8 *) skb_put(skb, RxLen - 4);//擷取skb的資料指標/* Read received packet from RX SRAM */(db->inblk)(db->io_data, rdptr, RxLen);//讀取資料dev->stats.rx_bytes += RxLen;/* Pass to upper layer */skb->protocol = eth_type_trans(skb, dev);netif_rx(skb);//將接收到的skb交給協議層dev->stats.rx_packets++;} else {/* need to dump the packet's data */(db->dumpblk)(db->io_data, RxLen);}} while (rxbyte == DM9000_PKT_RDY);}
6.發送完成
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 *///將資料包計數減1db->tx_pkt_cnt--;dev->stats.tx_packets++;if (netif_msg_tx_done(db))dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status);/* Queue packet check & send *///如果資料包數量依然大於0,說明是TX RAM中的第二個包,再次啟動發送,將TX RAM中第二個包發送出去if (db->tx_pkt_cnt > 0) { /*把資料的長度填到TXPLL(發送包長度低位元組)和TXPLH(發送包長度高位元組)中*/ iow(db, DM9000_TXPLL, skb->len); iow(db, DM9000_TXPLH, skb->len >> 8); /*置發送控制寄存器(TX Control Register)的發送請求位TXREQ(Auto clears after sending completely),這樣就可以發送出去了*/ dev->trans_start = jiffies;}netif_wake_queue(dev);//喚醒發送隊列}}
7.逾時處理
static void dm9000_timeout(struct net_device *dev){board_info_t *db = netdev_priv(dev);u8 reg_save;unsigned long flags;/* Save previous register address */reg_save = readb(db->io_addr);spin_lock_irqsave(&db->lock, flags);//停止發送隊列並複位DM9000網卡netif_stop_queue(dev);dm9000_reset(db);dm9000_init_dm9000(dev);/* We can accept TX packets again *///重新發送dev->trans_start = jiffies;netif_wake_queue(dev);/* Restore previous register address */writeb(reg_save, db->io_addr);spin_unlock_irqrestore(&db->lock, flags);}