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