- 論壇徽章:
- 0
|
在成功移植Dm9000驅(qū)動到我的EIEVK-100開發(fā)板的前提下,本文從以下幾個方面說明相關(guān)原理及過程:
1.硬件情況
2.Dm9000驅(qū)動移植詳細過程
3.Platform_device與platform_data
4.Dm9000驅(qū)動代碼簡要分析
一. 硬件情況
DM9000在電路板上的連接中與編程相關(guān)的如下:
1)EECS拉高:16bit模式;
2)EECK拉高,INT連接到2440 EINT7:INT腳為低時為有效中斷信號,中斷線為EINT7
3)cs連接到2440的nGCS2,CMD連接2440地址總線ADDR[2]:INDEX和DATA端口地址分別為0x1000_0000和0x1000_0004。
知道上面這些信息已經(jīng)足夠移植驅(qū)動了。
二. Dm9000驅(qū)動移植詳細過程
1.在內(nèi)核編譯配置選項中,driver-->net-->10/100M net-->DM9000 support 選項選中。選上之后dm9000.c才會被編譯進內(nèi)核。
2.在arch/arm/mach-s3c2410/devs.c 中添加dm9000的platform_device。
/* Add Dm9000 platform_device
beelike@126.com
*/
#include
static struct resource eievk_dm9000_resource[] = {
[0]= {
.start = 0x10000000, //this is based on EIEVK board
.end = 0x10000003,
.flags = IORESOURCE_MEM,
},
[1]={
.start = 0x10000004,
.end = 0x10000007,
.flags = IORESOURCE_MEM,
},
[2]={
.start = IRQ_EINT7,
.end = IRQ_EINT7,
.flags = IORESOURCE_IRQ,
}
};
static struct dm9000_plat_data eievk_dm9000_platdata ={
.flags = DM9000_PLATF_16BITONLY,//work in 16bit mode
};
struct platform_device eievk_dm9000_device = {
.name = "dm9000",
.id = -1,
.num_resources = 3,
.resource = eievk_dm9000_resource,
.dev = {
.platform_data = &eievk_dm9000_platdata,
}
};
EXPORT_SYMBOL(eievk_dm9000_device);
/*end add
beelike@126.com
*/
3.在arch/arm/mach-s3c2410/devs.h中 聲明平臺設(shè)備 eievk_dm9000_device :
extern struct platform_device eievk_dm9000_device;
4.在arm/arm/mach-s3c2410/mach-smdk2410.c中將eievk_dm9000_device添加到平臺設(shè)備列表中:
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
&eievk_dm9000_device, //add dm9000 by beelike
&s3c_device_nand,
};
5.OK,經(jīng)過上述努力,Dm9000設(shè)備已經(jīng)成功注冊進入驅(qū)動核心。下面進入driver/net/dm9000.C中,還需要做兩方面的工作:設(shè)置芯片MAC地址,使能DM9000的中斷。
根據(jù)2440資料。有幾個地方需要設(shè)置:
1)GPFCON (56000050) GPF7 [15:14] 置 10 ,功能設(shè)置為EINT7 。
這可以用函數(shù)實現(xiàn):s3c2410_gpio_cfgpin(S3C2410_GPF7, S3C2410_GPF3_EINT7);
2)EXTINT0 (56000088) [30:28] 置0 低電平觸發(fā)中斷 (復(fù)位默認為全0,可能不必設(shè))
3)外部中斷屏蔽寄存器。EINTMASK (560000a4) [7] 置0 以enable interrupt EINT7
4)全局中斷屏蔽寄存器 INTMASK (4A000008) [4] 置0 使能EINT4_7。
代碼修改:
在dm9000.C的開始添加如下定義:
/********************beelike add ********************/
#include
static char net_mac_addr[]={0x00,0xe0,0x3d,0xf4,0xdd,0xf7};//MAC
static void *extint0,*intmsk,*eintmsk;
#define EINTMASK (0x560000a4) //外部中斷屏蔽
#define EXTINT0 (0x56000088) //外部中斷方式
#define INTMSK (0x4A000008) //中斷屏蔽
/*******************end add ***********************/
在dm9000.C中dm9000_probe(struct platform_device *pdev)中適當位置(設(shè)置MAC之后,register_netdevice()之前)添加如下代碼:
/***********beelike add ********/
for(i=0;i
ndev->dev_addr=net_mac_addr;
}
extint0=ioremap_nocache(EXTINT0,4);//
intmsk=ioremap_nocache(INTMSK,4);
eintmsk=ioremap_nocache(EINTMASK ,4);
s3c2410_gpio_cfgpin(S3C2410_GPF7, S3C2410_GPF7_EINT7);
writel(readl(extint0)&0x8fffffff,extint0); //eint7 low level
writel(readl(intmsk)&(~(1
writel(readl(extint0)&(~(1
iounmap(intmsk);
iounmap(extint0);
iounmap(eintmsk);
/******************end *********/
OK,編譯下載之后,啟動內(nèi)核后:
Mount -t proc proc /proc
Ifconfig eth0 192.168.1.12 netmask 255.255.255.0
eth0: link up, 100Mbps, full-duplex, lpa 0x41E1
開發(fā)板與主機之間能正常ping通。整個驅(qū)動移植過程結(jié)束。
三. Platform_device和platform_driver
通過Platform機制開發(fā)發(fā)底層驅(qū)動的大致流程為: 定義 platform_device---注冊 platform_device ---定義 platform_driver-----注冊 platform_driver。
1. Platform_device 定義于 kernel\include\linux\platform_device.h中,
struct platform_device {
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
定義一個platform_device一般需要初始化兩個方面的內(nèi)容:設(shè)備占用的資源resource和設(shè)備私有數(shù)據(jù)dev.platform_data。最重要的是resource
設(shè)備占用的資源主要是兩個方面:IO內(nèi)存和irq資源。
Resource定義于kernel\include\linux\ioport.h中,
struct resource {
const char *name;
unsigned long start, end;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
實際上是對地址范圍及其屬性的一個描述。最后幾個用于樹型結(jié)構(gòu)的指針是內(nèi)核用于管理所有資源的。
而platform_data則是設(shè)置給struct device dev;中的platform_data指針(void *)。這個指針內(nèi)核并不使用,而是驅(qū)動自身來定義及使用。
比如說對于DM9000,對應(yīng)的platform_data定義于include/linux/dm9000.H中。
struct dm9000_plat_data {
unsigned int flags;
/* allow replacement IO routines */
void (*inblk)(void __iomem *reg, void *data, int len);
void (*outblk)(void __iomem *reg, void *data, int len);
void (*dumpblk)(void __iomem *reg, int len);
};
OK,初始化完資源和platform_data,一個平臺設(shè)備就定義好了。把這個平臺設(shè)備變量的地址添加到資源列表中去。比如在2410平臺:
在arm/arm/mach-s3c2410/mach-smdk2410.c把設(shè)備地址添加到*smdk2410_devices[] __initdata 數(shù)組中去。
最后在arch/arm/mach-3sc2410/cpu.c 中初始化函數(shù)__init s3c_arch_init(void)會對smdk2410_devices[]每一個設(shè)備的指針ptr調(diào)用platform_device_register(ptr)。主要是建立device的層次結(jié)構(gòu)(建立sysfs入口),將設(shè)備占用的資源添加到內(nèi)核資源管理。接下來看看platform_driver:
2. platform_driver結(jié)構(gòu)定義于include/linux/platform_device.H :
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
它內(nèi)部封裝了一個device_driver,更有意思的是其它的全是函數(shù),并且這些函數(shù)名與device_driver中提供的一樣,只是參數(shù)由device * 變成了 platform_device * 。
驅(qū)動應(yīng)該實現(xiàn)platform_driver中的這些操作,而內(nèi)嵌的device_driver中的對應(yīng)函數(shù)則在注冊時被指定為內(nèi)核指定的操作,這些指定操作只是把調(diào)用參數(shù)轉(zhuǎn)換成platform_driver和platform_device來調(diào)用platform_driver提供的操作而已。 好像有點亂。。不過代碼可以解釋一切:
平臺驅(qū)動注冊:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver);
}
OK,如果device_driver的方法沒有定義就會變成對應(yīng)的platform_drv_*方法。
來看看其中的一個的實現(xiàn):比如 platform_drv_probe
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->probe(dev);
}
事情很清楚,先把設(shè)備的device_driver轉(zhuǎn)成platform_driver,同樣轉(zhuǎn)換device為platform_device。然后去調(diào)用platform_driver提供的函數(shù)。類型轉(zhuǎn)換當然是通過container_of()宏實現(xiàn)的。
因此,驅(qū)動只需要實現(xiàn)platform_driver中的方法。然后注冊即可。
關(guān)于注冊,由上面的代碼可知,最終也是通過 driver_register(&drv->driver);來做的。
3.更深入的小窺一下平臺設(shè)備與平臺驅(qū)動的注冊:
根據(jù)LDD3中指出的設(shè)備模型,一個設(shè)備和驅(qū)動必然屬于某一個總線。Platform_device和platform-driver在層次上隸屬于叫platform_bus_type的總線類型。OK,平臺驅(qū)動注冊的時候(平臺設(shè)備必須先于驅(qū)動注冊)將引用它所屬總線的匹配函數(shù)去決定總線上每一個設(shè)備是否屬于自己。然后二者建立聯(lián)系:設(shè)備的驅(qū)動指針指向該驅(qū)動,驅(qū)動的設(shè)備列表中加入匹配的設(shè)備。
當然,這是在設(shè)備和驅(qū)動這一層面來說的,更深入一層,kobjects和ksets建立層次關(guān)系,建立sysfs入口等等。注意,platform_bus_type的匹配函數(shù)只是比較一下driver和device的name是否相同。因此,同一設(shè)備的platform_device和platform_driver的name應(yīng)該設(shè)為相同的。見platform_bus_type匹配函數(shù)定義:
static int platform_match(struct device * dev, struct device_driver * drv)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}
因此,dm9000的platform_device和platform_driver的name都為"Dm9000"。
4.下面一個問題:資源怎么用??Platform_data一般怎么用?
資源描述的是設(shè)備占用的IO內(nèi)存,IO端口,及中斷線。
Dm9000驅(qū)動中是這樣使用的。這符合慣例:
在probe中獲取資源,并且申請資源,最后映射到內(nèi)核空間,把映射結(jié)果保存起來。
在net_device中的open函數(shù)里,注冊中斷處理函數(shù)。
Platform_data的使用極為靈活,首先platform_data結(jié)構(gòu)不同設(shè)備之間沒有定論,一般可用來保存特定于設(shè)備的一些配置,操作等。比如對于DM9000,可以存在按字節(jié),按字訪問的不同模式,因此其platform_data定義成這樣:
struct dm9000_plat_data {
unsigned int flags;
/* allow replacement IO routines */
void (*inblk)(void __iomem *reg, void *data, int len);
void (*outblk)(void __iomem *reg, void *data, int len);
void (*dumpblk)(void __iomem *reg, int len);
};
其中flags是8/16位模式的選擇標志,下面三個是在該模式下的IO存取函數(shù)。
然后Dm9000驅(qū)動只使用了它的flags標志,其余的并不使用。
因為對于網(wǎng)絡(luò)net_device,有一個叫著private_data的指針,在分配一個net_device的時候可以讓內(nèi)核為其開辟指定大小的內(nèi)存。這部分內(nèi)存可以通過net_device訪問,而且內(nèi)容也是驅(qū)動開發(fā)者自定義的。在DM9000的驅(qū)動中,net_devict的private_data使用了一個叫board_info的結(jié)構(gòu)體來包括更多設(shè)備相關(guān)的信息和操作。
dm9000_plat_data提供的內(nèi)容也被包括進board_info。因此驅(qū)動只使用了初始時設(shè)置的flags,除此外dm9000_plat_data中的方法沒有使用的必要。
從中得到的啟示:
Device 包含一個platform_data。
Net_device則包含一個private區(qū)域.
這樣既實現(xiàn)了設(shè)備模型的統(tǒng)一管理,又實現(xiàn)了保持不同設(shè)備的信息與方法的靈活性。
四. Dm9000驅(qū)動源碼的簡要分析
1.定義并注冊DM9000 的 platform_device 。定義設(shè)備占用資源和platform_data。(具體的見前面)
2.將platform_device添加到板子的設(shè)備列表中去,在系統(tǒng)初始化時注冊入內(nèi)核。
3.在DM9000.C中,定義了dm9000的platform_driver。
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
},
.probe = dm9000_probe,
.remove = dm9000_drv_remove,
.suspend = dm9000_drv_suspend,
.resume = dm9000_drv_resume,
};
這里面關(guān)健的東西是name和probe,remove。
4.在模塊初始化函數(shù)module_init(dm9000_init);中注冊dm9000_driver。
platform_driver_register(&dm9000_driver);
這將導(dǎo)致驅(qū)動的probe函數(shù)被調(diào)用。
5.驅(qū)動還定義了一個數(shù)據(jù)結(jié)構(gòu):board_info來記錄芯片的信息及操作。如統(tǒng)計信息,讀寫操作,占用的IO地址資源,狀態(tài)。
6.OK,現(xiàn)在接著4說,模塊初始化函數(shù)最終將調(diào)用probe函數(shù)。這個函數(shù)完成的基本過程 :
1)獲取一個netdevice:
ndev = alloc_etherdev(sizeof (struct board_info));
2)獲取設(shè)備資源:
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);
3)申請IO資源,映射到內(nèi)核并保存映射地址:
db->addr_req = request_mem_region(db->addr_res->start, i,pdev->name);
db->data_req = request_mem_region(db->data_res->start, iosize,pdev->name);
db->io_addr = ioremap(db->addr_res->start, i);
db->io_data = ioremap(db->data_res->start, iosize);
這里的db即是驅(qū)動自定義的board_info結(jié)構(gòu)指針。伴隨ndev申請內(nèi)存。
4)根據(jù)DM9000數(shù)據(jù)位寬設(shè)置 讀寫數(shù)據(jù)幀的函數(shù)指針。
Dm9000_set_io(db, iosize);
5)OK,現(xiàn)在復(fù)位芯片:dm9000_reset(db); db中已經(jīng)包含了詳細的芯片信息。
6)讀取芯片ID號并判斷是否為0x90000A46。
7)初始化以太網(wǎng)ndev : ether_setup(ndev);
8)設(shè)置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->get_stats = &dm9000_get_stats;
ndev->set_multicast_list = &dm9000_hash_table;
9)添加一些打開中斷,設(shè)置MAC地址的操作在這里。
10)將ndev記錄于平臺設(shè)備platform_dev中去。注冊ndev。
platform_set_drvdata(pdev, ndev); //pdev->dev->dev_driver_data=ndev.
ret = register_netdev(ndev);
OK,probe的使命OVER了。這也是ndev與platform_dev建立聯(lián)系的地方。
可以這么理解,linux的設(shè)備模型負責(zé)的只是設(shè)備的管理(檢測,啟動,移除),而如何訪問這個設(shè)備的數(shù)據(jù),比如說以字符流模式,塊設(shè)備方式,網(wǎng)絡(luò)接口,則定義相應(yīng)的cdev,gendisk,ndev,然后注冊到內(nèi)核。所有的數(shù)據(jù)訪問工作都以這三種界面提供。
7.OK,一旦probe正常的執(zhí)行完,內(nèi)核中注冊好了eth0這個網(wǎng)絡(luò)接口(因為只有一個網(wǎng)卡)。在系統(tǒng)啟動之后,配置eth0,這將引起ndev->open()調(diào)用。來看看open做些什么?
Open(dev)流程:
申請中斷線:
request_irq(dev->irq, &dm9000_interrupt, IRQF_SHARED, dev->name, dev)
復(fù)位DM9000,初始化芯片的各個寄存器使其工作在適當?shù)臓顟B(tài)。
設(shè)置timer(用于傳輸超時),
調(diào)用netif_start_queue(dev);使設(shè)備可以開始收發(fā)數(shù)據(jù)。
8.OK,配置好eth0接口后,網(wǎng)絡(luò)設(shè)備連接好。數(shù)據(jù)收發(fā)就緒,F(xiàn)在簡要的分析一下收發(fā)過程:
發(fā)送數(shù)據(jù)包:協(xié)議層用已經(jīng)封裝好上層協(xié)議數(shù)據(jù)的skb_buffer調(diào)用dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)函數(shù)。
發(fā)送函數(shù)流程:
netif_stop_queue(dev); 暫停接口,使上層暫時不能發(fā)送數(shù)據(jù)。
正在發(fā)送中的數(shù)據(jù)計數(shù)加1。
如果只有當前包發(fā)送,寫指令,寫數(shù)據(jù)幀,發(fā)送包。
如果多于一包數(shù)據(jù)正在發(fā)送,當前幀不發(fā)送。
釋放skb。
重新使能接口:netif_wake_queue(dev);
發(fā)送結(jié)束,DM9000產(chǎn)生中斷,在中斷函數(shù)中讀取芯片相關(guān)寄存器判斷中斷原因,如果是發(fā)送結(jié)束,則遞減正發(fā)送包計數(shù)。并netif_wake_queue(dev);
9.接收過程:
網(wǎng)絡(luò)數(shù)據(jù)包到達,DM9000自動接收并存放在DM內(nèi)部RAM中,產(chǎn)生中斷。在中斷處理中識別中斷原因并調(diào)用接收處理函數(shù)dm9000_rx(struct net_device *dev)。
dm9000_rx:
讀取芯片相關(guān)寄存器確認DM9000正確的收到一幀數(shù)據(jù)。
申請skb_buffer,將數(shù)據(jù)從DM9000中拷貝到skb_buffer中。設(shè)置skb->dev=nev,skb->protocol=eth_type_trans(skb, dev)。
然后把skb_buffer交給上層協(xié)議:netif_rx(skb);
最后更新接口統(tǒng)計信息:db->stats.rx_packets ; 收到包總數(shù) 1。
整個DM9000驅(qū)動的移植和源碼主要部分的簡要分析至此結(jié)束。
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u3/97319/showart_2081239.html |
|