0%

Linux驱动学习日记(22) Linux MISC 驱动实验

MISC 设备驱动简介

  所有的 MISC 设备驱动的主设备号为 10,不同的设备使用不同的从设备号。随着 Linux 字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主设备号,MISC 设备驱动就是用来解决这个问题。MISC 设备会自动创建 cdev,不需要像我们以前那样手动创建,因此采用 MISC 设备驱动可以简化字符设备驱动的编写。我们需要向 Linux 注册一个 miscdevice 设备,miscdevice 是一个结构体,定义在文件 include/linux/miscdevice.h 中,内容如下

1
2
3
4
5
6
7
8
9
10
11
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;
};

  定义一个 misc 设备以后我们需要设置 minor、name 和 fops 这三个成员变量,minor 表示子设备好,MISC 设备的主设备号为 10,这个是固定的,需要用户指定子设备好,Linux 系统已经预定义了一些 MISC 设备的子设备号,这些预定义的子设备号定义在这个文件中,如下所示:

1
2
3
#define WATCHDOG_MINOR 130 /* Watchdog timer     */
#define TEMP_MINOR 131 /* Temperature Sensor */
#define APM_MINOR_DEV 134

  我们在使用的时候可以从这些预定义的子设备号中挑选一个,当然也可以自己定义,只要这个子设备号没有被其它设备使用接口。
  name 就是此 MISC 设备名字,当此设备注册成功以后就会在 /dev 目录下生成一个名为 name 的设备文件。fops 就是字符设备的操作合集,MISC 设备驱动最终是需要使用用户提供的 fops 操作集合。
  当设置好 miscdevice 以后就需要使用 misc_register 函数向系统中注册一个 MISC 设备,此函数原型如下

1
int misc_register(struct miscdevice *misc);

  其中,misc 表示要注册的 MISC 设备,返回值为0时成功,为负数失败。
  以前我们需要自己调用一堆函数去创建设备,比如在以前的字符设备驱动中我们会使用以下几个函数完成设备创建过程:

1
2
3
4
5
alloc_chrdev_region();
cdev_init();
cdev_add();
class_create();
device_create();

  而现在,我们可以直接使用 misc_register 一个函数来完成上述代码进行的操作。当我们在卸载设备驱动模块的时候需要调用 misc_deregister 函数来注销掉 MISC 设备,函数原型如下:

int misc_deregister(struct miscdevice *misc)

  其中,misc 就是你要注销的 MISC 设备,返回值为负数失败,为0成功。

  关于 MISC 设备驱动就讲到这里,接下来我们就使用 platform + MISC 设备框架来编写 beep 蜂鸣器驱动。

实验程序编写

修改设备树

  我手上的 I.MX6U 开发板上的 BEEP 使用了 SNVS_TAMPER1 这个 PIN,打开 imxull-hcw-emmc.dtsi,在 iomuxc 子节点下创建一个名为 pinctrl_beep 的子节点,内容如下:

1
2
3
4
5
pinctrl_beep: beepgrp{
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0
>;
};

  在根节点下创建 beep 节点,内容如下:

1
2
3
4
5
6
7
beep{
compatible = "hcw-beep";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_beep>;
beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
status = "okay";
}

  经过检查,PIN 没有被其他外设占用,编译设备树,使用新的设备树文件重新启动 Linux 内核。可以看到我们成功创建了 Beep 设备。

图 1 成功创建

驱动程序编写

  新建文件夹,新建 beep.c 文件,写入代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
* @Author: 胡城玮
* @FilePath: beep.c
* @Date: 2026-03-12
* @Description:
* @Version: 0.1
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.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_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/file.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/miscdevice.h>

#define BEEP_CNT 1
#define BEEP_MINOR 144
#define BEEP_NAME "beep"
#define BEEP_ON 1
#define BEEP_OFF 0

struct miscbeep_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int beep_gpio;
};

static struct miscbeep_dev beepdev;

static int miscbeep_open(struct inode *inode, struct file *filp)
{
filp->private_data = &beepdev;
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;
struct miscbeep_dev *dev = filp->private_data;

retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0)
{
printk("kernel read failed\r\n");
return -EFAULT;
}

beepstat = databuf[0];

if(beepstat == BEEP_ON)
gpio_set_value(dev->beep_gpio, 1);
else if(beepstat == BEEP_OFF)
gpio_set_value(dev->beep_gpio, 0);

return 0;
}

struct file_operations beepdev_fops = {
.owner = THIS_MODULE,
.open = miscbeep_open,
.write = miscbeep_write,
};

struct miscdevice beep_miscdev = {
.minor = BEEP_MINOR,
.name = BEEP_NAME,
.fops = &beepdev_fops
};

static int miscdev_probe(struct platform_device *dev)
{
int ret = 0;
printk("beep driver and device matched\e\n");

beepdev.nd = of_find_node_by_path("/beep");
if(beepdev.nd == NULL)
{
printk("beep node not found\e\n");
return -EINVAL;
}

beepdev.beep_gpio = of_get_named_gpio(beepdev.nd, "beep-gpio", 0);
if(beepdev.beep_gpio < 0)
{
printk("cant get beep-gpio\r\n");
return -EINVAL;
}

gpio_request(beepdev.beep_gpio, "beep");
ret = gpio_direction_output(beepdev.beep_gpio, 1);
if (ret < 0)
{
printk("cant set gpio\r\n");
}

ret = misc_register(&beep_miscdev);
if(ret < 0)
{
printk("miscdev register failed");
return -EFAULT;
}
return 0;
}

static int miscdev_remove(struct platform_device *dev)
{
gpio_set_value(beepdev.beep_gpio, 1);
gpio_free(beepdev.beep_gpio);

misc_deregister(&beep_miscdev);

return 0;
}

static const struct of_device_id beep_of_match[] = {
{.compatible = "hcw-beep"},
{}
};

static struct platform_driver beep_driver = {
.driver = {
.of_match_table = beep_of_match,
.name = "hcw-beepdrv"
},
.probe = miscdev_probe,
.remove = miscdev_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_AUTHOR("hcw");
MODULE_LICENSE("GPL");

  编译,装载 ko 文件,通过测试文件写入数据,成功让蜂鸣器响起来。

总结

  MISC 驱动通常用于简单的外设驱动,例如 LED、蜂鸣器、按键等等。