Linux驱动开发之字符设备驱动
1,字符设备驱动框架
作为字符设备驱动要素:
1,必须有一个设备号,用在众多的设备驱动中进行区分
2,用户必须知道设备驱动对应的设备节点(设备文件)
linux把所有的设备都看成文件
crw-r----- 1 root root 13, 64 Mar 28 20:14 event0
crw-r----- 1 root root 13, 65 Mar 28 20:14 event1
crw-r----- 1 root root 13, 66 Mar 28 20:14 event2
3,对设备操作其实就是对文件操作,应用空间操作open,read,write的时候
实际在驱动代码有对应到open, read,write
2,作为驱动必须有一个主设备号--向系统申请
int register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)//申请设备号
参数1:主设备号
设备号(32bit--dev_t)==主设备号(12bit) + 次设备号(20bit)
主设备号:表示一类设备--camera
次设备号: 表示一类设备中某一个:前置,后置
给定的方式有两种:
1,动态--参数1直接填0
2,静态--指定一个整数,250
参数2: 描述一个设备信息,可以自定义
/proc/devices列举出所有到已经注册的设备
参数3: 文件操作对象--提供open, read,write
返回值: 正确返回0,错误返回负数
void unregister_chrdev(unsigned int major, const char * name)//删除设备号
参数1:主设备号
参数2: 描述一个设备信息,可以自定义
3,创建设备节点:
1,手动创建--缺点/dev/目录中文件都是在内存中,断电后/dev/文件就会消失
mknod /dev/设备名 类型 主设备号 次设备号
比如:
mknod /dev/chr0 c 250 0
[root@farsight drv_module]# ls /dev/chr0 -l
crw-r--r-- 1 0 0 250, 0 Jan 1 00:33 /dev/chr0
2,自动创建(通过udev/mdev机制)
struct class *class_create(owner, name)//创建一个类
参数1: THIS_MODULE
参数2: 字符串名字,自定义
返回一个class指针
//创建一个设备文件
struct device *device_create(struct class * class, struct device * parent, dev_t devt,
void * drvdata, const char * fmt,...)
参数1: class结构体,class_create调用之后的返回值
参数2:表示父亲,一般直接填NULL
参数3: 设备号类型 dev_t
dev_t devt
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
参数4:私有数据,一般直接填NULL
参数5和6:表示可变参数,字符串,表示设备节点名字(应用程序中调用字符设备时使用该节点的名字,如:
fd = open("/dev/chr2", O_RDWR);)
static unsigned int dev_major = 250;//主设备号
devcls = class_create(THIS_MODULE, "chr_cls");
dev = device_create(devcls, NULL, MKDEV(dev_major,0), NULL, "chr2");
销毁动作:
void device_destroy(devcls, MKDEV(dev_major, 0));
参数1: class结构体,class_create调用之后到返回值
参数2: 设备号类型 dev_t
void class_destroy(devcls);
参数1: class结构体,class_create调用之后到返回值
4,在驱动中实现文件io的接口,应用程序可以调用文件io
a,驱动中实现文件io操作接口:struct file_operations
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 (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
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 *, loff_t, loff_t, 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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
}; //函数指针的集合,其实就是接口,我们写驱动到时候需要去实现
const struct file_operations my_fops = {
.open = chr_drv_open,
.read = chr_drv_read,
.write = chr_drv_write,
.release = chr_drv_close,
};
b,应用程序如何去调用文件io去控制驱动--open,read,...
fd = open("/dev/chr2", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
read(fd, &value, 4);
write(fd, &value, 4);
close(fd);
5, 应用程序需要传递数据给驱动
int copy_to_user(void __user * to, const void * from, unsigned long n)
//将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()用
参数1:应用驱动中的一个buffer
参数2:内核空间的一个buffer
参数3:个数
返回值:大于0,表示出错,剩下多少个没有拷贝成功
等于0,表示正确
int copy_from_user(void * to, const void __user * from, unsigned long n)
//将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用
参数1:内核驱动中的一个buffer
参数2:应用空间到一个buffer
参数3:个数
6, 控制外设,其实就是控制地址,内核驱动中是通过虚拟地址操作
void *ioremap(cookie, size)
参数1: 物理地址
参数2: 长度
返回值: 虚拟地址
去映射--解除映射
void iounmap(void __iomem *addr)
参数1: 映射之后到虚拟地址
7, 通过驱动控制led灯:
led--- GPX2_7 --- GPX2CON ==0x11000C40
GPX2DAT ==0x11000C44
将0x11000C40映射成虚拟地址
对虚拟地址中的[32:28] = 0x1
8, 应用程序和驱动扮演的是什么角色
用户态:应用程序
玩策略: 怎么去做
1, 一闪一闪
2,10s闪一次,也可以1s闪一次
3,一直亮
4,跑马灯
控制权是在应用程序(程序员)
--------------------------------------
内核态:驱动
玩机制: 能做什么
led:亮 和 灭
9,编写字符设备驱动到步骤和规范
步骤:
1,实现模块加载和卸载入口函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);
2,在模块加载入口函数中
a, 申请主设备号 (内核中用于区分和管理不同字符设备)
register_chrdev(dev_major, "chr_dev_test", &my_fops);
b,创建设备节点文件 (为用户提供一个可操作的文件接口--open())
struct class *class_create(THIS_MODULE, "chr_cls");
struct device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");
c, 硬件的初始化
1,地址的映射
#define GPX1_CON 0x11000c20
#define GPX1_SIZE 8 //8个字节,表示0x11000c20后面连续的8个字节
gpx2conf = ioremap(GPX2_CON, GPX2_SIZE);
2,中断的申请
3,实现硬件的寄存器的初始化
// 需要配置gpio功能为输出
*gpx2conf &= ~(0xf<<28);
*gpx2conf |= (0x1<<28);
e,实现file_operations
const struct file_operations my_fops = {
.open = chr_drv_open,
.read = chr_drv_read,
.write = chr_drv_write,
.release = chr_drv_close,
};
规范:
1,面向对象编程思想
用一个结构体来表示一个对象
//设计一个类型,描述一个设备的信息
struct led_desc{
unsigned int dev_major; //设备号
struct class *cls;
struct device *dev; //创建设备文件
void *reg_virt_base;
};
struct led_desc *led_dev;//表示一个全局的设备对象
// 0, 实例化全局的设备对象--分配空间
// GFP_KERNEL 如果当前内存不够用到时候,该函数会一直阻塞(休眠)
// #include <linux/slab.h>
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
if(led_dev == NULL)
{
printk(KERN_ERR "malloc error\n");
return -ENOMEM;
}
led_dev->dev_major = 250;
2,做出错处理
在某个位置出错了,要将之前申请的资源进行释放
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
if(led_dev->dev_major < 0)
{
printk(KERN_ERR "register_chrdev error\n");
ret = -ENODEV;
goto err_0;
}
err_0:
kfree(led_dev);
return ret;
10, 操作寄存器地址的方式:
1, volatile unsigned long *gpxcon;
*gpxcon &= ~(0xf<<28);
2, readl/writel();
u32 readl(const volatile void __iomem *addr)//从地址中读取地址空间的值
void writel(unsigned long value , const volatile void __iomem *add)
// 将value的值写入到addr地址
例子:
// gpio的输出功能的配置
u32 value = readl(led_dev->reg_virt_base);
value &= ~(0xf<<28);
value |= (0x1<<28)
writel(value, led_dev->reg_virt_bas);
或者:
*gpx2dat |= (1<<7);
替换成:
writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );