在Linux中,一开始bus下的device有一个device链表,driver也有一个链表,当注册platform_device的时候,会把新的platform_device结构体放入device链表,然后从driver的链表中把已有的driver一个一个地取出来跟platform_device结构体中的name进行比较,如果匹配的话,就会调用platform_driver结构体中的probe函数,此时才会成功创建设备节点。反之先注册driver也一样,所以先注册platform_device还是先注册platform_driver都可以,最终如果匹配,都会去调用platform_driver中的probe函数。
在platform_device.h文件中定义了platform_device与platform_driver结构体:
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
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;
const struct platform_device_id *id_table;
};
在platform_driver结构体中,成员platform_device_id结构体中有成员name:
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data
__attribute__((aligned(sizeof(kernel_ulong_t))));
};
成员struct device_driver中也有成员name:
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
platform_device与platform_driver通过platform.c文件中的platform_match函数进行匹配,优先比较id_table,如果没有id_table,则比较pdev->name与pdrv->name:
static int platform_match(struct device *dev, struct device_driver *drv)
{
...
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
在match函数中通过platform_match_id函数进行两者的比较:
static const struct platform_device_id *platform_match_id(const struct platform_device_id *id, struct platform_device *pdev)
{
while (id->name[0]) {
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id;
return id;
}
id++;
}
return NULL;
}
传统的字符设备驱动程序写法思路是这样的:
分配、设置一个file_operations结构体,通过file_operations结构体中的成员函数完成对led的驱动,完善led_open函数,led_write函数和led_release函数。
static int led_open(struct inode *node, struct file *filp)
{
......
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
......
}
static int led_release(struct inode *node, struct file *filp)
{
......
}
static struct file_operations myled_oprs = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
通过init函数与exit函数完成字符设备的注册或卸载,类与设备节点的创建和注销:
static int myled_init(void)
{
major = register_chrdev(0, "myled", &myled_oprs);
led_class = class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, MKDEV(major, 0), NULL, "led");
return 0;
}
static void myled_exit(void)
{
unregister_chrdev(major, "myled");
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
}
以上是传统字符设备驱动程序的框架结构。
而在总线设备驱动模型中,一个字符设备驱动程序的框架是这样的,需要有device.c和driver.c两个文件,device文件中通过platform_device结构体中的成员指定硬件资源:
static struce resource led_resource[] = {
[0] = {
.start = s3c2440_GPF(5),
.end = s3c2440_GPF(5),
.flags = IORESOURCE_MEM,
},
};
static void led_release(struct device * dev)
{
}
/* 分配/设置一个platform_device */
static struct platform_device led_dev = {
.name = "myled",
.id = -1,
.num_resources = ARREY_SIZE(led_resource),
.resource = led_resource,
};
/* 注册platform_device */
static int led_dev_init(void)
{
platform_device_register(&led_dev);
return 0;
}
/* 卸载platform_device */
static void led_dev_exit(void)
{
platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
在driver文件中是分配、设置一个platform_driver结构体,包含probe函数与remove函数:
struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
}
};
传统字符设备驱动程序的init函数中对字符设备的注册、类的创建与设备节点的创建现在在probe函数中完成:
static int led_probe(struct platform_device * pdev)
{
struct resource * res;
/* 根据platform_device的资源进行ioremap */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
led_pin = res->start;
major = register_chrdev(0, "myled", &myled_oprs);
led_class = class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
return 0;
}
在probe函数中会调用file_operations结构体中的open函数、write函数等,但该open函数中不再进行字符设备的注册等操作,release函数中不再进行字符设备的卸载等操作:
static int led_open(struct inode *node, struct file *filp)
{
/* 设置LED引脚为输出 */
/* GPF5 - 0x56000050 */
int bank = led_pin >> 16; /* 先获得端口信息 */
int base = gpio_base[bank]; /* 获得寄存器GPFCON的地址*/
int pin = led_pin & 0xffff; /* 再获得引脚信息 */
gpio_con = ioremap(base, 8);
gpio_dat = gpio_con + 1;
*gpio_con &= ~(3 << (pin * 2));
*gpio_con |= (1 << (pin * 2));
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
/* 根据app传入的值设置led引脚 */
unsigned char val;
int pin = led_pin & 0xffff;
copy_from_user(&val, buf, 1);
if (val)
{
/* 点灯 */
*gpio_dat &= ~(1 << pin);
}
else
{
/* 灭灯 */
*gpio_dat |= (1 << pin);
}
return 1; /* 已写入1个数据 */
}
static int led_release(struct inode *node, struct file *filp)
{
iounmap(gpio_con);
return 0;
}
static struct file_operations myled_oprs = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
对字符设备的卸载、类与设备节点的注销在remove函数中完成:
static int led_remove(struct platform_device * pdev)
{
unregister_chrdev(major, "myled");
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
return 0;
}
现在的init函数中只是注册platform_driver,在exit函数中卸载platform_driver:
static int myled_init(void)
{
platform_driver_register(&led_drv);
return 0;
}
static void myled_exit(void)
{
platform_driver_unregister(&led_drv);
}