1.什么是platform总线
我们先来思考这样一个问题,当我们把usb设备插到电脑上时,电脑是如何识别到这个usb设备的?其实,每一个usb中都有一个vid(厂商id)和pid(设备id),vid和pid在usb的生产过程中就已经设置好了。同时,对于一个usb驱动,在驱动里也保存着这个pid和vid,当usb设备中的vid和pid是与usb驱动中的vid和pid相同时,驱动便可以识别到这个设备。这是因为在linux内核中维护者两条链表,一个是设备链表,另一个是驱动链表,linux内核会把设备加到设备链表,把驱动加到驱动链表,然后一个设备到来时,就在去驱动链里找这个设备所对应的驱动。但是,对于led设备,并没有像usb设备一样,有这种设备链和驱动链,所以,我们需要虚拟出一条总线,这条总线负责让设备找到驱动,驱动找到设备,我们把这样的总线称为platform总线。
现在,我们就可以把驱动和设备分离,这样做的好处在与驱动是通用的,一个驱动可以支持多个设备,而如果将驱动和设备写在一起,则这个驱动就只能支持这个设备,当设备改变了,驱动也要改变。
对于platform总线下的设备和驱动,设备和驱动就通过name域来找到对方,设备的name域如果和驱动的name域相同,这个设备和这个驱动就是相互对应的。
设备中的name域:
static struct platform_device s3c_led_device = {
71 .name = "s3c_led",
72 .id = 1,
73 .dev =
74 {
75 .platform_data = &s3c_led_data,
76 .release = platform_led_release,
77 },
78 };
驱动中的name域:
static struct platform_driver s3c_led_driver = {
.probe = s3c_led_probe,
.remove = s3c_led_remove,
.driver = {
.name = "s3c_led",
.owner = THIS_MODULE,
},
};
2.设备端完成哪些工作
现在,设备和驱动是分离的,所以,需要由设备端向platform虚拟总线告知一些关于设备端的信息。例如,对于led设备,其需要告知的信息是:led设备用到的那几个引脚,如果要点亮led,需要给高电平还是低电平,需要设置成输入模式还是输出模式,以及该设备的name域是多少,这些信息都是需要在设备端完成的,
platform_device_register函数用来将设备注册到platform总线。其函数原型如下:
int platform_device_register(struct platform_device *pdev)
其参数是一个指向platform_device 类型的结构体指针,我们可以将该结构体指针指向包含的设备信息的结构体,从而完成了设备的注册。
下面是led设备的注册代码:
static int __init platdev_led_init(void)
{
int rv = 0;
rv = platform_device_register(&s3c_led_device);
if(rv)
{
printk(KERN_ERR "%s:%d: Can't register platform device %d\n", __FUNCTION__,__LINE__,
rv);
return rv;
}
printk("Regist S3C LED Platform Device successfully.\n");
return 0;
}
s3c_led_device结构体包含设备信息:
static struct platform_device s3c_led_device = {
.name = "s3c_led",
.id = 1,
.dev =
{
.platform_data = &s3c_led_data,
.release = platform_led_release,
},
};
于是通过s3c_led_device结构体就将设备信息注册到了platform虚拟总线上。platform_device 结构体的定义如下:
struct platform_device {
const char * name; //设备名称
int id; //设备id
struct device dev;
u32 num_resources; //设备使用各类资源的数量
struct resource * resource; //设备使用的资源
struct platform_device_id *id_entry;
};
其中struct device 结构体的定义如下:
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex 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 *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
struct dev_power_domain *pwr_domain;
#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;
struct device_node *of_node; /* associated device tree node */
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;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
};
在device结构体的platform_data成员中保存着设备的特定信息,对于以上讲到的led设备,我们关心以下的这些信息:
static struct s3c_led_platform_data s3c_led_data = {
.leds = s3c_leds,
.nleds = ARRAY_SIZE(s3c_leds),
};
其中s3c_led_platform_data是我们自己定义的结构体,结构体保存着这个led的特定信息。
通过以上这些步骤,设备端的信息基本描述清楚了。
3.驱动端需要完成的工作:
在驱动端,主要完成下面的工作:
1)调用module_init();当我们insmod时,就将从module_init()开始。
2)调用platform_driver_register(),将驱动注册到platform虚拟总线。
static int __init platdrv_led_init(void)
{
int rv = 0;
rv = platform_driver_register(&s3c_led_driver);
if(rv)
{
printk(KERN_ERR "%s:%d: Can't register platform driver %d\n", __FUNCTION__,__LINE__,
rv);
return rv;
}
printk("Regist S3C LED Platform Driver successfully.\n");
return 0;
}
3)platform_driver_register的参数是一个platform_driver类型的结构体指针,如果是insmod 一个模块,将会调用probe函数;如果是rmmod一个模块,将调用remove函数。
platform_driver结构体:
static struct platform_driver s3c_led_driver = {
.probe = s3c_led_probe,
.remove = s3c_led_remove,
.driver = {
.name = "s3c_led",
.owner = THIS_MODULE,
},
};
4)probe函数的参数以一个platform_device的结构体指针,该结构体指针指向设备端的设备的信息。在probe函数中完成:设备硬件的初始化;分配主次设备号;申请cdev结构体;cdev结构体的初始化;将cdev结构体与设备号绑定并将cdev结构体注册到内核。cdev结构体是linux驱动中的一种关键数据结构,在cdev结构体的初始化过程中,会把file_operations 结构体指向cdev结构体的ops成员。
在remove函数中,需要将在probe函数中申请到的资源释放,但是需要说明的是,remove函数是在rmmod一个模块时才调用的。
5)如果想在驱动中完成在/dev路径下创建设备节点,可以调用class_create函数和device_create函数。当我们调用device_create函数时,就会在内核里产生一个hotplug事件,内核本身不会处理这个hotplug事件,他会通知应用程序空间来处理这个hotplug事件。linux内核启动后,挂载根文件系统,根文件系统启动后,就执行init进程,然后管理权就交给了init进程,init进程会读/etc/inittab这个脚本,在inittab脚本文件中指定了系统启动时应该完成哪些事情。
#Use mdev to auto generate device nod and auto mount SD card and USB storage
::sysinit:/bin/echo /sbin/mdev > /proc/sys/kernel/hotplug
::sysinit:/sbin/mdev -s
proc文件系统是linux内核和应用程序空间进行信息传递的桥梁,就通过proc文件告诉应用程序。把/sbin/mdev写到/proc/sys/kernel/hotplug中,一旦产生一个hotplug事件,linux内核通知应用程序空间,调用/proc/sys/kernel/hotplug文件里所指定内容的程序,/sbin/medv 程序默认扫描所有的hotplug事件,创建设备节点。
4.设备和驱动如何找到对方:
我们知道,设备有自己的一条链,驱动有自己的一条链,而设备和驱动能够找到对方是借助操作系统来实现的,设备和驱动是通过name域来识别的,当有新的设备连接时,操作系统就会到驱动一端找是否有该设备对应的驱动;当有新的驱动时,操作系统会到设备端找是否有该驱动对应的设备。