| 1 | |
| 2 | |
| 3 | * Provide access to virtual console memory.
|
| 4 | * /dev/vcs: the screen as it is being viewed right now (possibly scrolled)
|
| 5 | * /dev/vcsN: the screen of /dev/ttyN (1 <= N <= 63)
|
| 6 | * [minor: N]
|
| 7 | *
|
| 8 | * /dev/vcsaN: idem, but including attributes, and prefixed with
|
| 9 | * the 4 bytes lines,columns,x,y (as screendump used to give).
|
| 10 | * Attribute/character pair is in native endianity.
|
| 11 | * [minor: N+128]
|
| 12 | *
|
| 13 | * /dev/vcsuN: similar to /dev/vcsaN but using 4-byte unicode values
|
| 14 | * instead of 1-byte screen glyph values.
|
| 15 | * [minor: N+64]
|
| 16 | *
|
| 17 | * /dev/vcsuaN: same idea as /dev/vcsaN for unicode (not yet implemented).
|
| 18 | *
|
| 19 | * This replaces screendump and part of selection, so that the system
|
| 20 | * administrator can control access using file system permissions.
|
| 21 | *
|
| 22 | * [email protected] - efter Friedas begravelse - 950211
|
| 23 | *
|
| 24 | * [email protected] - modified not to send characters to wrong console
|
| 25 | * - fixed some fatal off-by-one bugs (0-- no longer == -1 -> looping and looping and looping...)
|
| 26 | * - making it shorter - scr_readw are macros which expand in PRETTY long code
|
| 27 |
|
| 28 |
|
| 29 | #include <linux/kernel.h>
|
| 30 | #include <linux/major.h>
|
| 31 | #include <linux/errno.h>
|
| 32 | #include <linux/export.h>
|
| 33 | #include <linux/tty.h>
|
| 34 | #include <linux/interrupt.h>
|
| 35 | #include <linux/mm.h>
|
| 36 | #include <linux/init.h>
|
| 37 | #include <linux/vt_kern.h>
|
| 38 | #include <linux/selection.h>
|
| 39 | #include <linux/kbd_kern.h>
|
| 40 | #include <linux/console.h>
|
| 41 | #include <linux/device.h>
|
| 42 | #include <linux/sched.h>
|
| 43 | #include <linux/fs.h>
|
| 44 | #include <linux/poll.h>
|
| 45 | #include <linux/signal.h>
|
| 46 | #include <linux/slab.h>
|
| 47 | #include <linux/notifier.h>
|
| 48 |
|
| 49 | #include <linux/uaccess.h>
|
| 50 | #include <asm/byteorder.h>
|
| 51 | #include <linux/unaligned.h>
|
| 52 |
|
| 53 | #define HEADER_SIZE 4u
|
| 54 | #define CON_BUF_SIZE (IS_ENABLED(CONFIG_BASE_SMALL) ? 256 : PAGE_SIZE)
|
| 55 |
|
| 56 | |
| 57 | * Our minor space:
|
| 58 | *
|
| 59 | * 0 ... 63 glyph mode without attributes
|
| 60 | * 64 ... 127 unicode mode without attributes
|
| 61 | * 128 ... 191 glyph mode with attributes
|
| 62 | * 192 ... 255 unused (reserved for unicode with attributes)
|
| 63 | *
|
| 64 | * This relies on MAX_NR_CONSOLES being <= 63, meaning 63 actual consoles
|
| 65 | * with minors 0, 64, 128 and 192 being proxies for the foreground console.
|
| 66 |
|
| 67 | #if MAX_NR_CONSOLES > 63
|
| 68 | #warning "/dev/vcs* devices may not accommodate more than 63 consoles"
|
| 69 | #endif
|
| 70 |
|
| 71 | #define console(inode) (iminor(inode) & 63)
|
| 72 | #define use_unicode(inode) (iminor(inode) & 64)
|
| 73 | #define use_attributes(inode) (iminor(inode) & 128)
|
| 74 |
|
| 75 | struct vcs_poll_data {
|
| 76 | struct notifier_block notifier;
|
| 77 | unsigned int cons_num;
|
| 78 | int event;
|
| 79 | wait_queue_head_t waitq;
|
| 80 | struct fasync_struct *fasync;
|
| 81 | };
|
| 82 |
|
| 83 | static int
|
| 84 | vcs_notifier(struct notifier_block *nb, unsigned long code, void *_param)
|
| 85 | {
|
| 86 | struct vt_notifier_param *param = _param;
|
| 87 | struct vc_data *vc = param->vc;
|
| 88 | struct vcs_poll_data *poll =
|
| 89 | container_of(nb, struct vcs_poll_data, notifier);
|
| 90 | int currcons = poll->cons_num;
|
| 91 | int fa_band;
|
| 92 |
|
| 93 | switch (code) {
|
| 94 | case VT_UPDATE:
|
| 95 | fa_band = POLL_PRI;
|
| 96 | break;
|
| 97 | case VT_DEALLOCATE:
|
| 98 | fa_band = POLL_HUP;
|
| 99 | break;
|
| 100 | default:
|
| 101 | return NOTIFY_DONE;
|
| 102 | }
|
| 103 |
|
| 104 | if (currcons == 0)
|
| 105 | currcons = fg_console;
|
| 106 | else
|
| 107 | currcons--;
|
| 108 | if (currcons != vc->vc_num)
|
| 109 | return NOTIFY_DONE;
|
| 110 |
|
| 111 | poll->event = code;
|
| 112 | wake_up_interruptible(&poll->waitq);
|
| 113 | kill_fasync(&poll->fasync, SIGIO, fa_band);
|
| 114 | return NOTIFY_OK;
|
| 115 | }
|
| 116 |
|
| 117 | static void
|
| 118 | vcs_poll_data_free(struct vcs_poll_data *poll)
|
| 119 | {
|
| 120 | unregister_vt_notifier(&poll->notifier);
|
| 121 | kfree(poll);
|
| 122 | }
|
| 123 |
|
| 124 | static struct vcs_poll_data *
|
| 125 | vcs_poll_data_get(struct file *file)
|
| 126 | {
|
| 127 | struct vcs_poll_data *poll = file->private_data, *kill = NULL;
|
| 128 |
|
| 129 | if (poll)
|
| 130 | return poll;
|
| 131 |
|
| 132 | poll = kzalloc_obj(*poll);
|
| 133 | if (!poll)
|
| 134 | return NULL;
|
| 135 | poll->cons_num = console(file_inode(file));
|
| 136 | init_waitqueue_head(&poll->waitq);
|
| 137 | poll->notifier.notifier_call = vcs_notifier;
|
| 138 | |
| 139 | * In order not to lose any update event, we must pretend one might
|
| 140 | * have occurred before we have a chance to register our notifier.
|
| 141 | * This is also how user space has come to detect which kernels
|
| 142 | * support POLLPRI on /dev/vcs* devices i.e. using poll() with
|
| 143 | * POLLPRI and a zero timeout.
|
| 144 |
|
| 145 | poll->event = VT_UPDATE;
|
| 146 |
|
| 147 | if (register_vt_notifier(&poll->notifier) != 0) {
|
| 148 | kfree(poll);
|
| 149 | return NULL;
|
| 150 | }
|
| 151 |
|
| 152 | |
| 153 | * This code may be called either through ->poll() or ->fasync().
|
| 154 | * If we have two threads using the same file descriptor, they could
|
| 155 | * both enter this function, both notice that the structure hasn't
|
| 156 | * been allocated yet and go ahead allocating it in parallel, but
|
| 157 | * only one of them must survive and be shared otherwise we'd leak
|
| 158 | * memory with a dangling notifier callback.
|
| 159 |
|
| 160 | spin_lock(&file->f_lock);
|
| 161 | if (!file->private_data) {
|
| 162 | file->private_data = poll;
|
| 163 | } else {
|
| 164 |
|
| 165 | kill = poll;
|
| 166 | poll = file->private_data;
|
| 167 | }
|
| 168 | spin_unlock(&file->f_lock);
|
| 169 | if (kill)
|
| 170 | vcs_poll_data_free(kill);
|
| 171 |
|
| 172 | return poll;
|
| 173 | }
|
| 174 |
|
| 175 | |
| 176 | * vcs_vc - return VC for @inode
|
| 177 | * @inode: inode for which to return a VC
|
| 178 | * @viewed: returns whether this console is currently foreground (viewed)
|
| 179 | *
|
| 180 | * Must be called with console_lock.
|
| 181 |
|
| 182 | static struct vc_data *vcs_vc(struct inode *inode, bool *viewed)
|
| 183 | {
|
| 184 | unsigned int currcons = console(inode);
|
| 185 |
|
| 186 | WARN_CONSOLE_UNLOCKED();
|
| 187 |
|
| 188 | if (currcons == 0) {
|
| 189 | currcons = fg_console;
|
| 190 | if (viewed)
|
| 191 | *viewed = true;
|
| 192 | } else {
|
| 193 | currcons--;
|
| 194 | if (viewed)
|
| 195 | *viewed = false;
|
| 196 | }
|
| 197 | return vc_cons[currcons].d;
|
| 198 | }
|
| 199 |
|
| 200 | |
| 201 | * vcs_size - return size for a VC in @vc
|
| 202 | * @vc: which VC
|
| 203 | * @attr: does it use attributes?
|
| 204 | * @unicode: is it unicode?
|
| 205 | *
|
| 206 | * Must be called with console_lock.
|
| 207 |
|
| 208 | static int vcs_size(const struct vc_data *vc, bool attr, bool unicode)
|
| 209 | {
|
| 210 | int size;
|
| 211 |
|
| 212 | WARN_CONSOLE_UNLOCKED();
|
| 213 |
|
| 214 | size = vc->vc_rows * vc->vc_cols;
|
| 215 |
|
| 216 | if (attr) {
|
| 217 | if (unicode)
|
| 218 | return -EOPNOTSUPP;
|
| 219 |
|
| 220 | size = 2 * size + HEADER_SIZE;
|
| 221 | } else if (unicode)
|
| 222 | size *= 4;
|
| 223 |
|
| 224 | return size;
|
| 225 | }
|
| 226 |
|
| 227 | static loff_t vcs_lseek(struct file *file, loff_t offset, int orig)
|
| 228 | {
|
| 229 | struct inode *inode = file_inode(file);
|
| 230 | struct vc_data *vc;
|
| 231 | int size;
|
| 232 |
|
| 233 | scoped_guard(console_lock) {
|
| 234 | vc = vcs_vc(inode, NULL);
|
| 235 | if (!vc)
|
| 236 | return -ENXIO;
|
| 237 |
|
| 238 | size = vcs_size(vc, use_attributes(inode), use_unicode(inode));
|
| 239 | }
|
| 240 | if (size < 0)
|
| 241 | return size;
|
| 242 | return fixed_size_llseek(file, offset, orig, size);
|
| 243 | }
|
| 244 |
|
| 245 | static int vcs_read_buf_uni(struct vc_data *vc, char *con_buf,
|
| 246 | unsigned int pos, unsigned int count, bool viewed)
|
| 247 | {
|
| 248 | unsigned int nr, row, col, maxcol = vc->vc_cols;
|
| 249 | int ret;
|
| 250 |
|
| 251 | ret = vc_uniscr_check(vc);
|
| 252 | if (ret)
|
| 253 | return ret;
|
| 254 |
|
| 255 | pos /= 4;
|
| 256 | row = pos / maxcol;
|
| 257 | col = pos % maxcol;
|
| 258 | nr = maxcol - col;
|
| 259 | do {
|
| 260 | if (nr > count / 4)
|
| 261 | nr = count / 4;
|
| 262 | vc_uniscr_copy_line(vc, con_buf, viewed, row, col, nr);
|
| 263 | con_buf += nr * 4;
|
| 264 | count -= nr * 4;
|
| 265 | row++;
|
| 266 | col = 0;
|
| 267 | nr = maxcol;
|
| 268 | } while (count);
|
| 269 |
|
| 270 | return 0;
|
| 271 | }
|
| 272 |
|
| 273 | static void vcs_read_buf_noattr(const struct vc_data *vc, char *con_buf,
|
| 274 | unsigned int pos, unsigned int count, bool viewed)
|
| 275 | {
|
| 276 | u16 *org;
|
| 277 | unsigned int col, maxcol = vc->vc_cols;
|
| 278 |
|
| 279 | org = screen_pos(vc, pos, viewed);
|
| 280 | col = pos % maxcol;
|
| 281 | pos += maxcol - col;
|
| 282 |
|
| 283 | while (count-- > 0) {
|
| 284 | *con_buf++ = (vcs_scr_readw(vc, org++) & 0xff);
|
| 285 | if (++col == maxcol) {
|
| 286 | org = screen_pos(vc, pos, viewed);
|
| 287 | col = 0;
|
| 288 | pos += maxcol;
|
| 289 | }
|
| 290 | }
|
| 291 | }
|
| 292 |
|
| 293 | static unsigned int vcs_read_buf(const struct vc_data *vc, char *con_buf,
|
| 294 | unsigned int pos, unsigned int count, bool viewed,
|
| 295 | unsigned int *skip)
|
| 296 | {
|
| 297 | u16 *org, *con_buf16;
|
| 298 | unsigned int col, maxcol = vc->vc_cols;
|
| 299 | unsigned int filled = count;
|
| 300 |
|
| 301 | if (pos < HEADER_SIZE) {
|
| 302 |
|
| 303 | con_buf[0] = min(vc->vc_rows, 0xFFu);
|
| 304 | con_buf[1] = min(vc->vc_cols, 0xFFu);
|
| 305 | getconsxy(vc, con_buf + 2);
|
| 306 |
|
| 307 | *skip += pos;
|
| 308 | count += pos;
|
| 309 | if (count > CON_BUF_SIZE) {
|
| 310 | count = CON_BUF_SIZE;
|
| 311 | filled = count - pos;
|
| 312 | }
|
| 313 |
|
| 314 |
|
| 315 | count -= min(HEADER_SIZE, count);
|
| 316 | pos = HEADER_SIZE;
|
| 317 | con_buf += HEADER_SIZE;
|
| 318 |
|
| 319 | } else if (pos & 1) {
|
| 320 | |
| 321 | * Skip first byte for output if start address is odd. Update
|
| 322 | * region sizes up/down depending on free space in buffer.
|
| 323 |
|
| 324 | (*skip)++;
|
| 325 | if (count < CON_BUF_SIZE)
|
| 326 | count++;
|
| 327 | else
|
| 328 | filled--;
|
| 329 | }
|
| 330 |
|
| 331 | if (!count)
|
| 332 | return filled;
|
| 333 |
|
| 334 | pos -= HEADER_SIZE;
|
| 335 | pos /= 2;
|
| 336 | col = pos % maxcol;
|
| 337 |
|
| 338 | org = screen_pos(vc, pos, viewed);
|
| 339 | pos += maxcol - col;
|
| 340 |
|
| 341 | |
| 342 | * Buffer has even length, so we can always copy character + attribute.
|
| 343 | * We do not copy last byte to userspace if count is odd.
|
| 344 |
|
| 345 | count = (count + 1) / 2;
|
| 346 | con_buf16 = (u16 *)con_buf;
|
| 347 |
|
| 348 | while (count) {
|
| 349 | *con_buf16++ = vcs_scr_readw(vc, org++);
|
| 350 | count--;
|
| 351 | if (++col == maxcol) {
|
| 352 | org = screen_pos(vc, pos, viewed);
|
| 353 | col = 0;
|
| 354 | pos += maxcol;
|
| 355 | }
|
| 356 | }
|
| 357 |
|
| 358 | return filled;
|
| 359 | }
|
| 360 |
|
| 361 | static ssize_t
|
| 362 | vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
|
| 363 | {
|
| 364 | struct inode *inode = file_inode(file);
|
| 365 | struct vc_data *vc;
|
| 366 | struct vcs_poll_data *poll;
|
| 367 | unsigned int read;
|
| 368 | ssize_t ret;
|
| 369 | loff_t pos;
|
| 370 | bool viewed, attr, uni_mode;
|
| 371 |
|
| 372 | char *con_buf __free(kfree) = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
| 373 | if (!con_buf)
|
| 374 | return -ENOMEM;
|
| 375 |
|
| 376 | pos = *ppos;
|
| 377 |
|
| 378 | |
| 379 | * sanity of the situation under the console lock.
|
| 380 |
|
| 381 | guard(console_lock)();
|
| 382 |
|
| 383 | uni_mode = use_unicode(inode);
|
| 384 | attr = use_attributes(inode);
|
| 385 |
|
| 386 | if (pos < 0)
|
| 387 | return -EINVAL;
|
| 388 |
|
| 389 | if (uni_mode && (pos | count) & 3)
|
| 390 | return -EINVAL;
|
| 391 |
|
| 392 | poll = file->private_data;
|
| 393 | if (count && poll)
|
| 394 | poll->event = 0;
|
| 395 | read = 0;
|
| 396 | ret = 0;
|
| 397 | while (count) {
|
| 398 | unsigned int this_round, skip = 0;
|
| 399 | int size;
|
| 400 |
|
| 401 | vc = vcs_vc(inode, &viewed);
|
| 402 | if (!vc) {
|
| 403 | ret = -ENXIO;
|
| 404 | break;
|
| 405 | }
|
| 406 |
|
| 407 | |
| 408 | * as copy_to_user at the end of this loop
|
| 409 | * could sleep.
|
| 410 |
|
| 411 | size = vcs_size(vc, attr, uni_mode);
|
| 412 | if (size < 0) {
|
| 413 | ret = size;
|
| 414 | break;
|
| 415 | }
|
| 416 | if (pos >= size)
|
| 417 | break;
|
| 418 | if (count > size - pos)
|
| 419 | count = size - pos;
|
| 420 |
|
| 421 | this_round = count;
|
| 422 | if (this_round > CON_BUF_SIZE)
|
| 423 | this_round = CON_BUF_SIZE;
|
| 424 |
|
| 425 | |
| 426 | * Then we can drop the console spinlock and safely
|
| 427 | * attempt to move it to userspace.
|
| 428 |
|
| 429 |
|
| 430 | if (uni_mode) {
|
| 431 | ret = vcs_read_buf_uni(vc, con_buf, pos, this_round,
|
| 432 | viewed);
|
| 433 | if (ret)
|
| 434 | break;
|
| 435 | } else if (!attr) {
|
| 436 | vcs_read_buf_noattr(vc, con_buf, pos, this_round,
|
| 437 | viewed);
|
| 438 | } else {
|
| 439 | this_round = vcs_read_buf(vc, con_buf, pos, this_round,
|
| 440 | viewed, &skip);
|
| 441 | }
|
| 442 |
|
| 443 | |
| 444 | * all the data to userspace from our temporary buffer.
|
| 445 | *
|
| 446 | * AKPM: Even though it's a semaphore, we should drop it because
|
| 447 | * the pagefault handling code may want to call printk().
|
| 448 |
|
| 449 |
|
| 450 | console_unlock();
|
| 451 | ret = copy_to_user(buf, con_buf + skip, this_round);
|
| 452 | console_lock();
|
| 453 |
|
| 454 | if (ret) {
|
| 455 | read += this_round - ret;
|
| 456 | ret = -EFAULT;
|
| 457 | break;
|
| 458 | }
|
| 459 | buf += this_round;
|
| 460 | pos += this_round;
|
| 461 | read += this_round;
|
| 462 | count -= this_round;
|
| 463 | }
|
| 464 | *ppos += read;
|
| 465 | if (read)
|
| 466 | return read;
|
| 467 |
|
| 468 | return ret;
|
| 469 | }
|
| 470 |
|
| 471 | static u16 *vcs_write_buf_noattr(struct vc_data *vc, const char *con_buf,
|
| 472 | unsigned int pos, unsigned int count, bool viewed, u16 **org0)
|
| 473 | {
|
| 474 | u16 *org;
|
| 475 | unsigned int col, maxcol = vc->vc_cols;
|
| 476 |
|
| 477 | *org0 = org = screen_pos(vc, pos, viewed);
|
| 478 | col = pos % maxcol;
|
| 479 | pos += maxcol - col;
|
| 480 |
|
| 481 | while (count > 0) {
|
| 482 | unsigned char c = *con_buf++;
|
| 483 |
|
| 484 | count--;
|
| 485 | vcs_scr_writew(vc,
|
| 486 | (vcs_scr_readw(vc, org) & 0xff00) | c, org);
|
| 487 | org++;
|
| 488 | if (++col == maxcol) {
|
| 489 | org = screen_pos(vc, pos, viewed);
|
| 490 | col = 0;
|
| 491 | pos += maxcol;
|
| 492 | }
|
| 493 | }
|
| 494 |
|
| 495 | return org;
|
| 496 | }
|
| 497 |
|
| 498 | |
| 499 | * Compilers (gcc 10) are unable to optimize the swap in cpu_to_le16. So do it
|
| 500 | * the poor man way.
|
| 501 |
|
| 502 | static inline u16 vc_compile_le16(u8 hi, u8 lo)
|
| 503 | {
|
| 504 | #ifdef __BIG_ENDIAN
|
| 505 | return (lo << 8u) | hi;
|
| 506 | #else
|
| 507 | return (hi << 8u) | lo;
|
| 508 | #endif
|
| 509 | }
|
| 510 |
|
| 511 | static u16 *vcs_write_buf(struct vc_data *vc, const char *con_buf,
|
| 512 | unsigned int pos, unsigned int count, bool viewed, u16 **org0)
|
| 513 | {
|
| 514 | u16 *org;
|
| 515 | unsigned int col, maxcol = vc->vc_cols;
|
| 516 | unsigned char c;
|
| 517 |
|
| 518 |
|
| 519 | if (pos < HEADER_SIZE) {
|
| 520 | char header[HEADER_SIZE];
|
| 521 |
|
| 522 | getconsxy(vc, header + 2);
|
| 523 | while (pos < HEADER_SIZE && count > 0) {
|
| 524 | count--;
|
| 525 | header[pos++] = *con_buf++;
|
| 526 | }
|
| 527 | if (!viewed)
|
| 528 | putconsxy(vc, header + 2);
|
| 529 | }
|
| 530 |
|
| 531 | if (!count)
|
| 532 | return NULL;
|
| 533 |
|
| 534 | pos -= HEADER_SIZE;
|
| 535 | col = (pos/2) % maxcol;
|
| 536 |
|
| 537 | *org0 = org = screen_pos(vc, pos/2, viewed);
|
| 538 |
|
| 539 |
|
| 540 | if (pos & 1) {
|
| 541 | count--;
|
| 542 | c = *con_buf++;
|
| 543 | vcs_scr_writew(vc, vc_compile_le16(c, vcs_scr_readw(vc, org)),
|
| 544 | org);
|
| 545 | org++;
|
| 546 | pos++;
|
| 547 | if (++col == maxcol) {
|
| 548 | org = screen_pos(vc, pos/2, viewed);
|
| 549 | col = 0;
|
| 550 | }
|
| 551 | }
|
| 552 |
|
| 553 | pos /= 2;
|
| 554 | pos += maxcol - col;
|
| 555 |
|
| 556 |
|
| 557 | while (count > 1) {
|
| 558 | unsigned short w;
|
| 559 |
|
| 560 | w = get_unaligned(((unsigned short *)con_buf));
|
| 561 | vcs_scr_writew(vc, w, org++);
|
| 562 | con_buf += 2;
|
| 563 | count -= 2;
|
| 564 | if (++col == maxcol) {
|
| 565 | org = screen_pos(vc, pos, viewed);
|
| 566 | col = 0;
|
| 567 | pos += maxcol;
|
| 568 | }
|
| 569 | }
|
| 570 |
|
| 571 | if (!count)
|
| 572 | return org;
|
| 573 |
|
| 574 |
|
| 575 | c = *con_buf++;
|
| 576 | vcs_scr_writew(vc, vc_compile_le16(vcs_scr_readw(vc, org) >> 8, c),
|
| 577 | org);
|
| 578 |
|
| 579 | return org;
|
| 580 | }
|
| 581 |
|
| 582 | static ssize_t
|
| 583 | vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
|
| 584 | {
|
| 585 | struct inode *inode = file_inode(file);
|
| 586 | struct vc_data *vc;
|
| 587 | u16 *org0, *org;
|
| 588 | unsigned int written;
|
| 589 | int size;
|
| 590 | ssize_t ret;
|
| 591 | loff_t pos;
|
| 592 | bool viewed, attr;
|
| 593 |
|
| 594 | if (use_unicode(inode))
|
| 595 | return -EOPNOTSUPP;
|
| 596 |
|
| 597 | char *con_buf __free(kfree) = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
| 598 | if (!con_buf)
|
| 599 | return -ENOMEM;
|
| 600 |
|
| 601 | pos = *ppos;
|
| 602 |
|
| 603 | |
| 604 | * sanity of the situation under the console lock.
|
| 605 |
|
| 606 | guard(console_lock)();
|
| 607 |
|
| 608 | attr = use_attributes(inode);
|
| 609 | vc = vcs_vc(inode, &viewed);
|
| 610 | if (!vc)
|
| 611 | return -ENXIO;
|
| 612 |
|
| 613 | size = vcs_size(vc, attr, false);
|
| 614 | if (size < 0)
|
| 615 | return size;
|
| 616 | if (pos < 0 || pos > size)
|
| 617 | return -EINVAL;
|
| 618 | if (count > size - pos)
|
| 619 | count = size - pos;
|
| 620 | written = 0;
|
| 621 | while (count) {
|
| 622 | unsigned int this_round = count;
|
| 623 |
|
| 624 | if (this_round > CON_BUF_SIZE)
|
| 625 | this_round = CON_BUF_SIZE;
|
| 626 |
|
| 627 | |
| 628 | * in the write data from userspace safely.
|
| 629 |
|
| 630 | console_unlock();
|
| 631 | ret = copy_from_user(con_buf, buf, this_round);
|
| 632 | console_lock();
|
| 633 |
|
| 634 | |
| 635 | * while we slept to grab the user buffer; recheck here, before
|
| 636 | * if (ret), so every break path below passes a fresh vc to the
|
| 637 | * post-loop vcs_scr_updated(). Return data written so far.
|
| 638 |
|
| 639 | vc = vcs_vc(inode, &viewed);
|
| 640 | if (!vc) {
|
| 641 | if (written)
|
| 642 | break;
|
| 643 | return -ENXIO;
|
| 644 | }
|
| 645 |
|
| 646 | if (ret) {
|
| 647 | this_round -= ret;
|
| 648 | if (!this_round) {
|
| 649 | |
| 650 | * fail with -EFAULT.
|
| 651 |
|
| 652 | if (written)
|
| 653 | break;
|
| 654 | return -EFAULT;
|
| 655 | }
|
| 656 | }
|
| 657 | size = vcs_size(vc, attr, false);
|
| 658 | if (size < 0) {
|
| 659 | if (written)
|
| 660 | break;
|
| 661 | return size;
|
| 662 | }
|
| 663 | if (pos >= size)
|
| 664 | break;
|
| 665 | if (this_round > size - pos)
|
| 666 | this_round = size - pos;
|
| 667 |
|
| 668 | |
| 669 | * under the lock using the local kernel buffer.
|
| 670 |
|
| 671 |
|
| 672 | if (attr)
|
| 673 | org = vcs_write_buf(vc, con_buf, pos, this_round,
|
| 674 | viewed, &org0);
|
| 675 | else
|
| 676 | org = vcs_write_buf_noattr(vc, con_buf, pos, this_round,
|
| 677 | viewed, &org0);
|
| 678 |
|
| 679 | count -= this_round;
|
| 680 | written += this_round;
|
| 681 | buf += this_round;
|
| 682 | pos += this_round;
|
| 683 | if (org)
|
| 684 | update_region(vc, (unsigned long)(org0), org - org0);
|
| 685 | }
|
| 686 | *ppos += written;
|
| 687 | ret = written;
|
| 688 | if (written && vc)
|
| 689 | vcs_scr_updated(vc);
|
| 690 |
|
| 691 | return ret;
|
| 692 | }
|
| 693 |
|
| 694 | static __poll_t
|
| 695 | vcs_poll(struct file *file, poll_table *wait)
|
| 696 | {
|
| 697 | struct vcs_poll_data *poll = vcs_poll_data_get(file);
|
| 698 | __poll_t ret = DEFAULT_POLLMASK|EPOLLERR;
|
| 699 |
|
| 700 | if (poll) {
|
| 701 | poll_wait(file, &poll->waitq, wait);
|
| 702 | switch (poll->event) {
|
| 703 | case VT_UPDATE:
|
| 704 | ret = DEFAULT_POLLMASK|EPOLLPRI;
|
| 705 | break;
|
| 706 | case VT_DEALLOCATE:
|
| 707 | ret = DEFAULT_POLLMASK|EPOLLHUP|EPOLLERR;
|
| 708 | break;
|
| 709 | case 0:
|
| 710 | ret = DEFAULT_POLLMASK;
|
| 711 | break;
|
| 712 | }
|
| 713 | }
|
| 714 | return ret;
|
| 715 | }
|
| 716 |
|
| 717 | static int
|
| 718 | vcs_fasync(int fd, struct file *file, int on)
|
| 719 | {
|
| 720 | struct vcs_poll_data *poll = file->private_data;
|
| 721 |
|
| 722 | if (!poll) {
|
| 723 |
|
| 724 | if (!on)
|
| 725 | return 0;
|
| 726 | poll = vcs_poll_data_get(file);
|
| 727 | if (!poll)
|
| 728 | return -ENOMEM;
|
| 729 | }
|
| 730 |
|
| 731 | return fasync_helper(fd, file, on, &poll->fasync);
|
| 732 | }
|
| 733 |
|
| 734 | static int
|
| 735 | vcs_open(struct inode *inode, struct file *filp)
|
| 736 | {
|
| 737 | unsigned int currcons = console(inode);
|
| 738 | bool attr = use_attributes(inode);
|
| 739 | bool uni_mode = use_unicode(inode);
|
| 740 |
|
| 741 |
|
| 742 | if (attr && uni_mode)
|
| 743 | return -EOPNOTSUPP;
|
| 744 |
|
| 745 | guard(console_lock)();
|
| 746 |
|
| 747 | if (currcons && !vc_cons_allocated(currcons - 1))
|
| 748 | return -ENXIO;
|
| 749 |
|
| 750 | return 0;
|
| 751 | }
|
| 752 |
|
| 753 | static int vcs_release(struct inode *inode, struct file *file)
|
| 754 | {
|
| 755 | struct vcs_poll_data *poll = file->private_data;
|
| 756 |
|
| 757 | if (poll)
|
| 758 | vcs_poll_data_free(poll);
|
| 759 | return 0;
|
| 760 | }
|
| 761 |
|
| 762 | static const struct file_operations vcs_fops = {
|
| 763 | .llseek = vcs_lseek,
|
| 764 | .read = vcs_read,
|
| 765 | .write = vcs_write,
|
| 766 | .poll = vcs_poll,
|
| 767 | .fasync = vcs_fasync,
|
| 768 | .open = vcs_open,
|
| 769 | .release = vcs_release,
|
| 770 | };
|
| 771 |
|
| 772 | static const struct class vc_class = {
|
| 773 | .name = "vc",
|
| 774 | };
|
| 775 |
|
| 776 | void vcs_make_sysfs(int index)
|
| 777 | {
|
| 778 | device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, index + 1), NULL, "vcs%u", index + 1);
|
| 779 | device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, index + 65), NULL, "vcsu%u", index + 1);
|
| 780 | device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, index + 129), NULL, "vcsa%u", index + 1);
|
| 781 | }
|
| 782 |
|
| 783 | void vcs_remove_sysfs(int index)
|
| 784 | {
|
| 785 | device_destroy(&vc_class, MKDEV(VCS_MAJOR, index + 1));
|
| 786 | device_destroy(&vc_class, MKDEV(VCS_MAJOR, index + 65));
|
| 787 | device_destroy(&vc_class, MKDEV(VCS_MAJOR, index + 129));
|
| 788 | }
|
| 789 |
|
| 790 | int __init vcs_init(void)
|
| 791 | {
|
| 792 | unsigned int i;
|
| 793 |
|
| 794 | if (register_chrdev(VCS_MAJOR, "vcs", &vcs_fops))
|
| 795 | panic("unable to get major %d for vcs device", VCS_MAJOR);
|
| 796 | if (class_register(&vc_class))
|
| 797 | panic("unable to create vc_class");
|
| 798 |
|
| 799 | device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, 0), NULL, "vcs");
|
| 800 | device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, 64), NULL, "vcsu");
|
| 801 | device_create(&vc_class, NULL, MKDEV(VCS_MAJOR, 128), NULL, "vcsa");
|
| 802 | for (i = 0; i < MIN_NR_CONSOLES; i++)
|
| 803 | vcs_make_sysfs(i);
|
| 804 | return 0;
|
| 805 | }
|