一、Linux2.6 字符设备驱动编写框架
Linux2.6 他是咱们字符设备驱动编写的第二种方法,这种方法要比杂项的复杂,过程和函数是比较多的,他最大的优点就在于他的资源很多 — 设备号,而杂项设备号是非常有限的,也就是 0 — 255 因为主设备号是固定是 10 。但是Linux2.6 他使用一个 32 位的数字表示设备号,这个设备号里包含了主设备号和次设备号,32 位他做一个划分,前 12 位是主设备号,后 20 位是次设备
号,因此他的设备号的取值范围是比较大的。主设备号的范围:2^12 次设备号的范围:2^20, 他使用主设备号和次设备号合成了一个完整的设备号。
1、合成一个完整的设备号函数
函数头文件:#include <linux/cdev.h>
函数参数:
ma: — 主设备号
mi: ---- 次设备号
函数返回值:合成之后的完整的设备号
函数功能:合成一个完整的设备号
函数原型:MKDEV(ma,mi)
2、从完整的设备号里提取主设备号
函数头文件:#include <linux/cdev.h>
函数参数:dev :设备号
函数返回值:提取的主设备,提取的次设备
函数功能:从完整的设备号里提取主设备号
函数原型:MAJOR(dev)
MINOR(dev)
3、动态申请设备号
**函数头文件:#include <linux/fs.h>
函数参数:dev:保存申请成功的设备号
baseminor:次设备号起始值
count:连续申请次设备号的数量
name:设备的名字 — 无所谓
函数返回值:成功返回 0 失败负数
函数功能:动态申请设备号
函数原型:int alloc_chrdev_region(
dev_t dev,
unsigned baseminor,
unsigned count,
const char name)
4、静态申请设备号
*函数功能:静态申请设备号
函数原型:int register_chrdev_region(
dev_t from,
unsigned count,
const char name)
函数头文件:同上
函数参数:from:是你自己提前使用 MKDEV 函数合成一个完整的设备号
count:申请的次设备号的数量
name:设备的名字 — 无所谓
函数返回值:成功返回 0 失败负数
5、释放申请的设备号
函数功能:释放申请的设备号
函数原型:void unregister_chrdev_region(
dev_t from,
unsigned count)
函数头文件:同上
函数参数:from:设备号
count:申请的次设备号的数量
函数返回值:无
6、Linux2.6 字符设备驱动的核心结构体
**这个结构体一般只需要咱们定义一个结构体变量即可。
struct cdev {
struct kobject kobj;
struct module owner;//代表这个模块 THIS_MODULE
const struct file_operations ops;//操作设备集合的方法
struct list_head list;
dev_t dev;//设备号
unsigned int count;//次设备号的数量
} __randomize_layout;
7、初始化核心结构体
**函数功能:初始化核心结构体
函数原型:void cdev_init(
struct cdev cdev,
const struct file_operations fops)
函数头文件:#include<linux/cdev.h>
函数参数:cdev:定义的核心结构体
fops:定义操作设备方法集合的结构体变量
函数返回值:无
8、向内核去申请 linux2.6 字符设备
*函数功能:向内核去申请 linux2.6 字符设备
函数原型:int cdev_add(
struct cdev p,
dev_t dev,
unsigned count)
函数头文件:#include<linux/cdev.h>
函数参数:p:定义的核心结构体
dev:设备号
count:次设备号的数量
函数返回值:成功返回 0 失败负数
9、释放申请的设备
*函数功能:释放申请的设备
函数原型:void cdev_del(struct cdev p)
函数头文件:#include<linux/cdev.h>
函数参数:p:定义的核心结构体
函数返回值:无
10、创建一个类,去管理你注册的设备 /sys/class/name
**函数功能:创建一个类,去管理你注册的设备
函数原型:struct class * class_create(
struct module owner,
const char name)
函数头文件:#include<linux/device.h>
函数参数:owner:他是一个固定的值 THIS_MODULE
name:创建类的名字
函数返回值:成功返回指向 struct class 失败 NULL
11、自动创建设备节点
**函数功能:自动创建设备节点
函数原型:struct device *device_create(
struct class *class,
struct device *parent,
dev_t devt,
void drvdata,
const char fmt,…)
函数头文件:#include<linux/device.h>
函数参数:class:创建的类
parent:父设备 — 写 NULL
devt:设备号
drvdata:内核的私有数据 — 写 NULL
fmt:他一般就是你创建的设备节点的名字
函数返回值:成功返回一个指向 struct device 失败 NULL
12、销毁类
*函数功能:销毁类
函数原型:void class_destroy(struct class cls)
函数头文件:#include<linux/device.h>
函数参数:cls:就是定义的类的变量名
函数返回值:无
13、销毁设备节点,节点位于 /dev/name
*函数功能:销毁设备节点 /dev/name
函数原型:void device_destroy(struct class class,dev_t devt)
函数头文件:#include<linux/device.h>
函数参数:class:定义的类名
devt:设备号
二、GPIO子系统的使用
所谓的 gpio 子系统值的就是使用内核封装好的函数对设备进行操作,这里一般操
作设备都是通过 gpio 口,因此咱们需要掌握操作 gpio 口函数。在使用 GPIO 口区操作硬件的时候,你需要先申请注册才能使用当前的 gpio 口的资源。我以下检测是灯,高电平亮,低电平不亮。
下面演示 GPIO1_D0 pin 脚计算方法:
bank = 1; //GPIO1_D0 => 1, bank ∈ [0,4]
group = 3; //GPIO1_D0 => 3, group ∈ {(A=0), (B=1), (C=2), (D=3)}
X = 0; //GPIO1_D0 => 0, X ∈ [0,7]
number = group * 8 + X = 3 * 8 + 0 = 24
pin = bank*32 + number= 1 * 32 + 24 = 56;
最后要得到的是pin
1、申请你要使用 gpio 口的资源
*函数功能:申请你要使用 gpio 口的资源
函数原型: int gpio_request(unsigned gpio, const char label)
函数头文件:#include <linux/gpio.h>
函数参数:gpio:这里就是你要申请注册的 gpio 口的编号
label:标签 — 一般没有太大作用,就是标识
函数返回值:成功返回 0 失败负数
2、释放gpio口资源
函数功能: 释放 gpio 口资源
函数原型: void gpio_free(unsigned gpio)
函数头文件: #include <linux/gpio.h>
函数参数:gpio:gpio:这里就是你要申请注册的 gpio 口的编号
函数返回值:无
3、配置 gpio 口的工作模式为输出
函数功能:配置 gpio 口的工作模式为输出
函数原型: int gpio_direction_output(unsigned gpio, int value)
函数头文件:#include <linux/gpio.h>
函数参数:gpio:这里就是你要申请注册的 gpio 口的编号
value:默认给的值 — 一般就是 0 或者是 1 代表高低电平
函数返回值:成功返回 0 失败负数
4、配置 gpio 口的工作模式为输入
函数功能: 配置 gpio 口的工作模式为输入
函数原型: int gpio_direction_input(unsigned gpio)
函数头文件:#include <linux/gpio.h>
函数参数:gpio:这里就是你要申请注册的 gpio 口的编号
函数返回值:成功返回 0 失败负数
这里配置的 gpio 口模式为输入和输出那么这里的输入和输出是针对于 CPU 来说的。
5、配置 gpio 口的工作模式为输入
函数功能:获取这个 gpio 口引脚上的电平的状态
函数原型: int gpio_get_value(unsigned gpio)
函数头文件:#include <linux/gpio.h>
函数参数:gpio:这里就是你要申请注册的 gpio 口的编号
函数返回值:返回获取的电平的状态 — 高电平或者是低电平 1/0
6、 设置 gpio 的电平状态
函数功能: 设置 gpio 的电平状态
函数原型: void gpio_set_value(unsigned gpio, int value)
函数头文件:#include <linux/gpio.h>
函数参数:gpio:这里就是你要申请注册的 gpio 口的编号
value:你要设置的电平的状态 — 高电平 1 低电平 0
函数返回值:无
三、代码实现
linux26.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
dev_t dev;
struct cdev mydev;
struct class *myclass=NULL;
int gpio_value = 0;
int myled_open (struct inode *inode, struct file *fp)
{
gpio_set_value(21,1);
printk("myled open ok\n");
printk("myled open 正确打开\n");
return 0;
}
int myled_close (struct inode *inode, struct file *fp)
{
gpio_set_value(21,0);
printk("myled close ok\n");
printk("myled close 关闭正确\n");
return 0;
}
struct file_operations myfops={
.open = myled_open,
.release = myled_close,
};
static int __init myled_init(void)
{
int all;
gpio_value=gpio_request(21, "led5");
printk("gpio_value:%d\n",gpio_value);
if(gpio_value==0)
{
printk("申请成功\n");
}
gpio_direction_output(21, 1);
all=alloc_chrdev_region(&dev,0, 1,"led");
if(all<0)
{
printk("alloc_chrdev_region error\n");
printk("动态创建失败\n");
return -1;
}
printk("主设备号:%d\n",MAJOR(dev));
printk("次设备号:%d\n",MINOR(dev));
cdev_init(&mydev,&myfops);
cdev_add(&mydev,dev,1);
myclass=class_create(THIS_MODULE,"class_led");
if(myclass == NULL)
{
printk("class_create error\n");
printk("class_create 类创建失败\n");
return -1;
}
device_create(myclass,NULL,dev,NULL,"myled");
return 0;
}
static void __exit myled_exit(void)
{
device_destroy(myclass,dev);
class_destroy(myclass);
cdev_del(&mydev);
unregister_chrdev_region(dev,1);
gpio_free(21);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
app.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd = 0;
if(argc < 2)
{
printf("请输入正确的参数\n");
return -1;
}
while(1)
{
fd = open(argv[1],O_RDWR); // --- 底层的open函数
sleep(5);
close(fd);//底层的close
sleep(5);
}
return 0;
}
Makefile
obj-m += linux26.o #最终生成模块的名字就是 led.ko
KDIR:=/home/stephen/RK3588S/kernel #他就是你现在rk3588s里内核的路径
CROSS_COMPILE_FLAG=/home/stephen/RK3588S/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
#这是你的交叉编译器路径 --- 这里你也要替换成你自己的交叉编译工具的路径
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
aarch64-none-linux-gnu-gcc app.c -o app
#调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
# 架构 ARCH=arm64
clean:
rm -f *.o *.mod.o *.mod.c *.symvers *.markers *.order app *.mod
编译之后将app和linux.ko推送金开发板中。
开发板中的现象:
灯现象: