17.2Linux的MISC驱动实验(编程)_csdn
我尽量讲的更详细,为了关注我的粉丝!!!
本章实验我们采用 platform 加 misc 的方式编写 beep 驱动,这也是实际的 Linux 驱动中很常用的方法。采用 platform 来实现总线、设备和驱动, misc 主要负责完成字符设备的创建。
回顾:
提到platform,我们现在一般是在设备树下实现platform架构。所以要更改stm32mp157d-atk.dts文件,修改设备节点。
更改镜像:我们的platform结构依赖pinctrl子系统,所以要在pinctrl节点下添加beep相关的节点,添加引脚的电气属性。
1、添加 pinctrl 节点:
在 STM32MP157 开发板上的蜂鸣器使用了 PC7 这个引脚,打开 stm32mp15-pinctrl.dtsi 文件,在 pinctrl 节点下添加一个‘beep_pins_a’子节点,这个节点就是蜂鸣器的 pinctrl 配置:
编译:
make uImage LOADADDR=0XC2000040 -j8 //编译内核
复制给内核的镜像路径:
2、添加 beep 设备节点:
打开stm32mp157d-atk.dts文件,修改设备节点。
我可以看到以前做实验的beep节点:
目前是没有涉及pinctrl的架构,所以要改这个节点;
改完之后的节点如下:
这里仅仅添加了第 4~5 行,这两行指定 BEEP 所使用的引脚 pinctrl 子节点。
编译:
make dtbs
复制给内核的镜像路径:
现在我们把内核修改完了,就要编写代码了!
3、编写代码程序
之前的博客也是跟大家按照肌肉记忆来编写程序!一步一步按照思路来编写!
总代码会放在最后。
为了让大家更能明白,可以先对着总代码,进行对我的写代码流程更加详细得当!
3.1、头文件
比以前的添加了:#include <linux/miscdevice.h>
#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>
3.2、驱动注册和注销
有了platform注册和注销驱动后,就要编写platform_driver驱动结构体:跟之前的字符驱动注册差不多!
作用:利用platform注册和注销驱动。驱动的分离和分层!
3.2.1、编写platform_driver驱动结构体
这个name主要在驱动中显示:/driver中。若注册成功就会在driver显示:stm32mp1-beep。
而设备树匹配表:
主要匹配设备树下的compatible = “alientek,beep”;值
1.编写设备树匹配表:
数组最后一个元素必须为空;
MODULE_DEVICE_TABLE(of,beep_of_match);//声明这个匹配表
一旦匹配表中的:compatible匹配成功,就会执行:
.probe = miscbeep_probe,
.remove = miscbeep_remove,
2.编写probe函数和remove函数:
3.3、补充probe和remove函数
添加匹配成功后的信息:
一旦匹配成功,打印出:beep driver and device was matched!
3.3.1、配置miscdevice结构体
之前在probe和remove函数内存放着注册和注销字符设备,及GPIO的初始化配置!
由于我们在上一章讲到过
直接使用 misc_register 一个函数来完成:
直接使用需要一个 misc_deregister 函数即可完成:
同时也要定义miscdevice结构体:其中修改minor、name、fops;
misc_register和 misc_deregister 函数虽然能够一次性注册和注销,但是设备结构体的设备号、cdev、class、device还是需要驱动开发者定义的。也就是设备结构体中的东西!
1、首先配置设备结构体:
2、配置miscdevice结构体:
其中宏定义名字和次设备号:
name 就是此 MISC 设备名字,当此设备注册成功以后就会在/dev 目录下生成一个名为 name的设备文件;fops 就是字符设备的操作集合, MISC 设备驱动最终是需要使用用户提供的 fops操作集合。
然后一次性注册misc设备驱动:
这里就可以实现/dev 目录下生成一个名为 name的设备。
注册失败后打印:
同样在remove函数内一次性注销misc设备驱动:
3、配置用户提供的fops操作集合
4、配置beep所使用的 GPIO
这些代码之前的文章讲到过;注意前面的代码。
当然,也可以直接在probe函数内获得设备节点信息、申请IO、设置输出模式。
我们这里直接集成了!
把这些通过struct platform_device *pdev
和pdev->dev.of_node、struct device_node *nd
来获取设备节点信息。
我们发现设备结构体没有定义struct device_node *node
我们这里的设备节点信息不需要存储在设备结构体里。所以不需要在miscbeep_dev里定义。
设备节点信息主要在 beep_gpio_init
函数调用时作为参数传入,在函数内部使用完就可以了。
5、配置open,write,release操作集合函数:
这上面很多代码之前就讲过:
由用户App函数操作集write传递字符来驱动文件中进行驱动蜂鸣器。
其中宏定义蜂鸣器状态值:
初始是高电平,这个是低电平时蜂鸣器响;
6、配置remove函数:
7.配置错误信息:
这个不可以在这里添加:
/*注销misc设备*/misc_deregister(&beep_miscdev);
因为最好GPIO初始化的错误信息,只是释放GPIO。不涉及其他注销任务。
不然会发生未定义的错误!
4.效果测试
当驱动模块加载成功以后我们可以在/sys/class/misc 这个目录下看到一个名为“miscbeep”的子目录:
可以看出, /dev/miscbeep 这个设备的主设备号为 10,次设备号为 144,和我们驱动程序里面设置的一致。
./miscbeepApp /dev/miscbeep 1 //打开 BEEP./miscbeepApp /dev/miscbeep 0 //关闭 BEEP
5、总代码
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 /* 开蜂鸣器 *//*miscbeep设备结构体*/
struct miscbeep_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int beep_gpio; /* beep 所使用的 GPIO 编号 */
};
struct miscbeep_dev miscbeep; /*beep 设备*//*struct platform_device 指针*/
/*配置beep gpio设置*/
static int beep_gpio_init(struct device_node *nd)
{int ret;/*从设备树中获取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");ret = -EINVAL;goto fail_findnd;}/*申请使用GPIO*/ret = gpio_request(miscbeep.beep_gpio, "beep");if(ret) {printk("beep: Failed to request beep-gpio\n");ret = -EINVAL;goto fail_findnd;}/*将GPIO设置为输出模式并设置GPIO初始化电平状态*/ret = gpio_direction_output(miscbeep.beep_gpio, 1);if (ret < 0) {ret = -EINVAL;goto fail_setoutput;}return 0;
fail_setoutput:gpio_free(miscbeep.beep_gpio);
fail_findnd:return ret;
}static int miscbeep_open(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t miscbeep_write(struct file *filp,const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char beepstat;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 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 int miscbeep_release(struct inode *inode, struct file *filp)
{return 0;
}/*设备操作函数*/
static struct file_operations miscbeep_fops = {.owner = THIS_MODULE,.open = miscbeep_open,.write = miscbeep_write,.release = miscbeep_release,
};/*MISC设备结构体*/
static struct miscdevice beep_miscdev = {.minor = MISCBEEP_MINOR,.name = MISCBEEP_NAME,.fops = &miscbeep_fops,
};
/*platform 驱动的 probe 函数,当驱动与
设备匹配以后此函数就会执行*/
static int miscbeep_probe(struct platform_device *pdev)
{int ret=0;printk("beep driver and device was matched!\r\n");/*2.初始化BEEP*/ret = beep_gpio_init(pdev->dev.of_node);if(ret < 0){return ret;}/* 一般情况下会注册对应的字符设备,但是这里我们使用 MISC 设备* 所以我们不需要自己注册字符设备驱动,只需要注册 misc 设备驱动即可*//*1.注册字符设备*/ret = misc_register(&beep_miscdev);if(ret < 0){printk("misc device register failed!\r\n");goto fail_misc_register;}return 0;
fail_misc_register:return ret;
}/*platform 驱动的 remove 函数*/
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,beep" },{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of,beep_of_match);/*platform_driver驱动结构体*/
static struct platform_driver beep_driver = {.driver = {.name = "stm32mp1-beep", /*驱动名字,用于和设备匹配*/.of_match_table = beep_of_match, /*设备树匹配表*/},.probe = miscbeep_probe,.remove = miscbeep_remove,
};/*驱动注册*/
static int __init miscbeep_init(void)
{return platform_driver_register(&beep_driver);
}
/*驱动注销*/
static void __exit miscbeep_exit(void)
{platform_driver_unregister(&beep_driver);
}
module_init(miscbeep_init);
module_exit(miscbeep_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree,"Y");
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 1int 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/chensir/linux/atk-mp1/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)
obj-m := miscbeep.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean