diff options
Diffstat (limited to 'drivers/char/misc.c')
-rw-r--r-- | drivers/char/misc.c | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/drivers/char/misc.c b/drivers/char/misc.c new file mode 100644 index 000000000000..0937544762da --- /dev/null +++ b/drivers/char/misc.c | |||
@@ -0,0 +1,331 @@ | |||
1 | /* | ||
2 | * linux/drivers/char/misc.c | ||
3 | * | ||
4 | * Generic misc open routine by Johan Myreen | ||
5 | * | ||
6 | * Based on code from Linus | ||
7 | * | ||
8 | * Teemu Rantanen's Microsoft Busmouse support and Derrick Cole's | ||
9 | * changes incorporated into 0.97pl4 | ||
10 | * by Peter Cervasio (pete%q106fm.uucp@wupost.wustl.edu) (08SEP92) | ||
11 | * See busmouse.c for particulars. | ||
12 | * | ||
13 | * Made things a lot mode modular - easy to compile in just one or two | ||
14 | * of the misc drivers, as they are now completely independent. Linus. | ||
15 | * | ||
16 | * Support for loadable modules. 8-Sep-95 Philip Blundell <pjb27@cam.ac.uk> | ||
17 | * | ||
18 | * Fixed a failing symbol register to free the device registration | ||
19 | * Alan Cox <alan@lxorguk.ukuu.org.uk> 21-Jan-96 | ||
20 | * | ||
21 | * Dynamic minors and /proc/mice by Alessandro Rubini. 26-Mar-96 | ||
22 | * | ||
23 | * Renamed to misc and miscdevice to be more accurate. Alan Cox 26-Mar-96 | ||
24 | * | ||
25 | * Handling of mouse minor numbers for kerneld: | ||
26 | * Idea by Jacques Gelinas <jack@solucorp.qc.ca>, | ||
27 | * adapted by Bjorn Ekwall <bj0rn@blox.se> | ||
28 | * corrected by Alan Cox <alan@lxorguk.ukuu.org.uk> | ||
29 | * | ||
30 | * Changes for kmod (from kerneld): | ||
31 | * Cyrus Durgin <cider@speakeasy.org> | ||
32 | * | ||
33 | * Added devfs support. Richard Gooch <rgooch@atnf.csiro.au> 10-Jan-1998 | ||
34 | */ | ||
35 | |||
36 | #include <linux/module.h> | ||
37 | #include <linux/config.h> | ||
38 | |||
39 | #include <linux/fs.h> | ||
40 | #include <linux/errno.h> | ||
41 | #include <linux/miscdevice.h> | ||
42 | #include <linux/kernel.h> | ||
43 | #include <linux/major.h> | ||
44 | #include <linux/slab.h> | ||
45 | #include <linux/proc_fs.h> | ||
46 | #include <linux/seq_file.h> | ||
47 | #include <linux/devfs_fs_kernel.h> | ||
48 | #include <linux/stat.h> | ||
49 | #include <linux/init.h> | ||
50 | #include <linux/device.h> | ||
51 | #include <linux/tty.h> | ||
52 | #include <linux/kmod.h> | ||
53 | |||
54 | /* | ||
55 | * Head entry for the doubly linked miscdevice list | ||
56 | */ | ||
57 | static LIST_HEAD(misc_list); | ||
58 | static DECLARE_MUTEX(misc_sem); | ||
59 | |||
60 | /* | ||
61 | * Assigned numbers, used for dynamic minors | ||
62 | */ | ||
63 | #define DYNAMIC_MINORS 64 /* like dynamic majors */ | ||
64 | static unsigned char misc_minors[DYNAMIC_MINORS / 8]; | ||
65 | |||
66 | extern int rtc_DP8570A_init(void); | ||
67 | extern int rtc_MK48T08_init(void); | ||
68 | extern int pmu_device_init(void); | ||
69 | extern int tosh_init(void); | ||
70 | extern int i8k_init(void); | ||
71 | |||
72 | #ifdef CONFIG_PROC_FS | ||
73 | static void *misc_seq_start(struct seq_file *seq, loff_t *pos) | ||
74 | { | ||
75 | struct miscdevice *p; | ||
76 | loff_t off = 0; | ||
77 | |||
78 | down(&misc_sem); | ||
79 | list_for_each_entry(p, &misc_list, list) { | ||
80 | if (*pos == off++) | ||
81 | return p; | ||
82 | } | ||
83 | return NULL; | ||
84 | } | ||
85 | |||
86 | static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos) | ||
87 | { | ||
88 | struct list_head *n = ((struct miscdevice *)v)->list.next; | ||
89 | |||
90 | ++*pos; | ||
91 | |||
92 | return (n != &misc_list) ? list_entry(n, struct miscdevice, list) | ||
93 | : NULL; | ||
94 | } | ||
95 | |||
96 | static void misc_seq_stop(struct seq_file *seq, void *v) | ||
97 | { | ||
98 | up(&misc_sem); | ||
99 | } | ||
100 | |||
101 | static int misc_seq_show(struct seq_file *seq, void *v) | ||
102 | { | ||
103 | const struct miscdevice *p = v; | ||
104 | |||
105 | seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : ""); | ||
106 | return 0; | ||
107 | } | ||
108 | |||
109 | |||
110 | static struct seq_operations misc_seq_ops = { | ||
111 | .start = misc_seq_start, | ||
112 | .next = misc_seq_next, | ||
113 | .stop = misc_seq_stop, | ||
114 | .show = misc_seq_show, | ||
115 | }; | ||
116 | |||
117 | static int misc_seq_open(struct inode *inode, struct file *file) | ||
118 | { | ||
119 | return seq_open(file, &misc_seq_ops); | ||
120 | } | ||
121 | |||
122 | static struct file_operations misc_proc_fops = { | ||
123 | .owner = THIS_MODULE, | ||
124 | .open = misc_seq_open, | ||
125 | .read = seq_read, | ||
126 | .llseek = seq_lseek, | ||
127 | .release = seq_release, | ||
128 | }; | ||
129 | #endif | ||
130 | |||
131 | static int misc_open(struct inode * inode, struct file * file) | ||
132 | { | ||
133 | int minor = iminor(inode); | ||
134 | struct miscdevice *c; | ||
135 | int err = -ENODEV; | ||
136 | struct file_operations *old_fops, *new_fops = NULL; | ||
137 | |||
138 | down(&misc_sem); | ||
139 | |||
140 | list_for_each_entry(c, &misc_list, list) { | ||
141 | if (c->minor == minor) { | ||
142 | new_fops = fops_get(c->fops); | ||
143 | break; | ||
144 | } | ||
145 | } | ||
146 | |||
147 | if (!new_fops) { | ||
148 | up(&misc_sem); | ||
149 | request_module("char-major-%d-%d", MISC_MAJOR, minor); | ||
150 | down(&misc_sem); | ||
151 | |||
152 | list_for_each_entry(c, &misc_list, list) { | ||
153 | if (c->minor == minor) { | ||
154 | new_fops = fops_get(c->fops); | ||
155 | break; | ||
156 | } | ||
157 | } | ||
158 | if (!new_fops) | ||
159 | goto fail; | ||
160 | } | ||
161 | |||
162 | err = 0; | ||
163 | old_fops = file->f_op; | ||
164 | file->f_op = new_fops; | ||
165 | if (file->f_op->open) { | ||
166 | err=file->f_op->open(inode,file); | ||
167 | if (err) { | ||
168 | fops_put(file->f_op); | ||
169 | file->f_op = fops_get(old_fops); | ||
170 | } | ||
171 | } | ||
172 | fops_put(old_fops); | ||
173 | fail: | ||
174 | up(&misc_sem); | ||
175 | return err; | ||
176 | } | ||
177 | |||
178 | /* | ||
179 | * TODO for 2.7: | ||
180 | * - add a struct class_device to struct miscdevice and make all usages of | ||
181 | * them dynamic. | ||
182 | */ | ||
183 | static struct class_simple *misc_class; | ||
184 | |||
185 | static struct file_operations misc_fops = { | ||
186 | .owner = THIS_MODULE, | ||
187 | .open = misc_open, | ||
188 | }; | ||
189 | |||
190 | |||
191 | /** | ||
192 | * misc_register - register a miscellaneous device | ||
193 | * @misc: device structure | ||
194 | * | ||
195 | * Register a miscellaneous device with the kernel. If the minor | ||
196 | * number is set to %MISC_DYNAMIC_MINOR a minor number is assigned | ||
197 | * and placed in the minor field of the structure. For other cases | ||
198 | * the minor number requested is used. | ||
199 | * | ||
200 | * The structure passed is linked into the kernel and may not be | ||
201 | * destroyed until it has been unregistered. | ||
202 | * | ||
203 | * A zero is returned on success and a negative errno code for | ||
204 | * failure. | ||
205 | */ | ||
206 | |||
207 | int misc_register(struct miscdevice * misc) | ||
208 | { | ||
209 | struct miscdevice *c; | ||
210 | dev_t dev; | ||
211 | int err; | ||
212 | |||
213 | down(&misc_sem); | ||
214 | list_for_each_entry(c, &misc_list, list) { | ||
215 | if (c->minor == misc->minor) { | ||
216 | up(&misc_sem); | ||
217 | return -EBUSY; | ||
218 | } | ||
219 | } | ||
220 | |||
221 | if (misc->minor == MISC_DYNAMIC_MINOR) { | ||
222 | int i = DYNAMIC_MINORS; | ||
223 | while (--i >= 0) | ||
224 | if ( (misc_minors[i>>3] & (1 << (i&7))) == 0) | ||
225 | break; | ||
226 | if (i<0) { | ||
227 | up(&misc_sem); | ||
228 | return -EBUSY; | ||
229 | } | ||
230 | misc->minor = i; | ||
231 | } | ||
232 | |||
233 | if (misc->minor < DYNAMIC_MINORS) | ||
234 | misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7); | ||
235 | if (misc->devfs_name[0] == '\0') { | ||
236 | snprintf(misc->devfs_name, sizeof(misc->devfs_name), | ||
237 | "misc/%s", misc->name); | ||
238 | } | ||
239 | dev = MKDEV(MISC_MAJOR, misc->minor); | ||
240 | |||
241 | misc->class = class_simple_device_add(misc_class, dev, | ||
242 | misc->dev, misc->name); | ||
243 | if (IS_ERR(misc->class)) { | ||
244 | err = PTR_ERR(misc->class); | ||
245 | goto out; | ||
246 | } | ||
247 | |||
248 | err = devfs_mk_cdev(dev, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, | ||
249 | misc->devfs_name); | ||
250 | if (err) { | ||
251 | class_simple_device_remove(dev); | ||
252 | goto out; | ||
253 | } | ||
254 | |||
255 | /* | ||
256 | * Add it to the front, so that later devices can "override" | ||
257 | * earlier defaults | ||
258 | */ | ||
259 | list_add(&misc->list, &misc_list); | ||
260 | out: | ||
261 | up(&misc_sem); | ||
262 | return err; | ||
263 | } | ||
264 | |||
265 | /** | ||
266 | * misc_deregister - unregister a miscellaneous device | ||
267 | * @misc: device to unregister | ||
268 | * | ||
269 | * Unregister a miscellaneous device that was previously | ||
270 | * successfully registered with misc_register(). Success | ||
271 | * is indicated by a zero return, a negative errno code | ||
272 | * indicates an error. | ||
273 | */ | ||
274 | |||
275 | int misc_deregister(struct miscdevice * misc) | ||
276 | { | ||
277 | int i = misc->minor; | ||
278 | |||
279 | if (list_empty(&misc->list)) | ||
280 | return -EINVAL; | ||
281 | |||
282 | down(&misc_sem); | ||
283 | list_del(&misc->list); | ||
284 | class_simple_device_remove(MKDEV(MISC_MAJOR, misc->minor)); | ||
285 | devfs_remove(misc->devfs_name); | ||
286 | if (i < DYNAMIC_MINORS && i>0) { | ||
287 | misc_minors[i>>3] &= ~(1 << (misc->minor & 7)); | ||
288 | } | ||
289 | up(&misc_sem); | ||
290 | return 0; | ||
291 | } | ||
292 | |||
293 | EXPORT_SYMBOL(misc_register); | ||
294 | EXPORT_SYMBOL(misc_deregister); | ||
295 | |||
296 | static int __init misc_init(void) | ||
297 | { | ||
298 | #ifdef CONFIG_PROC_FS | ||
299 | struct proc_dir_entry *ent; | ||
300 | |||
301 | ent = create_proc_entry("misc", 0, NULL); | ||
302 | if (ent) | ||
303 | ent->proc_fops = &misc_proc_fops; | ||
304 | #endif | ||
305 | misc_class = class_simple_create(THIS_MODULE, "misc"); | ||
306 | if (IS_ERR(misc_class)) | ||
307 | return PTR_ERR(misc_class); | ||
308 | #ifdef CONFIG_MVME16x | ||
309 | rtc_MK48T08_init(); | ||
310 | #endif | ||
311 | #ifdef CONFIG_BVME6000 | ||
312 | rtc_DP8570A_init(); | ||
313 | #endif | ||
314 | #ifdef CONFIG_PMAC_PBOOK | ||
315 | pmu_device_init(); | ||
316 | #endif | ||
317 | #ifdef CONFIG_TOSHIBA | ||
318 | tosh_init(); | ||
319 | #endif | ||
320 | #ifdef CONFIG_I8K | ||
321 | i8k_init(); | ||
322 | #endif | ||
323 | if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) { | ||
324 | printk("unable to get major %d for misc devices\n", | ||
325 | MISC_MAJOR); | ||
326 | class_simple_destroy(misc_class); | ||
327 | return -EIO; | ||
328 | } | ||
329 | return 0; | ||
330 | } | ||
331 | subsys_initcall(misc_init); | ||