字符设备驱动笔记
一、简介
在所有Linux设备驱动中,字符设备驱动最为基础,本笔记将讲解Linux字符设备驱动的结构,并解释其主要组成部分的编程方法。
二、主要结构体及API函数
- cdev结构
struct cdev {
struct kobject kobj;/*内嵌的kobject对象*/
struct module *owner;/*所属模块*/
const struct file_operations *ops;/*文件操作结构体*/
struct list_head list;
dev_t dev;/*设备号*/
unsigned int count;
};
cdev结构体的dev_t成员定义了设备号,为32位,其中12bit为主设备号,20bit为次设备号。使用如下宏可以从dev_t获得主设备号和次设备号。
MAJOR(dev_t dev)
MINOR(dev_t dev)
使用如下宏可以用主设备号和次设备号生成设备号
MKDEV(int major, int minor)
内核提供了一组函数用于操作cdev结构体:
void cdev_init(struct cdev *,struct file_operations *);/*初始化cdev成员,建立cdev和file_operations的连接*/
struct cdev *cdev_alloc(void);/*动态申请一个cdev内存*/
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned); /*向系统添加一个cdev*/
void cdev_del(struct cdev *);/*删除系统中的一个cdev*/
设备号的申请和释放
int register_chrdev_region(dev_t from, unsigned count, const char *name);/*用于已知起始设备号的情况下申请设备号*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);/*用于未知起始设备号的情况下申请未被占用的设备号*/
void unregister_chrdev_region(dev_t from, unsigned count);/*设防设备号*/
-
file_operations结构
cdev结构体的一个重要成员file_operations定义了字符设备驱动提供给文件系统的接口函数。
struct file_operations {
struct module *owner;/*拥有该结构的模块的指针,一般为THIS_MODULES*/
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);/*仅用于读取目录,对于设备文件,该字段为NULL*/
unsigned int (*poll) (struct file *, struct poll_table_struct *);/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);/*执行设备IO控制命令*/
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);/*不使用BLK的文件系统,将使用此函数指针代替ioctl*/
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);/*在64位的系统上,32位的ioctl调用,将使用此函数指针代替*/
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);/*异步fsync*/
int (*fasync) (int, struct file *, int);/*通知设备FASYNC标志发生变化*/
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);/*通常为NULL*/
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);/*在当前进程地址空间找到一个未映射的内存段*/
int (*check_flags)(int);/*允许模块检查传递给fcntl(F_SETEL...)调用的标志*/
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);/*由VFS调用,将管道数据粘贴到文件*/
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);/*由VFS调用,将文件数据粘贴到管道*/
int (*setlease)(struct file *, long, struct file_lock **);
};
三、驱动代码及分析
如下是一个最简单的字符设备驱动,四个LED灯的控制,可以作为字符设备驱动的模板。
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <mach/regs-clock.h>
#include <plat/regs-timer.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h>
static int led_major = 0; /* 主设备号 */
static struct cdev LedDevs;
/* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */
#define LED_MAGIC 'k'
#define IOCTL_LED_ON _IOW (LED_MAGIC, 1, int)
#define IOCTL_LED_OFF _IOW (LED_MAGIC, 2, int)
#define IOCTL_LED_RUN _IOW (LED_MAGIC, 3, int)
#define IOCTL_LED_SHINE _IOW (LED_MAGIC, 4, int)
#define IOCTL_LED_ALLON _IOW (LED_MAGIC, 5, int)
#define IOCTL_LED_ALLOFF _IOW (LED_MAGIC, 6, int)
/* 用来指定LED所用的GPIO引脚 */
static unsigned long led_table [] = {
S3C2410_GPB(5),
S3C2410_GPB(6),
S3C2410_GPB(7),
S3C2410_GPB(8),
};
/* 应用程序对设备文件/dev/led执行open(...)时,
* 就会调用s3c24xx_leds_open函数
*/
static int s3c2440_leds_open(struct inode *inode, struct file *file)
{
int i;
for (i = 0; i < 4; i++) {
// 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能
s3c2410_gpio_cfgpin(led_table[i], S3C2410_GPIO_OUTPUT);
}
return 0;
}
//LEDS all light on
void leds_all_on()
{
int i;
for (i=0; i<4; i++) {
s3c2410_gpio_setpin(led_table[i], 0);
}
}
//LEDs all light off
void leds_all_off()
{
int i;
for (i=0; i<4; i++) {
s3c2410_gpio_setpin(led_table[i], 1);
}
}
/* 应用程序对设备文件/dev/leds执行ioctl(...)时,
* 就会调用s3c24xx_leds_ioctl函数
*/
static int s3c2440_leds_ioctl(struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
unsigned int data;
if (__get_user(data, (unsigned int __user *)arg))
return -EFAULT;
switch(cmd) {
case IOCTL_LED_ON:
// 设置指定引脚的输出电平为0
s3c2410_gpio_setpin(led_table[data], 0);
return 0;
case IOCTL_LED_OFF:
// 设置指定引脚的输出电平为1
s3c2410_gpio_setpin(led_table[data], 1);
return 0;
case IOCTL_LED_RUN:
// 跑马灯
{
int i,j;
leds_all_off();
//printk("IOCTL_LED_RUN");
for (i=0;i<data;i++)
for (j=0;j<4;j++) {
s3c2410_gpio_setpin(led_table[j], 0);
mdelay(400); //delay 400ms
s3c2410_gpio_setpin(led_table[j], 1);
mdelay(400); //delay 400ms
}
return 0;
}
case IOCTL_LED_SHINE:
// LED 闪烁
{
int i,j;
leds_all_off();
printk("IOCTL_LED_SHINE\n");
for (i=0;i<data;i++) {
for (j=0;j<4;j++)
s3c2410_gpio_setpin(led_table[j], 0);
mdelay(400); //delay 400ms
for (j=0;j<4;j++)
s3c2410_gpio_setpin(led_table[j], 1);
mdelay(400);
}
return 0;
}
case IOCTL_LED_ALLON:
// 设置指定引脚的输出电平为0
leds_all_on();
return 0;
case IOCTL_LED_ALLOFF:
// 设置指定引脚的输出电平为1
leds_all_off();
return 0;
default:
return -EINVAL;
}
}
/* 这个结构是字符设备驱动程序的核心
* 当应用程序操作设备文件时所调用的open、read、write等函数,
* 最终会调用这个结构中指定的对应函数
*/
static struct file_operations s3c2440_leds_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = s3c2440_leds_open,
.ioctl = s3c2440_leds_ioctl,
};
/*
* Set up the cdev structure for a device.
*/
static void led_setup_cdev(struct cdev *dev, int minor,
struct file_operations *fops)
{
int err, devno = MKDEV(led_major, minor);
cdev_init(dev, fops);
dev->owner = THIS_MODULE;
dev->ops = fops;
err = cdev_add (dev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk (KERN_NOTICE "Error %d adding Led%d", err, minor);
}
/*
* 执行“insmod s3c24xx_leds.ko”命令时就会调用这个函数
*/
static int __init s3c2440_leds_init(void)
{
int result;
dev_t dev = MKDEV(led_major, 0);
char dev_name[]="led"; /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
/* Figure out our device number. */
if (led_major)
result = register_chrdev_region(dev, 1, dev_name);
else {
result = alloc_chrdev_region(&dev, 0, 1, dev_name);
led_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "leds: unable to get major %d\n", led_major);
return result;
}
if (led_major == 0)
led_major = result;
/* Now set up cdev. */
led_setup_cdev(&LedDevs, 0, &s3c2440_leds_fops);
printk("Led device installed, with major %d\n", led_major);
printk("The device name is: %s\n", dev_name);
return 0;
}
/*
* 执行”rmmod s3c24xx_leds”命令时就会调用这个函数
*/
static void __exit s3c2440_leds_exit(void)
{
/* 卸载驱动程序 */
cdev_del(&LedDevs);
unregister_chrdev_region(MKDEV(led_major, 0), 1);
printk("Led device uninstalled\n");
}
/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c2440_leds_init);
module_exit(s3c2440_leds_exit);
/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("eurphan"); // 驱动程序的作者
MODULE_DESCRIPTION("s3c2440 LED Driver"); // 一些描述信息
MODULE_LICENSE("Dual BSD/GPL"); // 遵循的协议
四、测试应用程序
写好的驱动怎么测试呢,这就需要编写应用程序来测试了
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define LED_MAGIC 'k'
#define IOCTL_LED_ON _IOW (LED_MAGIC, 1, int)
#define IOCTL_LED_OFF _IOW (LED_MAGIC, 2, int)
#define IOCTL_LED_RUN _IOW (LED_MAGIC, 3, int)
#define IOCTL_LED_SHINE _IOW (LED_MAGIC, 4, int)
#define IOCTL_LED_ALLON _IOW (LED_MAGIC, 5, int)
#define IOCTL_LED_ALLOFF _IOW (LED_MAGIC, 6, int)
void usage(char *exename)
{
printf("Usage:\n");
printf(" %s <led_no> <on/off>\n", exename);
printf(" led_no = 1, 2, 3 or 4\n");
}
int main(int argc, char **argv)
{
unsigned int led_no;
int fd = -1;
unsigned int count=10;
if (argc > 3 || argc == 1)
goto err;
fd = open("/dev/led", 0); // 打开设备
if (fd < 0) {
printf("Can't open /dev/leds\n");
return -1;
}
if (argc == 2) {
if (!strcmp(argv[1], "on")) {
ioctl(fd, IOCTL_LED_ALLON, &count); // 点亮它
} else if (!strcmp(argv[1], "off")) {
ioctl(fd, IOCTL_LED_ALLOFF, &count); // 熄灭它
} else if (!strcmp(argv[1], "run")) {
ioctl(fd, IOCTL_LED_RUN, &count); //运行跑马灯
} else if (!strcmp(argv[1], "shine")) {
ioctl(fd, IOCTL_LED_SHINE, &count); //闪烁
} else {
goto err;
}
}
if (argc == 3) {
led_no = strtoul(argv[1], NULL, 0) - 1; // 操作哪个LED?
if (led_no > 3)
goto err;
if (!strcmp(argv[2], "on")) {
ioctl(fd, IOCTL_LED_ON, &led_no); // 点亮
} else if (!strcmp(argv[2], "off")) {
ioctl(fd, IOCTL_LED_OFF, &led_no); // 熄灭
} else {
goto err;
}
}
close(fd);
return 0;
err:
if (fd > 0)
close(fd);
usage(argv[0]);
return -1;
}