3.字符设备驱动模型

一、字符设备驱动的设计流程

1、定义一个字符设备
2、定义字符设备的file_operations和其接口函数
3、申请设备号:静态注册,动态分配
4、字符设备初始化cdev_init()
5、将字符设备加入内核cdev_add()
6、创建设备文件:手动创建(shell命令)、自动创建(调用函数)

=============================================================================================
二、定义一个字符设备?
在linux系统中,每个字符设备驱动都需要定义一个字符设备。字符设备使用一个结构体来描述---struct cdev
#include <linux/cdev.h>
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
cdev成员分析:
struct kobject kobj; --->给内核管理cdev来时用的--->/sys/,用户编程不用。
struct module *owner; --->cdev输入那个module,一般赋值成:THIS_MODULE
const struct file_operations *ops; --->cdev的文件操作集,用户编写
struct list_head list; --->内核链表,给系统使用。
dev_t dev; --->设备号,是一个32bits的整型值,用户申请
unsigned int count; --->次设备的数量,如串口有4个

# ls /dev/s3c2410_serial* -l
crw-rw---- 1 root root 204, 64 Feb 5 16:55 /dev/s3c2410_serial0
crw-rw---- 1 root root 204, 65 Feb 5 16:55 /dev/s3c2410_serial1
crw-rw---- 1 root root 204, 66 Feb 5 16:55 /dev/s3c2410_serial2
crw-rw---- 1 root root 204, 67 Feb 5 16:55 /dev/s3c2410_serial3


例:
static struct cdev gec210_led_cdev; //定义一个led驱动的cdev

================================================================================
三、定义及初始化文件操作集
1、文件操作集
一个字符设备需要有一个文件操作集,文件操作集是驱动程序中给应用程序提供的接口。使用文件操作集中的接口函数(open、read、write、ioctl、release)来访问文件,实现应用程序到硬件之间的控制。
发送数据:
open()--->write()----->file_operations.write()--->将数据发给寄存器--->硬件将数据发走。

文件操作集的原型:


#include <linux/fs.h>
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 *, 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 (*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 (*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);
int (*setlease)(struct file *, long, struct file_lock **);
};

常用的:
struct file_operations {
struct module *owner;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
};


例:
static int gec210_led_open(struct inode *, struct file *)
{
//设置GPIO为输出,输出高电平,灯灭

}

static ssize_t gec210_led_write(struct file *, const char __user *, size_t, loff_t *)
{
//得到应用程序write数据,根据该数据控制LED灯。

}

static int gec210_led_release(struct inode *, struct file *)
{


}

static struct file_operations gec210_led_fops = {
.owner = THIS_MODULE,
.open = gec210_led_open,
.write = gec210_led_write,
.release = gec210_led_release,
};


============可会讲=================================================================
四、设备号
1、什么是设备号?
设备号相当于一个设备驱动的ID,每个设备文件都有一个设备号,而且设备号是唯一的,相当于我们的身份证号。

设备号的类型 ---> dev_t

typedef __kernel_dev_t dev_t;
typedef __u32 __kernel_dev_t;
typedef unsigned int __u32;

设备号是一个无符号32位的整型值。

2、设备号的组成
主设备号+次设备号 = 设备号
1)主设备号:设备号[31:20]
硬件的类型;如UART串口。

2)次设备号:设备号[19:0]
该硬件类型下具体的模块,如uart串口有4个
# ls /dev/s3c2410_serial* -l
crw-rw---- 1 root root 204, 64 Feb 5 16:55 /dev/s3c2410_serial0
crw-rw---- 1 root root 204, 65 Feb 5 16:55 /dev/s3c2410_serial1
crw-rw---- 1 root root 204, 66 Feb 5 16:55 /dev/s3c2410_serial2
crw-rw---- 1 root root 204, 67 Feb 5 16:55 /dev/s3c2410_serial3

主设备号:204 ;次设备号:64~67

/dev/s3c2410_serial0的设备号 = 204<<20 + 64

设备号的三个函数:
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //MINORBITS=20

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))


3、如何得到设备号
1)静态注册
自己指定一个设备号,将该设备号注册进内核。如果该设备号非法,注册会失败。不需要判断函数的返回值。
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数说明:
dev_t from --->设备号的开始值
unsigned count --->申请设备号的个数
const char *name --->自定义设备名称,不是设备文件的名字
返回值:
成功:返回0
失败:返回一个负数错误码

例子:
static unsigned int led_major=120;
static unsigned int led_minor=0;
static dev_t led_dev_num;
int ret;
led_dev_num = MKDEV(led_major,led_minor);
ret = register_chrdev_region(led_dev_num, 1, "gec210_leds");
if(ret <0){
printk("register chrdev region error\n");
return ret; //ret是一个负数的错误码
}


2)动态分配
内核自动分配一个设备号。
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)

参数说明:
dev_t *dev ---> 分配后的设备号
unsigned baseminor --->次设备号的开始值
unsigned count --->设备号的个数
const char *name --->设备名称
返回值:
成功:返回0
失败:返回一个负数错误码

例子:
static unsigned int led_major=0;
static unsigned int led_minor=0;
static dev_t led_dev_num;
int ret;

ret = alloc_chrdev_region(&led_dev_num, led_minor,1, "gec210_leds");
if(ret <0){
printk("alloc chrdev region error\n");
return ret; //ret是一个负数的错误码
}


3)设备号的注销
我们在安装的驱动的时候需要注册设备号,驱动卸载的时候,需要将设备号注销掉。
/**
* unregister_chrdev_region() - return a range of device numbers
* @from: the first in the range of numbers to unregister
* @count: the number of device numbers to unregister
*
* This function will unregister a range of @count device numbers,
* starting with @from. The caller should normally be the one who
* allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count)


================================================================================
五、字符设备初始化
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)

例:
cdev_init(&gec210_led_cdev, &gec210_led_fops);


注意:
static struct cdev gec210_led_cdev; //gec210_led_cdev---有内存,占用空间
cdev_init(&gec210_led_cdev, &gec210_led_fops);
或:
static struct cdev *gec210_led_cdev; //*gec210_led_cdev -->4个字节,没有空间
cdev_init(gec210_led_cdev, &gec210_led_fops);
---->“野”指针->段错误。

正确的方法:
static struct cdev *gec210_led_cdev;
gec210_led_cdev = kmalloc(sizeof(struct cdev),...)
if(gec210_led_cdev != NULL)
cdev_init(gec210_led_cdev, &gec210_led_fops);


================================================================================
六、将字符设备加入内核
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数说明:
struct cdev *p -->定义及初始化好的cdev
dev_t dev --->字符设备的设备号
unsigned count --->次设备的数量

返回值:
成功:返回0
失败:返回一个负数错误码


void cdev_del(struct cdev *p)

注意问题:
内核代码的编程风格:code style

================================================================================
七、错误码
#include <linux/errno.h>
#define EPERM 1 /* Operation not permitted */ ######
#define ENOENT 2 /* No such file or directory */ ######
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */

================================================================================
八、用户空间与内核空间数据的交换
#include <linux/uaccess.h>
1、驱动程序得到应用程序的数据:
放在驱动程序的write函数中
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)

2、驱动程序将数据给应用程序
放在驱动程序的read函数中
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

返回值:
拷贝完成,返回0;
拷贝未完成,返回剩余的字节数。


================================================================================
九、编写应用程序
字符设备驱动的应用程序:系统IO函数:open/read/write/close/ioctl

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main(void)
{
int fd;
char buf[]="hello world\n";
fd = open("/dev/led_drv", O_WRONLY); // /dev/led_drv设备文件
if(fd < 0)
{
perror("open led_drv");
return -1;
}
write(fd, buf,sizeof(buf));
close(fd);
return 0;
}

================================================================================
十、创建设备文件

1、安装驱动
# insmod led_dev.ko
[ 122.878913] register led driver to kernel


2、查看设备
[root@GEC210 /test]# cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
21 sg
29 fb
81 video4linux
89 i2c
90 mtd
108 ppp
116 alsa
120 gec210_leds


3、手动创建设备文件---/dev/led_drv
信息点:
fd = open("/dev/led_drv", O_WRONLY);

static unsigned int led_major=120; //主设备号
static unsigned int led_minor=0; //次设备号

手动创建设备文件
# mknod /dev/led_drv c 120 0

mknod 设备文件 设备类型 主设备号 次设备号


4、查看设备文件
# ls /dev/led_drv -l
crw-r--r-- 1 root root 120, 0 Jan 1 12:06 /dev/led_drv


5、执行应用程序
# ./test
[ 556.402267] led driver open
[ 556.402325] kbuf=hello world
[ 556.402329]
[ 556.402377] led driver release


6、卸载驱动
# rmmod led_dev

作业:
实现从驱动程序中读数据
在驱动程序的文件操作集中增加read函数。

/* linux/arch/arm/mach-s5pv210/adc.c

static ssize_t
s3c_adc_read(struct file *file, char __user *buffer,
size_t size, loff_t *pos)
{
unsigned int adc_value = 0;
adc_value = s3c_adc_convert(); //得到ADC转换后的数字量

if (copy_to_user(buffer, &adc_value, sizeof(unsigned int)))
return -EFAULT;

return sizeof(unsigned int);
}

猜你喜欢

转载自www.cnblogs.com/kaosine/p/13379815.html