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