記得之前在面試某家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]
- obj-$(CONFIG_BFIN_MAC) += bfin_mac.o
- obj-$(CONFIG_DM9000) += dm9000.o
- obj-$(CONFIG_CS8900A) += cs8900a.o
- obj-$(CONFIG_FEC_8XX) += fec_8xx/
- obj-$(CONFIG_PASEMI_MAC) += pasemi_mac_driver.o
- pasemi_mac_driver-objs := pasemi_mac.o pasemi_mac_ethtool.o
- obj-$(CONFIG_MLX4_CORE) += mlx4/
- obj-$(CONFIG_ENC28J60) += enc28j60.o
[Kconfig]
- config CS8900A
- tristate "CS8900A support"
- depends on ARM || BLACKFIN || MIPS
- select CRC32
- select MII
- ---help---
- Support for CS8900A chipset.
-
- To compile this driver as a module, choose M here. The module
- will be called cs8900.
因為我是針對SMDK2410這個platform新增cs8900a這個driver resource,所以必需修改arch/arm/mach-s3c2410/mach-smdk2410.c這個檔案,修改內容如下
- static struct resource s3c_cs89x0_resources[] = {
- [0] = {
- .start = 0x19000300,
- .end = 0x19000300 + 16,
- .flags = IORESOURCE_MEM,
- },
- [1] = {
- .start = IRQ_EINT9,
- .end = IRQ_EINT9,
- .flags = IORESOURCE_IRQ,
- },
- };
-
- static struct platform_device s3c_cs89x0 = {
- .name = "cirrus-cs89x0",
- .num_resources = ARRAY_SIZE(s3c_cs89x0_resources),
- .resource = s3c_cs89x0_resources,
- };
-
- static struct platform_device *smdk2410_devices[] __initdata = {
- &s3c_device_usb,
- &s3c_device_lcd,
- &s3c_device_wdt,
- &s3c_device_i2c,
- &s3c_device_iis,
- &s3c_cs89x0,
- };
接下來開始打造cs8900a的linux driver骨架,我這邊用最簡單的骨架範例,所以這個driver不支持ethtool,而骨架中幾個重要的部份列如下
1. static int __init cs8900a_module_init(void):擺在linux _init節段,所以linux啟動時會主動呼叫此函式,這個函式裡面做的事很簡單,只要跟platform register自己這個module即可
- static int __init cs8900a_module_init(void)
- {
- printk("CS8900A driver initial\n");
- if (platform_driver_register(&cs8900a_driver))
- {
- printk("CS8900A Driver registration failed\n");
- return -ENODEV;
- }
-
- return 0;
- }
2. static void __exit cs8900a_module_exit(void):driver從platform卸載時呼叫的函式
- static void __exit cs8900a_module_exit(void)
- {
- platform_driver_unregister(&cs8900a_driver);
- }
3. static int __exit cs8900a_remove(struct platform_device *pdev):driver從kernel卸載時呼叫的函式(一般都是rmmod)
- static int __exit cs8900a_remove(struct platform_device *pdev)
- {
- struct net_device *dev = platform_get_drvdata(pdev);
- //struct cs8900a_private *cp = netdev_priv(dev);
-
- free_irq(dev->irq,dev);
- unregister_netdev(dev);
- free_netdev(dev);
- platform_set_drvdata(pdev, NULL);
-
- return 0;
- }
4. static int __init cs8900a_probe(struct
platform_device *pdev):最重要的函式,在這個函式中,會去要求IO memory
region並取得硬體的編號或識別碼,設定mac address也是在這個函式裡面,其它諸如初始化spinlock,和driver
method註冊也都在此
- static int __init cs8900a_probe(struct platform_device *pdev)
- {
- int err=0;
- struct net_device *dev;
- struct cs8900a_private *cp;
- struct resource *res;
- int size;
- DECLARE_MAC_BUF(mac);
- printk("cs8900a probing\n");
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (res == NULL)
- {
- printk("no memory resource specified\n");
- return -ENOENT;
- }
-
- size = (res->end-res->start)+1;
- cs8900a_mem = request_mem_region(res->start, size, pdev->name);
- if (cs8900a_mem == NULL)
- {
- printk("failed to get memory region\n");
- err = -ENOENT;
- goto err_out;
- }
-
- cs8900a_base = ioremap(res->start, size);
- if (cs8900a_base == 0)
- {
- printk("failed to ioremap() region\n");
- err = -EINVAL;
- goto err_out;
- }
- printk("cs8900a base address:%p\n",cs8900a_base);
- cs8900a_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (cs8900a_irq == NULL)
- {
- printk("no irq resource specified\n");
- err = -ENOENT;
- goto err_out;
- }
- printk("CS8900A:irq no %d\n",cs8900a_irq->start);
-
- if (ProbeCS8900A(cs8900a_base)==0) goto err_out;
-
- dev = alloc_etherdev(sizeof (struct cs8900a_private));
- if (!dev)
- {
- printk(KERN_ERR "cs8900a: Etherdev alloc failed, aborting.\n");
- err = -ENOMEM;
- goto err_out;
- }
- platform_set_drvdata(pdev, dev);
- cp = netdev_priv(dev);
- spin_lock_init(&cp->lock);
-
- dev->dev_addr[0] = CS8900Amac[0];
- dev->dev_addr[1] = CS8900Amac[1];
- dev->dev_addr[2] = CS8900Amac[2];
- dev->dev_addr[3] = CS8900Amac[3];
- dev->dev_addr[4] = CS8900Amac[4];
- dev->dev_addr[5] = CS8900Amac[5];
-
- dev->open = cs8900a_open;
- dev->stop = cs8900a_close;
- dev->hard_start_xmit = cs8900a_start_xmit;
- dev->tx_timeout = timeout;
- dev->set_multicast_list = cs8900a_set_multicast;
- dev->set_mac_address = cs8900a_set_mac_address;
- dev->irq = cs8900a_irq->start;
-
- //printk("CS8900A:irq no %d\n",cs8900a_irq);
- if (register_netdev(dev)) {
- printk(KERN_ERR "CS8900a: Cannot register net device, "
- "aborting.\n");
- err = -ENODEV;
- goto err_out;
- }
-
- //printk(KERN_INFO "%s: %s %s\n",
- // dev->name, cs8900astr, print_mac(mac, dev->dev_addr));
-
- return 0;
-
- //err_out_free_dev:
- // kfree(dev);
-
- err_out:
- return err;
- }
5. static int cs8900a_start_xmit(struct sk_buff *skb, struct net_device *dev):傳送封包的函式,這也是linux封包往外送的終點,到了這邊,就準備驅動硬體開始傳送封包
- static int cs8900a_start_xmit(struct sk_buff *skb, struct net_device *dev)
- {
- unsigned long flags;
- struct cs8900a_private *cp=(struct cs8900a_private *)dev->priv;
-
- spin_lock_irqsave(&cp->lock, flags);
- dev->stats.tx_bytes += skb->len;
- dev->stats.tx_packets++;
- TransmitPacket(cs8900a_base,skb->data,skb->len);
- dev->trans_start = jiffies;
- spin_unlock_irqrestore(&cp->lock, flags);
- dev_kfree_skb(skb);
- return 0;
- }
6. static irqreturn_t cs8900a_interrupt(int irq, void *dev_id):因為cs8900a提供接收封包的中斷,所以這個中斷函式只要接收封包並往linux kernel送即可
- static irqreturn_t cs8900a_interrupt(int irq, void *dev_id)
- {
- struct sk_buff *skb;
- u_short len,pktlen,temp,*ptr;
- struct net_device *dev = (struct net_device *) dev_id;
- u_short event;
- struct cs8900a_private *cp=(struct cs8900a_private *)dev->priv;
- unsigned long flags;
-
- spin_lock_irqsave(&cp->lock, flags);
- event = IOREAD(cs8900a_base,ISQ);
- do
- {
- if ( ((event & ISQ_REG_NUM ) == REG_NUM_RX_EVENT ) &&
- ((event & RX_EVENT_RX_OK) == RX_EVENT_RX_OK ) &&
- ((event & RX_EVENT_IND_ADDR) | (event & RX_CTL_BROADCAST_A)))
- {
- temp=IOREAD(cs8900a_base,IODATA0);//discard RxStatus
- len=IOREAD(cs8900a_base,IODATA0);//read frame length
- skb = dev_alloc_skb(len+2);
- skb->dev = dev;
- skb_reserve(skb, 2);
-
-
- ptr=(u_short *)skb->data;
-
- pktlen=len;
- while (len>0)
- {
-
- temp=IOREAD(cs8900a_base,IODATA0);
- if (len==1)
- {
- *((char *)ptr)=(char)temp;
- len-=1;
- continue;
- }
-
- *ptr=temp;
- len-=2;
- ++ptr;
- }
-
- skb_put(skb, pktlen);
- skb->protocol = eth_type_trans(skb, dev);
- netif_rx(skb);
- dev->stats.rx_packets++;
- dev->stats.rx_bytes += pktlen;
- }
- else
- {
- dev->stats.rx_dropped++;
- }
- event = IOREAD(cs8900a_base,ISQ);
- }while (event>0);
- spin_unlock_irqrestore(&cp->lock, flags);
- return IRQ_HANDLED;
- }
7. static int cs8900a_open(struct net_device *dev):註冊中斷和初始化硬體(讓cs8900a進入運作狀態)
- static int cs8900a_open(struct net_device *dev)
- {
- //struct cs8900a_private *cp = netdev_priv(dev);
- unsigned int irq = dev->irq;
- int err;
-
- InitEthernet();
- set_irq_type(dev->irq, IRQT_RISING);
- if (request_irq(irq, cs8900a_interrupt, 0, cs8900astr, dev))
- {
- printk(KERN_ERR "CS8900A: Can't get irq %d\n", dev->irq);
- err = -EAGAIN;
- return err;
- }
- netif_start_queue(dev);
- return 0;
- }
8. static int cs8900a_close(struct net_device *dev):halt cs8900a,暫停傳送
- static int cs8900a_close(struct net_device *dev)
- {
- //struct cs8900a_private *cp = netdev_priv(dev);
- unsigned int irq = dev->irq;
-
- WritePktPageReg(cs8900a_base,PKTPG_LINE_CTL,0);
- netif_stop_queue(dev);
-
- free_irq(irq, dev);
- return 0;
- }
我參考的範例是sgiseeq.c,所以骨架都是抄這個檔案的,為什麼會選sgiseeq.c這個來改呢?因為它夠小,架構又夠清楚,不改它還改誰,改完之後進到kernel選單選擇cs8900a driver,然後再開始編譯kernel,網卡啟動的過程如下
這邊我提一下cs8900a硬體的運作方式,,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中會提供一些驅動硬體時該遵循的步驟或方法,中就有提到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/]
2.[drivers/net/]
3.[arch/arm/mach-s3c2410/]
4.[drivers/net/]
5.[drivers/net/]