1/*
2 * linux/drivers/char/vc_screen.c
3 *
4 * Provide access to virtual console memory.
5 * /dev/vcs0: the screen as it is being viewed right now (possibly scrolled)
6 * /dev/vcsN: the screen of /dev/ttyN (1 <= N <= 63)
7 *            [minor: N]
8 *
9 * /dev/vcsaN: idem, but including attributes, and prefixed with
10 *	the 4 bytes lines,columns,x,y (as screendump used to give).
11 *	Attribute/character pair is in native endianity.
12 *            [minor: N+128]
13 *
14 * This replaces screendump and part of selection, so that the system
15 * administrator can control access using file system permissions.
16 *
17 * aeb@cwi.nl - efter Friedas begravelse - 950211
18 *
19 * machek@k332.feld.cvut.cz - modified not to send characters to wrong console
20 *	 - fixed some fatal off-by-one bugs (0-- no longer == -1 -> looping and looping and looping...)
21 *	 - making it shorter - scr_readw are macros which expand in PRETTY long code
22 */
23
24#include <linux/config.h>
25#include <linux/kernel.h>
26#include <linux/major.h>
27#include <linux/errno.h>
28#include <linux/tty.h>
29#include <linux/devfs_fs_kernel.h>
30#include <linux/sched.h>
31#include <linux/interrupt.h>
32#include <linux/mm.h>
33#include <linux/init.h>
34#include <linux/vt_kern.h>
35#include <linux/console_struct.h>
36#include <linux/selection.h>
37#include <linux/kbd_kern.h>
38#include <linux/console.h>
39#include <asm/uaccess.h>
40#include <asm/byteorder.h>
41#include <asm/unaligned.h>
42
43#undef attr
44#undef org
45#undef addr
46#define HEADER_SIZE	4
47
48static int
49vcs_size(struct inode *inode)
50{
51	int size;
52	int currcons = MINOR(inode->i_rdev) & 127;
53	if (currcons == 0)
54		currcons = fg_console;
55	else
56		currcons--;
57	if (!vc_cons_allocated(currcons))
58		return -ENXIO;
59
60	size = video_num_lines * video_num_columns;
61
62	if (MINOR(inode->i_rdev) & 128)
63		size = 2*size + HEADER_SIZE;
64	return size;
65}
66
67static loff_t vcs_lseek(struct file *file, loff_t offset, int orig)
68{
69	int size = vcs_size(file->f_dentry->d_inode);
70
71	switch (orig) {
72		default:
73			return -EINVAL;
74		case 2:
75			offset += size;
76			break;
77		case 1:
78			offset += file->f_pos;
79		case 0:
80			break;
81	}
82	if (offset < 0 || offset > size)
83		return -EINVAL;
84	file->f_pos = offset;
85	return file->f_pos;
86}
87
88/* We share this temporary buffer with the console write code
89 * so that we can easily avoid touching user space while holding the
90 * console spinlock.
91 */
92extern char con_buf[PAGE_SIZE];
93#define CON_BUF_SIZE	PAGE_SIZE
94extern struct semaphore con_buf_sem;
95
96static ssize_t
97vcs_read(struct file *file, char *buf, size_t count, loff_t *ppos)
98{
99	struct inode *inode = file->f_dentry->d_inode;
100	unsigned int currcons = MINOR(inode->i_rdev);
101	long pos = *ppos;
102	long viewed, attr, read;
103	int col, maxcol;
104	unsigned short *org = NULL;
105	ssize_t ret;
106
107	down(&con_buf_sem);
108
109	/* Select the proper current console and verify
110	 * sanity of the situation under the console lock.
111	 */
112	acquire_console_sem();
113
114	attr = (currcons & 128);
115	currcons = (currcons & 127);
116	if (currcons == 0) {
117		currcons = fg_console;
118		viewed = 1;
119	} else {
120		currcons--;
121		viewed = 0;
122	}
123	ret = -ENXIO;
124	if (!vc_cons_allocated(currcons))
125		goto unlock_out;
126
127	ret = -EINVAL;
128	if (pos < 0)
129		goto unlock_out;
130	read = 0;
131	ret = 0;
132	while (count) {
133		char *con_buf0, *con_buf_start;
134		long this_round, size;
135		ssize_t orig_count;
136		long p = pos;
137
138		/* Check whether we are above size each round,
139		 * as copy_to_user at the end of this loop
140		 * could sleep.
141		 */
142		size = vcs_size(inode);
143		if (pos >= size)
144			break;
145		if (count > size - pos)
146			count = size - pos;
147
148		this_round = count;
149		if (this_round > CON_BUF_SIZE)
150			this_round = CON_BUF_SIZE;
151
152		/* Perform the whole read into the local con_buf.
153		 * Then we can drop the console spinlock and safely
154		 * attempt to move it to userspace.
155		 */
156
157		con_buf_start = con_buf0 = con_buf;
158		orig_count = this_round;
159		maxcol = video_num_columns;
160		if (!attr) {
161			org = screen_pos(currcons, p, viewed);
162			col = p % maxcol;
163			p += maxcol - col;
164			while (this_round-- > 0) {
165				*con_buf0++ = (vcs_scr_readw(currcons, org++) & 0xff);
166				if (++col == maxcol) {
167					org = screen_pos(currcons, p, viewed);
168					col = 0;
169					p += maxcol;
170				}
171			}
172		} else {
173			if (p < HEADER_SIZE) {
174				size_t tmp_count;
175
176				con_buf0[0] = (char) video_num_lines;
177				con_buf0[1] = (char) video_num_columns;
178				getconsxy(currcons, con_buf0 + 2);
179
180				con_buf_start += p;
181				this_round += p;
182				if (this_round > CON_BUF_SIZE) {
183					this_round = CON_BUF_SIZE;
184					orig_count = this_round - p;
185				}
186
187				tmp_count = HEADER_SIZE;
188				if (tmp_count > this_round)
189					tmp_count = this_round;
190
191				/* Advance state pointers and move on. */
192				this_round -= tmp_count;
193				p = HEADER_SIZE;
194				con_buf0 = con_buf + HEADER_SIZE;
195				/* If this_round >= 0, then p is even... */
196			} else if (p & 1) {
197				/* Skip first byte for output if start address is odd
198				 * Update region sizes up/down depending on free
199				 * space in buffer.
200				 */
201				con_buf_start++;
202				if (this_round < CON_BUF_SIZE)
203					this_round++;
204				else
205					orig_count--;
206			}
207			if (this_round > 0) {
208				unsigned short *tmp_buf = (unsigned short *)con_buf0;
209
210				p -= HEADER_SIZE;
211				p /= 2;
212				col = p % maxcol;
213
214				org = screen_pos(currcons, p, viewed);
215				p += maxcol - col;
216
217				/* Buffer has even length, so we can always copy
218				 * character + attribute. We do not copy last byte
219				 * to userspace if this_round is odd.
220				 */
221				this_round = (this_round + 1) >> 1;
222
223				while (this_round) {
224					*tmp_buf++ = vcs_scr_readw(currcons, org++);
225					this_round --;
226					if (++col == maxcol) {
227						org = screen_pos(currcons, p, viewed);
228						col = 0;
229						p += maxcol;
230					}
231				}
232			}
233		}
234
235		/* Finally, release the console semaphore while we push
236		 * all the data to userspace from our temporary buffer.
237		 *
238		 * AKPM: Even though it's a semaphore, we should drop it because
239		 * the pagefault handling code may want to call printk().
240		 */
241
242		release_console_sem();
243		ret = copy_to_user(buf, con_buf_start, orig_count);
244		acquire_console_sem();
245
246		if (ret) {
247			read += (orig_count - ret);
248			ret = -EFAULT;
249			break;
250		}
251		buf += orig_count;
252		pos += orig_count;
253		read += orig_count;
254		count -= orig_count;
255	}
256	*ppos += read;
257	if (read)
258		ret = read;
259unlock_out:
260	release_console_sem();
261	up(&con_buf_sem);
262	return ret;
263}
264
265static ssize_t
266vcs_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
267{
268	struct inode *inode = file->f_dentry->d_inode;
269	unsigned int currcons = MINOR(inode->i_rdev);
270	long pos = *ppos;
271	long viewed, attr, size, written;
272	char *con_buf0;
273	int col, maxcol;
274	u16 *org0 = NULL, *org = NULL;
275	size_t ret;
276
277	down(&con_buf_sem);
278
279	/* Select the proper current console and verify
280	 * sanity of the situation under the console lock.
281	 */
282	acquire_console_sem();
283
284	attr = (currcons & 128);
285	currcons = (currcons & 127);
286
287	if (currcons == 0) {
288		currcons = fg_console;
289		viewed = 1;
290	} else {
291		currcons--;
292		viewed = 0;
293	}
294	ret = -ENXIO;
295	if (!vc_cons_allocated(currcons))
296		goto unlock_out;
297
298	size = vcs_size(inode);
299	ret = -EINVAL;
300	if (pos < 0 || pos > size)
301		goto unlock_out;
302	if (count > size - pos)
303		count = size - pos;
304	written = 0;
305	while (count) {
306		long this_round = count;
307		size_t orig_count;
308		long p;
309
310		if (this_round > CON_BUF_SIZE)
311			this_round = CON_BUF_SIZE;
312
313		/* Temporarily drop the console lock so that we can read
314		 * in the write data from userspace safely.
315		 */
316		release_console_sem();
317		ret = copy_from_user(con_buf, buf, this_round);
318		acquire_console_sem();
319
320		if (ret) {
321			this_round -= ret;
322			if (!this_round) {
323				/* Abort loop if no data were copied. Otherwise
324				 * fail with -EFAULT.
325				 */
326				if (written)
327					break;
328				ret = -EFAULT;
329				goto unlock_out;
330			}
331		}
332
333		/* The vcs_size might have changed while we slept to grab
334		 * the user buffer, so recheck.
335		 * Return data written up to now on failure.
336		 */
337		size = vcs_size(inode);
338		if (pos >= size)
339			break;
340		if (this_round > size - pos)
341			this_round = size - pos;
342
343		/* OK, now actually push the write to the console
344		 * under the lock using the local kernel buffer.
345		 */
346
347		con_buf0 = con_buf;
348		orig_count = this_round;
349		maxcol = video_num_columns;
350		p = pos;
351		if (!attr) {
352			org0 = org = screen_pos(currcons, p, viewed);
353			col = p % maxcol;
354			p += maxcol - col;
355
356			while (this_round > 0) {
357				unsigned char c = *con_buf0++;
358
359				this_round--;
360				vcs_scr_writew(currcons,
361					       (vcs_scr_readw(currcons, org) & 0xff00) | c, org);
362				org++;
363				if (++col == maxcol) {
364					org = screen_pos(currcons, p, viewed);
365					col = 0;
366					p += maxcol;
367				}
368			}
369		} else {
370			if (p < HEADER_SIZE) {
371				char header[HEADER_SIZE];
372
373				getconsxy(currcons, header + 2);
374				while (p < HEADER_SIZE && this_round > 0) {
375					this_round--;
376					header[p++] = *con_buf0++;
377				}
378				if (!viewed)
379					putconsxy(currcons, header + 2);
380			}
381			p -= HEADER_SIZE;
382			col = (p/2) % maxcol;
383			if (this_round > 0) {
384				org0 = org = screen_pos(currcons, p/2, viewed);
385				if ((p & 1) && this_round > 0) {
386					char c;
387
388					this_round--;
389					c = *con_buf0++;
390#ifdef __BIG_ENDIAN
391					vcs_scr_writew(currcons, c |
392					     (vcs_scr_readw(currcons, org) & 0xff00), org);
393#else
394					vcs_scr_writew(currcons, (c << 8) |
395					     (vcs_scr_readw(currcons, org) & 0xff), org);
396#endif
397					org++;
398					p++;
399					if (++col == maxcol) {
400						org = screen_pos(currcons, p/2, viewed);
401						col = 0;
402					}
403				}
404				p /= 2;
405				p += maxcol - col;
406			}
407			while (this_round > 1) {
408				unsigned short w;
409
410				w = get_unaligned(((const unsigned short *)con_buf0));
411				vcs_scr_writew(currcons, w, org++);
412				con_buf0 += 2;
413				this_round -= 2;
414				if (++col == maxcol) {
415					org = screen_pos(currcons, p, viewed);
416					col = 0;
417					p += maxcol;
418				}
419			}
420			if (this_round > 0) {
421				unsigned char c;
422
423				c = *con_buf0++;
424#ifdef __BIG_ENDIAN
425				vcs_scr_writew(currcons, (vcs_scr_readw(currcons, org) & 0xff) | (c << 8), org);
426#else
427				vcs_scr_writew(currcons, (vcs_scr_readw(currcons, org) & 0xff00) | c, org);
428#endif
429			}
430		}
431		count -= orig_count;
432		written += orig_count;
433		buf += orig_count;
434		pos += orig_count;
435		if (org0)
436			update_region(currcons, (unsigned long)(org0), org-org0);
437	}
438	*ppos += written;
439	ret = written;
440
441unlock_out:
442	release_console_sem();
443
444	up(&con_buf_sem);
445
446	return ret;
447}
448
449static int
450vcs_open(struct inode *inode, struct file *filp)
451{
452	unsigned int currcons = (MINOR(inode->i_rdev) & 127);
453	if(currcons && !vc_cons_allocated(currcons-1))
454		return -ENXIO;
455	return 0;
456}
457
458static struct file_operations vcs_fops = {
459	llseek:		vcs_lseek,
460	read:		vcs_read,
461	write:		vcs_write,
462	open:		vcs_open,
463};
464
465static devfs_handle_t devfs_handle;
466
467void vcs_make_devfs (unsigned int index, int unregister)
468{
469#ifdef CONFIG_DEVFS_FS
470    char name[8];
471
472    sprintf (name, "a%u", index + 1);
473    if (unregister)
474    {
475	devfs_unregister ( devfs_find_handle (devfs_handle, name + 1, 0, 0,
476					      DEVFS_SPECIAL_CHR, 0) );
477	devfs_unregister ( devfs_find_handle (devfs_handle, name, 0, 0,
478					      DEVFS_SPECIAL_CHR, 0) );
479    }
480    else
481    {
482	devfs_register (devfs_handle, name + 1, DEVFS_FL_DEFAULT,
483			VCS_MAJOR, index + 1,
484			S_IFCHR | S_IRUSR | S_IWUSR, &vcs_fops, NULL);
485	devfs_register (devfs_handle, name, DEVFS_FL_DEFAULT,
486			VCS_MAJOR, index + 129,
487			S_IFCHR | S_IRUSR | S_IWUSR, &vcs_fops, NULL);
488    }
489#endif /* CONFIG_DEVFS_FS */
490}
491
492int __init vcs_init(void)
493{
494	int error;
495
496	error = devfs_register_chrdev(VCS_MAJOR, "vcs", &vcs_fops);
497
498	if (error)
499		printk("unable to get major %d for vcs device", VCS_MAJOR);
500
501	devfs_handle = devfs_mk_dir (NULL, "vcc", NULL);
502	devfs_register (devfs_handle, "0", DEVFS_FL_DEFAULT,
503			VCS_MAJOR, 0,
504			S_IFCHR | S_IRUSR | S_IWUSR, &vcs_fops, NULL);
505	devfs_register (devfs_handle, "a", DEVFS_FL_DEFAULT,
506			VCS_MAJOR, 128,
507			S_IFCHR | S_IRUSR | S_IWUSR, &vcs_fops, NULL);
508
509	return error;
510}
511