08
26

CS8900A Linux驅動程式介紹

記得之前在面試某家CHIP Vendor時,面試官對我之前寫過的(應該是抄過的)S3C4510b網路驅動程式很有興趣,他很詳細的問我每個流程,而且還不准我看資料必需馬上回答他,說老實話,S3C4510B的網卡驅動有點小複雜,再加上我已經有一段時間(2,3年)沒碰S3C4510B,整個驅動的細節幾乎都忘了,不過我還記得我開發S3C4510B driver的大概流程

首先針對開發板寫driver時,我都會用ADS搭ICE寫Boot code開始,然後再一步一步新增driver code測試,像4510b上面的UART, nor flash, network,都是慢慢堆積起來的,而這樣邊寫邊看datasheet會讓自己學到很多東西,我覺得一開始寫driver的環境最好不要搭OS,用最乾淨的環境測試,問題會少很多,只要專注在硬體控制就可以了,而且實驗板大部份都通過QC,需要請示波器出來的情況幾乎沒有

當ADS的driver程式碼完成後,就可以準備進行Linux porting和撰寫linux driver,而linux driver只提供一組骨架,我們只要把之前在ADS寫好的driver搬進來即可
下面我會一步步提到linux driver的改法與步驟

CS8900A的driver code我已經用ADS完成了,請參考這個網頁,而我現在要做的事就是專心在linux-2.6.26裡面新增這個driver的骨架,首先在drivers/net/資料夾下新增cs8900a.c和cs8900a.h,並修改drivers/net/資料夾下的Makefile和Kconfig,範例如下

[Makefile]

  1. obj-$(CONFIG_BFIN_MAC) += bfin_mac.o 
  2. obj-$(CONFIG_DM9000) += dm9000.o 
  3. obj-$(CONFIG_CS8900A) += cs8900a.o 
  4. obj-$(CONFIG_FEC_8XX) += fec_8xx/
  5. obj-$(CONFIG_PASEMI_MAC) += pasemi_mac_driver.o 
  6. pasemi_mac_driver-objs := pasemi_mac.o pasemi_mac_ethtool.o 
  7. obj-$(CONFIG_MLX4_CORE) += mlx4/
  8. obj-$(CONFIG_ENC28J60) += enc28j60.o

[Kconfig]

  1. config CS8900A 
  2. tristate "CS8900A support" 
  3. depends on ARM || BLACKFIN || MIPS 
  4. select CRC32 
  5. select MII 
  6. ---help---
  7. Support for CS8900A chipset.
  8.  
  9. To compile this driver as a module, choose M here. The module 
  10. will be called cs8900.

因為我是針對SMDK2410這個platform新增cs8900a這個driver resource,所以必需修改arch/arm/mach-s3c2410/mach-smdk2410.c這個檔案,修改內容如下

  1. static struct resource s3c_cs89x0_resources[] = { 
  2. [0] = { 
  3. .start = 0x19000300,
  4. .end = 0x19000300 + 16,
  5. .flags = IORESOURCE_MEM,
  6. },
  7. [1] = { 
  8. .start = IRQ_EINT9,
  9. .end = IRQ_EINT9,
  10. .flags = IORESOURCE_IRQ,
  11. },
  12. };
  13.  
  14. static struct platform_device s3c_cs89x0 = { 
  15. .name = "cirrus-cs89x0",
  16. .num_resources = ARRAY_SIZE(s3c_cs89x0_resources),
  17. .resource = s3c_cs89x0_resources,
  18. };
  19.  
  20. static struct platform_device *smdk2410_devices[] __initdata = { 
  21. &s3c_device_usb,
  22. &s3c_device_lcd,
  23. &s3c_device_wdt,
  24. &s3c_device_i2c,
  25. &s3c_device_iis,
  26. &s3c_cs89x0,
  27. };

接下來開始打造cs8900a的linux driver骨架,我這邊用最簡單的骨架範例,所以這個driver不支持ethtool,而骨架中幾個重要的部份列如下
1. static int __init cs8900a_module_init(void):擺在linux _init節段,所以linux啟動時會主動呼叫此函式,這個函式裡面做的事很簡單,只要跟platform register自己這個module即可

  1. static int __init cs8900a_module_init(void) 
  2. { 
  3. printk("CS8900A driver initial\n");
  4. if (platform_driver_register(&cs8900a_driver)) 
  5. { 
  6. printk("CS8900A Driver registration failed\n");
  7. return -ENODEV;
  8. } 
  9.  
  10. return 0;
  11. }

2. static void __exit cs8900a_module_exit(void):driver從platform卸載時呼叫的函式

  1. static void __exit cs8900a_module_exit(void) 
  2. { 
  3. platform_driver_unregister(&cs8900a_driver);
  4. }

3. static int __exit cs8900a_remove(struct platform_device *pdev):driver從kernel卸載時呼叫的函式(一般都是rmmod)

  1. static int __exit cs8900a_remove(struct platform_device *pdev) 
  2. { 
  3. struct net_device *dev = platform_get_drvdata(pdev);
  4. //struct cs8900a_private *cp = netdev_priv(dev);
  5.  
  6. free_irq(dev->irq,dev);
  7. unregister_netdev(dev);
  8. free_netdev(dev);
  9. platform_set_drvdata(pdev, NULL);
  10.  
  11. return 0;
  12. }

4. static int __init cs8900a_probe(struct platform_device *pdev):最重要的函式,在這個函式中,會去要求IO memory region並取得硬體的編號或識別碼,設定mac address也是在這個函式裡面,其它諸如初始化spinlock,和driver method註冊也都在此

  1. static int __init cs8900a_probe(struct platform_device *pdev) 
  2. { 
  3. int err=0;
  4. struct net_device *dev;
  5. struct cs8900a_private *cp;
  6. struct resource *res;
  7. int size;
  8. DECLARE_MAC_BUF(mac);
  9. printk("cs8900a probing\n");
  10. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  11. if (res == NULL) 
  12. { 
  13. printk("no memory resource specified\n");
  14. return -ENOENT;
  15. } 
  16.  
  17. size = (res->end-res->start)+1;
  18. cs8900a_mem = request_mem_region(res->start, size, pdev->name);
  19. if (cs8900a_mem == NULL) 
  20. { 
  21. printk("failed to get memory region\n");
  22. err = -ENOENT;
  23. goto err_out;
  24. } 
  25.  
  26. cs8900a_base = ioremap(res->start, size);
  27. if (cs8900a_base == 0) 
  28. { 
  29. printk("failed to ioremap() region\n");
  30. err = -EINVAL;
  31. goto err_out;
  32. } 
  33. printk("cs8900a base address:%p\n",cs8900a_base);
  34. cs8900a_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
  35. if (cs8900a_irq == NULL) 
  36. { 
  37. printk("no irq resource specified\n");
  38. err = -ENOENT;
  39. goto err_out;
  40. } 
  41. printk("CS8900A:irq no %d\n",cs8900a_irq->start);
  42.  
  43. if (ProbeCS8900A(cs8900a_base)==0) goto err_out;
  44.  
  45. dev = alloc_etherdev(sizeof (struct cs8900a_private));
  46. if (!dev) 
  47. { 
  48. printk(KERN_ERR "cs8900a: Etherdev alloc failed, aborting.\n");
  49. err = -ENOMEM;
  50. goto err_out;
  51. } 
  52. platform_set_drvdata(pdev, dev);
  53. cp = netdev_priv(dev);
  54. spin_lock_init(&cp->lock);
  55.  
  56. dev->dev_addr[0] = CS8900Amac[0];
  57. dev->dev_addr[1] = CS8900Amac[1];
  58. dev->dev_addr[2] = CS8900Amac[2];
  59. dev->dev_addr[3] = CS8900Amac[3];
  60. dev->dev_addr[4] = CS8900Amac[4];
  61. dev->dev_addr[5] = CS8900Amac[5];
  62.  
  63. dev->open = cs8900a_open;
  64. dev->stop = cs8900a_close;
  65. dev->hard_start_xmit = cs8900a_start_xmit;
  66. dev->tx_timeout = timeout;
  67. dev->set_multicast_list = cs8900a_set_multicast;
  68. dev->set_mac_address = cs8900a_set_mac_address;
  69. dev->irq = cs8900a_irq->start;
  70.  
  71. //printk("CS8900A:irq no %d\n",cs8900a_irq);
  72. if (register_netdev(dev)) { 
  73. printk(KERN_ERR "CS8900a: Cannot register net device, " 
  74. "aborting.\n");
  75. err = -ENODEV;
  76. goto err_out;
  77. } 
  78.  
  79. //printk(KERN_INFO "%s: %s %s\n",
  80. // dev->name, cs8900astr, print_mac(mac, dev->dev_addr));
  81.  
  82. return 0;
  83.  
  84. //err_out_free_dev:
  85. // kfree(dev);
  86.  
  87. err_out:
  88. return err;
  89. }

5. static int cs8900a_start_xmit(struct sk_buff *skb, struct net_device *dev):傳送封包的函式,這也是linux封包往外送的終點,到了這邊,就準備驅動硬體開始傳送封包

  1. static int cs8900a_start_xmit(struct sk_buff *skb, struct net_device *dev) 
  2. { 
  3. unsigned long flags;
  4. struct cs8900a_private *cp=(struct cs8900a_private *)dev->priv;
  5.  
  6. spin_lock_irqsave(&cp->lock, flags);
  7. dev->stats.tx_bytes += skb->len;
  8. dev->stats.tx_packets++;
  9. TransmitPacket(cs8900a_base,skb->data,skb->len);
  10. dev->trans_start = jiffies;
  11. spin_unlock_irqrestore(&cp->lock, flags);
  12. dev_kfree_skb(skb);
  13. return 0;
  14. }

6. static irqreturn_t cs8900a_interrupt(int irq, void *dev_id):因為cs8900a提供接收封包的中斷,所以這個中斷函式只要接收封包並往linux kernel送即可

  1. static irqreturn_t cs8900a_interrupt(int irq, void *dev_id) 
  2. { 
  3. struct sk_buff *skb;
  4. u_short len,pktlen,temp,*ptr;
  5. struct net_device *dev = (struct net_device *) dev_id;
  6. u_short event;
  7. struct cs8900a_private *cp=(struct cs8900a_private *)dev->priv;
  8. unsigned long flags;
  9.  
  10. spin_lock_irqsave(&cp->lock, flags);
  11. event = IOREAD(cs8900a_base,ISQ);
  12. do 
  13. { 
  14. if ( ((event & ISQ_REG_NUM ) == REG_NUM_RX_EVENT ) &&
  15. ((event & RX_EVENT_RX_OK) == RX_EVENT_RX_OK ) &&
  16. ((event & RX_EVENT_IND_ADDR) | (event & RX_CTL_BROADCAST_A))) 
  17. { 
  18. temp=IOREAD(cs8900a_base,IODATA0);//discard RxStatus
  19. len=IOREAD(cs8900a_base,IODATA0);//read frame length
  20. skb = dev_alloc_skb(len+2);
  21. skb->dev = dev;
  22. skb_reserve(skb, 2);
  23.  
  24.  
  25. ptr=(u_short *)skb->data;
  26.  
  27. pktlen=len;
  28. while (len>0) 
  29. { 
  30.  
  31. temp=IOREAD(cs8900a_base,IODATA0);
  32. if (len==1) 
  33. { 
  34. *((char *)ptr)=(char)temp;
  35. len-=1;
  36. continue;
  37. } 
  38.  
  39. *ptr=temp;
  40. len-=2;
  41. ++ptr;
  42. } 
  43.  
  44. skb_put(skb, pktlen);
  45. skb->protocol = eth_type_trans(skb, dev);
  46. netif_rx(skb);
  47. dev->stats.rx_packets++;
  48. dev->stats.rx_bytes += pktlen;
  49. } 
  50. else 
  51. { 
  52. dev->stats.rx_dropped++;
  53. } 
  54. event = IOREAD(cs8900a_base,ISQ);
  55. }while (event>0);
  56. spin_unlock_irqrestore(&cp->lock, flags);
  57. return IRQ_HANDLED;
  58. }

7. static int cs8900a_open(struct net_device *dev):註冊中斷和初始化硬體(讓cs8900a進入運作狀態)

  1. static int cs8900a_open(struct net_device *dev) 
  2. { 
  3. //struct cs8900a_private *cp = netdev_priv(dev);
  4. unsigned int irq = dev->irq;
  5. int err;
  6.  
  7. InitEthernet();
  8. set_irq_type(dev->irq, IRQT_RISING);
  9. if (request_irq(irq, cs8900a_interrupt, 0, cs8900astr, dev)) 
  10. { 
  11. printk(KERN_ERR "CS8900A: Can't get irq %d\n", dev->irq);
  12. err = -EAGAIN;
  13. return err;
  14. } 
  15. netif_start_queue(dev);
  16. return 0;
  17. }

8. static int cs8900a_close(struct net_device *dev):halt cs8900a,暫停傳送

  1. static int cs8900a_close(struct net_device *dev) 
  2. { 
  3. //struct cs8900a_private *cp = netdev_priv(dev);
  4. unsigned int irq = dev->irq;
  5.  
  6. WritePktPageReg(cs8900a_base,PKTPG_LINE_CTL,0);
  7. netif_stop_queue(dev);
  8. /* Shutdown the Seeq. */ 
  9. free_irq(irq, dev);
  10. return 0;
  11. }

我參考的範例是sgiseeq.c,所以骨架都是抄這個檔案的,為什麼會選sgiseeq.c這個來改呢?因為它夠小,架構又夠清楚,不改它還改誰,改完之後進到kernel選單選擇cs8900a driver,然後再開始編譯kernel,網卡啟動的過程如下

這邊我提一下cs8900a硬體的運作方式,先下載cs8900a datasheet,CS8900A提供兩種驅動硬體的方式,一種是I/O mode,另外一種是memory mapping的方式,我這邊在撰寫linux driver時,所使用的是I/O mode,I/O mode提供8組暫存器讓使用者存取資料,0000h-0006h跟封包有關,0008h則是ISQ,當硬體有資訊改變時,會改動ISQ的值,000Ah-000Eh是讓使用者存取CS8900A內部的其它暫存器,各個暫存器詳細的用法請參考4.2節的PacketPage memory map

其實我在讀這類的datasheet都會先看它的FAQ,因為在FAQ中會提供一些驅動硬體時該遵循的步驟或方法,CS8900A的FAQ文件中就有提到probing時該下那些指令,並且還包含封包收送時要讀寫的暫存器,因為faq內容非常簡短,所以讀起來比較不吃力

而CS8900A跟QT2410電路圖如下,從下圖可看出CS8900A接在nGCS3,基底位址在0x18000000,而IRQ_LAN則是接在EINT9,memory mapping從0x18000000開始,而I/O port register則是擺在0x19000300(bit 24為切換memory mapping與I/O port register的開關, 300h為I/O register基底位址)

最後下面提供CS8900A driver source file和修改過檔案
1.[drivers/net/cs8900a.c]
2.[drivers/net/cs8900a.h]
3.[arch/arm/mach-s3c2410/mach-smdk2410.c]
4.[drivers/net/Kconfig]
5.[drivers/net/Makefile]

 

 

 

標籤: embedded
評論: 2 | 引用: 0 | 閱讀: 8742
  • 1 
luke [ 2009-05-21 16:48 | 回覆 | 編輯 刪除 ]
你好,還是不太清楚 IO port register 為何是從 0x19000000(?) + 0x300
cs8900a 的 address line 也不過 20 pin,0x19000000 這樣的位址也應該只有前 20bit 有效才對,所謂的 bit 24 又是甚麼呢...?!
luke [ 2009-05-21 18:29 | 回覆 | 編輯 刪除 ]
sorry.. 我知道原因了,Qt2410 的電路圖沒把該 pin 畫出來
  • 1 
發表評論
暱 稱: 密 碼:
網 址: E - mail:
驗證碼: 驗證碼圖片 選 項:
頭 像:
內 容: