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     char          *data;   /* data buffer */
30     uint32_t       size;   /* data buffer size */
31     uint32_t       bytes;  /* data bytes in the buffer */
32 } chrdev_t;
33
34 static struct chrdev_s   dev;
35
36 static ssize_t chrdev_read (struct file *file, char __user *buf, size_t count, loff_t *f_pos)
37 {
38     struct chrdev_s   *dev = file->private_data;
39     ssize_t nbytes;
40     ssize_t rv = 0;
41
42     /* no data in buffer  */
43     if( !dev->bytes )
44         return 0;
45
46     /* copy data to user space */
47     nbytes = count>dev->bytes ? dev->bytes : count;
48     if( copy_to_user(buf, dev->data, nbytes) )
49     {
50         rv = -EFAULT;
51         goto out;
52     }
53
54     /* update return value and data bytes in buffer */
55     rv = nbytes;
56     dev->bytes -= nbytes;
57
58 out:
59     return rv;
60 }
61
62 static ssize_t chrdev_write (struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
63 {
64     struct chrdev_s   *dev = file->private_data;
65     ssize_t nbytes;
66     ssize_t rv = 0;
67
68     /* no space left */
69     if( dev->bytes >= dev->size )
70         return -ENOSPC;
71
72     /* check copy data bytes */
73     if( dev->size - dev->bytes < count)
74         nbytes = dev->size - dev->bytes;
75     else
76         nbytes = count;
77
78     /* copy data from user space  */
79     if( copy_from_user(&dev->data[dev->bytes], buf, nbytes) )
80     {
81         rv = -EFAULT;
82         goto out;
83     }
84
85     /* update return value and data bytes in buffer */
86     rv = nbytes;
87     dev->bytes += nbytes;
88
89 out:
90     return rv;
91 }
92
93 static int chrdev_open (struct inode *inode, struct file *file)
94 {
95     struct chrdev_s    *dev; /* device struct address */
96
97     /* get the device struct address by container_of() */
98     dev = container_of(inode->i_cdev, struct chrdev_s, cdev);
99
100     /* save the device struct address for other methods */
101     file->private_data = dev;
102
103     return 0;
104 }
105
106 static int chrdev_close (struct inode *node, struct file *file)
107 {
108     return 0;
109 }
110
111 static struct file_operations chrdev_fops = {
b8e5f6 112     .owner          = THIS_MODULE,
G 113     .open           = chrdev_open,  /* open()  implementation */
114     .read           = chrdev_read,  /* read()  implementation */
115     .write          = chrdev_write, /* write() implementation */
116     .release        = chrdev_close, /* close() implementation */
f5c330 117 };
G 118
119 static int __init chrdev_init(void)
120 {
121     dev_t      devno;
122     int        rv;
123
124     /* malloc and initial device read/write buffer */
b8e5f6 125     dev.data = kmalloc(BUF_SIZE, GFP_KERNEL);
f5c330 126     if( !dev.data )
G 127     {
128         printk(KERN_ERR " %s driver kmalloc() failed\n", DEV_NAME);
129         return -ENOMEM;
130     }
b8e5f6 131     dev.size = BUF_SIZE;
f5c330 132     dev.bytes = 0;
G 133     memset(dev.data, 0, dev.size);
134
b8e5f6 135     /* allocate device number */
f5c330 136     if(0 != dev_major)
G 137     {
138         devno = MKDEV(dev_major, 0);
139         rv = register_chrdev_region(devno, 1, DEV_NAME);
140     }
141     else
142     {
143         rv = alloc_chrdev_region(&devno, 0, 1, DEV_NAME);
144         dev_major = MAJOR(devno);
145     }
146
147     if(rv < 0)
148     {
149         printk(KERN_ERR "%s driver can't use major %d\n", DEV_NAME, dev_major);
150         return -ENODEV;
151     }
152
b8e5f6 153     /* initialize cdev and setup fops */
f5c330 154     cdev_init(&dev.cdev, &chrdev_fops);
G 155     dev.cdev.owner = THIS_MODULE;
b8e5f6 156
G 157     /* register cdev to linux kernel */
f5c330 158     rv = cdev_add(&dev.cdev, devno, 1);
G 159     if( rv )
160     {
161         rv = -ENODEV;
162         printk(KERN_ERR "%s driver regist failed, rv=%d\n", DEV_NAME, rv);
163         goto failed1;
164     }
165
166     printk(KERN_INFO "%s driver on major[%d] installed.\n", DEV_NAME, dev_major);
167     return 0;
168
169 failed1:
170     unregister_chrdev_region(devno, 1);
171
172     printk(KERN_ERR "%s driver installed failed.\n", DEV_NAME);
173     return rv;
174 }
175
176 static void __exit chrdev_exit(void)
177 {
178     cdev_del(&dev.cdev);
179     unregister_chrdev_region(MKDEV(dev_major,0), 1);
180
181     kfree(dev.data);
182
183     printk(KERN_INFO "%s driver removed!\n", DEV_NAME);
184     return;
185 }
186
187 module_init(chrdev_init);
188 module_exit(chrdev_exit);
189
190 MODULE_LICENSE("Dual BSD/GPL");
191 MODULE_AUTHOR("GuoWenxue <guowenxue@gmail.com>");