目录
前言
框架
常用数据结构
常用函数
button 字符设备驱动
编译
编译进内核
编译成单独模块
测试
小结
LINUX 驱动针对的对象是存储器和外设,而不是针对cpu内核。存储器和外设分为3个基础大类:
字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、鼠标等。块设备可以按任意顺序进行访问,以块为单位进行操作,如硬盘、eMMC等。字符设备和块设备的驱动设计有很大的差异,但对于用户(应用程序)而言,它们都使用文件系统的接口(open()、write()、read()、close())来进行访问和操作。
网络设备面向数据的接收和发送设计,它不对应文件系统的节点。内核和网络设备的通信与内核和字符设备、块设备的通信方式完全不同,前者主要还是使用套接字接口。
include\linux\device.h
一个 struct class 结构体类型变量对应一个类,内核提供了class_create() 函数,可以用它来创建一个类,这个类存放于 sysfs 下面。 一旦创建了类,再用class_device_create() 函数就可以在 /dev 目录下创建相应的设备节点。
struct class { const char * name; //类名称 struct module * owner; //类所属的模块,比如led模块 struct kset subsys; struct list_head children; struct list_head devices; struct list_head interfaces; struct kset class_dirs; struct semaphore sem; /* locks both the children and interfaces lists */ struct class_attribute * class_attrs; // 类所添加的属性 struct class_device_attribute * class_dev_attrs; struct device_attribute * dev_attrs; int (*uevent)(struct class_device *dev, char **envp, int num_envp, char *buffer, int buffer_size); int (*dev_uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size); void (*release)(struct class_device *dev); void (*class_release)(struct class *class); // 类被释放时调用的函数 void (*dev_release)(struct device *dev); // 设备被释放时调用的函数 int (*suspend)(struct device *, pm_message_t state); int (*resume)(struct device *); };
一个class可以看成是一个容器,包含了很多的class_device,这些class_device是由class这个大的容器来管理的,而每个class_device都对应着一个具体的设备。每个class对象包括一个class_device链表,每个class_device对象表示一个逻辑设备并通过struct class_device中的dev成员(一个指向struct device的指针)关联一个物理设备。一个逻辑设备总是对应一个物理设备,而一个物理设备却可以对应多个逻辑设备。
struct class_device { struct list_head node; struct kobject kobj; struct class * class; /* required */ dev_t devt; /* dev_t, creates the sysfs "dev" */ struct class_device_attribute *devt_attr; struct class_device_attribute uevent_attr; struct device * dev; /* not necessary, but nice to have */ void * class_data; /* class-specific data */ struct class_device *parent; /* parent of this child device, if there is one */ struct attribute_group ** groups; /* optional groups */ void (*release)(struct class_device *dev); int (*uevent)(struct class_device *dev, char **envp, int num_envp, char *buffer, int buffer_size); char class_id[BUS_ID_SIZE]; /* unique to this class */ };
include\linux\fs.h
file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。用户进程利用系统调用在对设备进行读写操作时,系统调用通过设备文件的主设备号找到对应设备驱动程序,然后读取这个数据结构相应的函数指针,将控制权给到该函数。
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*dir_notify)(struct file *filp, unsigned long arg); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); };
// linux-2.6.22.6/fs/char_dev.c /* register_chrdev() - 注册字符设备. * @major: 主设备号 * @name: 设备名称 * @fops: file_operations结构体 如果major传入0,系统自动分配主设备号,返回值即为主设备号; 如果majos传入>0,返回0为成功,返回值<0表示失败 */ int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops) /* unregister_chrdev() - 卸载字符设备. * @major: 主设备号 * @name: 设备名称 */ int unregister_chrdev(unsigned int major, const char *name)
//linux-2.6.22.6/drivers/base/class.c /** * class_create - 创建一个设备类 * @owner: THIS_MODULE * @name: 设备类名称. * * 创建成功返回 struct class */ struct class *class_create(struct module *owner, const char *name) /** * class_create - 销毁一个设备类 * @cls: struct class * 创建成功返回 struct class */ void class_destroy(struct class *cls)
/** * class_device_create - creates a class device and registers it with sysfs * @cls: pointer to the struct class that this device should be registered to. * @parent: pointer to the parent struct class_device of this new device, if any. * @devt: the dev_t for the char device to be added. * @device: a pointer to a struct device that is assiociated with this class device. * @fmt: string for the class device's name * * This function can be used by char device classes. A struct * class_device will be created in sysfs, registered to the specified * class. * A "dev" file will be created, showing the dev_t for the device, if * the dev_t is not 0,0. * If a pointer to a parent struct class_device is passed in, the newly * created struct class_device will be a child of that device in sysfs. * The pointer to the struct class_device will be returned from the * call. Any further sysfs files that might be required can be created * using this pointer. * * Note: the struct class passed to this function must have previously * been created with a call to class_create(). */ struct class_device *class_device_create(struct class *cls, struct class_device *parent, dev_t devt, struct device *device, const char *fmt, ...) void class_device_unregister(struct class_device *class_dev)
// 从内核空间拷贝一块儿数据到用户空间 /* to 目标地址,这个地址是用户空间的地址; from 源地址,这个地址是内核空间的地址; n 将要拷贝的数据的字节数。 */ int copy_to_user(void __user *to, const void *from, int n)
module_init 修饰入口函数
module_exit 修饰 出口函数
MODULE_LICENSE 修饰 模块license
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "button" /* 加载模块后,执行”cat /proc/devices”命令看到的主设备名称 */
#define BUTTON_MAJOR 232 /* 主设备号 */
static struct class *buttons_class; //设备类 使用cd /sys/class 查看当前系统上的设备类
static struct class_device *buttons_class_dev; //类设备,一个设备类下可以有多个类设备 ls /dev/* 查看当前系统上的设备。或者 cd /sys/class/类名/ 查看当前设备类下的设备。
/* 应用程序对设备文件/dev/button 执行open(...)时,
* 就会调用s3c24xx_buttons_open函数
*/
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_INP);
s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_INP);
s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_INP);
return 0;
}
static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
char buttons_status[3];
buttons_status[0] = s3c2410_gpio_getpin(S3C2410_GPF0) & (0x1<<0)? 1: 0;
buttons_status[1] = s3c2410_gpio_getpin(S3C2410_GPF2) & (0x1<<2)? 1: 0;
buttons_status[2] = s3c2410_gpio_getpin(S3C2410_GPG3) & (0x1<<3)? 1: 0;
copy_to_user(buff, buttons_status, sizeof(buttons_status));
return 0;
}
static ssize_t s3c24xx_buttons_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
return 0;
}
/* 这个结构是字符设备驱动程序的核心
* 当应用程序操作设备文件时所调用的open、read、write等函数,
* 最终会调用这个结构中指定的对应函数
*/
static struct file_operations s3c24xx_buttons_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = s3c24xx_buttons_open,
.read = s3c24xx_buttons_read,
.write = s3c24xx_buttons_write,
};
/*
* 执行insmod命令时就会调用这个函数
*/
static int __init s3c24xx_button_init(void)
{
int ret;
/* 注册字符设备
* 参数为主设备号、设备名字、file_operations结构;
* 这样,主设备号就和具体的file_operations结构联系起来了,
* 操作主设备为BUTTON_MAJOR的设备文件时,就会调用s3c24xx_buttons_fops中的相关成员函数
* BUTTON_MAJOR可以设为0,表示由内核自动分配主设备号,返回值为主设备号
*/
ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't register major number\n");
return ret;
}
// 创建设备类
buttons_class = class_create(THIS_MODULE, "buttons");
if (IS_ERR(buttons_class))
return PTR_ERR(buttons_class);
// 创建类设备节点
buttons_class_dev = class_device_create(buttons_class, NULL, MKDEV(BUTTON_MAJOR,0), NULL, "button");
if (unlikely(IS_ERR(buttons_class_dev)))
return PTR_ERR(buttons_class_dev);
printk(DEVICE_NAME " initialized\n");
return 0;
}
/*
* 执行rmmod命令时就会调用这个函数
*/
static void __exit s3c24xx_button_exit(void)
{
printk("to unregister_chrdev \n");
unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
printk("to class_device_unregister \n");
class_device_unregister(buttons_class_dev);
printk("to class_destroy \n");
class_destroy(buttons_class);
}
/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_button_init);
module_exit(s3c24xx_button_exit);
/* 描述驱动程序的一些信息,不是必须的 */
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver");
MODULE_LICENSE("GPL");
KERN_DIR = /home/book/linuxSrc/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-y += buttons.o
KERN_DIR = /home/book/linuxSrc/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += buttons.o
insmod buttons.ko 手动加载模块
rmmod buttons 卸载模块
lsmod 列出当前已加载的模块
编译以下应用程序 arm-linux-gcc -o button_test buttons_test.c
#include
#include
#include
#include
int main(int argc, char **argv)
{
int i;
int ret;
int fd;
int press[3];
fd = open("/dev/button", 0); // 打开设备
if (fd < 0) {
printf("Can't open /dev/button\n");
return -1;
}
while (1) {
ret = read(fd, press, sizeof(press));
if (ret < 0) {
printf("read err!\n");
continue;
}
sleep(5);
printf(" read buttons status : 0(%d)--2(%d)--11(%d) \n",press[0],press[1],press[2]);
}
close(fd);
return 0;
}
字符设备驱动的一般流程为
- 构建file_operations结构体
static struct file_operations s3c24xx_buttons_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = s3c24xx_buttons_open, .read = s3c24xx_buttons_read, .write = s3c24xx_buttons_write, };
- 注册字符设备
int ret; /* 注册字符设备 * 参数为主设备号、设备名字、file_operations结构; * 这样,主设备号就和具体的file_operations结构联系起来了, * 操作主设备为BUTTON_MAJOR的设备文件时,就会调用s3c24xx_buttons_fops中的相关成员函数 * BUTTON_MAJOR可以设为0,表示由内核自动分配主设备号,返回值为主设备号 */ ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops); if (ret < 0) { printk(DEVICE_NAME " can't register major number\n"); return ret; }
- 创建类
// 创建设备类 buttons_class = class_create(THIS_MODULE, "buttons"); if (IS_ERR(buttons_class)) return PTR_ERR(buttons_class);
- 创建类设备节点
// 创建类设备节点 buttons_class_dev = class_device_create(buttons_class, NULL, MKDEV(BUTTON_MAJOR,0), NULL, "button"); if (unlikely(IS_ERR(buttons_class_dev))) return PTR_ERR(buttons_class_dev); printk(DEVICE_NAME " initialized\n"); return 0;
- 修饰驱动初始化和卸载接口
module_init(s3c24xx_button_init); module_exit(s3c24xx_button_exit);
另外需要加 MODULE_LICENSE("GPL");否则insmod加载模块时可能会提示“module license 'unspecified' taints kernel”