bus-driver-device
- bus:
总线作为主机和外设的连接通道,有些总线是比较规范的,形成了很多协议。如PCI,USB,1394,IIC等。任何设备都可以选择合适的总线连接到主机。当然主机也可能就是CPU本身。 - driver:
驱动程序是在CPU运行时,提供操作的软件接口。所有的设备必须有与之配套驱动程序才能正常工作。一个驱动程序可以驱动多个类似或者完全不同的设备。 - device:
设备就是连接在总线上的物理实体。设备是有功能之分的。具有相同功能的设备被归到一个类(CLASS中)。如音频设备(和声音相关的都算),输入设备(鼠标,键盘,游戏杆等) - 从宏观考虑,任何DEVICE必须要连接到主机才能发挥其作用。一个鼠标离开了电脑主机就不再是鼠标了。提到了连接就必然出现总线BUS。任何设备要正常运行必须有软件支持,所有的设备必须有DRIVER。
- 在linux驱动管理中,可以将驱动程序中用到的数据和代码分离开来:
设备是数据
驱动是代码 - 这种分离好处是:如果设备的参数变了,或者更改了同类的设备,那么只需要修改数据,二不需要修改代码,就能驱动设备了。
- 而设备和驱动的关联靠的是总线。
bus总线
- bus、device、dirver三者的定义在 include/linux/device.h中。
struct bus_type 表示总线
struct device_driver 表示驱动
struct device 表示设备
- 总线是处理器与设备之间通道,在设备模型中,所有的设备都通过总线相连。
- 总线上有两条链表klist_devices、klist_drivers;
- 每次出现一个设备就要向总线注册,添加到总线的klist_devices链表中;
- 每次出现一个驱动也要向总线注册,添加到总线的klist_drivers中。
- 比如系统初始化的时候,会扫描连接了哪些设备,并为每一个设备建立起一个struct device的变量,每一次有一个驱动程序,就要准备一个struct device_driver结构的变量。把这些变量统统加入相应的链表,device 插入devices 链表,driver插入drivers链表。这样通过总线就能找到每一个设备,每一个驱动.
- 假如计算机里只有设备却没有对应的驱动,那么设备无法工作。反过来,倘若只有驱动却没有设备,驱动也起不了任何作用。
- 链表里的device和driver又是如何联系
- 用“名字”关联:也就是device中的“name”于driver中的“name”一致。
- 用 ID 关联:device中的ID于driver中的ID一致,两者关联起来。
- 驱动核心可以注册多种类型的总线。
- 每种总线下面可以挂载许多设备。(通过kset devices)
- 每种总线下可以用很多设备驱动。(通过包含一个kset drivers)}
- 每个驱动可以处理一组设备。所有的设备都挂载到总线上,当加载驱动时,驱动就支总线上找到自己对应的设备。或者先把驱动加载上,来了一个设备就去总线找驱动。
- 总线由 bus_type 结构表示, 定义在
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 (*resume)(struct device *dev);//恢复时调用
const struct dev_pm_ops *pm;// 设备电源管理
struct bus_type_private *p;//私有数据。完全由驱动核心初始化并使用。
};
- 上面的总线的私有数据类型如下:
struct bus_type_private {
struct kset subsys;;/*与该总线相关的子系统*/
struct kset *drivers_kset;/*总线驱动程序的kset*/
struct kset *devices_kset;/* 挂在该总线的所有设备的kset*/
struct klist klist_devices;/*挂接在该总线的设备链表*/
struct klist klist_drivers;/*与该总线相关的驱动程序链表*/
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;/*处理热插拔、电源管理、探测和移除等事件的方法*/
struct bus_type *bus;
};
- struct bus_type类型里面又几个比较重要的成员,如下:
-
match() 匹配函数:
- 判定设备和驱动是否匹配,是总线体系相关的。驱动核心通过match和probe两个函数来完成匹配每当有设备添加到总线时,驱动核心遍历总线上的驱动链表查找设备驱动;每当有驱动添加到总线时,驱动核心遍历总线上的设备链表查找驱动可操控的设备。
- 当前,match一般只执行总线特定的匹配处理,而在probe中,通过回调设备驱动probe,完成设备特定的匹配、设备初始化等。
- match匹配成功则返回1,失配返回0。
-
probe()探测函数:
- probe执行设备相关的匹配探测、设备初始化、资源分配等。
- 对于一次遍历匹配而言,如果match和probe均成功,则结束匹配成功;如果match成功而probe失配,继续遍历查找匹配;如果遍历结束而没有找到成功的匹配,对于驱动而言表示没有可操控设备,对于设备而言表示没有适当的驱动。
remove()移除设备
设备移除时,调用该方法,完成部分清理工作。如删除设备驱动中,设备链表下的该设备。
-
shutdown()系统关机
- 系统关机时,调用该方法关闭设备。
-
suspend()设备休眠(挂起)。
- 设备休眠(挂起)时调用该方法。一般在该方法中设置设备为低耗电状态。
-
resume()设备恢复
- 设备从休眠中恢复时调用该方法。
-
pm()电源管理
- 一些设备有电源状态转换。结构体内部提供很多方法实现这个过程
-
deviece定义
- struct device结构中比较重要的成员如下:
struct device {
......
struct bus_type *bus; // 标识了该设备链接在哪一个总线上
struct device_driver *driver; // 管理该设备的驱动程序,一个设备只能有一个驱动程序
void *platform_data; //平台的特定数据
......
};
device注册的代码分析
- 将device注册到总线上的函数是
device_register();
- 跟踪这个注册函数的调用如下:
device_register(dev)
device_add(dev)
bus_add_device(dev)
bus_probe_device(dev)
device_attach(dev);
bus_for_each_drv(dev->bus, NULL, dev, __device_attach)
-
bus_for_each_drv()
函数是一个回调函数,意思是对于bus中的每一个driver,都调用__device_attach
这个函数 -
__device_attach
函数就是匹配总线上的设备与驱动的函数:
static int __device_attach(struct device_driver *drv, void *data)
{
struct device *dev = data;
if (!driver_match_device(drv, dev))//匹配条件检查
return 0;
return driver_probe_device(drv, dev);//进行真正的匹配操作
}
- bus的match存在就用bus的否则就直接匹配成功.match通常实现为首先扫描driver支持的id设备表,如果为NULL就用名字进行匹配
static inline int driver_match_device(struct device_driver *drv,
struct device *dev){
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
- 再来看下driver_probe_device(),进行匹配操作:
driver_probe_device(drv,dev)
really_probe(dev, drv); //调用really_probe
if (dev->bus->probe) { //利用probe初始化设备
ret = dev->bus->probe(dev); //如果bus的probe存在就用bus
} else if (drv->probe) {//如果bus的不存在driver的存在
ret = drv->probe(dev);//再用driver的
}
device_driver的定义
- struct device_driver结构中比较重要的成员如下:
struct device_driver {
const char *name; //名字
struct bus_type *bus;//其所在的bus
struct module *owner;//表示实现设备驱动程序的模块
const char *mod_name; //模块名
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
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;//私有属性
};
device_driver的注册代码分析
- driver的注册函数也跟device一样,也是注册的函数driver_register,下面来看看这个函数
driver_register(drv)
bus_add_driver(drv) //把该driver加入所在bus
driver_attach(drv)//到bus的devices上去匹配设备
- 匹配总线的驱动与设备的函数如下:
driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
//遍历bus的设备链表找到合适的设备就调用__driver_attach
- __driver_attach()函数
__driver_attach(drv, dev)
driver_match_device(drv, dev)
driver_probe_device(drv, dev)
really_probe(dev, drv)
这里真正开始调用用户在 device_driver 中注册的 probe()例程
整体框架
platform总线
- 在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于总线。
- 也就是说在嵌入式CPU中,访问外设跟访问内存是一样的。
- 基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为 platform_driver。
-
platform总线的定义如下:
-
platform总线中的match()函数是设备与驱动匹配的函数,我们看看这个函数的实现,就是匹配设备与驱动的名字一不一致。
- platform_device结构体的定义
struct platform_device {
const char *name;//名字,用于匹配driver
int id;
struct device dev;
u32 num_resources; //资源数目
struct resource * resource; //资源
const struct platform_device_id *id_entry;
struct mfd_cell *mfd_cell;
struct pdev_archdata archdata;
};
- 前面提到,设备代表数据,在这里数据由上面的num_resources、resource来指定;
- Platform驱动的资源一般驱动根据硬件来调整的参数。
- 主要是中断号和寄存器地址
- 原来驱动一般把这些资源以硬编码形式写死在驱动源码,如果调整必须修改驱动源码。
- 使用资源的优点是参数跟源码是分离,一般是在devs.c统一管理资源.而实现在代码写在原来platform驱动里。
- 常见资源类型
- I/O 地址(IO一般特指x86,I/O端口)
- 地址空间(寄存器空间) MEM,中断号,IRQ
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
#define IORESOURCE_IO 0x00000100
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
举个例子:
//引自于devs.c
/* Keypad interface */
static struct resource s3c_keypad_resource[] = {
[0] = {
.start = S3C_PA_KEYPAD,
.end = S3C_PA_KEYPAD+ S3C_SZ_KEYPAD - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_KEYPAD,
.end = IRQ_KEYPAD,
.flags = IORESOURCE_IRQ,
}
};
struct platform_device s3c_device_keypad = {
.name = "s3c-keypad",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_keypad_resource),
.resource = s3c_keypad_resource,
};
- 查找资源
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)
dev ,设备定立
Type ,资源类型,即resource.flags
num,序号,表示如果有相同的类型,选取的序号,1表取第二个资源
- 以下查找I/O地址并重映射
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dev = &pdev->dev;
size = (res->end - res->start) + 1;
base_addr = ioremap(res->start, size);
- 以下查找查找中断
ts_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
ret = request_irq(ts_irq->start, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c_updown", ts);
- 如果某一个platform设备有一个与本设备高度相关的参数。Platform建议把地址入struct device.platform_data。
- platform_data要求是动态分配的,方便device注销时释放。
-
platform_driver的定义如下:
- platform_device的注册函数
int platform_device_register(struct platform_device *);
- platform_driver的注册函数
int platform_driver_register(struct platform_driver *);
- 注册的流程跟前面分析的bus-device-driver一样
- 注册platform_device时
将设备添加到platform_bus_type的设备链表中;
然后匹配设备与驱动的“name”,如果一直调用驱动的probe方法; - 注册platform_driver时
将驱动添加到platform_bus_type的驱动链表中;
然后匹配设备与驱动的“name”,如果一直调用驱动的probe方法;
- 注册platform_device时
- platform驱动只是一种机制,这种机制就像模块机制一样;
- 模块机制:
在插入模块时,调用模块的入口函数(初始化函数)
在卸载模块时,调用模块的卸载函数 - bus_device_driver机制:
当注册device时,匹配driver,如果匹配就调用driver->probe方法;
当注册driver时,匹配device,如果匹配就调用driver->probe方法; - 不管注册设备还是注册驱动,都会调用driver->probe方法;
- 至于在驱动的probe方法中做什么事情,那是程序员的事情
- 模块机制:
实战代码
tiny4412_led_dev.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct led_t {
char *name;
unsigned int gpio;
} ;
static struct led_t tiny4412leds[] ={
{"LED1",EXYNOS4X12_GPM4(0)},
{"LED2",EXYNOS4X12_GPM4(1)},
{"LED3",EXYNOS4X12_GPM4(2)},
{"LED4",EXYNOS4X12_GPM4(3)},
};
static void led_release(struct device *dev)
{
}
static struct platform_device leddev = {
.name = "tiny4412_led",
.dev = {
.release = led_release,
.platform_data = (void *)tiny4412leds,
},
};
static int led_init(void)
{
platform_device_register(&leddev);
return 0;
}
static void led_exit(void)
{
platform_device_unregister(&leddev);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
tiny4412_led_dev.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct led_t {
char *name;
unsigned int gpio;
} ;
static struct led_t *led_p;
static int tiny4412_led_open(struct inode *_inode, struct file *fp)
{
int i;
for(i=0;i<4;i++){
s3c_gpio_cfgpin(led_p[i].gpio, S3C_GPIO_OUTPUT);
gpio_set_value(led_p[i].gpio,0);
}
return 0;
}
static int tiny4412_led_release(struct inode *_inode, struct file *fp)
{
int i;
for(i=0;i<4;i++){
gpio_set_value(led_p[i].gpio,1);
}
return 0;
}
static char value = 0xff;
static ssize_t tiny4412_led_read(struct file *fp, char __user *buf, size_t size, loff_t *off)
{
copy_to_user(buf, &value, sizeof(char));
return sizeof(char);
}
static ssize_t tiny4412_led_write(struct file *fp, const char __user *buf, size_t size, loff_t *off)
{
copy_from_user(&value, buf, sizeof(char));
if(value & (0x1<<0)) gpio_set_value(led_p[0].gpio,1);
else gpio_set_value(led_p[0].gpio,0);
if(value & (0x1<<1)) gpio_set_value(led_p[1].gpio,1);
else gpio_set_value(led_p[1].gpio,0);
if(value & (0x1<<2)) gpio_set_value(led_p[2].gpio,1);
else gpio_set_value(led_p[2].gpio,0);
if(value & (0x1<<3)) gpio_set_value(led_p[3].gpio,1);
else gpio_set_value(led_p[3].gpio,0);
return sizeof(char);
}
static struct file_operations fops={
.owner = THIS_MODULE,
.open = tiny4412_led_open,
.release = tiny4412_led_release,
.read = tiny4412_led_read,
.write = tiny4412_led_write,
};
#define TINY4412_LED_NAME "tiny4412_led"
static int major = 0;
static struct cdev *led_cdev;
static struct class *led_class;
static int led_probe(struct platform_device *pdev)
{
printk("my probe\n");
/*1 dev number*/
led_p = (struct led_t *)pdev->dev.platform_data;
dev_t devnum = 0;
alloc_chrdev_region(&devnum, 0, 1, TINY4412_LED_NAME);
major = MAJOR(devnum);
/*2 init cdev */
led_cdev = cdev_alloc();
cdev_init(led_cdev,&fops);
led_cdev->owner = THIS_MODULE;
/*3 add cdev */
cdev_add(led_cdev, devnum,1);
/*4 create dev file */
led_class=class_create(THIS_MODULE, TINY4412_LED_NAME);
device_create(led_class, NULL, devnum,NULL, TINY4412_LED_NAME); // /dev/tiny4412_led
return 0;
return 0;
}
static int led_remove(struct platform_device *pdev)
{
printk("my remove\n");
device_destroy(led_class, MKDEV(major,0));
class_destroy(led_class);
cdev_del(led_cdev);
unregister_chrdev_region(MKDEV(major,0), 1);
return 0;
}
static struct platform_driver leddrv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "tiny4412_led",
},
};
static int led_init(void)
{
platform_driver_register(&leddrv);
return 0;
}
static void led_exit(void)
{
platform_driver_unregister(&leddrv);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
makefile
obj-m += tiny4412_led_dev.o tiny4412_led_drv.o
all:
make -C /home/sice/linux-3.5 M=`pwd` modules
clean:
make -C /home/sice/linux-3.5 M=`pwd` clean
app.c
#include
#include
#include
#include
#include
int main(void)
{
int fd;
fd = open("/dev/tiny4412_led",O_RDWR);
sleep(5);
close(fd);
return 0;
}