内核中的MISC“杂项”驱动
1、简介
MISC是杂项英文单词miscellaneous的简写,内核里面用于描述那些类别不是很明确的设备,通常结合platform总线驱动来编写驱动。其中所有MISC驱动的主设备号都是10,而次设备号则可以从内核源码的include/linux/miscdevice.h文件中选择或者自定义,同时该头文件里也定义了MISC设备结构体和驱动的注册/注销方法,如下所示:
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
int misc_register(struct miscdevice *misc);
int misc_deregister(struct miscdevice *misc);
可以看到,MISC设备驱动的注册并不需要像普通的字符设备驱动一样需要定义cdev、class等变量,只需要提供minor次设备号、name作为设备节点的名字、file_operations驱动操作的结构体就可以编写出一个简单的驱动程序。而misc_register就相当于“代替”了普通字符设备的cdev_add/class_create/device_create等函数,并且在装载驱动之后可以在系统目录/sys/class/misc/下查看驱动的相关信息。
2、使用方法(MISC驱动程序框架)
static int xxx_open(struct inode *inode, struct file *file)
{
file->private_data = &led_drv;
return 0;
}
static int xxx_close(struct inode *inode, struct file *filp)
{
return 0;
}
// 配置驱动操作的结构体
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.release = xxx_close,
};
static struct miscdevice xxx_misc_device = {
.minor = 100,
.name = "xxx",
.fops = &xxx_fops,
};
static int xxx_probe(struct platform_device *dev)
{
// ...
misc_register(&xxx_misc_device);
return 0;
}
static int xxx_remove(struct platform_device *dev)
{
// ...
misc_deregister(&xxx_misc_device);
return 0;
}
static const struct of_device_id xxx_of_match[] = {
{
.compatible = "xxx,xxx" },
{
}
};
static struct platform_driver xxx_platform_driver = {
.driver = {
.name = "xxx,xxx", /* 用于传统不使用设备树的设备名称 */
.of_match_table = xxx_of_match, /* 用于匹配设备树节点的属性 */
},
.probe = xxx_probe,
.remove = xxx_remove,
};
static int __init xxx_driver_init(void)
{
return platform_driver_register(&xxx_platform_driver);
}
static void __exit xxx_driver_exit(void)
{
platform_driver_unregister(&xxx_platform_driver);
}
module_init(xxx_driver_init);
module_exit(xxx_driver_exit);
MODULE_LICENSE("GPL");
3、使用示例
3.1 修改设备树添加设备节点
仅列出节点部分,pinctrl子系统和gpio子系统在同一个专栏的文章里,这里省略。
/{
...
led0 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "red-led";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led0>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
...
3.2 驱动程序
以led为例编写一个misc驱动,简单了解程序的框架:
#include <linux/ide.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#define LED_CNT 1
struct led_drv_info{
struct device_node *node;
int gpio;
};
static struct led_drv_info led_drv;
static int led_open(struct inode *inode, struct file *file)
{
file->private_data = &led_drv;
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
char databuf[10];
unsigned long ret;
struct led_drv_info *drv_priv_data = filp->private_data;
ret = copy_from_user(databuf, buf, cnt);
if(ret){
printk("\t Kernel space: copy_from_user error!\n");
return -1;
}
if(strcmp(databuf, "on") == 0){
gpio_set_value(drv_priv_data->gpio, 0);
}else if(strcmp(databuf, "off") == 0){
gpio_set_value(drv_priv_data->gpio, 1);
}else{
printk("\t Kernel space: led_write error!\n");
return -1;
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
// 配置驱动操作的结构体
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
static struct miscdevice led_misc_device = {
.minor = 100,
.name = "led_misc",
.fops = &led_fops,
};
static int led_probe(struct platform_device *dev)
{
int ret;
led_drv.node = of_find_node_by_path("/led0"); // 通过节点名称查找
if(!led_drv.node){
printk("There is no \"led0\" node in your dtb file!\n");
return -1;
}
led_drv.gpio = of_get_named_gpio(led_drv.node, "led-gpio", 0);
if (led_drv.gpio < 0){
printk("Get gpio number error!\n");
return -1;
}
ret = gpio_request(led_drv.gpio, "led0");
if(ret){
printk("Request gpio error!\n");
return -1;
}
ret = gpio_direction_output(led_drv.gpio, 1);
if(ret){
printk("Set gpio output error!\n");
return -1;
}
misc_register(&led_misc_device);
return 0;
}
static int led_remove(struct platform_device *dev)
{
misc_deregister(&led_misc_device);
gpio_free(led_drv.gpio);
return 0;
}
static const struct of_device_id led_of_match[] = {
{
.compatible = "red-led" }, /* 设备树中led节点的属性 */
{
}
};
static struct platform_driver led_platform_driver = {
.driver = {
.name = "red-led", /* 用于传统不使用设备树的设备名称 */
.of_match_table = led_of_match, /* 用于匹配设备树节点的属性 */
},
.probe = led_probe,
.remove = led_remove,
};
static int __init led_driver_init(void)
{
return platform_driver_register(&led_platform_driver);
}
static void __exit led_driver_exit(void)
{
platform_driver_unregister(&led_platform_driver);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
4、测试程序
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
int ret;
if(argc != 3 ){
printf("\t Usage: ./ledAPP /dev/ledx <on/off>\n");
return -1;
}
fd = open(argv[1], O_RDWR);
if(fd < 0){
printf("\t User space: open error!\n");
return -1;
}
ret = write(fd, argv[2], sizeof(argv[2]));
if(ret){
printf("\t User space: write error!\n");
return -1;
}
ret = close(fd);
if(ret){
printf("\t User space: close error!\n");
return -1;
}
return 0;
}