- 论坛徽章:
- 0
|
Linux
Platform Device and Driver
作者:Dongas
日期:08-06-27
从Linux
2.6起引入了一套新的驱动管理和注册机制:Platform_device和Platform_driver。
Linux中大部分的设备驱动,都可以使用这套机制,
设备用Platform_device表示,驱动用Platform_driver进行注册。
Linux
platform driver机制和传统的device
driver
机制(通过driver_register函数进行注册)相比,一个十分明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过platform
device提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性(这些标准接口是安全的)。
Platform机制的本身使用并不复杂,由两部分组成:platform_device和platfrom_driver。
通过Platform机制开发发底层驱动的大致流程为:
定义
platform_device
à
注册
platform_device
à
定义
platform_driver
à注册
platform_driver。
首先要确认的就是设备的资源信息,例如设备的地址,中断号等。
在2.6内核中platform设备用结构体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;
};
struct device {
struct klist klist_children;
struct klist_node knode_parent; /* node in sibling list */
struct klist_node knode_driver;
struct klist_node knode_bus;
struct device *parent;
struct kobject kobj;
char bus_id[BUS_ID_SIZE]; /* position on parent bus */
unsigned uevent_suppress:1;
const char *init_name; /* initial name of the device */
struct device_type *type;
struct semaphore sem; /* semaphore to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *driver_data; /* data private to the driver */
void *platform_data; /* Platform specific data, device
core doesn't touch it ----------这里可以添加私有数据,作额外的补充,增加devie功能。*/
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
/* arch specific additions */
struct dev_archdata archdata;
dev_t devt; /* dev_t, creates the sysfs "dev" */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
};
该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息,定义在kernel\include\linux\ioport.h中,
struct
resource {
const
char *name;
unsigned
long start, end;
unsigned
long flags;
struct
resource *parent, *sibling, *child;
};
下面举s3c2410平台的i2c驱动作为例子来说明:
/*
arch/arm/mach-s3c2410/devs.c */[color="#000000"]
/*
I2C */[color="#000000"]
[color="#0000ff"]static
[color="#0000ff"]struct
resource s3c_i2c_resource[color="#0000cc"][]
[color="#0000cc"]=
[color="#0000cc"]{[color="#000000"]
[color="#0000cc"][[color="#000000"]0[color="#0000cc"]]
[color="#0000cc"]=
[color="#0000cc"]{[color="#000000"]
[color="#0000cc"].start
[color="#0000cc"]=
S3C24XX_PA_IIC[color="#0000cc"],[color="#000000"]
[color="#0000cc"].end
[color="#0000cc"]=
S3C24XX_PA_IIC [color="#0000cc"]+
S3C24XX_SZ_IIC [color="#0000cc"]-
1[color="#0000cc"],[color="#000000"]
[color="#0000cc"].flags
[color="#0000cc"]=
IORESOURCE_MEM[color="#0000cc"],[color="#000000"]
[color="#0000cc"]},[color="#000000"]
[color="#0000cc"][[color="#000000"]1[color="#0000cc"]]
[color="#0000cc"]=
[color="#0000cc"]{[color="#000000"]
[color="#0000cc"].start
[color="#0000cc"]=
IRQ_IIC[color="#0000cc"],
[color="#ff9900"]//S3C2410_IRQ(27)[color="#000000"]
[color="#0000cc"].end
[color="#0000cc"]=
IRQ_IIC[color="#0000cc"],[color="#000000"]
[color="#0000cc"].flags
[color="#0000cc"]=
IORESOURCE_IRQ[color="#0000cc"],[color="#000000"]
[color="#0000cc"]}[color="#000000"]
[color="#0000cc"]};
这里定义了两组resource,它描述了一个I2C设备的资源,第1组描述了这个I2C设备所占用的总线地址范围,IORESOURCE_MEM表示第1组描述的是内存类型的资源信息,第2组描述了这个I2C设备的中断号,IORESOURCE_IRQ表示第2组描述的是中断资源信息。设备驱动会根据flags来获取相应的资源信息。
有了resource信息,就可以定义platform_device了:
[color="#000000"]
定义好了platform_device结构体后就可以调用函数platform_add_devices向系统中添加该设备了,之后可以调用platform_driver_register()进行设备注册。要注意的是,这里的platform_device设备的注册过程必须在相应设备驱动加载之前被调用,即执行platform_driver_register之前,原因是因为驱动注册时需要匹配内核中所以已注册的设备名。
s3c2410-i2c的platform_device是在系统启动时,在cpu.c里的s3c_arch_init()函数里进行注册的,这个函数申明为arch_initcall(s3c_arch_init);会在系统初始化阶段被调用。
arch_initcall的优先级高于module_init。所以会在Platform驱动注册之前调用。(详细参考include/linux/init.h)
s3c_arch_init函数如下:
/*
arch/arm/mach-3sc2410/cpu.c */[color="#000000"]
[color="#0000ff"]static
[color="#0000ff"]int
__init s3c_arch_init[color="#0000cc"]([color="#0000ff"]void[color="#0000cc"])[color="#000000"]
[color="#0000cc"]{[color="#000000"]
[color="#0000ff"]int
ret[color="#0000cc"];[color="#000000"]
……
/*
[color="#ff9900"]这里[color="#ff9900"]board[color="#ff9900"]指针指向在[color="#ff9900"]mach-smdk2410.c[color="#ff9900"]里的定义的[color="#ff9900"]smdk2410_board[color="#ff9900"],里面包含了预先定义的I2C
Platform_device[color="#ff9900"]等.
*/[color="#000000"]
[color="#0000ff"]if
[color="#0000cc"]([color="#ff0000"]board
[color="#0000cc"]!=
[color="#ff0000"]NULL[color="#0000cc"])
[color="#0000cc"]{[color="#000000"]
[color="#0000ff"]struct
platform_device [color="#0000cc"]**ptr
[color="#0000cc"]=
board[color="#0000cc"]->[color="#000000"]devices[color="#0000cc"];[color="#000000"]
[color="#0000ff"]int
i[color="#0000cc"];[color="#000000"]
[color="#0000ff"]for
[color="#0000cc"](i
[color="#0000cc"]=
0[color="#0000cc"];
i [color="#0000cc"]
board[color="#0000cc"]->[color="#000000"]devices_count[color="#0000cc"];
i[color="#0000cc"]++,
ptr[color="#0000cc"]++)
[color="#0000cc"]{
ret
[color="#0000cc"]=
[color="#ff0000"]pla
[color="#ff0000"]tform_device_register[color="#0000cc"](*[color="#ff0000"]ptr[color="#0000cc"]);[color="#ff0000"] [color="#ff9900"]//[color="#ff9900"]在这里进行注册[color="#ff9900"]
[color="#ff0000"]
[color="#0000ff"]if
[color="#0000cc"]([color="#ff0000"]ret[color="#0000cc"])
[color="#0000cc"]{[color="#ff0000"]
printk[color="#0000cc"](KERN_ERR
"s3c24xx:
failed to add board device %s (%d) @%p\n"[color="#0000cc"],
[color="#0000cc"](*[color="#ff0000"]ptr[color="#0000cc"])->[color="#ff0000"]name[color="#0000cc"],
ret[color="#0000cc"],
[color="#0000cc"]*[color="#ff0000"]ptr[color="#0000cc"]);[color="#ff0000"]
[color="#0000cc"]}[color="#ff0000"]
[color="#0000cc"]}[color="#ff0000"]
/*
mask any error, we may not need all these board
* devices */
ret
[color="#0000cc"]=
0[color="#0000cc"];[color="#ff0000"]
[color="#0000cc"]}[color="#ff0000"]
[color="#0000ff"]return
ret[color="#0000cc"];[color="#ff0000"]
[color="#0000cc"]}
同时被注册还有很多其他平台的platform_device,详细查看arch/arm/mach-s3c2410/mach-smdk2410.c里的smdk2410_devices结构体。
驱动程序需要实现结构体struct
platform_driver,参考drivers/i2c/busses
/* device
driver for platform bus bits */
[color="#000000"]
[color="#0000ff"]static
[color="#0000ff"]struct
platform_driver s3c2410_i2c_driver [color="#0000cc"]=
[color="#0000cc"]{[color="#000000"]
[color="#0000cc"].probe
[color="#0000cc"]=
s3c24xx_i2c_probe[color="#0000cc"],[color="#000000"]
[color="#0000cc"].[color="#ff0000"]remove
[color="#0000cc"]=
s3c24xx_i2c_remove[color="#0000cc"],[color="#000000"]
[color="#0000cc"].resume
[color="#0000cc"]=
s3c24xx_i2c_resume[color="#0000cc"],[color="#000000"]
[color="#0000cc"].driver
[color="#0000cc"]=
[color="#0000cc"]{[color="#000000"]
[color="#0000cc"].owner
[color="#0000cc"]=
THIS_MODULE[color="#0000cc"],[color="#000000"]
[color="#0000cc"].name
[color="#0000cc"]=
[color="#ff00ff"]"s3c2410-i2c"[color="#0000cc"],[color="#000000"]
[color="#0000cc"]},[color="#000000"]
[color="#0000cc"]};
在驱动初始化函数中调用函数platform_driver_register()注册platform_driver,需要注意的是s3c_device_i2c结构中name元素和s3c2410_i2c_driver结构中driver.name必须是相同的,这样在platform_driver_register()注册时会对所有已注册的所有platform_device中的name和当前注册的platform_driver的driver.name进行比较,只有找到相同的名称的platfomr_device才能注册成功,当注册成功时会调用platform_driver结构元素probe函数指针,这里就是s3c24xx_i2c_probe,当进入probe函数后,需要获取设备的资源信息,常用获取资源的函数主要是:
struct
resource * platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
根据参数type所指定类型,例如IORESOURCE_MEM,来获取指定的资源。
struct
int platform_get_irq(struct platform_device *dev, unsigned int num);
获取资源中的中断号。
下面举s3c24xx_i2c_probe函数分析,看看这些接口是怎么用的。
前面已经讲了,s3c2410_i2c_driver注册成功后会调用s3c24xx_i2c_probe执行,下面看代码:
/*
drivers/i2c/busses/i2c-s3c2410.c */[color="#000000"]
[color="#0000ff"]static
[color="#0000ff"]int
s3c24xx_i2c_probe[color="#0000cc"]([color="#0000ff"]struct
platform_device [color="#0000cc"]*[color="#000000"]pdev[color="#0000cc"])[color="#000000"]
[color="#0000cc"]{[color="#000000"]
[color="#0000ff"]struct
s3c24xx_i2c [color="#0000cc"]*i2c
[color="#0000cc"]=
[color="#0000cc"]&[color="#000000"]s3c24xx_i2c[color="#0000cc"];[color="#000000"]
[color="#0000ff"]struct
resource [color="#0000cc"]*[color="#000000"]res[color="#0000cc"];[color="#000000"]
[color="#0000ff"]int
ret[color="#0000cc"];[color="#000000"]
/*
find the clock and enable it */[color="#000000"]
i2c[color="#0000cc"]->dev
[color="#0000cc"]=
[color="#0000cc"]&[color="#000000"]pdev[color="#0000cc"]->[color="#000000"]dev[color="#0000cc"];[color="#000000"]
i2c[color="#0000cc"]->clk
[color="#0000cc"]=
clk_get[color="#0000cc"](&[color="#000000"]pdev[color="#0000cc"]->[color="#000000"]dev[color="#0000cc"],
[color="#ff00ff"]"i2c"[color="#0000cc"]);[color="#000000"]
[color="#0000ff"]if
[color="#0000cc"]([color="#000000"]IS_ERR[color="#0000cc"]([color="#000000"]i2c[color="#0000cc"]->[color="#000000"]clk[color="#0000cc"]))
[color="#0000cc"]{
dev_err[color="#0000cc"](&[color="#000000"]pdev[color="#0000cc"]->[color="#000000"]dev[color="#0000cc"],
"cannot
get clock\n"[color="#0000cc"]);
ret [color="#0000cc"]=
[color="#0000cc"]-[color="#000000"]ENOENT[color="#0000cc"];
[color="#0000ff"]goto
out[color="#0000cc"];[color="#000000"]
[color="#0000cc"]}[color="#000000"]
dev_dbg[color="#0000cc"](&[color="#000000"]pdev[color="#0000cc"]->[color="#000000"]dev[color="#0000cc"],
"clock
source %p\n"[color="#0000cc"],
i2c[color="#0000cc"]->[color="#000000"]clk[color="#0000cc"]);[color="#000000"]
clk_enable[color="#0000cc"]([color="#000000"]i2c[color="#0000cc"]->[color="#000000"]clk[color="#0000cc"]);
[color="#000000"]
/*
map the registers */[color="#000000"]
res
[color="#0000cc"]=
platform_get_resource[color="#0000cc"]([color="#ff0000"]pdev[color="#0000cc"],
IORESOURCE_MEM[color="#0000cc"],
0[color="#0000cc"]);
/*
[color="#ff9900"]获取设备的[color="#ff9900"]IO资源地址
[color="#ff9900"]*/[color="#000000"]
[color="#0000ff"]if
[color="#0000cc"](res
[color="#0000cc"]==
NULL)
[color="#0000cc"]{
dev_err[color="#0000cc"](&[color="#ff0000"]pdev[color="#0000cc"]->[color="#ff0000"]dev[color="#0000cc"],
"cannot
find IO resource\n"[color="#0000cc"]);
ret [color="#0000cc"]=
[color="#0000cc"]-[color="#ff0000"]ENOENT[color="#0000cc"];
[color="#0000ff"]goto
out[color="#0000cc"];[color="#ff0000"]
[color="#0000cc"]}[color="#ff0000"]
i2c[color="#0000cc"]->ioarea
[color="#0000cc"]=
request_mem_region[color="#0000cc"]([color="#ff0000"]res[color="#0000cc"]->[color="#ff0000"]start[color="#0000cc"],
[color="#0000cc"]([color="#ff0000"]res[color="#0000cc"]->[color="#ff0000"]end[color="#0000cc"]-[color="#ff0000"]res[color="#0000cc"]->[color="#ff0000"]start[color="#0000cc"])+[color="#ff0000"]1[color="#0000cc"],
pdev[color="#0000cc"]->[color="#ff0000"]name[color="#0000cc"]);
/*
申请这块IO
Region */[color="#ff0000"]
[color="#0000ff"]if
[color="#0000cc"]([color="#ff0000"]i2c[color="#0000cc"]->ioarea
[color="#0000cc"]==
NULL[color="#0000cc"])
[color="#0000cc"]{
dev_err[color="#0000cc"](&[color="#ff0000"]pdev[color="#0000cc"]->[color="#ff0000"]dev[color="#0000cc"],
"cannot
request IO\n"[color="#0000cc"]);
ret [color="#0000cc"]=
[color="#0000cc"]-[color="#ff0000"]ENXIO[color="#0000cc"];
[color="#0000ff"]goto
out[color="#0000cc"];[color="#ff0000"]
[color="#0000cc"]}[color="#ff0000"]
i2c[color="#0000cc"]->regs
[color="#0000cc"]=
ioremap[color="#0000cc"]([color="#ff0000"]res[color="#0000cc"]->[color="#ff0000"]start[color="#0000cc"],
[color="#0000cc"]([color="#ff0000"]res[color="#0000cc"]->[color="#ff0000"]end[color="#0000cc"]-[color="#ff0000"]res[color="#0000cc"]->[color="#ff0000"]start[color="#0000cc"])+[color="#ff0000"]1[color="#0000cc"]);
/*
映射至内核虚拟空间
[color="#ff9900"]*/[color="#ff0000"]
[color="#0000ff"]if
[color="#0000cc"]([color="#ff0000"]i2c[color="#0000cc"]->regs
[color="#0000cc"]==
NULL[color="#0000cc"])
[color="#0000cc"]{
dev_err[color="#0000cc"](&[color="#ff0000"]pdev[color="#0000cc"]->[color="#ff0000"]dev[color="#0000cc"],
"cannot
map IO\n"[color="#0000cc"]);
ret [color="#0000cc"]=
[color="#0000cc"]-[color="#ff0000"]ENXIO[color="#0000cc"];
[color="#0000ff"]goto
out[color="#0000cc"];[color="#ff0000"]
[color="#0000cc"]}[color="#ff0000"]
dev_dbg[color="#0000cc"](&[color="#ff0000"]pdev[color="#0000cc"]->[color="#ff0000"]dev[color="#0000cc"],
"registers
%p (%p, %p)\n"[color="#0000cc"],
i2c[color="#0000cc"]->[color="#ff0000"]regs[color="#0000cc"],
i2c[color="#0000cc"]->[color="#ff0000"]ioarea[color="#0000cc"],
res[color="#0000cc"]);[color="#ff0000"]
/*
setup info block for the i2c core */[color="#ff0000"]
i2c[color="#0000cc"]->[color="#ff0000"]adap[color="#0000cc"].algo_data
[color="#0000cc"]=
i2c[color="#0000cc"];[color="#ff0000"]
i2c[color="#0000cc"]->[color="#ff0000"]adap[color="#0000cc"].[color="#ff0000"]dev[color="#0000cc"].parent
[color="#0000cc"]=
[color="#0000cc"]&[color="#ff0000"]pdev[color="#0000cc"]->[color="#ff0000"]dev[color="#0000cc"];[color="#ff0000"]
/*
initialise the i2c controller */
ret
[color="#0000cc"]=
s3c24xx_i2c_init[color="#0000cc"]([color="#ff0000"]i2c[color="#0000cc"]);[color="#ff0000"]
[color="#0000ff"]if
[color="#0000cc"](ret
[color="#0000cc"]!=
0[color="#0000cc"])
[color="#0000ff"]goto
out[color="#0000cc"];[color="#ff0000"]
/*
find the IRQ for this unit (note, this relies on the init call to
ensure no current IRQs pending */
res
[color="#0000cc"]=
platform_get_resource[color="#0000cc"]([color="#ff0000"]pdev[color="#0000cc"],
IORESOURCE_IRQ[color="#0000cc"],
0[color="#0000cc"]);
/*
获取设备[color="#ff9900"]IRQ中断号
[color="#ff9900"]*/[color="#ff0000"]
[color="#0000ff"]if
[color="#0000cc"](res
[color="#0000cc"]==
NULL[color="#0000cc"])
[color="#0000cc"]{
dev_err[color="#0000cc"](&[color="#ff0000"]pdev[color="#0000cc"]->[color="#ff0000"]dev,:
#ff00ff">"cannot find IRQ\n");
ret = -ENOENT;
[color="#0000ff"]goto
out;
}
ret
[color="#0000cc"]=
request_irq[color="#0000cc"]([color="#ff0000"]res[color="#0000cc"]->[color="#ff0000"]start[color="#0000cc"],
s3c24xx_i2c_irq[color="#0000cc"],
IRQF_DISABLED,
/*
申请IRQ
*/
pdev->name, i2c);
……
[color="#0000ff"]return
ret;
}
[color="#000000"]小思考:
那什么情况可以使用platform
driver机制编写驱动呢?
我的理解是只要和内核本身运行依赖性不大的外围设备(换句话说只要不在内核运行所需的一个最小系统之内的设备),相对独立的,拥有各自独自的资源(addresses
and
IRQs),都可以用platform_driver实现。如:lcd,usb,uart等,都可以用platfrom_driver写,而timer,irq等最小系统之内的设备则最好不用platfrom_driver机制,实际上内核实现也是这样的。
参考资料:
linux-2.6.24/Documentation/driver-model/platform.txt
《platform
_device和platform_driver注册过程》
http://blog.chinaunix.net/u2/60011/showart.php?id=1018999
[color="#800080"]http://www.eetop.cn/blog/html/45/11145-676.html
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/94108/showart_2090877.html |
|