首先針對開發板寫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;
}

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);
/* Shutdown the Seeq. */ 
free_irq(irq, dev);
return 0;
}

我參考的範例是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]

最後修改日期: 3 6 月, 2022

作者

留言

撰寫回覆或留言

發佈留言必須填寫的電子郵件地址不會公開。