文章目录
1、字符设备基础知识
由图所示,Linux系统中有为三类设备:字符设备、块设备、网络接口设备。
字符设备: 字符设备是指在I/O传输过程中以字符为单位传输的设备,例如键盘、打印机。
块设备: 块设备是I/O设备中的一类,是将信息存储在固定大小的块中,每个块都有自己的地址,还可以在设备的任意位置读取一定长度的数据,例如硬盘,U盘,SD卡等。
在Linux系统中,“一切皆文件”。所以在Linux系统将字符设备、块设备当作文件来处理,以访问文件的方式来处理。
2、字符设备驱动开发
2.1 字符设备驱动要素
1、必须有一个设备号,用与区分众多设备;
2、用户必须知道设备驱动对应的设备节点(设备文件)
3、实现对设备操作的文件接口,例如open、read、write、close等文件接口。
2.1.1 申请主设备号与注销主设备号
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: 描述一个设备信息,可以自定义
2.1.2 创建设备节点
手动创建
指令:mknod /dev/设备名 类型 主设备号 次设备号
例如:mknod /dev/chr0 c 250 0
缺点:/dev/目录中的文件都是在内存中的,断电后/dev/文件就会消失
自动创建
(自动创建设备节点通过udev/mdev机制)
使用函数如下:
1、创建一个类
struct class* class_create(owner, name);
- 参数一:THIS_MODULE
- 参数二:字符串名字,自定义
- 返回值:成功返回一个class指针
2、创建一个设备文件
struct device* device_create(struct class* class , struct device * parent ,dev_t devt, void * drvdata , const char* fmt ,…);
- 参数一:class结构体,class_create调用后的返回值
- 参数二:表示父类,一般填NULL
- 参数三:设备号类型(次设备号)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) - 参数四:私有数据,一般直接填NULL
- 参数五:表示可变参数,字符串,表示设备节点名字
销毁动作
void device_destroy(devcls, MKDEV(dev_major, 0));
参数1: class结构体,class_create调用之后到返回值
参数2: 设备号类型 dev_t
void class_destroy(devcls);
参数1: class结构体,class_create调用之后到返回值
2.1.3 在驱动中实现文件操作的I/O接口
在驱动中实现文件I/O接口,使得应用程序可以调用文件I/O驱动该设备。
1)驱动中实现文件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,
};
2)应用程序如何去调用文件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);
2.1.4 应用程序和驱动的数据交互
主要依靠两个函数:
1) int copy_to_user(void _user * to ,const void from,unsigned long n)
- 将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()使用
- 参数一:用户空间的一个Buffer
- 参数二:内核空间的一个Buffer
- 参数三:个数
- 返回值:大于0表示出错,剩下多少个没有拷贝成功,等于0,表示正确
2)int copy_from_user(void * to, const void __user * from, unsigned long n)
- 将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用
- 参数一:内核驱动中的一个buffer
- 参数二:应用空间到一个buffer
- 参数三:个数
- 返回值:大于0表示出错,剩下多少个没有拷贝成功,等于0,表示正确
2.1.5 控制外设
控制外设,其实就是控制地址,内核驱动中是通过虚拟地址操作,这里需要用到映射地址函数。
void * ioremap(cookie , size)
- 参数一:物理地址
- 参数二:长度
- 返回值:虚拟地址
去映射——解除映射
void * iounmap(void _iomem * addr)
- 参数:映射后的虚拟地址;
2.1.6 操作寄存器地址的方法
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);
或者替换成:
writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );
2.2 编写字符设备驱动的步骤和规范
步骤
1)实现模块加载和模块卸载入口函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);
2)在模块加载入口函数中实现以下功能:
-
申请设备号 (内核中用于区分和管理不同字符设备)
register_chrdev(dev_major, “chr_dev_test”, &my_fops); -
创建设备节点(为用户提供一个可操作到文件接口–open())
struct class *class_create(THIS_MODULE, “chr_cls”);
struct device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, “chr2”) -
硬件初始化
a.地址的映射
b.中断的申请
c.实现硬件的寄存器的初始化 -
实现file_operations
规范
- 面向对象编程思想
用一个结构体来表示一个对象,设计一个类型,描述一个设备的信息。
struct led_desc{
unsigned int dev_major; //设备号
struct class *cls;
struct device *dev; //创建设备文件
void *reg_virt_base;
};
struct led_desc *led_dev;//表示一个全局的设备对象
实例化全局的设备对象——分配空间
GFP_KENEL 如果当前内存不够用的时候,该函数会一直阻塞
led_dev = Kmalloc(sizeof(struct led_desc),GFP_KERNEL);
if(led_dev == NULL)
{
printk(KERN_ERR"malloc error\n");
return -ENOMEN;
}
- 出错处理
在某一个位置出错了,要将之前申请的资源进行释放
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;
2.3 Led的驱动代码
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/uaccess.h>
#include<linux/slab.h>
#include<asm/io.h>
#define GPX2_CON 0x11000C40
#define SIZE 8
struct led_desc {
unsigned int major;
struct class * cls;
struct device * dev;
void * reg_virt_base;
};
ssize_t my_led_read (struct file *fp, char __user *buf, size_t n, loff_t *loft)
{
printk("-----------%s-----------\n",__FUNCTION__);
return 0;
}
ssize_t my_led_write (struct file *fp, const char __user *buf, size_t n, loff_t *loft)
{
int ret ,value;
ret = copy_from_user(&value,buf,n);
if(ret>0)
{
printk("copy_from_user err\n");
return -EFAULT;
}
if(value==1)
{
writel(readl(led_dev->reg_virt_base+4) | (0x1<<7), led_dev->reg_virt_base+4);
}
else
{
writel(readl(led_dev->reg_virt_base+4) & ~(0x1<<7), led_dev->reg_virt_base+4);
}
return 0;
}
int my_led_close (struct inode *inode, struct file *fp)
{
printk("-----------%s-----------\n",__FUNCTION__);
return 0;
}
int my_led_open (struct inode *inode, struct file *fp)
{
printk("-----------%s-----------\n",__FUNCTION__);
return 0;
}
const struct file_operations my_fops={
.open = my_led_open,
.read = my_led_read,
.write = my_led_write,
.release = my_led_close,
};
struct led_desc *my_led;
static int __init test_chr_init(void)
{
int ret;
//给对象分配空间
my_led = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
if(my_led==NULL)
{
printk(KERN_ERR "malloc err\n");
return -ENOMEM;
}
//申请设备号
my_led->major = register_chrdev(0, "led_f", &my_fops);
if(my_led->major<0)
{
printk(KERN_ERR "register_chrdev err\n");
ret = -ENODEV;
goto err_0;
}
//创建设备节点
my_led->cls = class_create(THIS_MODULE, "chr_led");
if(IS_ERR(my_led->cls))
{
printk(KERN_ERR"class_create err\n");
ret = PTR_ERR(my_led->cls);
goto err_1;
}
my_led->dev = device_create(my_led->cls, NULL, MKDEV(my_led->major, 0), NULL, "led_yf");
if(IS_ERR(my_led->dev))
{
printk(KERN_ERR"device_create err\n");
ret =PTR_ERR(my_led->dev);
geto err_2;
}
//映射地址
my_led->reg_virt_base = ioremap(GPX2_CON, SIZE);
if(my_led->reg_virt_base == NULL)
{
printk(KERN_ERR "ioremap err\n");
ret = -ENOMEN;
goto err_3;
}
//初始化地址
writel((readl(my_led->reg_virt_base) & (~(0xf<<28))) | (0x1<<28),my_led->reg_virt_base);
return 0;
err_3:
device_destroy(my_led->cls,MKDEV(my_led->major, 0));
err_2:
class_destroy(my_led->cls);
err_1:
unregister_chrdev(my_led->major, "led_f");
err_0:
kfree(my_led);
return ret;
}
static void __exit test_chr_exit(void)
{
iounmap(my_led->reg_virt_base);
device_destroy(my_led->cls,MKDEV(my_led->major, 0));
class_destroy(my_led->cls);
unregister_chrdev(my_led->major, "led_f");
kfree(my_led);
}
module_init(test_chr_init);
module_exit(test_chr_exit);
MODULE_LICENSE("GPL");