1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Provide access to virtual console memory. |
4 | * /dev/vcs: the screen as it is being viewed right now (possibly scrolled) |
5 | * /dev/vcsN: the screen of /dev/ttyN (1 <= N <= 63) |
6 | * [minor: N] |
7 | * |
8 | * /dev/vcsaN: idem, but including attributes, and prefixed with |
9 | * the 4 bytes lines,columns,x,y (as screendump used to give). |
10 | * Attribute/character pair is in native endianity. |
11 | * [minor: N+128] |
12 | * |
13 | * /dev/vcsuN: similar to /dev/vcsaN but using 4-byte unicode values |
14 | * instead of 1-byte screen glyph values. |
15 | * [minor: N+64] |
16 | * |
17 | * /dev/vcsuaN: same idea as /dev/vcsaN for unicode (not yet implemented). |
18 | * |
19 | * This replaces screendump and part of selection, so that the system |
20 | * administrator can control access using file system permissions. |
21 | * |
22 | * aeb@cwi.nl - efter Friedas begravelse - 950211 |
23 | * |
24 | * machek@k332.feld.cvut.cz - modified not to send characters to wrong console |
25 | * - fixed some fatal off-by-one bugs (0-- no longer == -1 -> looping and looping and looping...) |
26 | * - making it shorter - scr_readw are macros which expand in PRETTY long code |
27 | */ |
28 | |
29 | #include <linux/kernel.h> |
30 | #include <linux/major.h> |
31 | #include <linux/errno.h> |
32 | #include <linux/export.h> |
33 | #include <linux/tty.h> |
34 | #include <linux/interrupt.h> |
35 | #include <linux/mm.h> |
36 | #include <linux/init.h> |
37 | #include <linux/vt_kern.h> |
38 | #include <linux/selection.h> |
39 | #include <linux/kbd_kern.h> |
40 | #include <linux/console.h> |
41 | #include <linux/device.h> |
42 | #include <linux/sched.h> |
43 | #include <linux/fs.h> |
44 | #include <linux/poll.h> |
45 | #include <linux/signal.h> |
46 | #include <linux/slab.h> |
47 | #include <linux/notifier.h> |
48 | |
49 | #include <linux/uaccess.h> |
50 | #include <asm/byteorder.h> |
51 | #include <asm/unaligned.h> |
52 | |
53 | #define 4u |
54 | #define CON_BUF_SIZE (CONFIG_BASE_SMALL ? 256 : PAGE_SIZE) |
55 | |
56 | /* |
57 | * Our minor space: |
58 | * |
59 | * 0 ... 63 glyph mode without attributes |
60 | * 64 ... 127 unicode mode without attributes |
61 | * 128 ... 191 glyph mode with attributes |
62 | * 192 ... 255 unused (reserved for unicode with attributes) |
63 | * |
64 | * This relies on MAX_NR_CONSOLES being <= 63, meaning 63 actual consoles |
65 | * with minors 0, 64, 128 and 192 being proxies for the foreground console. |
66 | */ |
67 | #if MAX_NR_CONSOLES > 63 |
68 | #warning "/dev/vcs* devices may not accommodate more than 63 consoles" |
69 | #endif |
70 | |
71 | #define console(inode) (iminor(inode) & 63) |
72 | #define use_unicode(inode) (iminor(inode) & 64) |
73 | #define use_attributes(inode) (iminor(inode) & 128) |
74 | |
75 | |
76 | struct vcs_poll_data { |
77 | struct notifier_block notifier; |
78 | unsigned int cons_num; |
79 | int event; |
80 | wait_queue_head_t waitq; |
81 | struct fasync_struct *fasync; |
82 | }; |
83 | |
84 | static int |
85 | vcs_notifier(struct notifier_block *nb, unsigned long code, void *_param) |
86 | { |
87 | struct vt_notifier_param *param = _param; |
88 | struct vc_data *vc = param->vc; |
89 | struct vcs_poll_data *poll = |
90 | container_of(nb, struct vcs_poll_data, notifier); |
91 | int currcons = poll->cons_num; |
92 | int fa_band; |
93 | |
94 | switch (code) { |
95 | case VT_UPDATE: |
96 | fa_band = POLL_PRI; |
97 | break; |
98 | case VT_DEALLOCATE: |
99 | fa_band = POLL_HUP; |
100 | break; |
101 | default: |
102 | return NOTIFY_DONE; |
103 | } |
104 | |
105 | if (currcons == 0) |
106 | currcons = fg_console; |
107 | else |
108 | currcons--; |
109 | if (currcons != vc->vc_num) |
110 | return NOTIFY_DONE; |
111 | |
112 | poll->event = code; |
113 | wake_up_interruptible(&poll->waitq); |
114 | kill_fasync(&poll->fasync, SIGIO, fa_band); |
115 | return NOTIFY_OK; |
116 | } |
117 | |
118 | static void |
119 | vcs_poll_data_free(struct vcs_poll_data *poll) |
120 | { |
121 | unregister_vt_notifier(nb: &poll->notifier); |
122 | kfree(objp: poll); |
123 | } |
124 | |
125 | static struct vcs_poll_data * |
126 | vcs_poll_data_get(struct file *file) |
127 | { |
128 | struct vcs_poll_data *poll = file->private_data, *kill = NULL; |
129 | |
130 | if (poll) |
131 | return poll; |
132 | |
133 | poll = kzalloc(size: sizeof(*poll), GFP_KERNEL); |
134 | if (!poll) |
135 | return NULL; |
136 | poll->cons_num = console(file_inode(file)); |
137 | init_waitqueue_head(&poll->waitq); |
138 | poll->notifier.notifier_call = vcs_notifier; |
139 | /* |
140 | * In order not to lose any update event, we must pretend one might |
141 | * have occurred before we have a chance to register our notifier. |
142 | * This is also how user space has come to detect which kernels |
143 | * support POLLPRI on /dev/vcs* devices i.e. using poll() with |
144 | * POLLPRI and a zero timeout. |
145 | */ |
146 | poll->event = VT_UPDATE; |
147 | |
148 | if (register_vt_notifier(nb: &poll->notifier) != 0) { |
149 | kfree(objp: poll); |
150 | return NULL; |
151 | } |
152 | |
153 | /* |
154 | * This code may be called either through ->poll() or ->fasync(). |
155 | * If we have two threads using the same file descriptor, they could |
156 | * both enter this function, both notice that the structure hasn't |
157 | * been allocated yet and go ahead allocating it in parallel, but |
158 | * only one of them must survive and be shared otherwise we'd leak |
159 | * memory with a dangling notifier callback. |
160 | */ |
161 | spin_lock(lock: &file->f_lock); |
162 | if (!file->private_data) { |
163 | file->private_data = poll; |
164 | } else { |
165 | /* someone else raced ahead of us */ |
166 | kill = poll; |
167 | poll = file->private_data; |
168 | } |
169 | spin_unlock(lock: &file->f_lock); |
170 | if (kill) |
171 | vcs_poll_data_free(poll: kill); |
172 | |
173 | return poll; |
174 | } |
175 | |
176 | /** |
177 | * vcs_vc - return VC for @inode |
178 | * @inode: inode for which to return a VC |
179 | * @viewed: returns whether this console is currently foreground (viewed) |
180 | * |
181 | * Must be called with console_lock. |
182 | */ |
183 | static struct vc_data *vcs_vc(struct inode *inode, bool *viewed) |
184 | { |
185 | unsigned int currcons = console(inode); |
186 | |
187 | WARN_CONSOLE_UNLOCKED(); |
188 | |
189 | if (currcons == 0) { |
190 | currcons = fg_console; |
191 | if (viewed) |
192 | *viewed = true; |
193 | } else { |
194 | currcons--; |
195 | if (viewed) |
196 | *viewed = false; |
197 | } |
198 | return vc_cons[currcons].d; |
199 | } |
200 | |
201 | /** |
202 | * vcs_size - return size for a VC in @vc |
203 | * @vc: which VC |
204 | * @attr: does it use attributes? |
205 | * @unicode: is it unicode? |
206 | * |
207 | * Must be called with console_lock. |
208 | */ |
209 | static int vcs_size(const struct vc_data *vc, bool attr, bool unicode) |
210 | { |
211 | int size; |
212 | |
213 | WARN_CONSOLE_UNLOCKED(); |
214 | |
215 | size = vc->vc_rows * vc->vc_cols; |
216 | |
217 | if (attr) { |
218 | if (unicode) |
219 | return -EOPNOTSUPP; |
220 | |
221 | size = 2 * size + HEADER_SIZE; |
222 | } else if (unicode) |
223 | size *= 4; |
224 | |
225 | return size; |
226 | } |
227 | |
228 | static loff_t vcs_lseek(struct file *file, loff_t offset, int orig) |
229 | { |
230 | struct inode *inode = file_inode(f: file); |
231 | struct vc_data *vc; |
232 | int size; |
233 | |
234 | console_lock(); |
235 | vc = vcs_vc(inode, NULL); |
236 | if (!vc) { |
237 | console_unlock(); |
238 | return -ENXIO; |
239 | } |
240 | |
241 | size = vcs_size(vc, use_attributes(inode), use_unicode(inode)); |
242 | console_unlock(); |
243 | if (size < 0) |
244 | return size; |
245 | return fixed_size_llseek(file, offset, whence: orig, size); |
246 | } |
247 | |
248 | static int vcs_read_buf_uni(struct vc_data *vc, char *con_buf, |
249 | unsigned int pos, unsigned int count, bool viewed) |
250 | { |
251 | unsigned int nr, row, col, maxcol = vc->vc_cols; |
252 | int ret; |
253 | |
254 | ret = vc_uniscr_check(vc); |
255 | if (ret) |
256 | return ret; |
257 | |
258 | pos /= 4; |
259 | row = pos / maxcol; |
260 | col = pos % maxcol; |
261 | nr = maxcol - col; |
262 | do { |
263 | if (nr > count / 4) |
264 | nr = count / 4; |
265 | vc_uniscr_copy_line(vc, dest: con_buf, viewed, row, col, nr); |
266 | con_buf += nr * 4; |
267 | count -= nr * 4; |
268 | row++; |
269 | col = 0; |
270 | nr = maxcol; |
271 | } while (count); |
272 | |
273 | return 0; |
274 | } |
275 | |
276 | static void vcs_read_buf_noattr(const struct vc_data *vc, char *con_buf, |
277 | unsigned int pos, unsigned int count, bool viewed) |
278 | { |
279 | u16 *org; |
280 | unsigned int col, maxcol = vc->vc_cols; |
281 | |
282 | org = screen_pos(vc, w_offset: pos, viewed); |
283 | col = pos % maxcol; |
284 | pos += maxcol - col; |
285 | |
286 | while (count-- > 0) { |
287 | *con_buf++ = (vcs_scr_readw(vc, org: org++) & 0xff); |
288 | if (++col == maxcol) { |
289 | org = screen_pos(vc, w_offset: pos, viewed); |
290 | col = 0; |
291 | pos += maxcol; |
292 | } |
293 | } |
294 | } |
295 | |
296 | static unsigned int vcs_read_buf(const struct vc_data *vc, char *con_buf, |
297 | unsigned int pos, unsigned int count, bool viewed, |
298 | unsigned int *skip) |
299 | { |
300 | u16 *org, *con_buf16; |
301 | unsigned int col, maxcol = vc->vc_cols; |
302 | unsigned int filled = count; |
303 | |
304 | if (pos < HEADER_SIZE) { |
305 | /* clamp header values if they don't fit */ |
306 | con_buf[0] = min(vc->vc_rows, 0xFFu); |
307 | con_buf[1] = min(vc->vc_cols, 0xFFu); |
308 | getconsxy(vc, xy: con_buf + 2); |
309 | |
310 | *skip += pos; |
311 | count += pos; |
312 | if (count > CON_BUF_SIZE) { |
313 | count = CON_BUF_SIZE; |
314 | filled = count - pos; |
315 | } |
316 | |
317 | /* Advance state pointers and move on. */ |
318 | count -= min(HEADER_SIZE, count); |
319 | pos = HEADER_SIZE; |
320 | con_buf += HEADER_SIZE; |
321 | /* If count >= 0, then pos is even... */ |
322 | } else if (pos & 1) { |
323 | /* |
324 | * Skip first byte for output if start address is odd. Update |
325 | * region sizes up/down depending on free space in buffer. |
326 | */ |
327 | (*skip)++; |
328 | if (count < CON_BUF_SIZE) |
329 | count++; |
330 | else |
331 | filled--; |
332 | } |
333 | |
334 | if (!count) |
335 | return filled; |
336 | |
337 | pos -= HEADER_SIZE; |
338 | pos /= 2; |
339 | col = pos % maxcol; |
340 | |
341 | org = screen_pos(vc, w_offset: pos, viewed); |
342 | pos += maxcol - col; |
343 | |
344 | /* |
345 | * Buffer has even length, so we can always copy character + attribute. |
346 | * We do not copy last byte to userspace if count is odd. |
347 | */ |
348 | count = (count + 1) / 2; |
349 | con_buf16 = (u16 *)con_buf; |
350 | |
351 | while (count) { |
352 | *con_buf16++ = vcs_scr_readw(vc, org: org++); |
353 | count--; |
354 | if (++col == maxcol) { |
355 | org = screen_pos(vc, w_offset: pos, viewed); |
356 | col = 0; |
357 | pos += maxcol; |
358 | } |
359 | } |
360 | |
361 | return filled; |
362 | } |
363 | |
364 | static ssize_t |
365 | vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) |
366 | { |
367 | struct inode *inode = file_inode(f: file); |
368 | struct vc_data *vc; |
369 | struct vcs_poll_data *poll; |
370 | unsigned int read; |
371 | ssize_t ret; |
372 | char *con_buf; |
373 | loff_t pos; |
374 | bool viewed, attr, uni_mode; |
375 | |
376 | con_buf = (char *) __get_free_page(GFP_KERNEL); |
377 | if (!con_buf) |
378 | return -ENOMEM; |
379 | |
380 | pos = *ppos; |
381 | |
382 | /* Select the proper current console and verify |
383 | * sanity of the situation under the console lock. |
384 | */ |
385 | console_lock(); |
386 | |
387 | uni_mode = use_unicode(inode); |
388 | attr = use_attributes(inode); |
389 | |
390 | ret = -EINVAL; |
391 | if (pos < 0) |
392 | goto unlock_out; |
393 | /* we enforce 32-bit alignment for pos and count in unicode mode */ |
394 | if (uni_mode && (pos | count) & 3) |
395 | goto unlock_out; |
396 | |
397 | poll = file->private_data; |
398 | if (count && poll) |
399 | poll->event = 0; |
400 | read = 0; |
401 | ret = 0; |
402 | while (count) { |
403 | unsigned int this_round, skip = 0; |
404 | int size; |
405 | |
406 | vc = vcs_vc(inode, viewed: &viewed); |
407 | if (!vc) { |
408 | ret = -ENXIO; |
409 | break; |
410 | } |
411 | |
412 | /* Check whether we are above size each round, |
413 | * as copy_to_user at the end of this loop |
414 | * could sleep. |
415 | */ |
416 | size = vcs_size(vc, attr, unicode: uni_mode); |
417 | if (size < 0) { |
418 | ret = size; |
419 | break; |
420 | } |
421 | if (pos >= size) |
422 | break; |
423 | if (count > size - pos) |
424 | count = size - pos; |
425 | |
426 | this_round = count; |
427 | if (this_round > CON_BUF_SIZE) |
428 | this_round = CON_BUF_SIZE; |
429 | |
430 | /* Perform the whole read into the local con_buf. |
431 | * Then we can drop the console spinlock and safely |
432 | * attempt to move it to userspace. |
433 | */ |
434 | |
435 | if (uni_mode) { |
436 | ret = vcs_read_buf_uni(vc, con_buf, pos, count: this_round, |
437 | viewed); |
438 | if (ret) |
439 | break; |
440 | } else if (!attr) { |
441 | vcs_read_buf_noattr(vc, con_buf, pos, count: this_round, |
442 | viewed); |
443 | } else { |
444 | this_round = vcs_read_buf(vc, con_buf, pos, count: this_round, |
445 | viewed, skip: &skip); |
446 | } |
447 | |
448 | /* Finally, release the console semaphore while we push |
449 | * all the data to userspace from our temporary buffer. |
450 | * |
451 | * AKPM: Even though it's a semaphore, we should drop it because |
452 | * the pagefault handling code may want to call printk(). |
453 | */ |
454 | |
455 | console_unlock(); |
456 | ret = copy_to_user(to: buf, from: con_buf + skip, n: this_round); |
457 | console_lock(); |
458 | |
459 | if (ret) { |
460 | read += this_round - ret; |
461 | ret = -EFAULT; |
462 | break; |
463 | } |
464 | buf += this_round; |
465 | pos += this_round; |
466 | read += this_round; |
467 | count -= this_round; |
468 | } |
469 | *ppos += read; |
470 | if (read) |
471 | ret = read; |
472 | unlock_out: |
473 | console_unlock(); |
474 | free_page((unsigned long) con_buf); |
475 | return ret; |
476 | } |
477 | |
478 | static u16 *vcs_write_buf_noattr(struct vc_data *vc, const char *con_buf, |
479 | unsigned int pos, unsigned int count, bool viewed, u16 **org0) |
480 | { |
481 | u16 *org; |
482 | unsigned int col, maxcol = vc->vc_cols; |
483 | |
484 | *org0 = org = screen_pos(vc, w_offset: pos, viewed); |
485 | col = pos % maxcol; |
486 | pos += maxcol - col; |
487 | |
488 | while (count > 0) { |
489 | unsigned char c = *con_buf++; |
490 | |
491 | count--; |
492 | vcs_scr_writew(vc, |
493 | val: (vcs_scr_readw(vc, org) & 0xff00) | c, org); |
494 | org++; |
495 | if (++col == maxcol) { |
496 | org = screen_pos(vc, w_offset: pos, viewed); |
497 | col = 0; |
498 | pos += maxcol; |
499 | } |
500 | } |
501 | |
502 | return org; |
503 | } |
504 | |
505 | /* |
506 | * Compilers (gcc 10) are unable to optimize the swap in cpu_to_le16. So do it |
507 | * the poor man way. |
508 | */ |
509 | static inline u16 vc_compile_le16(u8 hi, u8 lo) |
510 | { |
511 | #ifdef __BIG_ENDIAN |
512 | return (lo << 8u) | hi; |
513 | #else |
514 | return (hi << 8u) | lo; |
515 | #endif |
516 | } |
517 | |
518 | static u16 *vcs_write_buf(struct vc_data *vc, const char *con_buf, |
519 | unsigned int pos, unsigned int count, bool viewed, u16 **org0) |
520 | { |
521 | u16 *org; |
522 | unsigned int col, maxcol = vc->vc_cols; |
523 | unsigned char c; |
524 | |
525 | /* header */ |
526 | if (pos < HEADER_SIZE) { |
527 | char [HEADER_SIZE]; |
528 | |
529 | getconsxy(vc, xy: header + 2); |
530 | while (pos < HEADER_SIZE && count > 0) { |
531 | count--; |
532 | header[pos++] = *con_buf++; |
533 | } |
534 | if (!viewed) |
535 | putconsxy(vc, xy: header + 2); |
536 | } |
537 | |
538 | if (!count) |
539 | return NULL; |
540 | |
541 | pos -= HEADER_SIZE; |
542 | col = (pos/2) % maxcol; |
543 | |
544 | *org0 = org = screen_pos(vc, w_offset: pos/2, viewed); |
545 | |
546 | /* odd pos -- the first single character */ |
547 | if (pos & 1) { |
548 | count--; |
549 | c = *con_buf++; |
550 | vcs_scr_writew(vc, val: vc_compile_le16(hi: c, lo: vcs_scr_readw(vc, org)), |
551 | org); |
552 | org++; |
553 | pos++; |
554 | if (++col == maxcol) { |
555 | org = screen_pos(vc, w_offset: pos/2, viewed); |
556 | col = 0; |
557 | } |
558 | } |
559 | |
560 | pos /= 2; |
561 | pos += maxcol - col; |
562 | |
563 | /* even pos -- handle attr+character pairs */ |
564 | while (count > 1) { |
565 | unsigned short w; |
566 | |
567 | w = get_unaligned(((unsigned short *)con_buf)); |
568 | vcs_scr_writew(vc, val: w, org: org++); |
569 | con_buf += 2; |
570 | count -= 2; |
571 | if (++col == maxcol) { |
572 | org = screen_pos(vc, w_offset: pos, viewed); |
573 | col = 0; |
574 | pos += maxcol; |
575 | } |
576 | } |
577 | |
578 | if (!count) |
579 | return org; |
580 | |
581 | /* odd pos -- the remaining character */ |
582 | c = *con_buf++; |
583 | vcs_scr_writew(vc, val: vc_compile_le16(hi: vcs_scr_readw(vc, org) >> 8, lo: c), |
584 | org); |
585 | |
586 | return org; |
587 | } |
588 | |
589 | static ssize_t |
590 | vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) |
591 | { |
592 | struct inode *inode = file_inode(f: file); |
593 | struct vc_data *vc; |
594 | char *con_buf; |
595 | u16 *org0, *org; |
596 | unsigned int written; |
597 | int size; |
598 | ssize_t ret; |
599 | loff_t pos; |
600 | bool viewed, attr; |
601 | |
602 | if (use_unicode(inode)) |
603 | return -EOPNOTSUPP; |
604 | |
605 | con_buf = (char *) __get_free_page(GFP_KERNEL); |
606 | if (!con_buf) |
607 | return -ENOMEM; |
608 | |
609 | pos = *ppos; |
610 | |
611 | /* Select the proper current console and verify |
612 | * sanity of the situation under the console lock. |
613 | */ |
614 | console_lock(); |
615 | |
616 | attr = use_attributes(inode); |
617 | ret = -ENXIO; |
618 | vc = vcs_vc(inode, viewed: &viewed); |
619 | if (!vc) |
620 | goto unlock_out; |
621 | |
622 | size = vcs_size(vc, attr, unicode: false); |
623 | if (size < 0) { |
624 | ret = size; |
625 | goto unlock_out; |
626 | } |
627 | ret = -EINVAL; |
628 | if (pos < 0 || pos > size) |
629 | goto unlock_out; |
630 | if (count > size - pos) |
631 | count = size - pos; |
632 | written = 0; |
633 | while (count) { |
634 | unsigned int this_round = count; |
635 | |
636 | if (this_round > CON_BUF_SIZE) |
637 | this_round = CON_BUF_SIZE; |
638 | |
639 | /* Temporarily drop the console lock so that we can read |
640 | * in the write data from userspace safely. |
641 | */ |
642 | console_unlock(); |
643 | ret = copy_from_user(to: con_buf, from: buf, n: this_round); |
644 | console_lock(); |
645 | |
646 | if (ret) { |
647 | this_round -= ret; |
648 | if (!this_round) { |
649 | /* Abort loop if no data were copied. Otherwise |
650 | * fail with -EFAULT. |
651 | */ |
652 | if (written) |
653 | break; |
654 | ret = -EFAULT; |
655 | goto unlock_out; |
656 | } |
657 | } |
658 | |
659 | /* The vc might have been freed or vcs_size might have changed |
660 | * while we slept to grab the user buffer, so recheck. |
661 | * Return data written up to now on failure. |
662 | */ |
663 | vc = vcs_vc(inode, viewed: &viewed); |
664 | if (!vc) { |
665 | if (written) |
666 | break; |
667 | ret = -ENXIO; |
668 | goto unlock_out; |
669 | } |
670 | size = vcs_size(vc, attr, unicode: false); |
671 | if (size < 0) { |
672 | if (written) |
673 | break; |
674 | ret = size; |
675 | goto unlock_out; |
676 | } |
677 | if (pos >= size) |
678 | break; |
679 | if (this_round > size - pos) |
680 | this_round = size - pos; |
681 | |
682 | /* OK, now actually push the write to the console |
683 | * under the lock using the local kernel buffer. |
684 | */ |
685 | |
686 | if (attr) |
687 | org = vcs_write_buf(vc, con_buf, pos, count: this_round, |
688 | viewed, org0: &org0); |
689 | else |
690 | org = vcs_write_buf_noattr(vc, con_buf, pos, count: this_round, |
691 | viewed, org0: &org0); |
692 | |
693 | count -= this_round; |
694 | written += this_round; |
695 | buf += this_round; |
696 | pos += this_round; |
697 | if (org) |
698 | update_region(vc, start: (unsigned long)(org0), count: org - org0); |
699 | } |
700 | *ppos += written; |
701 | ret = written; |
702 | if (written) |
703 | vcs_scr_updated(vc); |
704 | |
705 | unlock_out: |
706 | console_unlock(); |
707 | free_page((unsigned long) con_buf); |
708 | return ret; |
709 | } |
710 | |
711 | static __poll_t |
712 | vcs_poll(struct file *file, poll_table *wait) |
713 | { |
714 | struct vcs_poll_data *poll = vcs_poll_data_get(file); |
715 | __poll_t ret = DEFAULT_POLLMASK|EPOLLERR; |
716 | |
717 | if (poll) { |
718 | poll_wait(filp: file, wait_address: &poll->waitq, p: wait); |
719 | switch (poll->event) { |
720 | case VT_UPDATE: |
721 | ret = DEFAULT_POLLMASK|EPOLLPRI; |
722 | break; |
723 | case VT_DEALLOCATE: |
724 | ret = DEFAULT_POLLMASK|EPOLLHUP|EPOLLERR; |
725 | break; |
726 | case 0: |
727 | ret = DEFAULT_POLLMASK; |
728 | break; |
729 | } |
730 | } |
731 | return ret; |
732 | } |
733 | |
734 | static int |
735 | vcs_fasync(int fd, struct file *file, int on) |
736 | { |
737 | struct vcs_poll_data *poll = file->private_data; |
738 | |
739 | if (!poll) { |
740 | /* don't allocate anything if all we want is disable fasync */ |
741 | if (!on) |
742 | return 0; |
743 | poll = vcs_poll_data_get(file); |
744 | if (!poll) |
745 | return -ENOMEM; |
746 | } |
747 | |
748 | return fasync_helper(fd, file, on, &poll->fasync); |
749 | } |
750 | |
751 | static int |
752 | vcs_open(struct inode *inode, struct file *filp) |
753 | { |
754 | unsigned int currcons = console(inode); |
755 | bool attr = use_attributes(inode); |
756 | bool uni_mode = use_unicode(inode); |
757 | int ret = 0; |
758 | |
759 | /* we currently don't support attributes in unicode mode */ |
760 | if (attr && uni_mode) |
761 | return -EOPNOTSUPP; |
762 | |
763 | console_lock(); |
764 | if(currcons && !vc_cons_allocated(console: currcons-1)) |
765 | ret = -ENXIO; |
766 | console_unlock(); |
767 | return ret; |
768 | } |
769 | |
770 | static int vcs_release(struct inode *inode, struct file *file) |
771 | { |
772 | struct vcs_poll_data *poll = file->private_data; |
773 | |
774 | if (poll) |
775 | vcs_poll_data_free(poll); |
776 | return 0; |
777 | } |
778 | |
779 | static const struct file_operations vcs_fops = { |
780 | .llseek = vcs_lseek, |
781 | .read = vcs_read, |
782 | .write = vcs_write, |
783 | .poll = vcs_poll, |
784 | .fasync = vcs_fasync, |
785 | .open = vcs_open, |
786 | .release = vcs_release, |
787 | }; |
788 | |
789 | static const struct class vc_class = { |
790 | .name = "vc" , |
791 | }; |
792 | |
793 | void vcs_make_sysfs(int index) |
794 | { |
795 | device_create(cls: &vc_class, NULL, MKDEV(VCS_MAJOR, index + 1), NULL, fmt: "vcs%u" , index + 1); |
796 | device_create(cls: &vc_class, NULL, MKDEV(VCS_MAJOR, index + 65), NULL, fmt: "vcsu%u" , index + 1); |
797 | device_create(cls: &vc_class, NULL, MKDEV(VCS_MAJOR, index + 129), NULL, fmt: "vcsa%u" , index + 1); |
798 | } |
799 | |
800 | void vcs_remove_sysfs(int index) |
801 | { |
802 | device_destroy(cls: &vc_class, MKDEV(VCS_MAJOR, index + 1)); |
803 | device_destroy(cls: &vc_class, MKDEV(VCS_MAJOR, index + 65)); |
804 | device_destroy(cls: &vc_class, MKDEV(VCS_MAJOR, index + 129)); |
805 | } |
806 | |
807 | int __init vcs_init(void) |
808 | { |
809 | unsigned int i; |
810 | |
811 | if (register_chrdev(VCS_MAJOR, name: "vcs" , fops: &vcs_fops)) |
812 | panic(fmt: "unable to get major %d for vcs device" , VCS_MAJOR); |
813 | if (class_register(class: &vc_class)) |
814 | panic(fmt: "unable to create vc_class" ); |
815 | |
816 | device_create(cls: &vc_class, NULL, MKDEV(VCS_MAJOR, 0), NULL, fmt: "vcs" ); |
817 | device_create(cls: &vc_class, NULL, MKDEV(VCS_MAJOR, 64), NULL, fmt: "vcsu" ); |
818 | device_create(cls: &vc_class, NULL, MKDEV(VCS_MAJOR, 128), NULL, fmt: "vcsa" ); |
819 | for (i = 0; i < MIN_NR_CONSOLES; i++) |
820 | vcs_make_sysfs(index: i); |
821 | return 0; |
822 | } |
823 | |