guowenxue
2024-12-23 b8e5f60912c77d52214c21e67fa91ec5f522c54c
commit | author | age
b8e5f6 1 /*
G 2  * Copyright (C) 2024 LingYun IoT System Studio
3  * Author: Guo Wenxue <guowenxue@gmail.com>
4  *
5  * A character skeleton driver example in linux kernel.
6  */
f5c330 7
G 8 #include <linux/module.h>
9 #include <linux/init.h>
10 #include <linux/kernel.h>   /* printk() */
11 #include <linux/fs.h>       /* everything... */
12 #include <linux/errno.h>    /* error codes */
13 #include <linux/types.h>    /* size_t */
14 #include <linux/cdev.h>     /* cdev */
b8e5f6 15 #include <linux/slab.h>     /* kmalloc() */
f5c330 16 #include <linux/version.h>  /* kernel version code */
b8e5f6 17 #include <linux/uaccess.h>  /* copy_from/to_user() */
f5c330 18 #include <linux/moduleparam.h>
G 19
20 /* device name and major number */
21 #define DEV_NAME         "chrdev"
b8e5f6 22 int dev_major = 0;
f5c330 23 module_param(dev_major, int, S_IRUGO);
G 24
b8e5f6 25 #define BUF_SIZE         1024
f5c330 26 typedef struct chrdev_s
G 27 {
28     struct cdev    cdev;
29     struct class  *class;
30     struct device *device;
31     char          *data;   /* data buffer */
32     uint32_t       size;   /* data buffer size */
33     uint32_t       bytes;  /* data bytes in the buffer */
34 } chrdev_t;
35
36 static struct chrdev_s   dev;
37
38 static ssize_t chrdev_read (struct file *file, char __user *buf, size_t count, loff_t *f_pos)
39 {
40     struct chrdev_s   *dev = file->private_data;
41     ssize_t nbytes;
42     ssize_t rv = 0;
43
44     /* no data in buffer  */
45     if( !dev->bytes )
46         return 0;
47
48     /* copy data to user space */
49     nbytes = count>dev->bytes ? dev->bytes : count;
50     if( copy_to_user(buf, dev->data, nbytes) )
51     {
52         rv = -EFAULT;
53         goto out;
54     }
55
56     /* update return value and data bytes in buffer */
57     rv = nbytes;
58     dev->bytes -= nbytes;
59
60 out:
61     return rv;
62 }
63
64 static ssize_t chrdev_write (struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
65 {
66     struct chrdev_s   *dev = file->private_data;
67     ssize_t nbytes;
68     ssize_t rv = 0;
69
70     /* no space left */
71     if( dev->bytes >= dev->size )
72         return -ENOSPC;
73
74     /* check copy data bytes */
75     if( dev->size - dev->bytes < count)
76         nbytes = dev->size - dev->bytes;
77     else
78         nbytes = count;
79
80     /* copy data from user space  */
81     if( copy_from_user(&dev->data[dev->bytes], buf, nbytes) )
82     {
83         rv = -EFAULT;
84         goto out;
85     }
86
87     /* update return value and data bytes in buffer */
88     rv = nbytes;
89     dev->bytes += nbytes;
90
91 out:
92     return rv;
93 }
94
b8e5f6 95 #if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0)
G 96 #define access_ok_wrapper(type,arg,cmd) access_ok(type, arg, cmd)
97 #else
98 #define access_ok_wrapper(type,arg,cmd) access_ok(arg, cmd)
99 #endif
100
101 /* ioctl definitions, use 'c' as magic number */
102 #define CHR_MAGIC           'c'
103 #define CHR_MAXNR           2
104 #define CMD_READ            _IOR(CHR_MAGIC, 0, int)
105 #define CMD_WRITE           _IOW(CHR_MAGIC, 1, int)
106
107 static long chrdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
f5c330 108 {
b8e5f6 109     static int value = 0xdeadbeef;
G 110     int rv = 0;
f5c330 111
G 112     /*
113      * extract the type and number bitfields, and don't decode
114      * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
115      */
b8e5f6 116     if (_IOC_TYPE(cmd) != CHR_MAGIC) return -ENOTTY;
G 117     if (_IOC_NR(cmd) > CHR_MAXNR) return -ENOTTY;
f5c330 118
G 119     /*
120      * the direction is a bitmask, and VERIFY_WRITE catches R/W transfers.
121      * `Type' is user-oriented, while access_ok is kernel-oriented,
122      * so the concept of "read" and "write" is reversed
123      */
124     if (_IOC_DIR(cmd) & _IOC_READ)
125         rv = !access_ok_wrapper(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
126     else if (_IOC_DIR(cmd) & _IOC_WRITE)
127         rv =  !access_ok_wrapper(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
128
b8e5f6 129     if (rv)
G 130         return -EFAULT;
131
132     switch (cmd) {
133         case CMD_READ:
134             if (copy_to_user((int __user *)arg, &value, sizeof(value)))
135                 return -EFAULT;
f5c330 136             break;
G 137
b8e5f6 138         case CMD_WRITE:
G 139             if (copy_from_user(&value, (int __user *)arg, sizeof(value)))
140                 return -EFAULT;
f5c330 141             break;
G 142
143         default:
b8e5f6 144             return -EINVAL;
f5c330 145     }
G 146
b8e5f6 147     return 0;
f5c330 148 }
G 149
150 static int chrdev_open (struct inode *inode, struct file *file)
151 {
152     struct chrdev_s    *dev; /* device struct address */
153
154     /* get the device struct address by container_of() */
155     dev = container_of(inode->i_cdev, struct chrdev_s, cdev);
156
157     /* save the device struct address for other methods */
158     file->private_data = dev;
159
160     return 0;
161 }
162
163 static int chrdev_close (struct inode *node, struct file *file)
164 {
165     return 0;
166 }
167
168 static struct file_operations chrdev_fops = {
b8e5f6 169     .owner          = THIS_MODULE,
G 170     .open           = chrdev_open,  /* open()  implementation */
171     .read           = chrdev_read,  /* read()  implementation */
172     .write          = chrdev_write, /* write() implementation */
173     .unlocked_ioctl = chrdev_ioctl, /* ioctl() implementation */
174     .release        = chrdev_close, /* close() implementation */
f5c330 175 };
G 176
177 static int __init chrdev_init(void)
178 {
179     dev_t      devno;
180     int        rv;
181
182     /* malloc and initial device read/write buffer */
b8e5f6 183     dev.data = kmalloc(BUF_SIZE, GFP_KERNEL);
f5c330 184     if( !dev.data )
G 185     {
186         printk(KERN_ERR " %s driver kmalloc() failed\n", DEV_NAME);
187         return -ENOMEM;
188     }
b8e5f6 189     dev.size = BUF_SIZE;
f5c330 190     dev.bytes = 0;
G 191     memset(dev.data, 0, dev.size);
192
b8e5f6 193     /* allocate device number */
f5c330 194     if(0 != dev_major)
G 195     {
196         devno = MKDEV(dev_major, 0);
197         rv = register_chrdev_region(devno, 1, DEV_NAME);
198     }
199     else
200     {
201         rv = alloc_chrdev_region(&devno, 0, 1, DEV_NAME);
202         dev_major = MAJOR(devno);
203     }
204
205     if(rv < 0)
206     {
207         printk(KERN_ERR "%s driver can't use major %d\n", DEV_NAME, dev_major);
208         return -ENODEV;
209     }
210
b8e5f6 211     /* initialize cdev and setup fops */
f5c330 212     cdev_init(&dev.cdev, &chrdev_fops);
G 213     dev.cdev.owner = THIS_MODULE;
b8e5f6 214
G 215     /* register cdev to linux kernel */
f5c330 216     rv = cdev_add(&dev.cdev, devno, 1);
G 217     if( rv )
218     {
219         rv = -ENODEV;
220         printk(KERN_ERR "%s driver regist failed, rv=%d\n", DEV_NAME, rv);
221         goto failed1;
222     }
223
224     /* create device node in user space */
225 #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
226     dev.class = class_create(DEV_NAME);
227 #else
228     dev.class = class_create(THIS_MODULE, DEV_NAME);
229 #endif
230     if (IS_ERR(dev.class)) {
231         rv = PTR_ERR(dev.class);
232         goto failed2;
233     }
234
b8e5f6 235     dev.device = device_create(dev.class, NULL, MKDEV(dev_major, 0), NULL, "%s%d", DEV_NAME, 0);
f5c330 236     if( !dev.device )
G 237     {
238         rv = -ENODEV;
239         printk(KERN_ERR "%s driver create device failed\n", DEV_NAME);
240         goto failed3;
241     }
242
243     printk(KERN_INFO "%s driver on major[%d] installed.\n", DEV_NAME, dev_major);
244     return 0;
245
246 failed3:
247     class_destroy(dev.class);
248
249 failed2:
250     cdev_del(&dev.cdev);
251
252 failed1:
253     unregister_chrdev_region(devno, 1);
254     kfree(dev.data);
255
256     printk(KERN_ERR "%s driver installed failed.\n", DEV_NAME);
257     return rv;
258 }
259
260 static void __exit chrdev_exit(void)
261 {
262     device_del(dev.device);
263     class_destroy(dev.class);
264
265     cdev_del(&dev.cdev);
266     unregister_chrdev_region(MKDEV(dev_major,0), 1);
267
268     kfree(dev.data);
269
270     printk(KERN_INFO "%s driver removed!\n", DEV_NAME);
271     return;
272 }
273
274 module_init(chrdev_init);
275 module_exit(chrdev_exit);
276
277 MODULE_LICENSE("Dual BSD/GPL");
278 MODULE_AUTHOR("GuoWenxue <guowenxue@gmail.com>");