以前做了很多设备驱动,对于字符设备,平台设备,I2C设备,块设备,SPI设备,USB设备等等,开始的时候一直没太明白是怎么回事。
做驱动的时候,对这些模型也没有搞清楚关系,主要都是挖掘一些硬件特性。
后来看了《设备驱动开发详解》以后,对这些设备驱动模型终于有了一个比较清晰的认识。
一、设备类型的关系
从时间上来看,
最早的设备类型是:字符设备,块设备,网络设备;这些设备模型不知道是什么时候就有的,自我学习2.4内核就已经有这些基本的类型了。
扩展的设备类型:2.6内核引入的bus_type、device_driver、device分别描述总线,驱动和设备,这就是所谓的“总线设备驱动模型”。
总线是三者联系起来的基础,通过一种总线类型,将设备和驱动联系起来。
总线类型中的match函数用来匹配设备和驱动,当match操作完成之后就会调用device_driver中的probe函数。
struct bus_type {
/*总线名*/
const char *name;
/*总线、设备、驱动属性*/
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
/*总线支持的函数操作*/
/*匹配函数,主要用来识别相应的设备和驱动,是两者直接形成关联
用来判断指定的驱动程序能否处理指定的设备
*/
int (*match)(struct device *dev, struct device_driver *drv);
/*在进行热插拔事件之前,为设备配置环境变量操作函数*/
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
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 (*suspend_late)(struct device *dev, pm_message_t state);
int (*resume_early)(struct device *dev);
int (*resume)(struct device *dev);
struct dev_pm_ops *pm;
struct bus_type_private *p;
};这段话和结构体是不是和platform的概念很相似?
实际上,platform,I2C,SPI等,都是BUS的一种。
引用《设备驱动开发详解》的一段话,大意:
对于依附于I2C SPI总线的设备而言,设备和驱动挂在这些总线上。
对于SOC内部外设控制器和外设,需要依附于linux的虚拟总线,称之为platform。
所以,这些概念可以分为两类,一类是访问方式:字符 or 块,一类是总线。
对于这两种分类方式,是不相互隔离的,也就是说,一个设备既可以使字符设备,也可以是某种总线设备。
比较经典的例子是在已有的字符设备驱动模块中,再添加总线设备驱动(后面会说明作用)。
引用http://blog.csdn.net/yicao821/article/details/6783261代码:
led_dev.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 参考arch/arm/plat-s3c24xx/devs.c */
/*1. 根据芯片手册来获取资源*/
static struct resource led_resource[] = {
[0] = {
.start = 0x56000050,
.end = 0x56000057,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 5,
.end = 5,
.flags = IORESOURCE_IRQ,
},
};
void led_release(struct device *dev)
{
}
/*1.构建平台设备结构体,将平台资源加入进来*/
struct platform_device led_device = {
.name = "myled", /* 使用名为"myled"的平台驱动 */
.id = -1,
.dev = {
.release = led_release,
},
.num_resources = ARRAY_SIZE(led_resource),
.resource = led_resource,
};
/*2。把我们的设备资源挂在到虚拟总线的设备连表中去*/
int led_dev_init(void)
{
platform_device_register(&led_device);
return 0;
}
void led_dev_exit(void)
{
platform_device_unregister(&led_device);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
led_drv.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int major = 0;
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int pin;
static struct class *cls;
int led_open(struct inode *inode, struct file *file)
{
/* 设置对应的引脚为输出引脚 */
*gpio_con &= ~(0x3 << (pin * 2));
*gpio_con |= (0x1 << (pin * 2));
return 0;
}
ssize_t led_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
char ker_buf[1];
/* 根据buf传入的值点/灭灯 */
/* buf[0] - 亮/灭 , 0 - 亮, 1 - 灭 */
if (size != 1)
{
return -EINVAL;
}
copy_from_user(ker_buf, buf, 1);
if ((ker_buf[0] != 0) && (ker_buf[0] != 1))
return -EINVAL;
if (ker_buf[0])
{
// 某个LED
*gpio_dat |= (0x1<< pin);
}
else
{
// 某个LED
*gpio_dat &= ~(0x1<
}
return 1;
}
/*4实现操作硬件的方法*/
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
/*3。实现probe函数*/
static int led_probe(struct platform_device *dev)
{
struct resource *r;
/* 根据平台设备确定点哪一个LED */
r = platform_get_resource(dev, IORESOURCE_MEM, 0);
/*获取到资源以后再进行映射*/
gpio_con = ioremap(r->start, r->end - r->start + 1);
gpio_dat = gpio_con + 1;
r = platform_get_resource(dev, IORESOURCE_IRQ, 0);
pin = r->start;
/* 注册驱动向内核里面注册我们的字符设备驱动 */
major = register_chrdev(0, "led", &led_fops);
/* sysfs ==> 挂接到/sys */
cls = class_create(THIS_MODULE, "led_class");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led");
// mdev会根据/sys下的这些内容创建/dev/led
return 0;
}
int led_remove(struct platform_device *dev)
{
unregister_chrdev(major, "led");
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
iounmap(gpio_con);
return 0;
}
/*1。构建平台驱动结构体,不知道的时候可以看别人的*/
static struct platform_driver led_driver = {
.probe = led_probe, /* 平台总线下增加一个平台设备时,调用枚举函数 */
.remove = led_remove, /* 平台总线下去掉一个平台设备时,调用remove函数 */
.driver = {
.name = "myled", /* 能支持名为"myled"的平台设备 */
.owner = THIS_MODULE,
},
};
/*2。注册,把我们的驱动加入到平台设备驱动连表中去*/
static int led_drv_init(void)
{
platform_driver_register(&led_driver);
return 0;
}
static int led_drv_exit(void)
{
platform_driver_unregister(&led_driver);
return 0;
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
二、设备类型的作用 这个典型的例子说明,字符设备相关驱动函数是基本功能性的:read write ioctl
bus设备相关驱动是扩展功能的,比如系统挂起,恢复,以及后面会说到的可维护性。
总线设备驱动模型和sysfs是一起配合使用的
类型
所包含的内容
对应内核数据结构
对应/sys项
设备(Devices)
设备是此模型中最基本的类型,以设备本身的连接按层次组织
struct device
/sys/devices/*/*/.../
设备驱动(Device Drivers)
在一个系统中安装多个相同设备,只需要一份驱动程序的支持
struct device_driver
/sys/bus/pci/drivers/*/
总线类型(Bus Types)
在整个总线级别对此总线上连接的所有设备进行管理
struct bus_type
/sys/bus/*/
设备类别(Device Classes)
这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在 /sys/class/input/ 下
struct class
/sys/class/*/
引用IBM网站的一段话:
在 Linux 2.5 内核的开发过程中,人们设计了一套新的设备模型,目的是为了对计算机上的所有设备进行统一地表示和操作,包括设备本身和设备之间的连接关系。这个模型是在分析了 PCI 和 USB 的总线驱动过程中得到的,这两个总线类型能代表当前系统中的大多数设备类型,它们都有完善的热挺拔机制和电源管理的支持,也都有级连机制的支持,以桥接的 PCI/USB 总线控制器的方式可以支持更多的 PCI/USB 设备。为了给所有设备添加统一的电源管理的支持,而不是让每个设备中去独立实现电源管理的支持,人们考虑的是如何尽可能地重用代码;而且在有层次模型的 PCI/USB 总线中,必须以合理形式展示出这个层次关系,这也是电源管理等所要求的必须有层次结构。
如在一个典型的 PC 系统中,中央处理器(CPU)能直接控制的是 PCI 总线设备,而 USB 总线设备是以一个 PCI 设备(PCI-USB桥)的形式接入在 PCI 总线设备上,外部 USB 设备再接入在 USB 总线设备上;当计算机执行挂起(suspend)操作时, Linux 内核应该以 “外部USB设备->USB总线设备->PCI总线设备” 的顺序通知每一个设备将电源挂起;执行恢复(resume)时则以相反的顺序通知;反之如果不按此顺序则将有设备得不到正确的电源状态变迁的通知,将无法正常工作。电源管理,正是我写这篇文章的目的之一。在系统进入待机状态时,只有实现了BUS驱动的设备驱动,才能被POWER模块识别,并调用PM的SUSPEND函数和RESUME函数。当然,sysfs也是一个复杂的系统,后面再分析它的好处。如果不联系sysfs,bus驱动能够提供的也就只有电源管理和热拔插相关机制了(对照bus_type的定义)。