Linux裝置驅動工程師之路——DM9000網路卡驅動程式分析

來源:互聯網
上載者:User

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);}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.