linux嵌入式驱动-总线设备驱动模型
来源:互联网 发布:对软件测试的认识 编辑:程序博客网 时间:2024/04/28 13:10
一、Kobject&Kset
Kobject就是在sysfs文件系统中创建一个目录,目录中包含一个文件。而Kset就是在sysfs文件系统中创建一个可以包含目录的目录。所以Kobject是Kset的父类或基类。
1、sysfs文件系统
“sysfs is a ram-based filesystem initially based on ramfs. It provides a means toexport kernel data structures, their attributes, and the linkages between them to userspace.”
---documentation/filesystems/sysfs.txt
Linux2.6内核引入了sysfs文件系统。sysfs被看成是与proc同类别的文件系统。sysfs把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问的到。也就是sysfs通过文件树的方式让用户知道内核的信息。
sysfs被加载在/sys/目录下,它的子目录包括:
.Block:在系统中发现的每个块设备在该目录下对应的一个子目录。每个子目录中又包含一些属性文件,它们描述了这个块设备的各个方面属性,如:设备大小。(loop块设备是使用文件来模拟的)
.Bus:在内核中注册的每条总线在该目录下对应一个子目录,如:ide pci scsi usb pcmcia 其中每个总线目录内又包含两个子目录:devices和drivers,devices目录包含了在整个系统中发现的属于该总线类型的设备,drivers目录包含了注册到该总线的所有驱动。
.class:将设备按照功能进行分类,如/sys/class/net目录下包含了所有网络接口
.Devices:包含系统所有的设备。
.kernel:内核中的配置参数
.Module:系统中所有模块的信息
.Firmware:系统中的固件
.Fs:描述系统中的文件系统
.Power:系统中电源选项
Sysfs文件系统
上面图显示了一个设备可以存在多个目录下,一个设备按照不同的分类方式分属于不同的分类。在Sysfs中的反映就是链接文件。
2、kobject
kobject实现了基本的面向对象管理机制,是构成Linux2.6设备模型的核心结构。它与sysfs文件系统紧密相连,在内核中注册的每个kobject对象对应sysfs文件系统的一个目录。
类似于C++中的基类,kobject常被嵌入于其他类型(即:容器)中。如bus,devices,drivers都是典型的容器。这些容器通过kobject连接起来,形成了一个树状结构。
struct kobject{const char *name;struct list_head entry;struct kobject *parent;//指向父对象struct kset *kset;struct kobj_type *ktype;struct sysfs_dirent *sd;struct kref kref;//对象引用计数unsigned int state_initialized:1;unsigned int state_in_sysfs:1;unsigned int state_add_uevent_sent:1;unsigned int state_remove_uevent_sent:1;};kobject操作
. void kobject_init(struct kobject * kobj)
初始化kobject结构
. int kobject_add(struct kobject * kobj)
将kobject对象注册到Linux系统
. int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...)
初始化kobject, 并将其注册到Linux系统.
. void kobject_del(struct kobject *kobj)
从Linux系统中删除kobject对象
. struct kobject *kobject_get(struct kobject *kobj)
将kobject对象的引用计数加1,同时返回该对象指针。
. void kobject_put(struct kobject * kobj)
将kobject对象的引用计数减1,如果引用计数降为0,则调用release方法释放该kobject对象。
Struct kobj_type
kobject的ktype成员是一个指向kobj_type结构的指针,该结构中记录了kobject对象的一些属性。
struct kobj_type{void (*release)(struct kobject *kobj);struct sysfs_ops *sysfs_ops;struct attribute **default_attrs;};release:用于释放kobject占用的资源,当kobject的引用计数为0时被调用。
struct attribute
struct attribute{char *name; /*属性文件名*/struct module *owner;mode_t mode; /*属性的保护位*/};struct attribute(属性):对应于kobject的目录下的一个文件,Name成员就是文件名。
struct sysfs_ops
struct sysfs_ops{ssize_t (*show)(struct kobject *, struct attribute *,char *);ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);};.show:当用户读属性文件时,该函数被调用,该函数将属性值存入buffer中返回给用户态;
.store:当用户写属性文件时,该函数被调用,用于存储用户传入的属性值。
在学习驱动的过程中经常会学习函数指针,那如何来学习他们呢?
分三步:1、看参数从哪里来,要到哪里去(因为这些函数指针内核也需要用,所以不能更改它们的参数)2、函数指针什么时候调用 3、函数用来干什么,例如:show将内核让用户知道的信息打印并让用户看。
实例演示:kobject.c
#include <linux/device.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/string.h>#include <linux/sysfs.h>#include <linux/stat.h> MODULE_AUTHOR("David Xie");MODULE_LICENSE("Dual BSD/GPL"); void obj_test_release(struct kobject *kobject);ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf);ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count); struct attribute test_attr = { .name = "kobj_config", .mode = S_IRWXUGO,}; static struct attribute *def_attrs[] = { &test_attr, NULL,}; struct sysfs_ops obj_test_sysops ={ .show = kobj_test_show, .store = kobj_test_store,}; struct kobj_type ktype = { .release = obj_test_release, .sysfs_ops=&obj_test_sysops, .default_attrs=def_attrs,}; void obj_test_release(struct kobject *kobject){ printk("eric_test: release .\n");} ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf){ printk("have show.\n"); printk("attrname:%s.\n", attr->name); sprintf(buf,"%s\n",attr->name); return strlen(attr->name)+2;} ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count){ printk("havestore\n"); printk("write: %s\n",buf); return count;} struct kobject kobj;static int kobj_test_init(){ printk("kboject test init.\n"); kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test"); return 0;} static int kobj_test_exit(){ printk("kobject test exit.\n"); kobject_del(&kobj); return 0;} module_init(kobj_test_init);module_exit(kobj_test_exit);上例完成的工作:
1、在sys文件系统中创建了kobjet_test目录
2、在kobject_test目录下创建了一个文件kobj_config
3、读写kobj_config文件时,会按编程的效果打印输出给用户空间显示
3、kset
kset是具有相同类型的kobject的集合,在sysfs中体现成一个目录,在内核中用kset数据结构表示,定义为:
struct kset
{
struct list_head list;//连接该kset中所有kobject的链表头
spinlock_t list_lock;
struct kobject kobj;//内嵌的kobject
struct kset_uevent_ops *uevent_ops; //处理热插拔事件的操作集合
};
kset的操作
int kset_register(struct kset *kset)
在内核中注册一个kset
void kset_unregister(struct kset *kset)
从内核中注销一个kset
热插拔事件
在Linux系统中,当系统配置发生变化时,如:添加kset到系统;移动kobject,一个通知会从内核空间发送到用户空间,这就是热插拔事件。热插拔事件会导致用户空间中相应的处理程序(如udev,mdev)被调用,这些处理程序会通过加载驱动程序,创建设备节点等来响应热插拔事件。
操作集合
struct kset_uevent_ops
{
int (*filter)(struct kset *kset, struct kobject *kobj);
const char *(*name)(struct kset *kset, struct kobject *kobj);
int (*uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);
};
这三个函数什么时候调用?
当该kset所管理的kobject和kset状态发生变化时(如被加入,移动),这三个函数将被调用。(例:kobject_uevent调用)
这三个函数的功能是什么?
filter:决定是否将事件传递到用户空间。如果filter返回0,将不传递事件。
name:用于将字符串传递给用户空间的热插拔处理程序。
uevent:将用户空间需要的参数添加到环境变量中。
kset与kobject的区别:
1、kobject是用于创建sys下面的目录
2、目录属性对应一个文件
3、注册和添加一个kset,对应一个目录。kset目录可以包含目录或文件,而kobject中只包含文件
热插拔事件由一个结构体来处理
实例kset.c
见网址:http://blog.csdn.net/mbh_1991/article/details/9170365
运行结果:
二、设备驱动模型
随着技术的不断进步,系统的拓扑结构也越来越复杂,对智能电源管理、热插拔的支持要求也越来越高,2.4内核已经难以满足这些需求。为适应这种形势的需要,Linux2.6内核提供了全新的内核设备模型。
设备模型元素:总线 驱动 设备
总线是处理器和设备之间的通道,在设备模型中,所有的设备都通过总线相连,甚至是内部虚拟“platform”总线(定时器、看门狗就是通过这种虚拟总线与处理器相连)。在linux设备模型中,总线由bus_type结构表示,定义在<linux/device.h>
struct bus_type
{
const char *name;//总线名称
struct bus_attribute *bus_attrs;//总线属性
struct device_attribute *dev_attrs;//设备属性
struct driver_attribute *drv_attrs;//驱动属性
int (*mach)(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;
}
bus_register(struct bus_type *bus)总线的注册使用,若成功,新的总线将被添加进系统,并可在sysfs的/sys/bus下看到。
void bus_unregister(struct bus_type *bus)总线删除的使用
总线方法
int (*match)(struct device *dev, struct device_driver *drv)当一个新设备或者驱动被添加到这个总线时,该方法被调用。用于判断指定的驱动程序是否能处理指定的设备。若可以,则返回非零值。
int (*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size)在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量。
总线属性由结构bus_attribute描述,定义如下:
struct bus_attribute
{
struct attribute attr;
ssize_t (*show)(struct bus_type *, char *buf);
ssize_t (*store)(struct bus_type *, const char *buf, size_t count);
}
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr) 创建属性
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr) 删除属性
实例分析:Bus_basic.c
#include <linux/device.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/string.h>MODULE_AUTHOR("David Xie");MODULE_LICENSE("Dual BSD/GPL");static char *Version = "$Revision: 1.0 $";static int my_match(struct device *dev, struct device_driver *driver){return !strncmp(dev->bus_id, driver->name, strlen(driver->name));}struct bus_type my_bus_type = {.name = "my_bus",.match = my_match,};static ssize_t show_bus_version(struct bus_type *bus, char *buf){return snprintf(buf, PAGE_SIZE, "%s\n", Version);}static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);static int __init my_bus_init(void){int ret; /*注册总线*/ret = bus_register(&my_bus_type);if (ret)return ret;/*创建属性文件*/if (bus_create_file(&my_bus_type, &bus_attr_version))printk(KERN_NOTICE "Fail to create version attribute!\n");return ret;}static void my_bus_exit(void){bus_unregister(&my_bus_type);}module_init(my_bus_init);module_exit(my_bus_exit);设备描述:
linux系统中的每个设备由一个struct device描述:
struct device
{
struct kobject kobj;
char bus_id[BUS_ID_SIZE];//在总线上唯一标识该设备的字符串
struct bus_type *bus; //设备所在总线
struct device_driver *driver;//管理该设备的驱动
void *driver_data; //该设备驱动使用的私有数据成员
struct klist_node knode_class;
struct class *class;
struct attribute_group **groups;
void (*release)(struct device *dev);
}
int device_register(struct device *dev)注册设备
void device_unregister(struct device *dev)注销设备
**一条总线也是个设备,也必须按照设备注册**
设备属性由struct device_attribute描述:
struct device_attribute
{
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute
*attr, char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
}
int device_create_file(struct device *device, struct device_attribute * entry)创建属性
void device_remove_file(struct device * dev, struct device_attribute *attr) 删除属性
驱动程序由struct device_driver描述:
struct device_driver
{
const char *name; //驱动程序的名字(体现在sysfs中)
struct bus_type *bus; //驱动程序所在的总线
struct module *owner;
const char *mod_name;
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);
struct attribute_group **groups;
struct dev_pm_ops *pm;
struct driver_private *p;
}
int driver_register(struct device_driver *drv) //注册驱动
void driver_unregister(struct device_driver *drv) //注销驱动
驱动的属性使用struct driver_attribute来描述:
struct driver_attribute
{
struct attribute attr;
ssize_t (*show)(struct device_driver *drv, char *buf);
ssize_t (*store)(struct device_driver *drv,
const char *buf, size_t count);
}
int driver_create_file(struct device_driver *drv, struct driver_attribute *attr)创建属性
void driver_remove_file(struct device_driver * drv, struct driver_attribute *attr)删除属性
实例分析见http://blog.csdn.net/mbh_1991/article/details/9183627
运行结果:
三、Platform驱动程序
platform总线
platform总线是linux2.6内核加入的一种虚拟总线。platform机制的本身使用并不复杂,由两部分组成
platform_device和platform_driver
platform驱动与传统的设备驱动模型相比,优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源时使用统一的接口,这样提高了程序的可移植性。
通过platform机制开发底层设备驱动的流程图:
平台设备使用struct platform_device来描述:
struct platform_device
{
const char *name;//设备名
int id; //设备编号,配合设备名使用
struct device dev;
u32 num_resource;
struct resource *resource;//设备资源
}
struct platform_device的分配使用:
struct platform_device *platform_device_alloc(const char *name, int id)
参数:
name:设备名
id:设备id,一般为-1
int platform_device_add(struct platform_device *pdev)
平台设备资源使用struct resource来描述:
struct resource
{
resource_size_t start; //资源的起始物理地址
resource_size_t end; //资源的结束物理地址
const char *name; //资源的名称
unsigned long flags; //资源的类型,比如MEM,IO,IRQ类型
struct resource *parent, *sibling, *child; //资源链表指针
}
实例分析如下:
static struct resource s3c_wdt_resource1 =
{
.start = 0x44100000,
.end = 0x44200000,
.flags = IORESOURCE_MEM,
}
static struct resource s3c_wdt_resource2 =
{
.start = 20,
.end = 20,
.flags = IORESOURCE_IRQ,
}
获取资源
struct resource * platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
参数:
dev:资源所属的设备
type:获取的资源类型
num:获取的资源号
例:
platform_get_resource(pdev, IORESOURCE_IRQ, 0) //获取中断号
平台驱动使用struct platform_driver描述:
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 (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
}
int platform_driver_register(struct platform_driver *) //平台驱动的注册函数
platform平台驱动的实例分析见:http://blog.csdn.net/mbh_1991/article/details/9188691
platform平台总线混杂设备驱动led:http://blog.csdn.net/mbh_1991/article/details/9199937
总线模型的作用就是使得dev能够找到属于它的drv,同理使得drv找到属于它的dev。
四、中断处理
1、为什么需要中断?
中断是外设与CPU之间信息交互的机制,除中断外,它们之间通信还有轮询,不过,轮询占用CPU较长的时间,会降低CPU的效率,但算法简单,轮询会导致外设得不到CPU的响应,它的实时处理不好。
具体答案:
1、外设的处理速度一般慢于CPU
2、CPU不能一直等待外部事件
所以设备必须有一种方法来通知CPU它的工作进度,这种方法就是中断。
中断实现
在Linux驱动程序中,为设备实现一个中断包含两个步骤:
1、向内核注册中断
2、实现中断处理函数
request_irq用于实现中断的注册功能:
int request_irq(unsigned int irq, void (*handler)(int, void *,
struct pt_regs *),
unsigned long flags,
const char *devname,
void *dev_id)
返回0表示成功,或者返回一个错误码
参数介绍:
unsigned int irq //中断号
void (*handler)(int, void *, struct pt_regs *) //中断处理函数
unsigned long flags //与中断管理有关的各种选项
const char *devname //设备号
void *dev_id //共享中断时使用
中断标志:在flags参数中,可以选择一些与中断管理有关的选项,如:
.IRQF_DISABLED (SA_INTERRUPT) //如果设置该位,表示是一个“快速”中断处理程序;如果没有设置这位,那么是一个“慢速”中断处理程序。
.IRQF_SHARED (SA_SHIRQ) //该位表明中断可以再设备间共享。
这里解释一下快速中断和共享中断
快速中断不允许嵌套中断,如果嵌套了中断就是慢速中断了。而共享中断是指几个设备共享一条中断线,即共享同一个中断号。
为什么要有共享中断?
早期的操作系统只有16条中断线,如无共享中断,那么就只能有16个外设,显然这远远不够的。所以就提出了共享中断的概念。
快速/慢速中断
这两种类型的中断处理程序的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其它类型的中断仍可以得到服务。
共享中断就是将不同的设备挂到同一个中断信号线上。Linux对共享的支持主要是为PCI设备服务。
共享中断也是通过request_irq函数来注册的,但有三个特别之处:
1、申请共享中断时,必须在flags参数中指定IRQF_SHARED位
2、dev_id参数必须是唯一的。
为什么要唯一?
我们可以看到后面的释放中断函数有两个参数,一个是中断,一个是设备号。因为共享中断的设备拥有用一个中断号,所以要释放这些设备就要给每个设备一个设备号,用于区分各个设备。
3、共享中断的处理程序中,不能使用disable_irq(unsigned int irq)
为什么?
如果使用了这个函数,共享中断信号线的其它设备将同样无法使用中断,也就无法正常工作了。
什么是中断处理程序,有何特别之处?中断处理程序就是普通的C代码。特别之处在于中断处理程序是在中断上下文中运行的,它的行为受到某些限制:
1、不能向用户空间发送或接受数据
2、不能使用可能引起阻塞的函数
3、不能使用可能引起调度的函数
中断处理函数流程
void short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
//判断是否是本设备产生了中断(为什么要做这样的检查?)
value = inb(short_base);
if(!(value & 0x80)) return;
//清除中断位(如果设备支持自动清除,则不需要这步)
outb(value & 0x7F, short_base);
//中断处理,通常是数据接收
。。。。。。。。。
//唤醒等待数据的进程
wake_up_interruptible(&short_queue);
}
释放中断
当设备不再需要使用中断时(通常在驱动卸载时),应当把它们返还给系统,使用
void free_irq(unsigned int irq, void *dev_id)
五、按键驱动程序
tiny6410的按键硬件连接:
参考博文:http://blog.csdn.net/mbh_1991/article/details/9229665
也同样可以参考内核中的mini6410_buttons.c,学习内核驱动时,内核是我们最好的榜样。
- linux嵌入式驱动-总线设备驱动模型
- Linux 设备总线驱动模型
- Linux总线设备驱动模型
- Linux总线、设备、驱动模型
- Linux总线设备驱动模型
- Linux 设备总线驱动模型
- Linux总线设备驱动模型
- Linux总线、设备、驱动模型
- 嵌入式linux之分离分层概念,总线驱动设备模型
- 嵌入式linux平台设备驱动(设备驱动模型)开发之linux内核中bus总线
- Linux 设备模型 --- 总线设备驱动模型 --- 驱动
- linux设备驱动模型——总线、设备、设备驱动
- Linux 设备模型 --- 总线设备驱动模型 --- 总线
- linux设备总线驱动模型 之 platform总线驱动
- linux设备总线驱动模型 之 platform总线驱动
- linux设备总线驱动模型 之 platform总线驱动
- 总线设备驱动模型
- 总线/设备/驱动模型
- C++ Linux 多线程之创建、管理线程
- 华为荣耀x1铃声设置教程!
- tail 命令
- 蓝桥杯——说好的进阶之数字三角形
- The connection to adb is down, and a severe error has occured.
- linux嵌入式驱动-总线设备驱动模型
- hibernate工作原理及其使用
- uml图验收问题集锦
- UI产品设计流程中的14个要点
- Activity 生命周期观察-Android Activity Lifecycle Observation
- 解决win8.1下visual c++无法运行问题
- 虚拟机镜像文件格式转换:VirtualBox to VMWare
- 2013多校联合4【Problem A: ZZ买衣服】
- 数据结构--二叉树(定义与存储结构)