/*
       * linux/drivers/char/vc_screen.c
       *
       * 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]
       *
       * 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/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>
      #include <linux/init.h>
      #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
      #undef addr
      #define HEADER_SIZE	4
      
      static int
  49  vcs_size(struct inode *inode)
      {
      	int size;
      	int currcons = MINOR(inode->i_rdev) & 127;
  53  	if (currcons == 0)
      		currcons = fg_console;
  55  	else
      		currcons--;
  57  	if (!vc_cons_allocated(currcons))
  58  		return -ENXIO;
      
      	size = video_num_lines * video_num_columns;
      
  62  	if (MINOR(inode->i_rdev) & 128)
      		size = 2*size + HEADER_SIZE;
  64  	return size;
      }
      
  67  static loff_t vcs_lseek(struct file *file, loff_t offset, int orig)
      {
      	int size = vcs_size(file->f_dentry->d_inode);
      
  71  	switch (orig) {
  72  		default:
  73  			return -EINVAL;
  74  		case 2:
      			offset += size;
  76  			break;
  77  		case 1:
      			offset += file->f_pos;
  79  		case 0:
  80  			break;
      	}
  82  	if (offset < 0 || offset > size)
  83  		return -EINVAL;
      	file->f_pos = offset;
  85  	return file->f_pos;
      }
      
      /* 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
  97  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 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.
      	 */
 112  	spin_lock_irq(&console_lock);
      
      	attr = (currcons & 128);
      	currcons = (currcons & 127);
 116  	if (currcons == 0) {
      		currcons = fg_console;
      		viewed = 1;
 119  	} else {
      		currcons--;
      		viewed = 0;
      	}
      	ret = -ENXIO;
 124  	if (!vc_cons_allocated(currcons))
 125  		goto unlock_out;
      
      	ret = -EINVAL;
 128  	if (pos < 0)
 129  		goto unlock_out;
      	read = 0;
      	ret = 0;
 132  	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);
 143  		if (pos >= size)
 144  			break;
 145  		if (count > size - pos)
      			count = size - pos;
      
      		this_round = count;
 149  		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;
 160  		if (!attr) {
      			org = screen_pos(currcons, p, viewed);
      			col = p % maxcol;
      			p += maxcol - col;
 164  			while (this_round-- > 0) {
      				*con_buf0++ = (vcs_scr_readw(currcons, org++) & 0xff);
 166  				if (++col == maxcol) {
      					org = screen_pos(currcons, p, viewed);
      					col = 0;
      					p += maxcol;
      				}
      			}
 172  		} else {
 173  			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;
 182  				if (this_round > CON_BUF_SIZE) {
      					this_round = CON_BUF_SIZE;
      					orig_count = this_round - p;
      				}
      
      				tmp_count = HEADER_SIZE;
 188  				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... */
 196  			} 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++;
 202  				if (this_round < CON_BUF_SIZE)
      					this_round++;
 204  				else
      					orig_count--;
      			}
 207  			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);
      				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;
      
 223  				while (this_round) {
      					*tmp_buf++ = vcs_scr_readw(currcons, org++);
      					this_round --;
 226  					if (++col == maxcol) {
      						org = screen_pos(currcons, p, viewed);
      						col = 0;
      						p += maxcol;
      					}
      				}
      			}
      		}
      
      		/* Finally, temporarily drop the console lock and push
      		 * all the data to userspace from our temporary buffer.
      		 */
      
 239  		spin_unlock_irq(&console_lock);
      		ret = copy_to_user(buf, con_buf_start, orig_count);
 241  		spin_lock_irq(&console_lock);
      
 243  		if (ret) {
      			read += (orig_count - ret);
      			ret = -EFAULT;
 246  			break;
      		}
      		buf += orig_count;
      		pos += orig_count;
      		read += orig_count;
      		count -= orig_count;
      	}
      	*ppos += read;
 254  	if (read)
      		ret = read;
      unlock_out:
 257  	spin_unlock_irq(&console_lock);
      	up(&con_buf_sem);
 259  	return ret;
      }
      
      static ssize_t
 263  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 pos = *ppos;
      	long viewed, attr, size, written;
      	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.
      	 */
 279  	spin_lock_irq(&console_lock);
      
      	attr = (currcons & 128);
      	currcons = (currcons & 127);
      
 284  	if (currcons == 0) {
      		currcons = fg_console;
      		viewed = 1;
 287  	} else {
      		currcons--;
      		viewed = 0;
      	}
      	ret = -ENXIO;
 292  	if (!vc_cons_allocated(currcons))
 293  		goto unlock_out;
      
      	size = vcs_size(inode);
      	ret = -EINVAL;
 297  	if (pos < 0 || pos > size)
 298  		goto unlock_out;
 299  	if (count > size - pos)
      		count = size - pos;
      	written = 0;
 302  	while (count) {
      		long this_round = count;
      		size_t orig_count;
      		long p;
      
 307  		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.
      		 */
 313  		spin_unlock_irq(&console_lock);
      		ret = copy_from_user(con_buf, buf, this_round);
 315  		spin_lock_irq(&console_lock);
      
 317  		if (ret) {
      			this_round -= ret;
 319  			if (!this_round) {
      				/* Abort loop if no data were copied. Otherwise
      				 * fail with -EFAULT.
      				 */
 323  				if (written)
 324  					break;
      				ret = -EFAULT;
 326  				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);
 335  		if (pos >= size)
 336  			break;
 337  		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;
 348  		if (!attr) {
      			org0 = org = screen_pos(currcons, p, viewed);
      			col = p % maxcol;
      			p += maxcol - col;
      
 353  			while (this_round > 0) {
      				unsigned char c = *con_buf0++;
      
      				this_round--;
      				vcs_scr_writew(currcons,
      					       (vcs_scr_readw(currcons, org) & 0xff00) | c, org);
      				org++;
 360  				if (++col == maxcol) {
      					org = screen_pos(currcons, p, viewed);
      					col = 0;
      					p += maxcol;
      				}
      			}
 366  		} else {
 367  			if (p < HEADER_SIZE) {
      				char header[HEADER_SIZE];
      
      				getconsxy(currcons, header + 2);
 371  				while (p < HEADER_SIZE && this_round > 0) {
      					this_round--;
      					header[p++] = *con_buf0++;
      				}
 375  				if (!viewed)
      					putconsxy(currcons, header + 2);
      			}
      			p -= HEADER_SIZE;
      			col = (p/2) % maxcol;
 380  			if (this_round > 0) {
      				org0 = org = screen_pos(currcons, p/2, viewed);
 382  				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);
      #else
      					vcs_scr_writew(currcons, (c << 8) |
      					     (vcs_scr_readw(currcons, org) & 0xff), org);
      #endif
      					org++;
      					p++;
 396  					if (++col == maxcol) {
      						org = screen_pos(currcons, p/2, viewed);
      						col = 0;
      					}
      				}
      				p /= 2;
      				p += maxcol - col;
      			}
 404  			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;
 411  				if (++col == maxcol) {
      					org = screen_pos(currcons, p, viewed);
      					col = 0;
      					p += maxcol;
      				}
      			}
 417  			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);
      #else
      				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;
 432  		if (org0)
      			update_region(currcons, (unsigned long)(org0), org-org0);
      	}
      	*ppos += written;
      	ret = written;
      
      unlock_out:
 439  	spin_unlock_irq(&console_lock);
      
      	up(&con_buf_sem);
      
 443  	return ret;
      }
      
      static int
 447  vcs_open(struct inode *inode, struct file *filp)
      {
      	unsigned int currcons = (MINOR(inode->i_rdev) & 127);
 450  	if(currcons && !vc_cons_allocated(currcons-1))
 451  		return -ENXIO;
 452  	return 0;
      }
      
      static struct file_operations vcs_fops = {
      	llseek:		vcs_lseek,
      	read:		vcs_read,
      	write:		vcs_write,
      	open:		vcs_open,
      };
      
      static devfs_handle_t devfs_handle;
      
 464  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,
      					      DEVFS_SPECIAL_CHR, 0) );
      	devfs_unregister ( devfs_find_handle (devfs_handle, name, 0, 0,
      					      DEVFS_SPECIAL_CHR, 0) );
          }
          else
          {
      	devfs_register (devfs_handle, name + 1, DEVFS_FL_DEFAULT,
      			VCS_MAJOR, index + 1,
      			S_IFCHR | S_IRUSR | S_IWUSR, &vcs_fops, NULL);
      	devfs_register (devfs_handle, name, DEVFS_FL_DEFAULT,
      			VCS_MAJOR, index + 129,
      			S_IFCHR | S_IRUSR | S_IWUSR, &vcs_fops, NULL);
          }
      #endif /* CONFIG_DEVFS_FS */
      }
      
 489  int __init vcs_init(void)
      {
      	int error;
      
      	error = devfs_register_chrdev(VCS_MAJOR, "vcs", &vcs_fops);
      
 495  	if (error)
      		printk("unable to get major %d for vcs device", VCS_MAJOR);
      
      	devfs_handle = devfs_mk_dir (NULL, "vcc", NULL);
      	devfs_register (devfs_handle, "0", DEVFS_FL_DEFAULT,
      			VCS_MAJOR, 0,
      			S_IFCHR | S_IRUSR | S_IWUSR, &vcs_fops, NULL);
      	devfs_register (devfs_handle, "a", DEVFS_FL_DEFAULT,
      			VCS_MAJOR, 128,
      			S_IFCHR | S_IRUSR | S_IWUSR, &vcs_fops, NULL);
      
 506  	return error;
      }