Added buffer length to proc entry.
[cascardo/kernel/samples/02.char/.git] / helloc.c
1 #include <linux/module.h>
2 /* Needed for struct file_operations and others */
3 #include <linux/fs.h>
4 /* Needed for struct cdev */
5 #include <linux/cdev.h>
6 /* Needed for copying to/from user space */
7 #include <asm/uaccess.h>
8 #include <linux/slab.h>
9 #include <linux/proc_fs.h>
10 #include <linux/seq_file.h>
11
12 MODULE_LICENSE("GPL");
13 MODULE_AUTHOR("Thadeu Lima de Souza Cascardo <cascardo@holoscopio.com>");
14 MODULE_DESCRIPTION("A hello world char device");
15 MODULE_VERSION("1.0.0");
16
17 #define BUFFER_SIZE 4096
18 #define DEVICE_NUMBER 2
19
20 struct message {
21         char *text;
22         size_t len;
23 };
24
25 /* Message buffer we send to upstream */
26 static struct message *hello_message[DEVICE_NUMBER];
27
28 static int helloc_open(struct inode *ino, struct file *filp)
29 {
30         struct message *msg;
31         printk(KERN_INFO "Opened file with minor %d\n", iminor(ino));
32         if (iminor(ino) >= DEVICE_NUMBER)
33                 return -ENODEV;
34         msg = hello_message[iminor(ino)];
35         filp->private_data = msg;
36         if (filp->f_flags & O_TRUNC) {
37                 memset(msg->text, 0, msg->len);
38                 msg->len = 0;
39                 printk(KERN_INFO "File opened with truncate flags\n");
40         }
41         if (filp->f_flags & O_APPEND) {
42                 filp->f_pos = msg->len;
43         }
44         return 0;
45 }
46
47 static int helloc_release(struct inode *ino, struct file *filp)
48 {
49         return 0;
50 }
51
52 /* our read function writes our message to the user buffer */
53 static ssize_t helloc_read(struct file *filp, char __user *buf, size_t len,
54                            loff_t *pos)
55 {
56         int r;
57         struct message *msg = filp->private_data;
58         /* do not read pass through the size of the message */
59         if (*pos >= msg->len)
60         /* return end of file */
61                 return 0;
62         /* if len is bigger than the rest of the message, clamp it */
63         if (len > msg->len - *pos)
64                 len = msg->len - *pos;
65         /* copy message to user space and return error if it fails */
66         r = copy_to_user(buf, msg->text + *pos, len);
67         if (r)
68                 return -EFAULT;
69         /* update the file position */
70         *pos += len;
71         return len;
72 }
73
74 static ssize_t helloc_write(struct file *filp, const char __user *buf,
75                             size_t len, loff_t *pos)
76 {
77         int r;
78         struct message *msg = filp->private_data;
79         /* do not read pass through the size of the message */
80         if (*pos >= BUFFER_SIZE)
81         /* return end of file */
82                 return 0;
83         /* if len is bigger than the rest of the message, clamp it */
84         if (len > BUFFER_SIZE - *pos)
85                 len = BUFFER_SIZE - *pos;
86
87         /* copy message to user space and return error if it fails */
88         r = copy_from_user(msg->text + *pos, buf, len);
89         if (r) {
90                 if (*pos + len > msg->len)
91                         memset(msg->text + msg->len, 0, *pos + len - msg->len);
92                 return -EFAULT;
93         }
94         /* update the file position */
95         *pos += len;
96         if (msg->len < *pos) {
97                 printk(KERN_DEBUG "Updating size from %d to %lld\n", msg->len, *pos);
98                 msg->len = *pos;
99         }
100
101         return len;
102 }
103
104 /* we only implement read */
105 static struct file_operations helloc_fops = {
106         .owner = THIS_MODULE,
107         .open = helloc_open,
108         .release = helloc_release,
109         .read = helloc_read,
110         .write = helloc_write,
111 };
112
113 static int hello_len_show(struct seq_file *seq, void *data)
114 {
115         struct message *msg = seq->private;
116         seq_printf(seq, "%d\n", msg->len);
117         return 0;
118 }
119
120 static int hello_proc_open(struct inode *ino, struct file *filp)
121 {
122         return single_open(filp, hello_len_show, hello_message[0]);
123 }
124
125 static struct file_operations hello_proc_fops = {
126         .owner = THIS_MODULE,
127         .open = hello_proc_open,
128         .llseek = seq_lseek,
129         .read = seq_read,
130         .release = seq_release,
131 };
132
133
134 /* the device number and the char device struct */
135 static dev_t dev;
136 static struct cdev *cdev;
137
138 static void helloc_free(void)
139 {
140         int i;
141         for (i = 0; i < DEVICE_NUMBER; i++) {
142                 if (hello_message[i])
143                         kfree(hello_message[i]->text);
144                 kfree(hello_message[i]);
145         }
146 }
147
148 static int __init helloc_alloc(void)
149 {
150         int i;
151         for (i = 0; i < DEVICE_NUMBER; i++) {
152                 hello_message[i] = kzalloc(sizeof(struct message), GFP_KERNEL);
153                 if (!hello_message[i])
154                         goto out;
155                 hello_message[i]->text = kzalloc(BUFFER_SIZE, GFP_KERNEL);
156                 if (!hello_message[i]->text)
157                         goto out;
158         }
159         return 0;
160 out:
161         for (; i >= 0; i--) {
162                 if (hello_message[i])
163                         kfree(hello_message[i]->text);
164                 kfree(hello_message[i]);
165         }
166         return -ENOMEM;
167 }
168
169 static int __init helloc_init(void)
170 {
171         struct proc_dir_entry *hello_pde;
172         int r;
173         r = helloc_alloc();
174         if (r)
175                 goto out_alloc2;
176         /* allocate any major number with only one minor */
177         r = alloc_chrdev_region(&dev, 0, DEVICE_NUMBER, "helloc");
178         if (r)
179                 goto out_region;
180         r = -ENOMEM;
181         /* print the major number allocated so we can create our device node */
182         printk(KERN_INFO "Allocated major number %d\n", MAJOR(dev));
183         /* allocate the character device struct */
184         cdev = cdev_alloc();
185         if (!cdev)
186                 goto out_alloc;
187         /* set the module owner and the file operations of our chardev */
188         cdev->owner = THIS_MODULE;
189         cdev->ops = &helloc_fops;
190         /* register the chardev to the system */
191         r = cdev_add(cdev, dev, DEVICE_NUMBER);
192         if (r)
193                 goto out_add;
194         hello_pde = proc_create("hello_len", 0666, NULL, &hello_proc_fops);
195         if (!hello_pde)
196                 goto out_pde;
197         return 0;
198 out_pde:
199         cdev_del(cdev);
200 out_add:
201         /* release memory allocated to the cdev device */
202         kfree(cdev);
203 out_alloc:
204         /* release the device number allocated */
205         unregister_chrdev_region(dev, DEVICE_NUMBER);
206 out_region:
207 out_alloc2:
208         return r;
209 }
210
211 static void __exit helloc_exit(void)
212 {
213         remove_proc_entry("hello_len", NULL);
214         /* remove the chardev from the system */
215         cdev_del(cdev);
216         /* release the device number allocated */
217         unregister_chrdev_region(dev, DEVICE_NUMBER);
218         helloc_free();
219 }
220
221 module_init(helloc_init);
222 module_exit(helloc_exit);