From 482368b1a8e45430672c58c9a42e7d2004367126 Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Thu, 24 Feb 2000 00:12:35 +0000 Subject: Merge with 2.3.47. Guys, this is buggy as shit. You've been warned. --- drivers/char/vc_screen.c | 473 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 333 insertions(+), 140 deletions(-) (limited to 'drivers/char/vc_screen.c') diff --git a/drivers/char/vc_screen.c b/drivers/char/vc_screen.c index e099b4ac3..1da60703e 100644 --- a/drivers/char/vc_screen.c +++ b/drivers/char/vc_screen.c @@ -21,10 +21,12 @@ * - making it shorter - scr_readw are macros which expand in PRETTY long code */ +#include #include #include #include #include +#include #include #include #include @@ -32,8 +34,11 @@ #include #include #include +#include +#include #include #include +#include #undef attr #undef org @@ -80,21 +85,34 @@ static loff_t vcs_lseek(struct file *file, loff_t offset, int orig) return file->f_pos; } -#define RETURN(x) { enable_bh(CONSOLE_BH); return x; } +/* We share this temporary buffer with the console write code + * so that we can easily avoid touching user space while holding the + * console spinlock. + */ +extern char con_buf[PAGE_SIZE]; +#define CON_BUF_SIZE PAGE_SIZE +extern struct semaphore con_buf_sem; + static ssize_t vcs_read(struct file *file, char *buf, size_t count, loff_t *ppos) { struct inode *inode = file->f_dentry->d_inode; unsigned int currcons = MINOR(inode->i_rdev); - long p = *ppos; - long viewed, attr, size, read; - char *buf0; + long pos = *ppos; + long viewed, attr, read; int col, maxcol; unsigned short *org = NULL; + ssize_t ret; + + down(&con_buf_sem); + + /* Select the proper current console and verify + * sanity of the situation under the console lock. + */ + spin_lock_irq(&console_lock); attr = (currcons & 128); currcons = (currcons & 127); - disable_bh(CONSOLE_BH); if (currcons == 0) { currcons = fg_console; viewed = 1; @@ -102,78 +120,143 @@ vcs_read(struct file *file, char *buf, size_t count, loff_t *ppos) currcons--; viewed = 0; } + ret = -ENXIO; if (!vc_cons_allocated(currcons)) - RETURN( -ENXIO ); + goto unlock_out; - size = vcs_size(inode); - if (p < 0 || p > size) - RETURN( -EINVAL ); - if (count > size - p) - count = size - p; - - buf0 = buf; - maxcol = video_num_columns; - if (!attr) { - org = screen_pos(currcons, p, viewed); - col = p % maxcol; - p += maxcol - col; - while (count-- > 0) { - put_user(vcs_scr_readw(currcons, org++) & 0xff, buf++); - if (++col == maxcol) { - org = screen_pos(currcons, p, viewed); - col = 0; - p += maxcol; - } - } - } else { - if (p < HEADER_SIZE) { - char header[HEADER_SIZE]; - header[0] = (char) video_num_lines; - header[1] = (char) video_num_columns; - getconsxy(currcons, header+2); - while (p < HEADER_SIZE && count > 0) - { count--; put_user(header[p++], buf++); } - } - p -= HEADER_SIZE; - col = (p/2) % maxcol; - if (count > 0) { - org = screen_pos(currcons, p/2, viewed); - if ((p & 1) && count > 0) { - count--; -#ifdef __BIG_ENDIAN - put_user(vcs_scr_readw(currcons, org++) & 0xff, buf++); -#else - put_user(vcs_scr_readw(currcons, org++) >> 8, buf++); -#endif - p++; + ret = -EINVAL; + if (pos < 0) + goto unlock_out; + 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 (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 = video_num_columns; + if (!attr) { + org = screen_pos(currcons, p, viewed); + col = p % maxcol; + p += maxcol - col; + while (this_round-- > 0) { + *con_buf0++ = (vcs_scr_readw(currcons, org++) & 0xff); if (++col == maxcol) { - org = screen_pos(currcons, p/2, viewed); + org = screen_pos(currcons, p, viewed); col = 0; + p += maxcol; } } - p /= 2; - p += maxcol - col; - } - while (count > 1) { - put_user(vcs_scr_readw(currcons, org++), (unsigned short *) buf); - buf += 2; - count -= 2; - if (++col == maxcol) { + } else { + if (p < HEADER_SIZE) { + size_t tmp_count; + + con_buf0[0] = (char) video_num_lines; + con_buf0[1] = (char) video_num_columns; + getconsxy(currcons, 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(currcons, p, viewed); - col = 0; - p += maxcol; + 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(currcons, org++); + this_round --; + if (++col == maxcol) { + org = screen_pos(currcons, p, viewed); + col = 0; + p += maxcol; + } + } } } - if (count > 0) -#ifdef __BIG_ENDIAN - put_user(vcs_scr_readw(currcons, org) >> 8, buf++); -#else - put_user(vcs_scr_readw(currcons, org) & 0xff, buf++); -#endif + + /* Finally, temporarily drop the console lock and push + * all the data to userspace from our temporary buffer. + */ + + spin_unlock_irq(&console_lock); + ret = copy_to_user(buf, con_buf_start, orig_count); + spin_lock_irq(&console_lock); + + if (ret) { + read += (orig_count - ret); + ret = -EFAULT; + break; + } + buf += orig_count; + pos += orig_count; + read += orig_count; + count -= orig_count; } - read = buf - buf0; *ppos += read; - RETURN( read ); + if (read) + ret = read; +unlock_out: + spin_unlock_irq(&console_lock); + up(&con_buf_sem); + return ret; } static ssize_t @@ -181,15 +264,23 @@ vcs_write(struct file *file, const char *buf, size_t count, loff_t *ppos) { struct inode *inode = file->f_dentry->d_inode; unsigned int currcons = MINOR(inode->i_rdev); - long p = *ppos; + long pos = *ppos; long viewed, attr, size, written; - const char *buf0; + char *con_buf0; int col, maxcol; u16 *org0 = NULL, *org = NULL; + size_t ret; + + down(&con_buf_sem); + + /* Select the proper current console and verify + * sanity of the situation under the console lock. + */ + spin_lock_irq(&console_lock); attr = (currcons & 128); currcons = (currcons & 127); - disable_bh(CONSOLE_BH); + if (currcons == 0) { currcons = fg_console; viewed = 1; @@ -197,94 +288,159 @@ vcs_write(struct file *file, const char *buf, size_t count, loff_t *ppos) currcons--; viewed = 0; } + ret = -ENXIO; if (!vc_cons_allocated(currcons)) - RETURN( -ENXIO ); + goto unlock_out; size = vcs_size(inode); - if (p < 0 || p > size) - RETURN( -EINVAL ); - if (count > size - p) - count = size - p; - - buf0 = buf; - maxcol = video_num_columns; - if (!attr) { - org0 = org = screen_pos(currcons, p, viewed); - col = p % maxcol; - p += maxcol - col; - while (count > 0) { - unsigned char c; - count--; - get_user(c, (const unsigned char*)buf++); - vcs_scr_writew(currcons, (vcs_scr_readw(currcons, org) & 0xff00) | c, org); - org++; - if (++col == maxcol) { - org = screen_pos(currcons, p, viewed); - col = 0; - p += maxcol; + 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. + */ + spin_unlock_irq(&console_lock); + ret = copy_from_user(con_buf, buf, this_round); + spin_lock_irq(&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; } } - } else { - if (p < HEADER_SIZE) { - char header[HEADER_SIZE]; - getconsxy(currcons, header+2); - while (p < HEADER_SIZE && count > 0) - { count--; get_user(header[p++], buf++); } - if (!viewed) - putconsxy(currcons, header+2); - } - p -= HEADER_SIZE; - col = (p/2) % maxcol; - if (count > 0) { - org0 = org = screen_pos(currcons, p/2, viewed); - if ((p & 1) && count > 0) { - char c; - count--; - get_user(c,buf++); + + /* 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 (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 = video_num_columns; + p = pos; + if (!attr) { + org0 = org = screen_pos(currcons, p, viewed); + col = p % maxcol; + p += maxcol - col; + + while (this_round > 0) { + unsigned char c = *con_buf0++; + + this_round--; + vcs_scr_writew(currcons, + (vcs_scr_readw(currcons, org) & 0xff00) | c, org); + org++; + if (++col == maxcol) { + org = screen_pos(currcons, p, viewed); + col = 0; + p += maxcol; + } + } + } else { + if (p < HEADER_SIZE) { + char header[HEADER_SIZE]; + + getconsxy(currcons, header + 2); + while (p < HEADER_SIZE && this_round > 0) { + this_round--; + header[p++] = *con_buf0++; + } + if (!viewed) + putconsxy(currcons, header + 2); + } + p -= HEADER_SIZE; + col = (p/2) % maxcol; + if (this_round > 0) { + org0 = org = screen_pos(currcons, p/2, viewed); + if ((p & 1) && this_round > 0) { + char c; + + this_round--; + c = *con_buf0++; #ifdef __BIG_ENDIAN - vcs_scr_writew(currcons, c | - (vcs_scr_readw(currcons, org) & 0xff00), org); + vcs_scr_writew(currcons, c | + (vcs_scr_readw(currcons, org) & 0xff00), org); #else - vcs_scr_writew(currcons, (c << 8) | - (vcs_scr_readw(currcons, org) & 0xff), org); + vcs_scr_writew(currcons, (c << 8) | + (vcs_scr_readw(currcons, org) & 0xff), org); #endif - org++; - p++; + org++; + p++; + if (++col == maxcol) { + org = screen_pos(currcons, p/2, viewed); + col = 0; + } + } + p /= 2; + p += maxcol - col; + } + while (this_round > 1) { + unsigned short w; + + w = get_unaligned(((const unsigned short *)con_buf0)); + vcs_scr_writew(currcons, w, org++); + con_buf0 += 2; + this_round -= 2; if (++col == maxcol) { - org = screen_pos(currcons, p/2, viewed); + org = screen_pos(currcons, p, viewed); col = 0; + p += maxcol; } } - p /= 2; - p += maxcol - col; - } - while (count > 1) { - unsigned short w; - get_user(w, (const unsigned short *) buf); - vcs_scr_writew(currcons, w, org++); - buf += 2; - count -= 2; - if (++col == maxcol) { - org = screen_pos(currcons, p, viewed); - col = 0; - p += maxcol; - } - } - if (count > 0) { - unsigned char c; - get_user(c, (const unsigned char*)buf++); + if (this_round > 0) { + unsigned char c; + + c = *con_buf0++; #ifdef __BIG_ENDIAN - vcs_scr_writew(currcons, (vcs_scr_readw(currcons, org) & 0xff) | (c << 8), org); + vcs_scr_writew(currcons, (vcs_scr_readw(currcons, org) & 0xff) | (c << 8), org); #else - vcs_scr_writew(currcons, (vcs_scr_readw(currcons, org) & 0xff00) | c, org); + vcs_scr_writew(currcons, (vcs_scr_readw(currcons, org) & 0xff00) | c, org); #endif + } } + count -= orig_count; + written += orig_count; + buf += orig_count; + pos += orig_count; + if (org0) + update_region(currcons, (unsigned long)(org0), org-org0); } - if (org0) - update_region(currcons, (unsigned long)(org0), org-org0); - written = buf - buf0; *ppos += written; - RETURN( written ); + ret = written; + +unlock_out: + spin_unlock_irq(&console_lock); + + up(&con_buf_sem); + + return ret; } static int @@ -303,12 +459,49 @@ static struct file_operations vcs_fops = { open: vcs_open, }; +static devfs_handle_t devfs_handle = NULL; + +void vcs_make_devfs (unsigned int index, int unregister) +{ +#ifdef CONFIG_DEVFS_FS + char name[8]; + + sprintf (name, "a%u", index + 1); + if (unregister) + { + devfs_unregister ( devfs_find_handle (devfs_handle, name + 1, 0, 0, 0, + DEVFS_SPECIAL_CHR, 0) ); + devfs_unregister ( devfs_find_handle (devfs_handle, name, 0, 0, 0, + DEVFS_SPECIAL_CHR, 0) ); + } + else + { + devfs_register (devfs_handle, name + 1, 0, DEVFS_FL_DEFAULT, + VCS_MAJOR, index + 1, + S_IFCHR | S_IRUSR | S_IWUSR, 0, 0, &vcs_fops, NULL); + devfs_register (devfs_handle, name, 0, DEVFS_FL_DEFAULT, + VCS_MAJOR, index + 129, + S_IFCHR | S_IRUSR | S_IWUSR, 0, 0, &vcs_fops, NULL); + } +#endif /* CONFIG_DEVFS_FS */ +} + int __init vcs_init(void) { int error; - error = register_chrdev(VCS_MAJOR, "vcs", &vcs_fops); + error = devfs_register_chrdev(VCS_MAJOR, "vcs", &vcs_fops); + if (error) printk("unable to get major %d for vcs device", VCS_MAJOR); + + devfs_handle = devfs_mk_dir (NULL, "vcc", 3, NULL); + devfs_register (devfs_handle, "0", 1, DEVFS_FL_DEFAULT, + VCS_MAJOR, 0, + S_IFCHR | S_IRUSR | S_IWUSR, 0, 0, &vcs_fops, NULL); + devfs_register (devfs_handle, "a", 1, DEVFS_FL_DEFAULT, + VCS_MAJOR, 128, + S_IFCHR | S_IRUSR | S_IWUSR, 0, 0, &vcs_fops, NULL); + return error; } -- cgit v1.2.3