前言
字符设备是Linux驱动中三大设备之一,字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少要实现open、close、read和write的系统调用。字符终端(/dev/console)和串口(/dev/ttyS0以及类似备)就是两个字符设备,它们能很好的说明“流”这种抽象概念。字符设备可以通过文件节点来访问,比如/dev/tty1和/dev/lp0等。这些设备文件和普通文件之间的唯一差别在于对普通文件的访问可以前后移动访问位置,而大多数字符设备是一个只能顺序访问的数据通道。然而,也存在具有数据区特性的字符设备,访问它们时可前后移动访问位置。
一、字符设备的编写步骤
1.实现入口函数xxx_init()和卸载函数xxx_exit()
2.申请设备号register_chrdev(与内核有关)
3.注册字符设备驱动cdev_alloc cdev_init cdev_add(与内核有关)
4.利用udev/mdev机制创建设备文件(节点),
class_create,device_create(与内核有关)
5.硬件部分初始化 io资源映射ioremap,内核提供gpio库函数(与硬件相关)
注册中断(与硬件相关)
初始化等待队列(与内核有关)
初始化定时器(与内核有关)
6.构建file_operation结构(与内核相关)
实现操作硬件方法xxx_open,xxx_read,xxx_write...(与硬件有关)
二、字符设备应用
1.视频教学中的使用
1.实现模块加载和卸载入口函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);
2.在模块加载入口函数中
A.申请主设备号(内核中用于区分和管理不通风字符设备)
register_chrdev(unsigned int major, const char * name, const struct file_operations * fops
B.创建设备节点文件(为用户提供哟个课操作到文件接口---open());
struct class class_create(owner, name)
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
C.硬件的初始化
1.地址的映射
gpx2conf = ioremp(GPX2_CON,GPX2_SIZE);
2.中断的申请
3.实现硬件的寄存器的初始化
//需要1配置gpio功能为输出
*gpx2conf &= ~(0xf<<28);
*gpx2conf |=(0x1<<28);
D.实现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.
//0,实例化全局的设备对象--分配空间
//GFP_KERNEL如果当前空间内存不够用的时候,该函数会一直阻塞(休眠)
//#include <linux/slab.h>
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNE);
If(led_dev == NULL)
{
printk(KERN_ERR “malloc error\n”);
return -ENOMEM;
}
led_dev->dev_major = 250;
2.做出错处理
在某个位置出错了,要将之前申请到的资源进行释放
led_dev->dev_major = register_che_dev(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.个人观点
按照视频的来最终在使用时,可能会遇到一些bug,比如:
在使用kzalloc申请内存的时候总是申请失败,在参考网上的解决办法后依旧未能解决,实例化对象需要用到kzalloc这一种方法在我学习的时候,并没有得到验证,始终卡在内存申请这儿,让后我使用传统方法,才达到自己想要的结果。
总结
字符设备驱动编写方法有多种,其中区别主要在于硬件资源的获取:传统字符设备驱动编写;平台总线;设备树。前面两种本质上区别其实并不大,但引入了分层的思想。后续将慢慢和大家分享。
附录
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 映射后的寄存器虚拟地址指针 */
static volatile unsigned int *IMX6U_CCM_CCGR1;
static volatile unsigned int *SW_MUX_GPIO1_IO03;
static volatile unsigned int *SW_PAD_GPIO1_IO03;
static volatile unsigned int *GPIO1_DR;
static volatile unsigned int *GPIO1_GDIR;
//静态指定
struct led_desc {
unsigned int dev_major;//设备号
struct class *cls;
struct device *dev;//创建设备文件
/* 映射后的寄存器虚拟地址指针 */
};
struct led_desc *led_dev;
static int kernel_val = 555;
//read(fd,buf,size);
ssize_t chr_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
int