-
Notifications
You must be signed in to change notification settings - Fork 534
/
chardev2.c
234 lines (192 loc) · 6.81 KB
/
chardev2.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*
* chardev2.c - Create an input/output character device
*/
#include <linux/atomic.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h> /* Specifically, a module */
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/uaccess.h> /* for get_user and put_user */
#include <linux/version.h>
#include <asm/errno.h>
#include "chardev.h"
#define SUCCESS 0
#define DEVICE_NAME "char_dev"
#define BUF_LEN 80
enum {
CDEV_NOT_USED,
CDEV_EXCLUSIVE_OPEN,
};
/* Is the device open right now? Used to prevent concurrent access into
* the same device
*/
static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED);
/* The message the device will give when asked */
static char message[BUF_LEN + 1];
static struct class *cls;
/* This is called whenever a process attempts to open the device file */
static int device_open(struct inode *inode, struct file *file)
{
pr_info("device_open(%p)\n", file);
try_module_get(THIS_MODULE);
return SUCCESS;
}
static int device_release(struct inode *inode, struct file *file)
{
pr_info("device_release(%p,%p)\n", inode, file);
module_put(THIS_MODULE);
return SUCCESS;
}
/* This function is called whenever a process which has already opened the
* device file attempts to read from it.
*/
static ssize_t device_read(struct file *file, /* see include/linux/fs.h */
char __user *buffer, /* buffer to be filled */
size_t length, /* length of the buffer */
loff_t *offset)
{
/* Number of bytes actually written to the buffer */
int bytes_read = 0;
/* How far did the process reading the message get? Useful if the message
* is larger than the size of the buffer we get to fill in device_read.
*/
const char *message_ptr = message;
if (!*(message_ptr + *offset)) { /* we are at the end of message */
*offset = 0; /* reset the offset */
return 0; /* signify end of file */
}
message_ptr += *offset;
/* Actually put the data into the buffer */
while (length && *message_ptr) {
/* Because the buffer is in the user data segment, not the kernel
* data segment, assignment would not work. Instead, we have to
* use put_user which copies data from the kernel data segment to
* the user data segment.
*/
put_user(*(message_ptr++), buffer++);
length--;
bytes_read++;
}
pr_info("Read %d bytes, %ld left\n", bytes_read, length);
*offset += bytes_read;
/* Read functions are supposed to return the number of bytes actually
* inserted into the buffer.
*/
return bytes_read;
}
/* called when somebody tries to write into our device file. */
static ssize_t device_write(struct file *file, const char __user *buffer,
size_t length, loff_t *offset)
{
int i;
pr_info("device_write(%p,%p,%ld)", file, buffer, length);
for (i = 0; i < length && i < BUF_LEN; i++)
get_user(message[i], buffer + i);
/* Again, return the number of input characters used. */
return i;
}
/* This function is called whenever a process tries to do an ioctl on our
* device file. We get two extra parameters (additional to the inode and file
* structures, which all device functions get): the number of the ioctl called
* and the parameter given to the ioctl function.
*
* If the ioctl is write or read/write (meaning output is returned to the
* calling process), the ioctl call returns the output of this function.
*/
static long
device_ioctl(struct file *file, /* ditto */
unsigned int ioctl_num, /* number and param for ioctl */
unsigned long ioctl_param)
{
int i;
long ret = SUCCESS;
/* We don't want to talk to two processes at the same time. */
if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN))
return -EBUSY;
/* Switch according to the ioctl called */
switch (ioctl_num) {
case IOCTL_SET_MSG: {
/* Receive a pointer to a message (in user space) and set that to
* be the device's message. Get the parameter given to ioctl by
* the process.
*/
char __user *tmp = (char __user *)ioctl_param;
char ch;
/* Find the length of the message */
get_user(ch, tmp);
for (i = 0; ch && i < BUF_LEN; i++, tmp++)
get_user(ch, tmp);
device_write(file, (char __user *)ioctl_param, i, NULL);
break;
}
case IOCTL_GET_MSG: {
loff_t offset = 0;
/* Give the current message to the calling process - the parameter
* we got is a pointer, fill it.
*/
i = device_read(file, (char __user *)ioctl_param, 99, &offset);
/* Put a zero at the end of the buffer, so it will be properly
* terminated.
*/
put_user('\0', (char __user *)ioctl_param + i);
break;
}
case IOCTL_GET_NTH_BYTE:
/* This ioctl is both input (ioctl_param) and output (the return
* value of this function).
*/
ret = (long)message[ioctl_param];
break;
}
/* We're now ready for our next caller */
atomic_set(&already_open, CDEV_NOT_USED);
return ret;
}
/* Module Declarations */
/* This structure will hold the functions to be called when a process does
* something to the device we created. Since a pointer to this structure
* is kept in the devices table, it can't be local to init_module. NULL is
* for unimplemented functions.
*/
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_ioctl,
.open = device_open,
.release = device_release, /* a.k.a. close */
};
/* Initialize the module - Register the character device */
static int __init chardev2_init(void)
{
/* Register the character device (at least try) */
int ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops);
/* Negative values signify an error */
if (ret_val < 0) {
pr_alert("%s failed with %d\n",
"Sorry, registering the character device ", ret_val);
return ret_val;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
cls = class_create(DEVICE_FILE_NAME);
#else
cls = class_create(THIS_MODULE, DEVICE_FILE_NAME);
#endif
device_create(cls, NULL, MKDEV(MAJOR_NUM, 0), NULL, DEVICE_FILE_NAME);
pr_info("Device created on /dev/%s\n", DEVICE_FILE_NAME);
return 0;
}
/* Cleanup - unregister the appropriate file from /proc */
static void __exit chardev2_exit(void)
{
device_destroy(cls, MKDEV(MAJOR_NUM, 0));
class_destroy(cls);
/* Unregister the device */
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
}
module_init(chardev2_init);
module_exit(chardev2_exit);
MODULE_LICENSE("GPL");