VID & PID
每一个设备都有一个设备id和厂商id,设备管理器–>硬件属性–>详细信息–>属性–>硬件id,
驱动和设备分离
从Linux 2.6起引入了一套新的驱动管理和注册机制:Platform_device和Platform_driver。Linux中大部分的设备驱动,都可以使用这套机制, 设备用Platform_device表示,驱动用Platform_driver进行注册。
Linux platform driver机制和传统的device driver 机制(通过driver_register函数进行注册)相比,一个十分明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过platform device提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性(这些标准接口是安全的)。
1、设备(platform_device)注册的过程中会调用platform总线的match函数去寻找目前已经挂接在platform总线上与设备(paltform_device)名字对应的驱动(platform_driver)。相反的驱动(platform_driver)注册的时候一样去寻找挂接在总线上名字对应的设备(platform_device)
2、匹配上后,会执行驱动(platform_driver)的probe函数
3、probe函数中会实现控制器的初始化和其他的一些准备工作,并且会有注册实际的设备(比如eeprom)
实现platform模型的过程就是总线对设备和驱动的匹配过程 。打个比方,就好比相亲,总线是红娘,设备是男方,驱动是女方:
a – 红娘(总线)负责男方(设备)和女方(驱动)的撮合;
b – 男方(女方)找到红娘,说我来登记一下,看有没有合适的姑娘(汉子)—— 设备或驱动的注册;
c – 红娘这时候就需要看看有没有八字(二者的name 字段)匹配的姑娘(汉子)——match 函数进行匹配,看name是否相同;
d – 如果八字不合,就告诉男方(女方)没有合适的对象,先等着,别急着乱做事 —— 设备和驱动会等待,直到匹配成功;
e – 终于遇到八字匹配的了,那就结婚呗!接完婚,男方就向女方交代,我有多少存款,我的房子在哪,钱放在哪等等( struct resource *resource),女方说好啊,于是去房子里拿钱,去给男方买菜啦,给自己买衣服、化妆品、首饰啊等等(int (*probe)(struct platform_device *) 匹配成功后驱动执行的第一个函数),当然如果男的跟小三跑了(设备卸载),女方也不会继续待下去的( int (*remove)(struct platform_device *))。
设备和驱动没有谁先谁后,谁先安装模块,谁就等待谁。
platform_device
定义在:include/linux/platform_device.h中
这是内核3.0.0中的定义
struct platform_device {
const char * name;//设备名称 重要
int id; //设备号,相当于次设备号
struct device dev; //所有设备通用的属性部分
u32 num_resources;//设备使用到的resource的个数
struct resource * resource;//设备使用到的资源数组的首地址
const struct platform_device_id *id_entry;//设备ID表
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata; //自留地,用来提供扩展性的
};
设备模块先初始化后,注册设备端的设备
__init platdev_init:
platform_device_register(&platform_device)
static struct platform_device s3c_led_device = {
.name = "s3c_led",
.id = 1,
.dev =
{
.platform_data = &s3c_led_data,
.release = platform_led_release, //一个关闭函数
},
};
比如这个led的驱动,设备的结构体被注册到内核设备链中,.name这个成员很重要,他是驱动和设备互相寻找的关键,相同类的设备类型会找到相匹配的设备或驱动。.dev是一个结构体成员,他是同类设备中的相同属性,dev.platform_data是该设备的私有成员,对于我们这个开发板来说他有四个外接LED,所以我们传一个私有信息给dev.platform_data。.platform_data = &s3c_led_data,
static struct s3c_led_platform_data s3c_led_data = {
.leds = s3c_leds, //各个led的信息
.nleds = ARRAY_SIZE(s3c_leds),//led的数量
};
s3c_led自定义结构体:
static struct s3c_led_info s3c_leds[] = {
[0] = {
.num = 1, //设备号
.gpio = S3C2410_GPB(5), //对应的文件的GPIO管脚
.active_level = LOWLEVEL, //设为低电平
.status = OFF, //初始状态为关
.blink = ENABLE, //轮流闪烁
},
[1] = {
.num = 2,
.gpio = S3C2410_GPB(6),
.active_level = LOWLEVEL,
.status = OFF,
.blink = DISABLE,
},
[2] = {
.num = 3,
.gpio = S3C2410_GPB(8),
.active_level = LOWLEVEL,
.status = OFF,
.blink = DISABLE,
},
[3] = {
.num = 4,
.gpio = S3C2410_GPB(10),
.active_level = LOWLEVEL,
.status = OFF,
.blink = DISABLE,
},
};
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;
const struct platform_device_id *id_table;
};
驱动端先初始化,并注册驱动模块:
__init platdrv_led_init:
platform_driver_register(&s3c_led_driver);
注册的driver结构体函数中,probe函数其至关重要的作用,当总线上驱动和设备配对后,就会调用驱动中的probe函数
.probe = s3c_led_probe
进行一些设备号分配,cdev结构体注册等工作,这个博客里有讲:
https://blog.csdn.net/qq_40215005/article/details/90180590
//一旦注册成功就会传设备的device结构体给probe函数
static int s3c_led_probe(struct platform_device *dev)
{
struct s3c_led_platform_data *pdata = dev->dev.platform_data; //就是将设备的私有属性给到驱动来操作
int result = 0;
int i;
dev_t devno; //设备号,不同于前面的设备
/* Initialize the LED status */
for(i=0; i<pdata->nleds; i++)
{
s3c2410_gpio_cfgpin(pdata->leds[i].gpio, S3C2410_GPIO_OUTPUT);
if(ON == pdata->leds[i].status) //status参数也是设备模块传过来的
{
s3c2410_gpio_setpin(pdata->leds[i].gpio, pdata->leds[i].active_level);
}
else
{
s3c2410_gpio_setpin(pdata->leds[i].gpio, ~pdata->leds[i].active_level);
}
}
/* Alloc the device for driver */
if (0 != dev_major)
{
devno = MKDEV(dev_major, 0); //获取设备号并注册
result = register_chrdev_region(devno, 1, DEV_NAME);
}
else
{
result = alloc_chrdev_region(&devno, 0, 1, DEV_NAME); //动态注册设备号
dev_major = MAJOR(devno);
}
/* Alloc for device major failure */
if (result < 0)
{
printk("%s driver can't get major %d\n", DEV_NAME, dev_major);
return result;
}
/* Initialize button structure and register cdev*/
memset(&led_device, 0, sizeof(led_device));
led_device.data = dev->dev.platform_data;
cdev_init (&(led_device.cdev), &led_fops); //cdev结构体初始化
led_device.cdev.owner = THIS_MODULE;
result = cdev_add (&(led_device.cdev), devno , 1); //cdev结构体注册
if (result)
{
printk (KERN_NOTICE "error %d add %s device", result, DEV_NAME);
goto ERROR;
}
led_device.dev_class = class_create(THIS_MODULE, DEV_NAME);
if(IS_ERR(led_device.dev_class))
{
printk("%s driver create class failture\n",DEV_NAME);
result = -ENOMEM;
goto ERROR;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) //由于版本依赖的问题,所以做了一个宏判断
device_create(led_device.dev_class, NULL, devno, NULL, DEV_NAME);
#else
device_create (led_device.dev_class, NULL, devno, DEV_NAME);
#endif
/* Initial the LED blink timer */
init_timer(&(led_device.blink_timer)); //初始化设备结构体中的timer
led_device.blink_timer.function = led_timer_handler;//时钟处理函数
led_device.blink_timer.data = (unsigned long)pdata;//设备数据信息传给timer
led_device.blink_timer.expires = jiffies + TIMER_TIMEOUT;//jiffies为当前的时间,expire为超时时间
add_timer(&(led_device.blink_timer));//激活timer
printk("S3C %s driver version 1.0.0 initiliazed.\n", DEV_NAME);
return 0;
ERROR:
printk("S3C %s driver version 1.0.0 install failure.\n", DEV_NAME);
cdev_del(&(led_device.cdev));
unregister_chrdev_region(devno, 1);
return result;
}
这里面有一个设备类struct class是一个设备的高级视图,它抽象出低级的实现细节,class操作函数:http://www.cnblogs.com/skywang12345/archive/2013/05/15/driver_class.html
这里面定时器的jiffes说明一下:
arm架构下,没过一秒,定时器给cpu100次中断信号,也就是说没一次信号间隔10ms,而每一次信号中断jiffes就会加一,所以jiffes就是代表最单位为10ms,就是可以看做时间的另外一种表达方式,代码中.expires=jiffes+40,意思就是.expire设置为jiffes加上(40乘10ms)以后的时间,即为超时时间。
在看驱动和设备的模块的时候,发现大部分的结构体都有一个数据成员data,
要不就是unsigned long data,要不就是private_data,具体干什么依赖设备属性,个人认为他的存在,使得不同模块传递数据更加的灵活,可以自定义结构体穿个这个数据然后赋值给不同的结构体,这样就能达到我们驱动和设备之间关联的目的。
相同的在卸载模块的时候platform_driver_unregister(&s3c_led_driver);会执行 .remove = s3c_led_remove,
static int s3c_led_remove(struct platform_device *dev)
{
dev_t devno = MKDEV(dev_major, 0);
del_timer(&(led_device.blink_timer));
cdev_del(&(led_device.cdev));
device_destroy(led_device.dev_class, devno);
class_destroy(led_device.dev_class);
unregister_chrdev_region(devno, 1);
printk("S3C %s driver removed\n", DEV_NAME);
return 0;
}
open 和 ioctl
驱动下还要实现系统调用的具体实现,用led的驱动来说说四个led灯如何使用一个驱动代码
static int led_open(struct inode *inode, struct file *file)
{
struct led_device *pdev ;
struct s3c_led_platform_data *pdata;
pdev = container_of(inode->i_cdev,struct led_device, cdev);
pdata = pdev->data;
file->private_data = pdata;
return 0;
}
上述函数执行流程:定义led_device结构体指针,该指针指向的结构体应该是代表打开的相应的设备,但实际上他里面并没有该设备的任何信息,只有执行container_of函数后,才将设备信息传入结构体,再将data数据传给file结构体中去,这样ioctl中就可以用file来赋值给pdata。
container_of(ptr, type,member)函数的实现包括两部分:
-
判断ptr 与 member 是否为同一类型。
-
计算size大小,结构体的起始地址 = (type *)((char *)ptr - size) (注:强转为该结构体指针)。container_of()的作用就是通过一个结构变量中一个成员的地址找到这个结构体变量的首地址。
container_of(ptr,type,member),这里面有ptr,type,member分别代表指针、类型、成员。
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct s3c_led_platform_data *pdata = file->private_data;
switch (cmd)
{
case LED_OFF:
if(pdata->nleds <= arg)
{
printk("LED%ld doesn't exist\n", arg);
return -ENOTTY;
}
pdata->leds[arg].status = OFF;
pdata->leds[arg].blink = DISABLE;
break;
case LED_ON:
if(pdata->nleds <= arg)
{
printk("LED%ld doesn't exist\n", arg);
return -ENOTTY;
}
pdata->leds[arg].status = ON;
pdata->leds[arg].blink = DISABLE;
break;
case LED_BLINK:
if(pdata->nleds <= arg)
{
printk("LED%ld doesn't exist\n", arg);
return -ENOTTY;
}
pdata->leds[arg].blink = ENABLE;
pdata->leds[arg].status = ON;
break;
default:
printk("%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
print_led_help();
return -EINVAL;
}
return 0;
}
ioctl通过调用args参数对led进行使能,开关或者闪烁。
代码中用到了错误宏,linux内核错误宏:
26— ETXTBSY — Text file busy
25— ENOTTY —Inappropriate ioctl for device
24— EMFILE —Too many open files
23— ENFILE —Too many open files in system
22— EINVAL —Invalid argument
21— EISDIR —Is a directory
20— ENOTDIR —Not a directory
19— ENODEV —No such device
18— EXDEV —Invalid cross-device link
17-- EEXIST —File exists
16-- EBUSY —Device or resource busy
15-- ENOTBLK— Block device required
14-- EFAULT —Bad address
13-- EACCES —Permission denied
12-- ENOMEM —Cannot allocate memory
11-- EAGAIN —Resource temporarily unavailable
10-- ECHILD —No child processes
9-- EBADF —Bad file descriptor
8-- ENOEXEC —Exec format error
7-- E2BIG —Argument list too long
6-- ENXIO —No such device or address
5-- EIO —Input/output error
4-- EINTR —Interrupted system call
3-- ESRCH —No such process
2-- ENOENT —No such file or directory
1-- EPERM —Operation not permitted
0-- Success
虚拟总线的好处
platform 驱动与传统的设备驱动模型相比,优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源的时使用统一的借口,提高了程序的可移植性。
测试
代码测试的时候,需要insmod驱动和设备的模块,对于led这种属性相同但数量多的外设,或者说极其相似的外设,这样的驱动将提供很大的兼容性,在创建led节点的时候,只需要创建一个设备节点就ok了,控制不同led的时候传args给ioctl即可。
本文介绍了Linux平台驱动模型,包括VID & PID、驱动和设备分离的概念。重点解析了platform_device和platform_driver的注册过程,以及probe函数在设备初始化中的作用。文章还讨论了虚拟总线带来的优势,如资源管理的独立性、可移植性和安全性,并提供了LED驱动的示例来说明open和ioctl的使用。
&spm=1001.2101.3001.5002&articleId=90239268&d=1&t=3&u=129f88ca636141fe80f59677666228e3)
5421

被折叠的 条评论
为什么被折叠?



