22 Linux MISC 驱动
一、MISC 设备驱动简介
misc 意思是杂项,所以当板子上的某些外设无法进行分类的时候可以使用 misc 驱动,一般 misc 驱动嵌套在 platform 总线驱动。
所有的 MISC 设备驱动的主设备号都是 10,不同的设备使用不同的设备号。MISC 设备会自动创建 cdev
,可以不用像之前手动创建。
首先得向 Linux 注册一个 miscdevice
设备。miscdevice
是一个结构体:
66 struct miscdevice {
67 int minor; /* 子设备号 */
68 const char *name; /* 设备名字 */
69 const struct file_operations *fops; /* 设备操作集 */
70 struct list_head list;
71 struct device *parent;
72 struct device *this_device;
73 const struct attribute_group **groups;
74 const char *nodename;
75 umode_t mode;
76 };
定义了 MISC 设备后需要自己去设置 minor
、name
和 fops
三个成员变量。minor
表示子设备号;name
就是 MISC 设备名字,当设备注册成功后会在 /dev 目录下生成一个名为 name 设备文件;fops
就是字符操作集合。注意,minor
中的有些被 Linux 所使用了,在include/linux/miscdevice.h 文件中,使用的时候需要从预定义的子设备号挑一个,也可以自己定义。
设置完成后,使用 misc_register
函数注册 MISC 设备:
/*
* @description : 注册 MISC
* @param - misc : 要注册的 MISC 设备
* @return : 负数,失败; 0,成功
*/
int misc_register(struct miscdevice * misc);
之前所有的实验中,需要自己去调用一堆函数去创建设备,现在使用 misc_register
后就可以把以下这些给省略了:
alloc_chrdev_region(); /* 申请设备号 */
cdev_init(); /* 初始化 cdev */
cdev_add(); /* 添加 cdev */
class_create(); /* 创建类 */
device_create(); /* 创建设备 */
注册使用完当然要卸载,使用 misc_deregister
完成卸载:
/*
* @description : 注销 MISC
* @param - misc : 要注册的 MISC 设备
* @return : 负数,失败; 0,成功
*/
int misc_deregister(struct miscdevice *misc)
以往需要注销的设备驱动需要调用一堆函数去删除,如下。但现在只需要使用 misc_deregister
完成注销。
cdev_del(); /* 删除 cdev */
unregister_chrdev_region(); /* 注销设备号 */
device_destroy(); /* 删除设备 */
class_destroy(); /* 删除类 */
二、驱动实验编写
这次是 platform 和 msci 结合的方式。
2.1 修改设备树
首先打开 /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts/stm32mp15-pinctrl.dtsi 文件,创建 pinctrl 节点:
beep_pins_a:beep-pins {
pins {
pinmux = <STM32_PINMUX('C', 7, GPIO)>; // Beep
drive-push-pull;
bias-pull-up;
output-high;
slew-rate = <0>;
};
};
之后打开 /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts/stm32mp157d-atk.dts 文件,在"/"根节点创建子节点:
beep {
compatible = "alientek,beep";
status = "okay";
pinctrl-0 = <&beep_pins_a>;
pinctrl-names = "defalut";
beep-gpio = <&gpioc 7 GPIO_ACTIVE_LOW>;
};
完成后需要重新编译设备树,并把编译完成的文件放在 tftpboot 文件夹下。
2.2 beep 驱动编写
在 linux/atk-mpl/Drivers 创建 19_miscbeep,在里面创建 Vscode 工作区,并且新建 miscbeep.c 文件,输入以下内容:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define MISCBEEP_NAME "miscbeep" /* 名字 */
#define MISCBEEP_MINOR 144 /* 子设备号 */
#define BEEPOFF 0 /* 关蜂鸣器 */
#define BEEPON 1 /* 开蜂鸣器 */
struct miscbeep_dev{
int beep_gpio; /* beep所使用的GPIO编号 */
};
struct miscbeep_dev miscbeep; /* beep设备 */
/*
* @description : beep相关初始化操作
* @param – pdev : struct platform_device指针,也就是platform设备指针
* @return : 成功返回0,失败返回负数
*/
static int beep_gpio_init(struct device_node *nd)
{
int err;
/* 从设备树中获取GPIO */
miscbeep.beep_gpio = of_get_named_gpio(nd, "beep-gpio", 0);
if(!gpio_is_valid(miscbeep.beep_gpio)) {
printk("miscbeep:Failed to get beep-gpio\n");
return -EINVAL;
}
/* 申请使用GPIO */
err = gpio_request(miscbeep.beep_gpio, "beep");
if(err) {
printk("beep: Failed to request beep-gpio\n");
return err;
}
/* 将GPIO设置为输出模式并设置GPIO初始化电平状态 */
gpio_direction_output(miscbeep.beep_gpio, 1);
return 0;
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int miscbeep_open(struct inode *inode, struct file *filp)
{
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t miscbeep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int error;
unsigned char databuf[1]; // 获取的状态值
unsigned char beepstat; // beep的状态
error = copy_from_user(databuf, buf, cnt);
if(error < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
beepstat = databuf[0];
if (beepstat = BEEPON)
{
gpio_set_value(miscbeep.beep_gpio, 0); // 打开蜂鳴器
}
else if (beepstat = BEEPOFF)
{
gpio_set_value(miscbeep.beep_gpio, 1); // 关闭蜂鳴器
}
return 0;
}
/* 设备操作函数 */
static struct file_operations miscbeep_fops = {
.owner = THIS_MODULE,
.open = miscbeep_open,
.write = miscbeep_write,
};
/* MISC设备结构体 */
static struct miscdevice beep_miscdev = {
.name = MISCBEEP_NAME,
.minor = MISCBEEP_MINOR,
.fops = &miscbeep_fops,
};
/*
* @description : flatform驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - dev : platform设备
* @return : 0,成功;其他负值,失败
*/
static int miscbeep_probe(struct platform_device *pdev)
{
int error;
printk("beep driver and device was matched!\r\n");
/* 初始化BEEP */
error = beep_gpio_init(pdev->dev.of_node);
if (error < 0)
{
return error;
}
/* 一般情况下会注册对应的字符设备,但是这里我们使用MISC设备
* 所以我们不需要自己注册字符设备驱动,只需要注册misc设备驱动即可
*/
error = misc_register(&beep_miscdev);
if(error < 0){
printk("misc device register failed!\r\n");
goto free_gpio;
}
return 0;
free_gpio:
gpio_free(miscbeep.beep_gpio);
return -EINVAL;
}
/*
* @description : platform驱动的remove函数,移除platform驱动的时候此函数会执行
* @param - dev : platform设备
* @return : 0,成功;其他负值,失败
*/
static int miscbeep_remove(struct platform_device *dev)
{
/* 注销设备的时候关闭LED灯 */
gpio_set_value(miscbeep.beep_gpio, 1);
/* 释放BEEP */
gpio_free(miscbeep.beep_gpio);
/* 注销misc设备 */
misc_deregister(&beep_miscdev);
return 0;
}
/* 匹配列表 */
static const struct of_device_id beep_of_match[] = {
{ .compatible = "alientek, bepp" },
{ /* Sentinel */ }
};
/* platform驱动结构体 */
static struct platform_driver beep_driver = {
.driver = {
.name = "stm32mp1-beep", // 设备匹配名字,用于于设备相互匹配
.of_match_table = beep_of_match, // 设备树匹配表
},
.probe = miscbeep_probe,
.remove = miscbeep_remove,
};
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static int __init miscbeep_init(void)
{
return platform_driver_register(&beep_driver);
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit miscbeep_exit(void)
{
platform_driver_unregister(&beep_driver);
}
module_init(miscbeep_init);
module_exit(miscbeep_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
2.3 beep 测试 APP 编写
新建 miscbeepApp.c 文件,输入以下内容:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#define BEEPOFF 0
#define BEEPON 1
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR); /* 打开beep驱动 */
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("BEEP Control Failed!\r\n");
close(fd);
return -1;
}
retvalue = close(fd); /* 关闭文件 */
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
三、运行测试
首先编写 Makefile 文件:
KERNELDIR := /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)
obj-m := miscbeep.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
之后编译 miscbeep.c 和 miscbeepApp.c 文件:
make
arm-none-linux-gnueabihf-gcc miscbeepApp.c -o miscbeepApp
将编译好的 miscbeepApp 和 miscbeep.ko 复制:
sudo cp miscbeepApp miscbeep.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f
开启开发板,进入 lib/modules/5.4.31,输入以下命令:
cd lib/modules/5.4.31/
加载驱动:
depmod
modprobe miscbeep.ko
可以输入以下命令来看设备号和从设备号:
ls /dev/miscbeep -l
从设备号和我们自己设置的 144 号一样,其实只要不去占用 Linux 已经用过的从设备号,其他从设备号都可以使用。
测试 beep 驱动:
./miscbeepApp /dev/miscbeep 1 # 打开 BEEP
./miscbeepApp /dev/miscbeep 0 # 关闭 BEEP
卸载驱动:
rmmod miscbeep.ko
总结
其实 MISC设备驱动就是变成了一种函数集合,这个函数集合包含注册字符设备驱动需要的工作。
使用 MISC 设备驱动也简单,首先先设置 MISC 设备结构体包括name、minor(次设备号)和 fops。设置完成后向 Linux 注册 MISC 设备,这个注册函数(misc_register()
)一般放在 probe 函数,注销函数(misc_deregister()
)放在 remove 中。
下面用两个流程图,一个流程图是传统框架,一个是 platform 驱动和 MISC 设备的常用框架对比:
传统结构在刚开始编写设备结构体的时候需要编写很大一堆结构体成员变量(cdev、类、设备号、设备等),常用框架就可以省去这些,只需要 GPIO 编号编号即可。传统框架有注册字符设备等等,常用框架只需要一个注册 MISC 设备就可以完成这些。传统结构很多初始化的工作都放在驱动入口,不好管理;常用框架就分成了多层,每层就很明确有什么。
本文作者:烟儿公主
本文链接:https://www.cnblogs.com/toutiegongzhu/p/17686037.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步