Linux总线设备驱动模型相关理论和实例(TQ2440)

时间:2022-07-31 23:38:44

Sysfs文件系统:

"sysfs is a ram-based filesystem initially based on ramfs.It provides a means to exportkernel data structures, their attributes, and the linkages between them to userspace.”
--- documentation/filesystems/sysfs.txt

Linux2.6内核引入了 sysfs 文件系统。sysfs 被看成是与 proc同类别的文件系统。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:系统中电源选项

Linux总线设备驱动模型相关理论和实例(TQ2440)

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;
};

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系统,如果parent是NULL则创建在/sys/下(类似proc)。
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对象。
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{
char * name; /*属性文件名*/
struct module * owner;
mode_t mode;/*属性的保护位*/
};
struct attribute (属性):对应于kobject的目录下的一个文件,Name成员就是文件名。

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:当用户写属性文件时,该函数被调用,用于存储用户传入的属性值。


Kobject是用来在/sys/创建目录,而目录下的文件就是其属性文件,其创建的目录下不能再有目录只能有属性文件。

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; //处理热插拔事件的操作集合
}
Linux总线设备驱动模型相关理论和实例(TQ2440)


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状态发生变化时(如被加入,移动),这三个函数将被调用。
filter:决定是否将事件传递到用户空间。如果 filter 返回 0,将不传递事件。(例: uevent_filter)
name:用于将字符串传递给用户空间的热插拔处理程序。
uevent:将用户空间需要的参数添加到环境变量中。(例:dev_uevent)


大概地说,Kset就是用来创建其有子目录的的目录,当其子目录所管理的kobject和kset状态发生变化时(如被加入,移动),调用其ops的三个函数。

设备驱动模型:

随着技术的不断进步,系统的拓扑结构也越来越复杂,对智能电源管理、热插拔的支持要求也越来越高,2.4内核已经难以满足这些需求。为适应这种形势的需要,Linux 2.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 (*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_earl y)(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)
删除属性

设备描述:

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;/*驱动程序的名字( 体现在 sy sfs 中 )*/
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)
删除属性

关于总线、设备、驱动的一个粗略总结:

初始化总线模块会在/sys/bus/下产生相应名字的一个文件夹(初始化总线必须要将其当成设备注册否则无法挂载设备在其下),加载(初始化)相应的设备模块(该设备的bus和parent和当前总线属性一致)之后会在总线文件夹下的device文件夹产生文件(注意设备模块需要一个release在struct device下,即使该函数是空的,否则在移除模块的时候不会删除相应的文件),加载相应的驱动模块会在总线文件夹下的driver文件夹下产生文件,关于driver和device是否匹配是由总线模块完成的(match函数),过程是总线ID和驱动名是否一致,如果一致则调用驱动模块中的probe,正因为总线模块做了匹配工作,所以不管设备和驱动加载的先后顺序如何,总线都会将它们匹配起来。

另外需要注意的几个宏定义:BUS_ATTR、DEVICE_ATTR、DRIVER_ATTR。

Platform总线

Platform总线是linux2.6内核加入的一种虚拟总线。platform机制的本身使用并不复杂,由两部分组成:
platform_device和platform_driver
Platform 驱动与传统的设备驱动模型相比,优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源时使用统一的接口,这样提高了程序的可移植性。

工作流程: 定义platform_device、注册platform_device、定义platform_driver、注册platform_driver 平台设备使用Struct Platform_device来描述:
struct platform_device{
const char *name; /*设备名*/
int id; /*设备编号,配合设备名使用*/
struct device dev;
u32 num_resources;
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, unsignedint type,  unsignedint 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是系统集成的,所以不需要注册总线,那么判断驱动和设备是否匹配的关键就在于它们的名字是否相同(而关于驱动和设备加载的前后顺序对于匹配也无影响,可能是内核MATCH)。
综上:总线存在的意义就是让驱动找到设备,让设备找到驱动,设备就是资源,驱动就是提取资源的接口,驱动程序里面带有设备方法以供用户空间操作设备。

中断处理:

中断实现: 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 对应于中断函数static irqreturn_t buttons_interrupt(int irq, void *dev_id)中的void *dev_id

在flags参数中,可以选择一些与中断管理有关的选项,如:
• IRQF_DISABLED(SA_INTERRUPT)
如果设置该位,表示是一个“快速”中断处理程序;如果没有设置这位,那么是一个“慢速”中断处理程序。
• IRQF_SHARED(SA_SHIRQ)
该位表明中断可以在设备间共享。
快速/慢速中断:
这两种类型的中断处理程序的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其它类型的中断仍可以得到服务。
共享中断: 共享中断就是将不同的设备挂到同一个中断信号线上。Linux对共享的支持主要是为PCI设备服务。
共享中断也是通过request_irq函数来注册的,但有三个特别之处:
1. 申请共享中断时,必须在flags参数中指定IRQF_SHARED位
2. dev_id参数必须是唯一的。Q: 为什么要唯一?
3. 共享中断的处理程序中,不能使用disable_irq(unsigned int irq)
为什么?如果使用了这个函数,共享中断信号线的其它设备将同样无法使用中断,也就无法正常工作了。
什么是中断处理程序,有何特别之处?
中断处理程序就是普通的C代码。特别之处在于中断处理程序是在中断上下文中运行的,它的行为受到某些限制:
1. 不能向用户空间发送或接受数据
2. 不能使用可能引起阻塞的函数
3. 不能使用可能引起调度的函数
中断处理函数流程:
void short_sh_interrupt(intirq, 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(unsignedintirq, void*dev_id)

综合实例:

开发环境:

Cent OS 6.4、TQ2440开发板、内核版本linux-2.6.29

硬件描述:

四个按键所在端口分别为EINT1/GPF1、EINT4/GPF4、EINT2/GPF2、EINT0/GPF0

平台设备程序:

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <mach/regs-gpio.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/io.h>

/*平台资源的定义*/
static struct resource s3c_buttons_resource[] = {
[0]={
.start = IRQ_EINT1,
.end = IRQ_EINT1,
.flags = IORESOURCE_IRQ,
},
[1]={
.start = IRQ_EINT4,
.end = IRQ_EINT4,
.flags = IORESOURCE_IRQ,
},
[2]={
.start = IRQ_EINT2,
.end = IRQ_EINT2,
.flags = IORESOURCE_IRQ,
},
[3]={
.start = IRQ_EINT0,
.end = IRQ_EINT0,
.flags = IORESOURCE_IRQ,
}
};

static struct platform_device *s3c_buttons;

static int __init platform_init(void)
{
s3c_buttons = platform_device_alloc("TQ2440-buttons",-1);//名字体现在/sys/bus/platform/下的/devices/中,名字用于和device匹配

platform_device_add_resources(s3c_buttons,&s3c_buttons_resource,4);

/*平台设备的注册*/
platform_device_add(s3c_buttons);
}

static void __exit platform_exit(void)
{
platform_device_unregister(s3c_buttons);
}

module_init(platform_init);
module_exit(platform_exit);

MODULE_AUTHOR("Zbffff");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:tq2440buttons");
平台驱动程序:
#include <linux/module.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/mm.h>#include <linux/sched.h>#include <linux/init.h>#include <linux/cdev.h>#include <asm/io.h>#include <asm/system.h>#include <asm/uaccess.h>#include <linux/device.h> /* device_create()*/#include <linux/interrupt.h> /*中断所需文件*/#include <linux/irq.h>/*中断所需文件*/#include <linux/poll.h>#include <linux/platform_device.h>#include <linux/device.h>#include <linux/miscdevice.h>static struct resource   *buttons_irq;static int button_irqs[4];#define DEVICE_NAME        "TQ2440-buttons"#ifndef MEMDEV_MAJOR#define MEMDEV_MAJOR 252   /*预设的mem的主设备号*/#endif#ifndef MEMDEV_NR_DEVS#define MEMDEV_NR_DEVS 1    /*设备数*/#endifstatic DECLARE_WAIT_QUEUE_HEAD(button_waitq);//初始化等待队列static int mem_major = MEMDEV_MAJOR;module_param(mem_major, int, S_IRUGO);//宏module_param指定模块参数,模块参数用于在加载模块时传递参数给模块。struct cdev *cdevp; struct button_irqs_desc      {int irq;   //中断号unsigned long flags; //中断标志,用来定义中断的触发方式 char *name;        //中断名称};static volatile int ev_press = 0;/* 按键被按下的次数(准确地说,是发生中断的次数) */static volatile int  press_cnt[] = {0,0,0,0};/*用来指定按键所用的外部中断引脚及中断触发方式,名字static struct button_irqs_desc button_irqs[] = {{IRQ_EINT1,IRQ_TYPE_EDGE_FALLING,"KEY1"},  {IRQ_EINT4,IRQ_TYPE_EDGE_FALLING,"KEY2"}, {IRQ_EINT2,IRQ_TYPE_EDGE_FALLING,"KEY3"},  {IRQ_EINT0,IRQ_TYPE_EDGE_FALLING,"KEY4"},  };*/static irqreturn_t buttons_interrupt(int irq,void *dev_id){volatile int *press_cnt = (volatile int *)dev_id;*press_cnt = 1;     /*按键计数器*/ev_press = 1;                    /*表示中断发生了*/wake_up_interruptible(&button_waitq);  /*唤醒休眠的进程*///printk(" IRQ:%d\n",irq);return IRQ_RETVAL(IRQ_HANDLED);}/*文件打开函数*/int mem_open(struct inode *inode, struct file *filp){int i;int err;for(i =0;i<sizeof(button_irqs)/sizeof(button_irqs[0]);i++)  {//注册中断处理函数//err = request_irq(button_irqs[i].irq,buttons_interrupt,button_irqs[i].flags,button_irqs[i].name,(void *)&press_cnt[i]); err = request_irq(button_irqs[i],buttons_interrupt,IRQ_TYPE_EDGE_RISING,NULL,(void *)&press_cnt[i]);if(err)    break;}//如果出错,释放已经注册的中断if (err) {i--;for (; i >= 0; i--) {if (button_irqs[i] < 0) {continue;}disable_irq(button_irqs[i]);free_irq(button_irqs[i], NULL);}return -EBUSY;}ev_press = 0;return 0;}/*文件释放函数*/int mem_release(struct inode *inode, struct file *filp){int i;for(i=0;i<sizeof(button_irqs)/sizeof(button_irqs[0]);i++)  {//释放已经注册的中断if (button_irqs[i] < 0) {continue;}free_irq(button_irqs[i],(void *)&press_cnt[i]);}return 0;}/*读函数*/static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){unsigned long err;/*如果ev_press等于0,休眠*///wait_event_interruptible(button_waitq,ev_press);/*执行到这里时ev_press肯定等于1,将它清0 */ev_press = 0;/*将按键状态复制给用户,并请0 */err = copy_to_user(buf,(const void *)press_cnt,sizeof(press_cnt));memset((void *)press_cnt,0,sizeof(press_cnt));return err? -EFAULT:0;}static unsigned int mem_poll( struct file *file, struct poll_table_struct *wait){unsigned int mask = 0;poll_wait(file, &button_waitq, wait);if (ev_press){mask |= POLLIN | POLLRDNORM;}return mask;}/*文件操作结构体*/static const struct file_operations mem_fops ={.owner = THIS_MODULE,.read = mem_read,.open = mem_open,.poll = mem_poll,.release = mem_release,};static struct miscdevice tq2440_miscdev = {    .minor = MISC_DYNAMIC_MINOR,    .name = DEVICE_NAME,    .fops = &mem_fops,};/* device interface */static int tq2440_buttons_probe(struct platform_device *pdev){/*struct class *myclass;//用于自动创建节点文件int result=0;非混杂设备注册用int err;dev_t devno = MKDEV(mem_major, 0);//主次设备号合成一个数字*/struct device *dev;int ret;int i;printk("probe:%s\n", __func__);dev = &pdev->dev;/*get irq number*/for(i=0; i<4; i++){buttons_irq = platform_get_resource(pdev,IORESOURCE_IRQ,i);if(buttons_irq == NULL){dev_err(dev,"no irq resource specified\n");ret = -ENOENT;}button_irqs[i] = buttons_irq->start;//printk("button_irqs[%d]=%d\n",i,button_irqs[i]);  }#if 0/* 静态申请设备号*/if (mem_major)result = register_chrdev_region(devno, MEMDEV_NR_DEVS, DEVICE_NAME);//memdev设备名体现在/proc/devicesif(!mem_major||result<0)  /* 动态分配设备号 */{result = alloc_chrdev_region(&devno, 0, MEMDEV_NR_DEVS, DEVICE_NAME);mem_major = MAJOR(devno);}  if(result < 0)return result;cdevp=cdev_alloc();/*初始化cdev结构*/cdev_init(cdevp, &mem_fops);cdevp->owner = THIS_MODULE;//cdevp->ops = &mem_fops;这句不要,cdev_init已经做了/* 注册字符设备 */cdev_add(cdevp, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);if(IS_ERR(&err))printk(KERN_NOTICE "Error %d adding buttons",err);/*自动创建设备文件*/myclass = class_create(THIS_MODULE,DEVICE_NAME); /*在sys下创建类目录/sys/class/test_char*/device_create(myclass, NULL, MKDEV(mem_major,0), NULL, DEVICE_NAME);  //体现在/dev//需要注意的是调试过程发现有自动创建节点文件函数的模块在一次insmod和rmmod之后再insmod会发生段错误#endifret = misc_register(&tq2440_miscdev);return 0;}static int tq2440_buttons_remove(struct platform_device *dev){#if 0cdev_del(cdevp);   /*注销设备*/unregister_chrdev_region(MKDEV(mem_major, 0), MEMDEV_NR_DEVS); /*释放设备号*/#endifmisc_deregister(&tq2440_miscdev);return 0;}/*平台驱动定义*/static struct platform_driver tq2440buttons_driver = {.probe= tq2440_buttons_probe,.remove= tq2440_buttons_remove,.driver= {.owner= THIS_MODULE,.name= DEVICE_NAME,//名字体现在/sys/bus/platform/下的/drivers/中,名字用于和device匹配},};static char banner[] __initdata = "TQ2440 Buttons Driver\n";/*设备驱动模块加载函数*/static int memdev_init(void){printk(banner);platform_driver_register(&tq2440buttons_driver);return 0;}/*模块卸载函数*/static void memdev_exit(void){platform_driver_unregister(&tq2440buttons_driver);}MODULE_AUTHOR("Zbffff");MODULE_DESCRIPTION("TQ2440 Buttons Driver");MODULE_LICENSE("GPL");module_init(memdev_init);module_exit(memdev_exit);
测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
int main(int argc,char **argv)
{
int i;
int ret;
int fd;
int press_cnt[4];
fd_set rds;
fd=open("/dev/TQ2440-buttons",0);
if(fd<0) {
printf("Can't open /dev/tq2440-buttons \n");
return -1;
}
//这是个无限循环,进程在select函数中休眠,当有按键按下时,它才返回
//如果被按下的次数不为0,打印出来
for (;;) {
FD_ZERO(&rds);
FD_SET(fd, &rds);
ret = select(fd + 1, &rds, NULL, NULL, NULL);
printf("passed the poll");
if (ret < 0) {
perror("select");
exit(1);
}
if (ret == 0) {
printf("Timeout.\n");
}
else if (FD_ISSET(fd, &rds)) {
ret = read(fd,press_cnt,sizeof(press_cnt));
if(ret<0)printf("read error");
else {
for(i=0;i<sizeof(press_cnt)/sizeof(press_cnt[0]);i++){
if(press_cnt[i])printf("Key%d has been pressed %d times \n",i+1,press_cnt[i]);
}
}
}
}
close(fd);
}