diff options
Diffstat (limited to 'drivers/tty/vt/vc_screen.c')
-rw-r--r-- | drivers/tty/vt/vc_screen.c | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c new file mode 100644 index 000000000000..273ab44cc91d --- /dev/null +++ b/drivers/tty/vt/vc_screen.c | |||
@@ -0,0 +1,644 @@ | |||
1 | /* | ||
2 | * linux/drivers/char/vc_screen.c | ||
3 | * | ||
4 | * Provide access to virtual console memory. | ||
5 | * /dev/vcs0: the screen as it is being viewed right now (possibly scrolled) | ||
6 | * /dev/vcsN: the screen of /dev/ttyN (1 <= N <= 63) | ||
7 | * [minor: N] | ||
8 | * | ||
9 | * /dev/vcsaN: idem, but including attributes, and prefixed with | ||
10 | * the 4 bytes lines,columns,x,y (as screendump used to give). | ||
11 | * Attribute/character pair is in native endianity. | ||
12 | * [minor: N+128] | ||
13 | * | ||
14 | * This replaces screendump and part of selection, so that the system | ||
15 | * administrator can control access using file system permissions. | ||
16 | * | ||
17 | * aeb@cwi.nl - efter Friedas begravelse - 950211 | ||
18 | * | ||
19 | * machek@k332.feld.cvut.cz - modified not to send characters to wrong console | ||
20 | * - fixed some fatal off-by-one bugs (0-- no longer == -1 -> looping and looping and looping...) | ||
21 | * - making it shorter - scr_readw are macros which expand in PRETTY long code | ||
22 | */ | ||
23 | |||
24 | #include <linux/kernel.h> | ||
25 | #include <linux/major.h> | ||
26 | #include <linux/errno.h> | ||
27 | #include <linux/tty.h> | ||
28 | #include <linux/interrupt.h> | ||
29 | #include <linux/mm.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/mutex.h> | ||
32 | #include <linux/vt_kern.h> | ||
33 | #include <linux/selection.h> | ||
34 | #include <linux/kbd_kern.h> | ||
35 | #include <linux/console.h> | ||
36 | #include <linux/device.h> | ||
37 | #include <linux/smp_lock.h> | ||
38 | #include <linux/sched.h> | ||
39 | #include <linux/fs.h> | ||
40 | #include <linux/poll.h> | ||
41 | #include <linux/signal.h> | ||
42 | #include <linux/slab.h> | ||
43 | #include <linux/notifier.h> | ||
44 | |||
45 | #include <asm/uaccess.h> | ||
46 | #include <asm/byteorder.h> | ||
47 | #include <asm/unaligned.h> | ||
48 | |||
49 | #undef attr | ||
50 | #undef org | ||
51 | #undef addr | ||
52 | #define HEADER_SIZE 4 | ||
53 | |||
54 | struct vcs_poll_data { | ||
55 | struct notifier_block notifier; | ||
56 | unsigned int cons_num; | ||
57 | bool seen_last_update; | ||
58 | wait_queue_head_t waitq; | ||
59 | struct fasync_struct *fasync; | ||
60 | }; | ||
61 | |||
62 | static int | ||
63 | vcs_notifier(struct notifier_block *nb, unsigned long code, void *_param) | ||
64 | { | ||
65 | struct vt_notifier_param *param = _param; | ||
66 | struct vc_data *vc = param->vc; | ||
67 | struct vcs_poll_data *poll = | ||
68 | container_of(nb, struct vcs_poll_data, notifier); | ||
69 | int currcons = poll->cons_num; | ||
70 | |||
71 | if (code != VT_UPDATE) | ||
72 | return NOTIFY_DONE; | ||
73 | |||
74 | if (currcons == 0) | ||
75 | currcons = fg_console; | ||
76 | else | ||
77 | currcons--; | ||
78 | if (currcons != vc->vc_num) | ||
79 | return NOTIFY_DONE; | ||
80 | |||
81 | poll->seen_last_update = false; | ||
82 | wake_up_interruptible(&poll->waitq); | ||
83 | kill_fasync(&poll->fasync, SIGIO, POLL_IN); | ||
84 | return NOTIFY_OK; | ||
85 | } | ||
86 | |||
87 | static void | ||
88 | vcs_poll_data_free(struct vcs_poll_data *poll) | ||
89 | { | ||
90 | unregister_vt_notifier(&poll->notifier); | ||
91 | kfree(poll); | ||
92 | } | ||
93 | |||
94 | static struct vcs_poll_data * | ||
95 | vcs_poll_data_get(struct file *file) | ||
96 | { | ||
97 | struct vcs_poll_data *poll = file->private_data; | ||
98 | |||
99 | if (poll) | ||
100 | return poll; | ||
101 | |||
102 | poll = kzalloc(sizeof(*poll), GFP_KERNEL); | ||
103 | if (!poll) | ||
104 | return NULL; | ||
105 | poll->cons_num = iminor(file->f_path.dentry->d_inode) & 127; | ||
106 | init_waitqueue_head(&poll->waitq); | ||
107 | poll->notifier.notifier_call = vcs_notifier; | ||
108 | if (register_vt_notifier(&poll->notifier) != 0) { | ||
109 | kfree(poll); | ||
110 | return NULL; | ||
111 | } | ||
112 | |||
113 | /* | ||
114 | * This code may be called either through ->poll() or ->fasync(). | ||
115 | * If we have two threads using the same file descriptor, they could | ||
116 | * both enter this function, both notice that the structure hasn't | ||
117 | * been allocated yet and go ahead allocating it in parallel, but | ||
118 | * only one of them must survive and be shared otherwise we'd leak | ||
119 | * memory with a dangling notifier callback. | ||
120 | */ | ||
121 | spin_lock(&file->f_lock); | ||
122 | if (!file->private_data) { | ||
123 | file->private_data = poll; | ||
124 | } else { | ||
125 | /* someone else raced ahead of us */ | ||
126 | vcs_poll_data_free(poll); | ||
127 | poll = file->private_data; | ||
128 | } | ||
129 | spin_unlock(&file->f_lock); | ||
130 | |||
131 | return poll; | ||
132 | } | ||
133 | |||
134 | static int | ||
135 | vcs_size(struct inode *inode) | ||
136 | { | ||
137 | int size; | ||
138 | int minor = iminor(inode); | ||
139 | int currcons = minor & 127; | ||
140 | struct vc_data *vc; | ||
141 | |||
142 | if (currcons == 0) | ||
143 | currcons = fg_console; | ||
144 | else | ||
145 | currcons--; | ||
146 | if (!vc_cons_allocated(currcons)) | ||
147 | return -ENXIO; | ||
148 | vc = vc_cons[currcons].d; | ||
149 | |||
150 | size = vc->vc_rows * vc->vc_cols; | ||
151 | |||
152 | if (minor & 128) | ||
153 | size = 2*size + HEADER_SIZE; | ||
154 | return size; | ||
155 | } | ||
156 | |||
157 | static loff_t vcs_lseek(struct file *file, loff_t offset, int orig) | ||
158 | { | ||
159 | int size; | ||
160 | |||
161 | mutex_lock(&con_buf_mtx); | ||
162 | size = vcs_size(file->f_path.dentry->d_inode); | ||
163 | switch (orig) { | ||
164 | default: | ||
165 | mutex_unlock(&con_buf_mtx); | ||
166 | return -EINVAL; | ||
167 | case 2: | ||
168 | offset += size; | ||
169 | break; | ||
170 | case 1: | ||
171 | offset += file->f_pos; | ||
172 | case 0: | ||
173 | break; | ||
174 | } | ||
175 | if (offset < 0 || offset > size) { | ||
176 | mutex_unlock(&con_buf_mtx); | ||
177 | return -EINVAL; | ||
178 | } | ||
179 | file->f_pos = offset; | ||
180 | mutex_unlock(&con_buf_mtx); | ||
181 | return file->f_pos; | ||
182 | } | ||
183 | |||
184 | |||
185 | static ssize_t | ||
186 | vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) | ||
187 | { | ||
188 | struct inode *inode = file->f_path.dentry->d_inode; | ||
189 | unsigned int currcons = iminor(inode); | ||
190 | struct vc_data *vc; | ||
191 | struct vcs_poll_data *poll; | ||
192 | long pos; | ||
193 | long viewed, attr, read; | ||
194 | int col, maxcol; | ||
195 | unsigned short *org = NULL; | ||
196 | ssize_t ret; | ||
197 | |||
198 | mutex_lock(&con_buf_mtx); | ||
199 | |||
200 | pos = *ppos; | ||
201 | |||
202 | /* Select the proper current console and verify | ||
203 | * sanity of the situation under the console lock. | ||
204 | */ | ||
205 | acquire_console_sem(); | ||
206 | |||
207 | attr = (currcons & 128); | ||
208 | currcons = (currcons & 127); | ||
209 | if (currcons == 0) { | ||
210 | currcons = fg_console; | ||
211 | viewed = 1; | ||
212 | } else { | ||
213 | currcons--; | ||
214 | viewed = 0; | ||
215 | } | ||
216 | ret = -ENXIO; | ||
217 | if (!vc_cons_allocated(currcons)) | ||
218 | goto unlock_out; | ||
219 | vc = vc_cons[currcons].d; | ||
220 | |||
221 | ret = -EINVAL; | ||
222 | if (pos < 0) | ||
223 | goto unlock_out; | ||
224 | poll = file->private_data; | ||
225 | if (count && poll) | ||
226 | poll->seen_last_update = true; | ||
227 | read = 0; | ||
228 | ret = 0; | ||
229 | while (count) { | ||
230 | char *con_buf0, *con_buf_start; | ||
231 | long this_round, size; | ||
232 | ssize_t orig_count; | ||
233 | long p = pos; | ||
234 | |||
235 | /* Check whether we are above size each round, | ||
236 | * as copy_to_user at the end of this loop | ||
237 | * could sleep. | ||
238 | */ | ||
239 | size = vcs_size(inode); | ||
240 | if (pos >= size) | ||
241 | break; | ||
242 | if (count > size - pos) | ||
243 | count = size - pos; | ||
244 | |||
245 | this_round = count; | ||
246 | if (this_round > CON_BUF_SIZE) | ||
247 | this_round = CON_BUF_SIZE; | ||
248 | |||
249 | /* Perform the whole read into the local con_buf. | ||
250 | * Then we can drop the console spinlock and safely | ||
251 | * attempt to move it to userspace. | ||
252 | */ | ||
253 | |||
254 | con_buf_start = con_buf0 = con_buf; | ||
255 | orig_count = this_round; | ||
256 | maxcol = vc->vc_cols; | ||
257 | if (!attr) { | ||
258 | org = screen_pos(vc, p, viewed); | ||
259 | col = p % maxcol; | ||
260 | p += maxcol - col; | ||
261 | while (this_round-- > 0) { | ||
262 | *con_buf0++ = (vcs_scr_readw(vc, org++) & 0xff); | ||
263 | if (++col == maxcol) { | ||
264 | org = screen_pos(vc, p, viewed); | ||
265 | col = 0; | ||
266 | p += maxcol; | ||
267 | } | ||
268 | } | ||
269 | } else { | ||
270 | if (p < HEADER_SIZE) { | ||
271 | size_t tmp_count; | ||
272 | |||
273 | con_buf0[0] = (char)vc->vc_rows; | ||
274 | con_buf0[1] = (char)vc->vc_cols; | ||
275 | getconsxy(vc, con_buf0 + 2); | ||
276 | |||
277 | con_buf_start += p; | ||
278 | this_round += p; | ||
279 | if (this_round > CON_BUF_SIZE) { | ||
280 | this_round = CON_BUF_SIZE; | ||
281 | orig_count = this_round - p; | ||
282 | } | ||
283 | |||
284 | tmp_count = HEADER_SIZE; | ||
285 | if (tmp_count > this_round) | ||
286 | tmp_count = this_round; | ||
287 | |||
288 | /* Advance state pointers and move on. */ | ||
289 | this_round -= tmp_count; | ||
290 | p = HEADER_SIZE; | ||
291 | con_buf0 = con_buf + HEADER_SIZE; | ||
292 | /* If this_round >= 0, then p is even... */ | ||
293 | } else if (p & 1) { | ||
294 | /* Skip first byte for output if start address is odd | ||
295 | * Update region sizes up/down depending on free | ||
296 | * space in buffer. | ||
297 | */ | ||
298 | con_buf_start++; | ||
299 | if (this_round < CON_BUF_SIZE) | ||
300 | this_round++; | ||
301 | else | ||
302 | orig_count--; | ||
303 | } | ||
304 | if (this_round > 0) { | ||
305 | unsigned short *tmp_buf = (unsigned short *)con_buf0; | ||
306 | |||
307 | p -= HEADER_SIZE; | ||
308 | p /= 2; | ||
309 | col = p % maxcol; | ||
310 | |||
311 | org = screen_pos(vc, p, viewed); | ||
312 | p += maxcol - col; | ||
313 | |||
314 | /* Buffer has even length, so we can always copy | ||
315 | * character + attribute. We do not copy last byte | ||
316 | * to userspace if this_round is odd. | ||
317 | */ | ||
318 | this_round = (this_round + 1) >> 1; | ||
319 | |||
320 | while (this_round) { | ||
321 | *tmp_buf++ = vcs_scr_readw(vc, org++); | ||
322 | this_round --; | ||
323 | if (++col == maxcol) { | ||
324 | org = screen_pos(vc, p, viewed); | ||
325 | col = 0; | ||
326 | p += maxcol; | ||
327 | } | ||
328 | } | ||
329 | } | ||
330 | } | ||
331 | |||
332 | /* Finally, release the console semaphore while we push | ||
333 | * all the data to userspace from our temporary buffer. | ||
334 | * | ||
335 | * AKPM: Even though it's a semaphore, we should drop it because | ||
336 | * the pagefault handling code may want to call printk(). | ||
337 | */ | ||
338 | |||
339 | release_console_sem(); | ||
340 | ret = copy_to_user(buf, con_buf_start, orig_count); | ||
341 | acquire_console_sem(); | ||
342 | |||
343 | if (ret) { | ||
344 | read += (orig_count - ret); | ||
345 | ret = -EFAULT; | ||
346 | break; | ||
347 | } | ||
348 | buf += orig_count; | ||
349 | pos += orig_count; | ||
350 | read += orig_count; | ||
351 | count -= orig_count; | ||
352 | } | ||
353 | *ppos += read; | ||
354 | if (read) | ||
355 | ret = read; | ||
356 | unlock_out: | ||
357 | release_console_sem(); | ||
358 | mutex_unlock(&con_buf_mtx); | ||
359 | return ret; | ||
360 | } | ||
361 | |||
362 | static ssize_t | ||
363 | vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
364 | { | ||
365 | struct inode *inode = file->f_path.dentry->d_inode; | ||
366 | unsigned int currcons = iminor(inode); | ||
367 | struct vc_data *vc; | ||
368 | long pos; | ||
369 | long viewed, attr, size, written; | ||
370 | char *con_buf0; | ||
371 | int col, maxcol; | ||
372 | u16 *org0 = NULL, *org = NULL; | ||
373 | size_t ret; | ||
374 | |||
375 | mutex_lock(&con_buf_mtx); | ||
376 | |||
377 | pos = *ppos; | ||
378 | |||
379 | /* Select the proper current console and verify | ||
380 | * sanity of the situation under the console lock. | ||
381 | */ | ||
382 | acquire_console_sem(); | ||
383 | |||
384 | attr = (currcons & 128); | ||
385 | currcons = (currcons & 127); | ||
386 | |||
387 | if (currcons == 0) { | ||
388 | currcons = fg_console; | ||
389 | viewed = 1; | ||
390 | } else { | ||
391 | currcons--; | ||
392 | viewed = 0; | ||
393 | } | ||
394 | ret = -ENXIO; | ||
395 | if (!vc_cons_allocated(currcons)) | ||
396 | goto unlock_out; | ||
397 | vc = vc_cons[currcons].d; | ||
398 | |||
399 | size = vcs_size(inode); | ||
400 | ret = -EINVAL; | ||
401 | if (pos < 0 || pos > size) | ||
402 | goto unlock_out; | ||
403 | if (count > size - pos) | ||
404 | count = size - pos; | ||
405 | written = 0; | ||
406 | while (count) { | ||
407 | long this_round = count; | ||
408 | size_t orig_count; | ||
409 | long p; | ||
410 | |||
411 | if (this_round > CON_BUF_SIZE) | ||
412 | this_round = CON_BUF_SIZE; | ||
413 | |||
414 | /* Temporarily drop the console lock so that we can read | ||
415 | * in the write data from userspace safely. | ||
416 | */ | ||
417 | release_console_sem(); | ||
418 | ret = copy_from_user(con_buf, buf, this_round); | ||
419 | acquire_console_sem(); | ||
420 | |||
421 | if (ret) { | ||
422 | this_round -= ret; | ||
423 | if (!this_round) { | ||
424 | /* Abort loop if no data were copied. Otherwise | ||
425 | * fail with -EFAULT. | ||
426 | */ | ||
427 | if (written) | ||
428 | break; | ||
429 | ret = -EFAULT; | ||
430 | goto unlock_out; | ||
431 | } | ||
432 | } | ||
433 | |||
434 | /* The vcs_size might have changed while we slept to grab | ||
435 | * the user buffer, so recheck. | ||
436 | * Return data written up to now on failure. | ||
437 | */ | ||
438 | size = vcs_size(inode); | ||
439 | if (pos >= size) | ||
440 | break; | ||
441 | if (this_round > size - pos) | ||
442 | this_round = size - pos; | ||
443 | |||
444 | /* OK, now actually push the write to the console | ||
445 | * under the lock using the local kernel buffer. | ||
446 | */ | ||
447 | |||
448 | con_buf0 = con_buf; | ||
449 | orig_count = this_round; | ||
450 | maxcol = vc->vc_cols; | ||
451 | p = pos; | ||
452 | if (!attr) { | ||
453 | org0 = org = screen_pos(vc, p, viewed); | ||
454 | col = p % maxcol; | ||
455 | p += maxcol - col; | ||
456 | |||
457 | while (this_round > 0) { | ||
458 | unsigned char c = *con_buf0++; | ||
459 | |||
460 | this_round--; | ||
461 | vcs_scr_writew(vc, | ||
462 | (vcs_scr_readw(vc, org) & 0xff00) | c, org); | ||
463 | org++; | ||
464 | if (++col == maxcol) { | ||
465 | org = screen_pos(vc, p, viewed); | ||
466 | col = 0; | ||
467 | p += maxcol; | ||
468 | } | ||
469 | } | ||
470 | } else { | ||
471 | if (p < HEADER_SIZE) { | ||
472 | char header[HEADER_SIZE]; | ||
473 | |||
474 | getconsxy(vc, header + 2); | ||
475 | while (p < HEADER_SIZE && this_round > 0) { | ||
476 | this_round--; | ||
477 | header[p++] = *con_buf0++; | ||
478 | } | ||
479 | if (!viewed) | ||
480 | putconsxy(vc, header + 2); | ||
481 | } | ||
482 | p -= HEADER_SIZE; | ||
483 | col = (p/2) % maxcol; | ||
484 | if (this_round > 0) { | ||
485 | org0 = org = screen_pos(vc, p/2, viewed); | ||
486 | if ((p & 1) && this_round > 0) { | ||
487 | char c; | ||
488 | |||
489 | this_round--; | ||
490 | c = *con_buf0++; | ||
491 | #ifdef __BIG_ENDIAN | ||
492 | vcs_scr_writew(vc, c | | ||
493 | (vcs_scr_readw(vc, org) & 0xff00), org); | ||
494 | #else | ||
495 | vcs_scr_writew(vc, (c << 8) | | ||
496 | (vcs_scr_readw(vc, org) & 0xff), org); | ||
497 | #endif | ||
498 | org++; | ||
499 | p++; | ||
500 | if (++col == maxcol) { | ||
501 | org = screen_pos(vc, p/2, viewed); | ||
502 | col = 0; | ||
503 | } | ||
504 | } | ||
505 | p /= 2; | ||
506 | p += maxcol - col; | ||
507 | } | ||
508 | while (this_round > 1) { | ||
509 | unsigned short w; | ||
510 | |||
511 | w = get_unaligned(((unsigned short *)con_buf0)); | ||
512 | vcs_scr_writew(vc, w, org++); | ||
513 | con_buf0 += 2; | ||
514 | this_round -= 2; | ||
515 | if (++col == maxcol) { | ||
516 | org = screen_pos(vc, p, viewed); | ||
517 | col = 0; | ||
518 | p += maxcol; | ||
519 | } | ||
520 | } | ||
521 | if (this_round > 0) { | ||
522 | unsigned char c; | ||
523 | |||
524 | c = *con_buf0++; | ||
525 | #ifdef __BIG_ENDIAN | ||
526 | vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff) | (c << 8), org); | ||
527 | #else | ||
528 | vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff00) | c, org); | ||
529 | #endif | ||
530 | } | ||
531 | } | ||
532 | count -= orig_count; | ||
533 | written += orig_count; | ||
534 | buf += orig_count; | ||
535 | pos += orig_count; | ||
536 | if (org0) | ||
537 | update_region(vc, (unsigned long)(org0), org - org0); | ||
538 | } | ||
539 | *ppos += written; | ||
540 | ret = written; | ||
541 | if (written) | ||
542 | vcs_scr_updated(vc); | ||
543 | |||
544 | unlock_out: | ||
545 | release_console_sem(); | ||
546 | |||
547 | mutex_unlock(&con_buf_mtx); | ||
548 | |||
549 | return ret; | ||
550 | } | ||
551 | |||
552 | static unsigned int | ||
553 | vcs_poll(struct file *file, poll_table *wait) | ||
554 | { | ||
555 | struct vcs_poll_data *poll = vcs_poll_data_get(file); | ||
556 | int ret = 0; | ||
557 | |||
558 | if (poll) { | ||
559 | poll_wait(file, &poll->waitq, wait); | ||
560 | if (!poll->seen_last_update) | ||
561 | ret = POLLIN | POLLRDNORM; | ||
562 | } | ||
563 | return ret; | ||
564 | } | ||
565 | |||
566 | static int | ||
567 | vcs_fasync(int fd, struct file *file, int on) | ||
568 | { | ||
569 | struct vcs_poll_data *poll = file->private_data; | ||
570 | |||
571 | if (!poll) { | ||
572 | /* don't allocate anything if all we want is disable fasync */ | ||
573 | if (!on) | ||
574 | return 0; | ||
575 | poll = vcs_poll_data_get(file); | ||
576 | if (!poll) | ||
577 | return -ENOMEM; | ||
578 | } | ||
579 | |||
580 | return fasync_helper(fd, file, on, &poll->fasync); | ||
581 | } | ||
582 | |||
583 | static int | ||
584 | vcs_open(struct inode *inode, struct file *filp) | ||
585 | { | ||
586 | unsigned int currcons = iminor(inode) & 127; | ||
587 | int ret = 0; | ||
588 | |||
589 | tty_lock(); | ||
590 | if(currcons && !vc_cons_allocated(currcons-1)) | ||
591 | ret = -ENXIO; | ||
592 | tty_unlock(); | ||
593 | return ret; | ||
594 | } | ||
595 | |||
596 | static int vcs_release(struct inode *inode, struct file *file) | ||
597 | { | ||
598 | struct vcs_poll_data *poll = file->private_data; | ||
599 | |||
600 | if (poll) | ||
601 | vcs_poll_data_free(poll); | ||
602 | return 0; | ||
603 | } | ||
604 | |||
605 | static const struct file_operations vcs_fops = { | ||
606 | .llseek = vcs_lseek, | ||
607 | .read = vcs_read, | ||
608 | .write = vcs_write, | ||
609 | .poll = vcs_poll, | ||
610 | .fasync = vcs_fasync, | ||
611 | .open = vcs_open, | ||
612 | .release = vcs_release, | ||
613 | }; | ||
614 | |||
615 | static struct class *vc_class; | ||
616 | |||
617 | void vcs_make_sysfs(int index) | ||
618 | { | ||
619 | device_create(vc_class, NULL, MKDEV(VCS_MAJOR, index + 1), NULL, | ||
620 | "vcs%u", index + 1); | ||
621 | device_create(vc_class, NULL, MKDEV(VCS_MAJOR, index + 129), NULL, | ||
622 | "vcsa%u", index + 1); | ||
623 | } | ||
624 | |||
625 | void vcs_remove_sysfs(int index) | ||
626 | { | ||
627 | device_destroy(vc_class, MKDEV(VCS_MAJOR, index + 1)); | ||
628 | device_destroy(vc_class, MKDEV(VCS_MAJOR, index + 129)); | ||
629 | } | ||
630 | |||
631 | int __init vcs_init(void) | ||
632 | { | ||
633 | unsigned int i; | ||
634 | |||
635 | if (register_chrdev(VCS_MAJOR, "vcs", &vcs_fops)) | ||
636 | panic("unable to get major %d for vcs device", VCS_MAJOR); | ||
637 | vc_class = class_create(THIS_MODULE, "vc"); | ||
638 | |||
639 | device_create(vc_class, NULL, MKDEV(VCS_MAJOR, 0), NULL, "vcs"); | ||
640 | device_create(vc_class, NULL, MKDEV(VCS_MAJOR, 128), NULL, "vcsa"); | ||
641 | for (i = 0; i < MIN_NR_CONSOLES; i++) | ||
642 | vcs_make_sysfs(i); | ||
643 | return 0; | ||
644 | } | ||