patch-2.3.46 linux/drivers/char/vc_screen.c

Next file: linux/drivers/char/videodev.c
Previous file: linux/drivers/char/tty_io.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.45/linux/drivers/char/vc_screen.c linux/drivers/char/vc_screen.c
@@ -25,6 +25,7 @@
 #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,6 +33,8 @@
 #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>
 
@@ -80,21 +83,34 @@
 	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 +118,156 @@
 		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;
+	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, 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--;   
+		} 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);
+
+				tmp_count = HEADER_SIZE - p;
+				if (tmp_count > this_round)
+					tmp_count = this_round;
+
+				/* Advance state pointers and move on. */
+				this_round -= tmp_count;
+				con_buf_start += p;
+				orig_count -= p;
+				p += tmp_count;
+				con_buf0 = con_buf + p;
+			}
+			p -= HEADER_SIZE;
+			col = (p/2) % maxcol;
+			if (this_round > 0) {
+				char tmp_byte;
+
+				org = screen_pos(currcons, p/2, viewed);
+				if ((p & 1) && this_round > 0) {
 #ifdef __BIG_ENDIAN
-				put_user(vcs_scr_readw(currcons, org++) & 0xff, buf++);
+					tmp_byte = vcs_scr_readw(currcons, org++) & 0xff;
 #else
-				put_user(vcs_scr_readw(currcons, org++) >> 8, buf++);
+					tmp_byte = vcs_scr_readw(currcons, org++) >> 8;
 #endif
-				p++;
-				if (++col == maxcol) {
-					org = screen_pos(currcons, p/2, viewed);
-					col = 0;
+
+					*con_buf0++ = tmp_byte;
+
+					this_round--;   
+					p++;
+					if (++col == maxcol) {
+						org = screen_pos(currcons, p/2, viewed);
+						col = 0;
+					}
 				}
+				p /= 2;
+				p += maxcol - col;
 			}
-			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) {
-				org = screen_pos(currcons, p, viewed);
-				col = 0;
-				p += maxcol;
+
+			if (this_round > 1) {
+				size_t tmp_count = this_round;
+				unsigned short *tmp_buf = (unsigned short *)con_buf0;
+
+				while (tmp_count > 1) {
+					*tmp_buf++ = vcs_scr_readw(currcons, org++);
+					tmp_count -= 2;
+					if (++col == maxcol) {
+						org = screen_pos(currcons, p, viewed);
+						col = 0;
+						p += maxcol;
+					}
+				}
+
+				/* Advance pointers, and move on. */
+				this_round = tmp_count;
+				con_buf0 = (char*)tmp_buf;
 			}
-		}
-		if (count > 0)
+			if (this_round > 0) {
+				char tmp_byte;
+
 #ifdef __BIG_ENDIAN
-			put_user(vcs_scr_readw(currcons, org) >> 8, buf++);
+				tmp_byte = vcs_scr_readw(currcons, org) >> 8;
 #else
-			put_user(vcs_scr_readw(currcons, org) & 0xff, buf++);
+				tmp_byte = vcs_scr_readw(currcons, org) & 0xff;
 #endif
+
+				*con_buf0++ = tmp_byte;
+			}
+		}
+
+		/* 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 +275,23 @@
 {
 	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 +299,159 @@
 		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 = *((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 +470,49 @@
 	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;
 }

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)