目录
一、Linux2.6 字符设备开发
1.1 介绍
驱动开发方式之一
1.2 特点
1、真正意义上的 32 位设备号
2、设备号需要开发者申请/释放
3、无法自动生成设备文件
4、相对比较复杂
1.3 相关 API
关键字:cdev
头文件:#include <linux/cdev.h>
#include <linux/fs.h>
Linux2.6 的开发过程
添加头文件
申请设备号
函数原型
int alloc_chrdev_region(dev_t *dev, unsigned int baseminor, unsigned int count, const char *name)
函数参数
dev:设备号存放的地址,是连续开辟的
baseminor:申请的次设备号起始值
次设备号是我们指定的,主设备号是系统分配的
count:连续申请次设备号的个数
例如:第二个参数填 54,第三个参数填 2
那么会得到两个设备号,其中第一个次设备为 54,第二个次设备号为 55
name:无所谓,尽可能有意义 ,申请设备号操作,这个 name 不要重复
函数返回值
成功返回 0,失败返回负数
释放设备号
函数原型
void unregister_chrdev_region(dev_t dev, unsigned int count)
函数参数
dev:要释放设备号的起始值
count:释放的个数
Linux2.6 核心结构体初始化
函数原型
void cdev_init(struct cdev *cdev, const struct file_operations *ops)
函数参数
cdev:Linux2.6 核心结构体
struct cdev
{
const struct file_operations *ops; //文件接口核心结构体
dev_t dev; //起始设备号
unsigned int count; //设备号连续的数量
struct module *owner; //固定填写
}
加粗的部分都是通过函数去填写的,我们只需要给 owner 赋值
Linux2.6 核心结构体添加
函数原型
int cdev_add(struct cdev *cdev, dev_t dev, unsigned int count)
函数参数
cdev:Linux2.6 核心结构体
dev:起始设备号
count:设备号的个数
函数返回值
成功返回 0,失败返回负数
Linux2.6 核心结构体删除
函数原型
void cdev_del(struct cdev *cdev)
函数参数
cdev:Linux2.6 核心结构体
1.4 通过指令生成设备文件
1.5 自动生成设备文件
设备创建
头文件:#include <linux/device.h>
函数原型
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void
*drvdata, const char *fmt, ...)
函数参数
cls:是一个类 --- 这个类只需要调用函数生成即可
parent:设备的上一级,但是我们的设备是你我创建,没有填 NULL
devt:设备使用的设备号
drvdata:传递给内核的参数,不使用填 NULL
fmt:可以填格式控制符,也可以直接填一个字符串作为文件的名字
函数返回值
无所谓,不用
类的创建
函数原型
class_create(owner, name)
函数参数
struct module *owner:固定填写
const char *name:无所谓,尽量有意义,不要重复
函数返回值
类的核心结构体,struct class
设备的销毁
函数原型
void device_destroy(struct class *cls, dev_t devt)
函数参数
cls:类
devt:设备号
类的销毁
函数原型
void class_destroy(struct class *cls)
函数参数
cls:类
二、文件接口
对于系统层来说:read 函数可以获取一个数据,数据由内核产生,所以对于内核来说本质上是输出了一个数据
对于系统层来说:wirt 函数可以写入一个数据,数据由内核接收,所以对于内核来说本质上是获取了一个数据
2.1 read 文件接口
函数原型
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
函数参数
struct file:用于多节点判断
char __user *:应用层 read 函数存放数据空间的首地址
size_t:获取内容的长度
loff_t *:用在块设备,指代偏移量
read 的实现函数
头文件:#include <linux/uaccess.h>
函数原型
static inline int copy_to_user(void __user volatile *to, const void *from, unsigned long n)
函数参数
to:数据到哪里去 --- 直接填写(*read)的第二个参数
from:数据从哪里来 --- 内核定义一个变量,然后取地址即可
n:数据的长度 --- 直接填写(*read)的第三个参数
函数返回值
必须承接,否则会报警告,而警告会被当成错误
2.2 write文件接口
函数原型
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
函数参数
struct file:用于多节点判断
const char __user *:应用层 write 函数存放数据空间的首地址
size_t:获取内容的长度
loff_t *:用在块设备,指代偏移量
write 往内核中写数据,内核需要做接收
write 的实现函数
函数原型
static inline int copy_from_user(void *to, const void __user volatile *from, unsigned long n)
函数参数
to:数据到哪里去 --- 数据要到内核
内核就需要定义一个变量去承接 wirte 写入的数据
from:数据从哪里来 --- (*write)第二个参数
n:传入数据的长度
函数返回值
必须承接,否则会报错
2.3 ioctl
应用接口
头文件
#include <sys/ioctl.h>
函数原型
int ioctl(int fd, unsigned long request, ...)
函数参数
fd:文件描述符
request:就是传递给内核的命令
…:表示可变参数
可变参数具体是什么由第二个参数命令决定
函数返回值
成功返回 0,失败返回负数
内核接口
函数原型
long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long n);
函数参数
struct file:用于多节点判断
cmd:命令 --- 本质上是一个数字,但是数据传递无法直接传递数字
所以这个命令其实就是一个宏定义
命令中,2 值已经被系统保留(我们没法用)
n:数据长度
ioctl 的应用主要在:标准文件接口
例如:
在 V4L2 摄像头开发中用于获取摄像头参数,以及设置摄像头参数,在屏幕开发中用于获取屏幕的参数
三、GPIO 子系统
设置 GPIO 为输入方向
函数原型
int gpio_direction_input(unsigned gpio)
函数参数
gpio:要设置的 GPIO 号
函数返回值
成功返回 0,失败返回负数
获取 GPIO 当前的电平状态
函数原型
int gpio_get_value(unsigned gpio)
函数参数
gpio:要获取电平状态的 GPIO 号
函数返回值
高电平,就返回 1,低电平返回 0