dm9000源碼的分析

通過Platform機制開發發底層驅動的大致流程爲: 定義 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一般需要初始化兩個方面的內容:設備佔用的資源resource和設備私有數據dev.platform_data。最重要的是resource


設備佔用的資源主要是兩個方面:IO內存和irq資源。

Resource定義於kernel/include/linux/ioport.h中,

struct resource {

 const char *name;

 unsigned long start, end;

 unsigned long flags;

 struct resource *parent, *sibling, *child;

};

實際上是對地址範圍及其屬性的一個描述。最後幾個用於樹型結構的指針是內核用於管理所有資源的。

而platform_data則是設置給struct device dev;中的platform_data指針(void *)。這個指針內核並不使用,而是驅動自身來定義及使用。

比如說對於DM9000,對應的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,一個平臺設備就定義好了。把這個平臺設備變量的地址添加到資源列表中去。比如在2410平臺:

在arm/arm/mach-s3c2410/mach-smdk2410.c把設備地址添加到*smdk2410_devices[] __initdata 數組中去。

最後在arch/arm/mach-3sc2410/cpu.c 中初始化函數__init s3c_arch_init(void)會對smdk2410_devices[]每一個設備的指針ptr調用platform_device_register(ptr)。主要是建立device的層次結構(建立sysfs入口),將設備佔用的資源添加到內核資源管理。接下來看看platform_driver:

2. platform_driver結構定義於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;

};

它內部封裝了一個device_driver,更有意思的是其它的全是函數,並且這些函數名與device_driver中提供的一樣,只是參數由device * 變成了 platform_device *  。

驅動應該實現platform_driver中的這些操作,而內嵌的device_driver中的對應函數則在註冊時被指定爲內核指定的操作,這些指定操作只是把調用參數轉換成platform_driver和platform_device來調用platform_driver提供的操作而已。 好像有點亂。。不過代碼可以解釋一切:

平臺驅動註冊:

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的方法沒有定義就會變成對應的platform_drv_*方法。

來看看其中的一個的實現:比如 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);

}

事情很清楚,先把設備的device_driver轉成platform_driver,同樣轉換device爲platform_device。然後去調用platform_driver提供的函數。類型轉換當然是通過container_of()宏實現的。

因此,驅動只需要實現platform_driver中的方法。然後註冊即可。

關於註冊,由上面的代碼可知,最終也是通過 driver_register(&drv->driver);來做的。

3.更深入的小窺一下平臺設備與平臺驅動的註冊:

根據LDD3中指出的設備模型,一個設備和驅動必然屬於某一個總線。Platform_device和platform-driver在層次上隸屬於叫platform_bus_type的總線類型。OK,平臺驅動註冊的時候(平臺設備必須先於驅動註冊)將引用它所屬總線的匹配函數去決定總線上每一個設備是否屬於自己。然後二者建立聯繫:設備的驅動指針指向該驅動,驅動的設備列表中加入匹配的設備。

當然,這是在設備和驅動這一層面來說的,更深入一層,kobjects和ksets建立層次關係,建立sysfs入口等等。。

注意,platform_bus_type的匹配函數只是比較一下driver和device的name是否相同。因此,同一設備的platform_device和platform_driver的name應該設爲相同的。見platform_bus_type匹配函數定義:
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一般怎麼用?

資源描述的是設備佔用的IO內存,IO端口,及中斷線。

Dm9000驅動中是這樣使用的。這符合慣例:

在probe中獲取資源,並且申請資源,最後映射到內核空間,把映射結果保存起來。

在net_device中的open函數裏,註冊中斷處理函數。

Platform_data的使用極爲靈活,首先platform_data結構不同設備之間沒有定論,一般可用來保存特定於設備的一些配置,操作等。比如對於DM9000,可以存在按字節,按字訪問的不同模式,因此其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存取函數。 

然後Dm9000驅動只使用了它的flags標誌,其餘的並不使用。

因爲對於網絡net_device,有一個叫着private_data的指針,在分配一個net_device的時候可以讓內核爲其開闢指定大小的內存。這部分內存可以通過net_device訪問,而且內容也是驅動開發者自定義的。在DM9000的驅動中,net_devict的private_data使用了一個叫board_info的結構體來包括更多設備相關的信息和操作。

dm9000_plat_data提供的內容也被包括進board_info。因此驅動只使用了初始時設置的flags,除此外dm9000_plat_data中的方法沒有使用的必要。

從中得到的啓示:
Device 包含一個platform_data。

Net_device則包含一個private區域.

這樣既實現了設備模型的統一管理,又實現了保持不同設備的信息與方法的靈活性。

四. Dm9000驅動源碼的簡要分析。

1.定義並註冊DM9000 的 platform_device 。定義設備佔用資源和platform_data。(具體的見前面)

2.將platform_device添加到板子的設備列表中去,在系統初始化時註冊入內核。

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,

};

這裏面關健的東西是name和probe,remove。

4.在模塊初始化函數module_init(dm9000_init);中註冊dm9000_driver。
platform_driver_register(&dm9000_driver);
這將導致驅動的probe函數被調用。

5.驅動還定義了一個數據結構:board_info來記錄芯片的信息及操作。如統計信息,讀寫操作,佔用的IO地址資源,狀態。

6.OK,現在接着4說,模塊初始化函數最終將調用probe函數。這個函數完成的基本過程 :

1)獲取一個netdevice:
ndev = alloc_etherdev(sizeof (struct board_info));

2)獲取設備資源:
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資源,映射到內核並保存映射地址:
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即是驅動自定義的board_info結構指針。伴隨ndev申請內存。

4)根據DM9000數據位寬設置 讀寫數據幀的函數指針。
Dm9000_set_io(db, iosize);

5)OK,現在復位芯片:dm9000_reset(db); db中已經包含了詳細的芯片信息。

6)讀取芯片ID號並判斷是否爲0x90000A46。

7)初始化以太網ndev : ether_setup(ndev);

8)設置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)添加一些打開中斷,設置MAC地址的操作在這裏。

10)將ndev記錄於平臺設備platform_dev中去。註冊ndev。
platform_set_drvdata(pdev, ndev);  //pdev->dev->dev_driver_data=ndev.
ret = register_netdev(ndev);

OK,probe的使命OVER了。這也是ndev與platform_dev建立聯繫的地方。
可以這麼理解,linux的設備模型負責的只是設備的管理(檢測,啓動,移除),而如何訪問這個設備的數據,比如說以字符流模式,塊設備方式,網絡接口,則定義相應的cdev,gendisk,ndev,然後註冊到內核。所有的數據訪問工作都以這三種界面提供。

7.OK,一旦probe正常的執行完,內核中註冊好了eth0這個網絡接口(因爲只有一個網卡)。在系統啓動之後,配置eth0,這將引起ndev->open()調用。來看看open做些什麼?
Open(dev)流程:
申請中斷線:
request_irq(dev->irq, &dm9000_interrupt, IRQF_SHARED, dev->name, dev)
復位DM9000,初始化芯片的各個寄存器使其工作在適當的狀態。
設置timer(用於傳輸超時),
調用netif_start_queue(dev);使設備可以開始收發數據。

8.OK,配置好eth0接口後,網絡設備連接好。數據收發就緒。現在簡要的分析一下收發過程:
發送數據包:協議層用已經封裝好上層協議數據的skb_buffer調用dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)函數。
發送函數流程:
netif_stop_queue(dev); 暫停接口,使上層暫時不能發送數據。
正在發送中的數據計數加1。
如果只有當前包發送,寫指令,寫數據幀,發送包。
如果多於一包數據正在發送,當前幀不發送。
釋放skb。
重新使能接口:netif_wake_queue(dev);


發送結束,DM9000產生中斷,在中斷函數中讀取芯片相關寄存器判斷中斷原因,如果是發送結束,則遞減正發送包計數。並netif_wake_queue(dev);

9.接收過程:
網絡數據包到達,DM9000自動接收並存放在DM內部RAM中,產生中斷。在中斷處理中識別中斷原因並調用接收處理函數dm9000_rx(struct net_device *dev)。
dm9000_rx
讀取芯片相關寄存器確認DM9000正確的收到一幀數據。
申請skb_buffer,將數據從DM9000中拷貝到skb_buffer中。設置skb->dev=nev,skb->protocol=eth_type_trans(skb, dev)。

然後把skb_buffer交給上層協議:netif_rx(skb);

最後更新接口統計信息:db->stats.rx_packets++; 收到包總數+1。

整個DM9000驅動的移植和源碼主要部分的簡要分析至此結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章