-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathtest_chardev.c
More file actions
175 lines (140 loc) · 4.31 KB
/
test_chardev.c
File metadata and controls
175 lines (140 loc) · 4.31 KB
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
165
166
167
168
169
170
171
172
173
174
175
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/**
* Test character device kernel APIs. Tested on kernel 5.8.
*/
#include <linux/kernel.h> // printk(), pr_*()
#include <linux/module.h> // THIS_MODULE, MODULE_VERSION, ...
#include <linux/moduleparam.h> // module_param()
#include <linux/init.h> // module_{init,exit}()
#include <linux/types.h> // dev_t, loff_t, etc.
#include <linux/kdev_t.h> // MAJOR(), MINOR(), MKDEV()
#include <linux/fs.h> // file_operations, file, etc.
#include <linux/cdev.h> // cdev and related functions
#include <linux/uaccess.h> // put_user(), copy_to_user(), etc.
#include <linux/device.h> // {device,class}_{create,destroy}() from udev
#include <linux/string.h> // strlen()
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
static dev_t devno;
static struct cdev *cdevice;
static struct class *mebeim_class;
static struct device *mebeim_device;
static int mebeim_mode = 0666;
static char* mebeim_content = "mebeim";
static size_t mebeim_content_length = 6;
module_param_named(mode, mebeim_mode, int, S_IRUGO);
module_param_named(content, mebeim_content, charp, S_IRUGO);
MODULE_PARM_DESC(mebeim_mode, "Device permissions.");
MODULE_PARM_DESC(mebeim_content,
"String to keep spitting out when the device is read.");
static ssize_t read_mebeim(struct file *filp, char __user *buf, size_t n,
loff_t *off)
{
size_t i;
size_t o = (size_t)*off;
for (i = 0; i < n; i++) {
put_user(mebeim_content[(i + o) % mebeim_content_length],
buf++);
}
*off += n;
return n;
}
static ssize_t write_mebeim(struct file *filp, const char __user *buf, size_t n,
loff_t *off)
{
return n;
}
static char *mebeim_devnode(struct device *dev, umode_t *mode)
{
if (mode != NULL)
*mode = (umode_t)mebeim_mode;
return NULL;
}
static const struct file_operations mebeim_fops = {
.read = read_mebeim,
.write = write_mebeim
};
static int __init mychardev_init(void)
{
int res;
pr_debug("init\n");
mebeim_content_length = strlen(mebeim_content);
res = alloc_chrdev_region(&devno, 0, 1, "mebeim");
if (res != 0) {
pr_err("error getting dev major (%d)\n", res);
goto fail_chrdev_alloc;
}
pr_debug("got dev 0x%08x major %d minor %d\n", devno, MAJOR(devno),
MINOR(devno));
mebeim_class = class_create(THIS_MODULE, "mebeim");
if (IS_ERR(mebeim_class)) {
res = PTR_ERR(mebeim_class);
pr_err("error creating device class (%d)\n", res);
goto fail_class_create;
}
/*
* This is how drivers/char/mem.c does it. See commit
* e454cea20bdcff10ee698d11b8882662a0153a47. Seems to only apply at
* mount time (i.e. if module param "mode" is made S_IWUSR, writing to
* /sys/module/mychardev/parameters/mode doesn't have any effect).
*
* This SO answer suggests an alternative using ->dev_uevent:
* https://stackoverflow.com/a/21774410/3889449
*/
mebeim_class->devnode = mebeim_devnode;
cdevice = cdev_alloc();
if (IS_ERR(cdevice)) {
res = PTR_ERR(cdevice);
pr_err("error allocating cdev (%d)\n", res);
goto fail_cdev_alloc;
}
cdev_init(cdevice, &mebeim_fops);
cdevice->owner = THIS_MODULE;
res = cdev_add(cdevice, devno, 1);
if (res != 0) {
pr_err("error adding cdev (%d)\n", res);
goto fail_cdev_add;
}
pr_debug("cdev added\n");
mebeim_device = device_create(mebeim_class, NULL, devno, NULL,
"mebeim");
if (IS_ERR(mebeim_device)) {
res = PTR_ERR(mebeim_device);
pr_err("error creating device (%d)\n", res);
goto fail_device_create;
}
pr_debug("device created\n");
pr_debug("init done\n");
return 0;
fail_device_create:
fail_cdev_add:
/*
* TODO: find out if this makes sense...
* Is cdev_del() ok to get rid of a cdev after only alloc (not add)?
*/
cdev_del(cdevice);
fail_cdev_alloc:
class_destroy(mebeim_class);
fail_class_create:
unregister_chrdev_region(devno, 1);
fail_chrdev_alloc:
return res;
}
static void __exit mychardev_cleanup(void)
{
pr_debug("cleanup\n");
device_destroy(mebeim_class, devno);
cdev_del(cdevice);
class_destroy(mebeim_class);
unregister_chrdev_region(devno, 1);
pr_debug("cleanup done\n");
}
module_init(mychardev_init);
module_exit(mychardev_cleanup);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Silly character device always spitting out the same string "
"over and over.");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("Dual MIT/GPL");