diff options
Diffstat (limited to 'drivers/tty/vt/vc_screen.c')
-rw-r--r-- | drivers/tty/vt/vc_screen.c | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c new file mode 100644 index 000000000..2fb509d57 --- /dev/null +++ b/drivers/tty/vt/vc_screen.c @@ -0,0 +1,719 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Provide access to virtual console memory. + * /dev/vcs0: the screen as it is being viewed right now (possibly scrolled) + * /dev/vcsN: the screen of /dev/ttyN (1 <= N <= 63) + * [minor: N] + * + * /dev/vcsaN: idem, but including attributes, and prefixed with + * the 4 bytes lines,columns,x,y (as screendump used to give). + * Attribute/character pair is in native endianity. + * [minor: N+128] + * + * /dev/vcsuN: similar to /dev/vcsaN but using 4-byte unicode values + * instead of 1-byte screen glyph values. + * [minor: N+64] + * + * /dev/vcsuaN: same idea as /dev/vcsaN for unicode (not yet implemented). + * + * This replaces screendump and part of selection, so that the system + * administrator can control access using file system permissions. + * + * aeb@cwi.nl - efter Friedas begravelse - 950211 + * + * machek@k332.feld.cvut.cz - modified not to send characters to wrong console + * - fixed some fatal off-by-one bugs (0-- no longer == -1 -> looping and looping and looping...) + * - making it shorter - scr_readw are macros which expand in PRETTY long code + */ + +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/tty.h> +#include <linux/interrupt.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/vt_kern.h> +#include <linux/selection.h> +#include <linux/kbd_kern.h> +#include <linux/console.h> +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/notifier.h> + +#include <linux/uaccess.h> +#include <asm/byteorder.h> +#include <asm/unaligned.h> + +#undef attr +#undef org +#undef addr +#define HEADER_SIZE 4 + +#define CON_BUF_SIZE (CONFIG_BASE_SMALL ? 256 : PAGE_SIZE) + +/* + * Our minor space: + * + * 0 ... 63 glyph mode without attributes + * 64 ... 127 unicode mode without attributes + * 128 ... 191 glyph mode with attributes + * 192 ... 255 unused (reserved for unicode with attributes) + * + * This relies on MAX_NR_CONSOLES being <= 63, meaning 63 actual consoles + * with minors 0, 64, 128 and 192 being proxies for the foreground console. + */ +#if MAX_NR_CONSOLES > 63 +#warning "/dev/vcs* devices may not accommodate more than 63 consoles" +#endif + +#define console(inode) (iminor(inode) & 63) +#define use_unicode(inode) (iminor(inode) & 64) +#define use_attributes(inode) (iminor(inode) & 128) + + +struct vcs_poll_data { + struct notifier_block notifier; + unsigned int cons_num; + bool seen_last_update; + wait_queue_head_t waitq; + struct fasync_struct *fasync; +}; + +static int +vcs_notifier(struct notifier_block *nb, unsigned long code, void *_param) +{ + struct vt_notifier_param *param = _param; + struct vc_data *vc = param->vc; + struct vcs_poll_data *poll = + container_of(nb, struct vcs_poll_data, notifier); + int currcons = poll->cons_num; + + if (code != VT_UPDATE) + return NOTIFY_DONE; + + if (currcons == 0) + currcons = fg_console; + else + currcons--; + if (currcons != vc->vc_num) + return NOTIFY_DONE; + + poll->seen_last_update = false; + wake_up_interruptible(&poll->waitq); + kill_fasync(&poll->fasync, SIGIO, POLL_IN); + return NOTIFY_OK; +} + +static void +vcs_poll_data_free(struct vcs_poll_data *poll) +{ + unregister_vt_notifier(&poll->notifier); + kfree(poll); +} + +static struct vcs_poll_data * +vcs_poll_data_get(struct file *file) +{ + struct vcs_poll_data *poll = file->private_data, *kill = NULL; + + if (poll) + return poll; + + poll = kzalloc(sizeof(*poll), GFP_KERNEL); + if (!poll) + return NULL; + poll->cons_num = console(file_inode(file)); + init_waitqueue_head(&poll->waitq); + poll->notifier.notifier_call = vcs_notifier; + if (register_vt_notifier(&poll->notifier) != 0) { + kfree(poll); + return NULL; + } + + /* + * This code may be called either through ->poll() or ->fasync(). + * If we have two threads using the same file descriptor, they could + * both enter this function, both notice that the structure hasn't + * been allocated yet and go ahead allocating it in parallel, but + * only one of them must survive and be shared otherwise we'd leak + * memory with a dangling notifier callback. + */ + spin_lock(&file->f_lock); + if (!file->private_data) { + file->private_data = poll; + } else { + /* someone else raced ahead of us */ + kill = poll; + poll = file->private_data; + } + spin_unlock(&file->f_lock); + if (kill) + vcs_poll_data_free(kill); + + return poll; +} + +/* + * Returns VC for inode. + * Must be called with console_lock. + */ +static struct vc_data* +vcs_vc(struct inode *inode, int *viewed) +{ + unsigned int currcons = console(inode); + + WARN_CONSOLE_UNLOCKED(); + + if (currcons == 0) { + currcons = fg_console; + if (viewed) + *viewed = 1; + } else { + currcons--; + if (viewed) + *viewed = 0; + } + return vc_cons[currcons].d; +} + +/* + * Returns size for VC carried by inode. + * Must be called with console_lock. + */ +static int +vcs_size(struct inode *inode) +{ + int size; + struct vc_data *vc; + + WARN_CONSOLE_UNLOCKED(); + + vc = vcs_vc(inode, NULL); + if (!vc) + return -ENXIO; + + size = vc->vc_rows * vc->vc_cols; + + if (use_attributes(inode)) { + if (use_unicode(inode)) + return -EOPNOTSUPP; + size = 2*size + HEADER_SIZE; + } else if (use_unicode(inode)) + size *= 4; + return size; +} + +static loff_t vcs_lseek(struct file *file, loff_t offset, int orig) +{ + int size; + + console_lock(); + size = vcs_size(file_inode(file)); + console_unlock(); + if (size < 0) + return size; + return fixed_size_llseek(file, offset, orig, size); +} + + +static ssize_t +vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + struct inode *inode = file_inode(file); + struct vc_data *vc; + struct vcs_poll_data *poll; + long pos, read; + int attr, uni_mode, row, col, maxcol, viewed; + unsigned short *org = NULL; + ssize_t ret; + char *con_buf; + + con_buf = (char *) __get_free_page(GFP_KERNEL); + if (!con_buf) + return -ENOMEM; + + pos = *ppos; + + /* Select the proper current console and verify + * sanity of the situation under the console lock. + */ + console_lock(); + + uni_mode = use_unicode(inode); + attr = use_attributes(inode); + ret = -ENXIO; + vc = vcs_vc(inode, &viewed); + if (!vc) + goto unlock_out; + + ret = -EINVAL; + if (pos < 0) + goto unlock_out; + /* we enforce 32-bit alignment for pos and count in unicode mode */ + if (uni_mode && (pos | count) & 3) + goto unlock_out; + + poll = file->private_data; + if (count && poll) + poll->seen_last_update = true; + read = 0; + ret = 0; + while (count) { + char *con_buf0, *con_buf_start; + long this_round, size; + ssize_t orig_count; + long p = pos; + + /* Check whether we are above size each round, + * as copy_to_user at the end of this loop + * could sleep. + */ + size = vcs_size(inode); + if (size < 0) { + if (read) + break; + ret = size; + goto unlock_out; + } + if (pos >= size) + break; + if (count > size - pos) + count = size - pos; + + this_round = count; + if (this_round > CON_BUF_SIZE) + this_round = CON_BUF_SIZE; + + /* Perform the whole read into the local con_buf. + * Then we can drop the console spinlock and safely + * attempt to move it to userspace. + */ + + con_buf_start = con_buf0 = con_buf; + orig_count = this_round; + maxcol = vc->vc_cols; + if (uni_mode) { + unsigned int nr; + + ret = vc_uniscr_check(vc); + if (ret) + break; + p /= 4; + row = p / vc->vc_cols; + col = p % maxcol; + nr = maxcol - col; + do { + if (nr > this_round/4) + nr = this_round/4; + vc_uniscr_copy_line(vc, con_buf0, viewed, + row, col, nr); + con_buf0 += nr * 4; + this_round -= nr * 4; + row++; + col = 0; + nr = maxcol; + } while (this_round); + } else if (!attr) { + org = screen_pos(vc, p, viewed); + col = p % maxcol; + p += maxcol - col; + while (this_round-- > 0) { + *con_buf0++ = (vcs_scr_readw(vc, org++) & 0xff); + if (++col == maxcol) { + org = screen_pos(vc, p, viewed); + col = 0; + p += maxcol; + } + } + } else { + if (p < HEADER_SIZE) { + size_t tmp_count; + + con_buf0[0] = (char)vc->vc_rows; + con_buf0[1] = (char)vc->vc_cols; + getconsxy(vc, con_buf0 + 2); + + con_buf_start += p; + this_round += p; + if (this_round > CON_BUF_SIZE) { + this_round = CON_BUF_SIZE; + orig_count = this_round - p; + } + + tmp_count = HEADER_SIZE; + if (tmp_count > this_round) + tmp_count = this_round; + + /* Advance state pointers and move on. */ + this_round -= tmp_count; + p = HEADER_SIZE; + con_buf0 = con_buf + HEADER_SIZE; + /* If this_round >= 0, then p is even... */ + } else if (p & 1) { + /* Skip first byte for output if start address is odd + * Update region sizes up/down depending on free + * space in buffer. + */ + con_buf_start++; + if (this_round < CON_BUF_SIZE) + this_round++; + else + orig_count--; + } + if (this_round > 0) { + unsigned short *tmp_buf = (unsigned short *)con_buf0; + + p -= HEADER_SIZE; + p /= 2; + col = p % maxcol; + + org = screen_pos(vc, p, viewed); + p += maxcol - col; + + /* Buffer has even length, so we can always copy + * character + attribute. We do not copy last byte + * to userspace if this_round is odd. + */ + this_round = (this_round + 1) >> 1; + + while (this_round) { + *tmp_buf++ = vcs_scr_readw(vc, org++); + this_round --; + if (++col == maxcol) { + org = screen_pos(vc, p, viewed); + col = 0; + p += maxcol; + } + } + } + } + + /* Finally, release the console semaphore while we push + * all the data to userspace from our temporary buffer. + * + * AKPM: Even though it's a semaphore, we should drop it because + * the pagefault handling code may want to call printk(). + */ + + console_unlock(); + ret = copy_to_user(buf, con_buf_start, orig_count); + console_lock(); + + if (ret) { + read += (orig_count - ret); + ret = -EFAULT; + break; + } + buf += orig_count; + pos += orig_count; + read += orig_count; + count -= orig_count; + } + *ppos += read; + if (read) + ret = read; +unlock_out: + console_unlock(); + free_page((unsigned long) con_buf); + return ret; +} + +static ssize_t +vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + struct inode *inode = file_inode(file); + struct vc_data *vc; + long pos; + long attr, size, written; + char *con_buf0; + int col, maxcol, viewed; + u16 *org0 = NULL, *org = NULL; + size_t ret; + char *con_buf; + + if (use_unicode(inode)) + return -EOPNOTSUPP; + + con_buf = (char *) __get_free_page(GFP_KERNEL); + if (!con_buf) + return -ENOMEM; + + pos = *ppos; + + /* Select the proper current console and verify + * sanity of the situation under the console lock. + */ + console_lock(); + + attr = use_attributes(inode); + ret = -ENXIO; + vc = vcs_vc(inode, &viewed); + if (!vc) + goto unlock_out; + + size = vcs_size(inode); + ret = -EINVAL; + if (pos < 0 || pos > size) + goto unlock_out; + if (count > size - pos) + count = size - pos; + written = 0; + while (count) { + long this_round = count; + size_t orig_count; + long p; + + if (this_round > CON_BUF_SIZE) + this_round = CON_BUF_SIZE; + + /* Temporarily drop the console lock so that we can read + * in the write data from userspace safely. + */ + console_unlock(); + ret = copy_from_user(con_buf, buf, this_round); + console_lock(); + + if (ret) { + this_round -= ret; + if (!this_round) { + /* Abort loop if no data were copied. Otherwise + * fail with -EFAULT. + */ + if (written) + break; + ret = -EFAULT; + goto unlock_out; + } + } + + /* The vcs_size might have changed while we slept to grab + * the user buffer, so recheck. + * Return data written up to now on failure. + */ + size = vcs_size(inode); + if (size < 0) { + if (written) + break; + ret = size; + goto unlock_out; + } + if (pos >= size) + break; + if (this_round > size - pos) + this_round = size - pos; + + /* OK, now actually push the write to the console + * under the lock using the local kernel buffer. + */ + + con_buf0 = con_buf; + orig_count = this_round; + maxcol = vc->vc_cols; + p = pos; + if (!attr) { + org0 = org = screen_pos(vc, p, viewed); + col = p % maxcol; + p += maxcol - col; + + while (this_round > 0) { + unsigned char c = *con_buf0++; + + this_round--; + vcs_scr_writew(vc, + (vcs_scr_readw(vc, org) & 0xff00) | c, org); + org++; + if (++col == maxcol) { + org = screen_pos(vc, p, viewed); + col = 0; + p += maxcol; + } + } + } else { + if (p < HEADER_SIZE) { + char header[HEADER_SIZE]; + + getconsxy(vc, header + 2); + while (p < HEADER_SIZE && this_round > 0) { + this_round--; + header[p++] = *con_buf0++; + } + if (!viewed) + putconsxy(vc, header + 2); + } + p -= HEADER_SIZE; + col = (p/2) % maxcol; + if (this_round > 0) { + org0 = org = screen_pos(vc, p/2, viewed); + if ((p & 1) && this_round > 0) { + char c; + + this_round--; + c = *con_buf0++; +#ifdef __BIG_ENDIAN + vcs_scr_writew(vc, c | + (vcs_scr_readw(vc, org) & 0xff00), org); +#else + vcs_scr_writew(vc, (c << 8) | + (vcs_scr_readw(vc, org) & 0xff), org); +#endif + org++; + p++; + if (++col == maxcol) { + org = screen_pos(vc, p/2, viewed); + col = 0; + } + } + p /= 2; + p += maxcol - col; + } + while (this_round > 1) { + unsigned short w; + + w = get_unaligned(((unsigned short *)con_buf0)); + vcs_scr_writew(vc, w, org++); + con_buf0 += 2; + this_round -= 2; + if (++col == maxcol) { + org = screen_pos(vc, p, viewed); + col = 0; + p += maxcol; + } + } + if (this_round > 0) { + unsigned char c; + + c = *con_buf0++; +#ifdef __BIG_ENDIAN + vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff) | (c << 8), org); +#else + vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff00) | c, org); +#endif + } + } + count -= orig_count; + written += orig_count; + buf += orig_count; + pos += orig_count; + if (org0) + update_region(vc, (unsigned long)(org0), org - org0); + } + *ppos += written; + ret = written; + if (written) + vcs_scr_updated(vc); + +unlock_out: + console_unlock(); + free_page((unsigned long) con_buf); + return ret; +} + +static __poll_t +vcs_poll(struct file *file, poll_table *wait) +{ + struct vcs_poll_data *poll = vcs_poll_data_get(file); + __poll_t ret = DEFAULT_POLLMASK|EPOLLERR|EPOLLPRI; + + if (poll) { + poll_wait(file, &poll->waitq, wait); + if (poll->seen_last_update) + ret = DEFAULT_POLLMASK; + } + return ret; +} + +static int +vcs_fasync(int fd, struct file *file, int on) +{ + struct vcs_poll_data *poll = file->private_data; + + if (!poll) { + /* don't allocate anything if all we want is disable fasync */ + if (!on) + return 0; + poll = vcs_poll_data_get(file); + if (!poll) + return -ENOMEM; + } + + return fasync_helper(fd, file, on, &poll->fasync); +} + +static int +vcs_open(struct inode *inode, struct file *filp) +{ + unsigned int currcons = console(inode); + bool attr = use_attributes(inode); + bool uni_mode = use_unicode(inode); + int ret = 0; + + /* we currently don't support attributes in unicode mode */ + if (attr && uni_mode) + return -EOPNOTSUPP; + + console_lock(); + if(currcons && !vc_cons_allocated(currcons-1)) + ret = -ENXIO; + console_unlock(); + return ret; +} + +static int vcs_release(struct inode *inode, struct file *file) +{ + struct vcs_poll_data *poll = file->private_data; + + if (poll) + vcs_poll_data_free(poll); + return 0; +} + +static const struct file_operations vcs_fops = { + .llseek = vcs_lseek, + .read = vcs_read, + .write = vcs_write, + .poll = vcs_poll, + .fasync = vcs_fasync, + .open = vcs_open, + .release = vcs_release, +}; + +static struct class *vc_class; + +void vcs_make_sysfs(int index) +{ + device_create(vc_class, NULL, MKDEV(VCS_MAJOR, index + 1), NULL, + "vcs%u", index + 1); + device_create(vc_class, NULL, MKDEV(VCS_MAJOR, index + 65), NULL, + "vcsu%u", index + 1); + device_create(vc_class, NULL, MKDEV(VCS_MAJOR, index + 129), NULL, + "vcsa%u", index + 1); +} + +void vcs_remove_sysfs(int index) +{ + device_destroy(vc_class, MKDEV(VCS_MAJOR, index + 1)); + device_destroy(vc_class, MKDEV(VCS_MAJOR, index + 65)); + device_destroy(vc_class, MKDEV(VCS_MAJOR, index + 129)); +} + +int __init vcs_init(void) +{ + unsigned int i; + + if (register_chrdev(VCS_MAJOR, "vcs", &vcs_fops)) + panic("unable to get major %d for vcs device", VCS_MAJOR); + vc_class = class_create(THIS_MODULE, "vc"); + + device_create(vc_class, NULL, MKDEV(VCS_MAJOR, 0), NULL, "vcs"); + device_create(vc_class, NULL, MKDEV(VCS_MAJOR, 64), NULL, "vcsu"); + device_create(vc_class, NULL, MKDEV(VCS_MAJOR, 128), NULL, "vcsa"); + for (i = 0; i < MIN_NR_CONSOLES; i++) + vcs_make_sysfs(i); + return 0; +} |