在Linux裝置驅動模型摘抄(2)中,我們通過追蹤amba_pl011.c中的驅動程式的註冊過程展示了一個裝置如何被統一到Linux統一裝置模型的一個過程,amba_pl011是一個generic的裝置下面我們以Android Goldfish TTY Driver為例,再次說明一下這個過程(/drivers/char/goldfish_tty.c):
goldfish_tty_init(void)->platform_driver_register(&goldfish_tty);
platform_driver_register()和goldfish_tty以及相關的定義如下:
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 (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
struct goldfish_tty {
spinlock_t lock;
uint32_t base;
uint32_t irq;
int opencount;
struct tty_struct *tty;
struct console console;
};
static struct platform_driver goldfish_tty = {
.probe = goldfish_tty_probe,
.remove = goldfish_tty_remove,
.driver = {
.name = "goldfish_tty"
}
};
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
int driver_register(struct device_driver *drv)
{
int ret;
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods/n", drv->name);
ret = bus_add_driver(drv);
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups);
if (ret)
bus_remove_driver(drv);
return ret;
}
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);
}
從這些代碼我們可以看出一條調用路徑如下:
platform_driver_register->driver_register->bus_add_driver->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)->__driver_attach->really_probe
really_probe->driver_sysfs_add(dev)->sysfs_create_link 在/sys/drivers/裡面/sys/dev/下面建立連結
->dev->bus->probe(dev) ? 找不到可以調用的合適函數
->drv->probe(dev)->platform_drv_probe->goldfish_tty_probe(); //
從這個分析可以看出,(2)和(3)還是不同的,(2)使用兩次register來先註冊uart協議,再註冊amba匯流排,但是在(3)裡面,使用platform_driver_register(struct platform_driver)這樣一次性註冊,至於Bus和protocol的問題,在後面用probe方式回呼函數解決。
最後一個問題,關於probe的過程,我們可以發現在goldfish_tty_probe裡面,關鍵性的函數是platform_get_resource(),這個函數可以擷取該裝置的base_addr和irq。但是platform_get_resource的過程依賴於在初始化階段的request_resource函數,如arch/arm/kernel/setup.c中所示。還有另外一種形式是resource直接賦值,例如:
static struct resource udc_resources[] = {
[0] = {
.start = AT91RM9200_BASE_UDP,
.end = AT91RM9200_BASE_UDP + SZ_16K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = AT91RM9200_ID_UDP,
.end = AT91RM9200_ID_UDP,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device at91rm9200_udc_device = {
.name = "at91_udc",
.id = -1,
.dev = {
.platform_data = &udc_data,
},
.resource = udc_resources,
.num_resources = ARRAY_SIZE(udc_resources),
};
關於platform_device中的id是這樣的,如果id==-1,那麼這個裝置的名字就是定義的名字,如上所示,就是at91_udc,但是如果不是-1,那麼名字的格式將是name.id這個樣子,可能表示同樣的名字可以有多個裝置吧。
直接用udc_resources給platform_device賦值,然後用probe來檢查這個裝置,找出一個可用的。
在最後,我們再來看看這些結構體之間的關係:
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
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 (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
因此platform_driver類似device_driver, platform_device類似device, 關於他們之間的轉換,有下面函數可供參考:
struct platform_driver *drv = to_platform_driver(struct *device->driver);
struct platform_device *dev = to_platform_device(struct *device)
#define to_platform_driver(drv) (container_of((drv), struct platform_driver, driver))
#define to_platform_device(x) container_of((x), struct platform_device, dev)
看這2個宏,我們就會發現,(3)比起(2)來,抽象程度似乎更高一些,統一性應該更好一些。而且在比較複雜的驅動如MMC,USB等裡面,使用platform_device和platform_driver也越來越多了。