summaryrefslogtreecommitdiffstats
path: root/drivers/char/vc_screen.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>2000-02-24 00:12:35 +0000
committerRalf Baechle <ralf@linux-mips.org>2000-02-24 00:12:35 +0000
commit482368b1a8e45430672c58c9a42e7d2004367126 (patch)
treece2a1a567d4d62dee7c2e71a46a99cf72cf1d606 /drivers/char/vc_screen.c
parente4d0251c6f56ab2e191afb70f80f382793e23f74 (diff)
Merge with 2.3.47. Guys, this is buggy as shit. You've been warned.
Diffstat (limited to 'drivers/char/vc_screen.c')
-rw-r--r--drivers/char/vc_screen.c473
1 files changed, 333 insertions, 140 deletions
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 <linux/config.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/errno.h>
#include <linux/tty.h>
+#include <linux/devfs_fs_kernel.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
@@ -32,8 +34,11 @@
#include <linux/vt_kern.h>
#include <linux/console_struct.h>
#include <linux/selection.h>
+#include <linux/kbd_kern.h>
+#include <linux/console.h>
#include <asm/uaccess.h>
#include <asm/byteorder.h>
+#include <asm/unaligned.h>
#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;
}