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