vc_screen.c 16.6 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0
L
Linus Torvalds 已提交
2 3
/*
 * Provide access to virtual console memory.
4
 * /dev/vcs: the screen as it is being viewed right now (possibly scrolled)
L
Linus Torvalds 已提交
5 6 7 8 9 10 11 12
 * /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]
 *
13 14 15 16 17 18
 * /dev/vcsuN: similar to /dev/vcsaN but using 4-byte unicode values
 *	instead of 1-byte screen glyph values.
 *            [minor: N+64]
 *
 * /dev/vcsuaN: same idea as /dev/vcsaN for unicode (not yet implemented).
 *
L
Linus Torvalds 已提交
19 20 21 22 23 24 25 26 27 28 29 30 31
 * 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/kernel.h>
#include <linux/major.h>
#include <linux/errno.h>
32
#include <linux/export.h>
L
Linus Torvalds 已提交
33 34 35 36 37 38 39 40 41
#include <linux/tty.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/vt_kern.h>
#include <linux/selection.h>
#include <linux/kbd_kern.h>
#include <linux/console.h>
#include <linux/device.h>
N
Nicolas Pitre 已提交
42 43 44 45 46 47
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/signal.h>
#include <linux/slab.h>
#include <linux/notifier.h>
48

49
#include <linux/uaccess.h>
L
Linus Torvalds 已提交
50 51 52 53 54 55 56 57
#include <asm/byteorder.h>
#include <asm/unaligned.h>

#undef attr
#undef org
#undef addr
#define HEADER_SIZE	4

58 59
#define CON_BUF_SIZE (CONFIG_BASE_SMALL ? 256 : PAGE_SIZE)

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
/*
 * Our minor space:
 *
 *   0 ... 63	glyph mode without attributes
 *  64 ... 127	unicode mode without attributes
 * 128 ... 191	glyph mode with attributes
 * 192 ... 255	unused (reserved for unicode with attributes)
 *
 * This relies on MAX_NR_CONSOLES being  <= 63, meaning 63 actual consoles
 * with minors 0, 64, 128 and 192 being proxies for the foreground console.
 */
#if MAX_NR_CONSOLES > 63
#warning "/dev/vcs* devices may not accommodate more than 63 consoles"
#endif

#define console(inode)		(iminor(inode) & 63)
#define use_unicode(inode)	(iminor(inode) & 64)
#define use_attributes(inode)	(iminor(inode) & 128)


N
Nicolas Pitre 已提交
80 81 82
struct vcs_poll_data {
	struct notifier_block notifier;
	unsigned int cons_num;
83
	int event;
N
Nicolas Pitre 已提交
84 85 86 87 88 89 90 91 92 93 94 95
	wait_queue_head_t waitq;
	struct fasync_struct *fasync;
};

static int
vcs_notifier(struct notifier_block *nb, unsigned long code, void *_param)
{
	struct vt_notifier_param *param = _param;
	struct vc_data *vc = param->vc;
	struct vcs_poll_data *poll =
		container_of(nb, struct vcs_poll_data, notifier);
	int currcons = poll->cons_num;
96
	int fa_band;
N
Nicolas Pitre 已提交
97

98 99 100 101 102 103 104 105
	switch (code) {
	case VT_UPDATE:
		fa_band = POLL_PRI;
		break;
	case VT_DEALLOCATE:
		fa_band = POLL_HUP;
		break;
	default:
N
Nicolas Pitre 已提交
106
		return NOTIFY_DONE;
107
	}
N
Nicolas Pitre 已提交
108 109 110 111 112 113 114 115

	if (currcons == 0)
		currcons = fg_console;
	else
		currcons--;
	if (currcons != vc->vc_num)
		return NOTIFY_DONE;

116
	poll->event = code;
N
Nicolas Pitre 已提交
117
	wake_up_interruptible(&poll->waitq);
118
	kill_fasync(&poll->fasync, SIGIO, fa_band);
N
Nicolas Pitre 已提交
119 120 121 122 123 124 125 126 127 128 129 130 131
	return NOTIFY_OK;
}

static void
vcs_poll_data_free(struct vcs_poll_data *poll)
{
	unregister_vt_notifier(&poll->notifier);
	kfree(poll);
}

static struct vcs_poll_data *
vcs_poll_data_get(struct file *file)
{
132
	struct vcs_poll_data *poll = file->private_data, *kill = NULL;
N
Nicolas Pitre 已提交
133 134 135 136 137 138 139

	if (poll)
		return poll;

	poll = kzalloc(sizeof(*poll), GFP_KERNEL);
	if (!poll)
		return NULL;
140
	poll->cons_num = console(file_inode(file));
N
Nicolas Pitre 已提交
141 142
	init_waitqueue_head(&poll->waitq);
	poll->notifier.notifier_call = vcs_notifier;
143 144 145 146 147 148 149 150 151
	/*
	 * In order not to lose any update event, we must pretend one might
	 * have occurred before we have a chance to register our notifier.
	 * This is also how user space has come to detect which kernels
	 * support POLLPRI on /dev/vcs* devices i.e. using poll() with
	 * POLLPRI and a zero timeout.
	 */
	poll->event = VT_UPDATE;

N
Nicolas Pitre 已提交
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
	if (register_vt_notifier(&poll->notifier) != 0) {
		kfree(poll);
		return NULL;
	}

	/*
	 * This code may be called either through ->poll() or ->fasync().
	 * If we have two threads using the same file descriptor, they could
	 * both enter this function, both notice that the structure hasn't
	 * been allocated yet and go ahead allocating it in parallel, but
	 * only one of them must survive and be shared otherwise we'd leak
	 * memory with a dangling notifier callback.
	 */
	spin_lock(&file->f_lock);
	if (!file->private_data) {
		file->private_data = poll;
	} else {
		/* someone else raced ahead of us */
170
		kill = poll;
N
Nicolas Pitre 已提交
171 172 173
		poll = file->private_data;
	}
	spin_unlock(&file->f_lock);
174 175
	if (kill)
		vcs_poll_data_free(kill);
N
Nicolas Pitre 已提交
176 177 178 179

	return poll;
}

180 181 182 183 184 185 186
/*
 * Returns VC for inode.
 * Must be called with console_lock.
 */
static struct vc_data*
vcs_vc(struct inode *inode, int *viewed)
{
187
	unsigned int currcons = console(inode);
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206

	WARN_CONSOLE_UNLOCKED();

	if (currcons == 0) {
		currcons = fg_console;
		if (viewed)
			*viewed = 1;
	} else {
		currcons--;
		if (viewed)
			*viewed = 0;
	}
	return vc_cons[currcons].d;
}

/*
 * Returns size for VC carried by inode.
 * Must be called with console_lock.
 */
L
Linus Torvalds 已提交
207 208 209 210 211 212
static int
vcs_size(struct inode *inode)
{
	int size;
	struct vc_data *vc;

213 214 215 216
	WARN_CONSOLE_UNLOCKED();

	vc = vcs_vc(inode, NULL);
	if (!vc)
L
Linus Torvalds 已提交
217 218 219 220
		return -ENXIO;

	size = vc->vc_rows * vc->vc_cols;

221 222 223
	if (use_attributes(inode)) {
		if (use_unicode(inode))
			return -EOPNOTSUPP;
L
Linus Torvalds 已提交
224
		size = 2*size + HEADER_SIZE;
225 226
	} else if (use_unicode(inode))
		size *= 4;
L
Linus Torvalds 已提交
227 228 229 230 231 232 233
	return size;
}

static loff_t vcs_lseek(struct file *file, loff_t offset, int orig)
{
	int size;

J
Jiri Olsa 已提交
234
	console_lock();
A
Al Viro 已提交
235
	size = vcs_size(file_inode(file));
J
Jiri Olsa 已提交
236
	console_unlock();
237
	if (size < 0)
J
Jiri Olsa 已提交
238
		return size;
A
Al Viro 已提交
239
	return fixed_size_llseek(file, offset, orig, size);
L
Linus Torvalds 已提交
240 241 242 243 244 245
}


static ssize_t
vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
A
Al Viro 已提交
246
	struct inode *inode = file_inode(file);
L
Linus Torvalds 已提交
247
	struct vc_data *vc;
N
Nicolas Pitre 已提交
248
	struct vcs_poll_data *poll;
249 250
	long pos, read;
	int attr, uni_mode, row, col, maxcol, viewed;
L
Linus Torvalds 已提交
251 252
	unsigned short *org = NULL;
	ssize_t ret;
253
	char *con_buf;
L
Linus Torvalds 已提交
254

255 256 257
	con_buf = (char *) __get_free_page(GFP_KERNEL);
	if (!con_buf)
		return -ENOMEM;
L
Linus Torvalds 已提交
258 259 260 261 262 263

	pos = *ppos;

	/* Select the proper current console and verify
	 * sanity of the situation under the console lock.
	 */
264
	console_lock();
L
Linus Torvalds 已提交
265

266 267
	uni_mode = use_unicode(inode);
	attr = use_attributes(inode);
L
Linus Torvalds 已提交
268
	ret = -ENXIO;
269 270
	vc = vcs_vc(inode, &viewed);
	if (!vc)
L
Linus Torvalds 已提交
271 272 273 274 275
		goto unlock_out;

	ret = -EINVAL;
	if (pos < 0)
		goto unlock_out;
276 277 278 279
	/* we enforce 32-bit alignment for pos and count in unicode mode */
	if (uni_mode && (pos | count) & 3)
		goto unlock_out;

N
Nicolas Pitre 已提交
280 281
	poll = file->private_data;
	if (count && poll)
282
		poll->event = 0;
L
Linus Torvalds 已提交
283 284 285 286 287 288 289 290 291 292 293 294 295
	read = 0;
	ret = 0;
	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);
J
Jiri Olsa 已提交
296 297 298 299 300 301
		if (size < 0) {
			if (read)
				break;
			ret = size;
			goto unlock_out;
		}
L
Linus Torvalds 已提交
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
		if (pos >= size)
			break;
		if (count > size - pos)
			count = size - pos;

		this_round = count;
		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 = vc->vc_cols;
319 320 321 322 323 324 325 326 327 328 329 330 331
		if (uni_mode) {
			unsigned int nr;

			ret = vc_uniscr_check(vc);
			if (ret)
				break;
			p /= 4;
			row = p / vc->vc_cols;
			col = p % maxcol;
			nr = maxcol - col;
			do {
				if (nr > this_round/4)
					nr = this_round/4;
332 333
				vc_uniscr_copy_line(vc, con_buf0, viewed,
						    row, col, nr);
334 335 336 337 338 339 340
				con_buf0 += nr * 4;
				this_round -= nr * 4;
				row++;
				col = 0;
				nr = maxcol;
			} while (this_round);
		} else if (!attr) {
L
Linus Torvalds 已提交
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
			org = screen_pos(vc, p, viewed);
			col = p % maxcol;
			p += maxcol - col;
			while (this_round-- > 0) {
				*con_buf0++ = (vcs_scr_readw(vc, org++) & 0xff);
				if (++col == maxcol) {
					org = screen_pos(vc, p, viewed);
					col = 0;
					p += maxcol;
				}
			}
		} else {
			if (p < HEADER_SIZE) {
				size_t tmp_count;

356 357 358
				/* clamp header values if they don't fit */
				con_buf0[0] = min(vc->vc_rows, 0xFFu);
				con_buf0[1] = min(vc->vc_cols, 0xFFu);
L
Linus Torvalds 已提交
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
				getconsxy(vc, con_buf0 + 2);

				con_buf_start += p;
				this_round += p;
				if (this_round > CON_BUF_SIZE) {
					this_round = CON_BUF_SIZE;
					orig_count = this_round - p;
				}

				tmp_count = HEADER_SIZE;
				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... */
			} 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++;
				if (this_round < CON_BUF_SIZE)
					this_round++;
				else
					orig_count--;
			}
			if (this_round > 0) {
				unsigned short *tmp_buf = (unsigned short *)con_buf0;

				p -= HEADER_SIZE;
				p /= 2;
				col = p % maxcol;

				org = screen_pos(vc, 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;

				while (this_round) {
					*tmp_buf++ = vcs_scr_readw(vc, org++);
					this_round --;
					if (++col == maxcol) {
						org = screen_pos(vc, p, viewed);
						col = 0;
						p += maxcol;
					}
				}
			}
		}

		/* Finally, release the console semaphore while we push
		 * all the data to userspace from our temporary buffer.
		 *
		 * AKPM: Even though it's a semaphore, we should drop it because
		 * the pagefault handling code may want to call printk().
		 */

423
		console_unlock();
L
Linus Torvalds 已提交
424
		ret = copy_to_user(buf, con_buf_start, orig_count);
425
		console_lock();
L
Linus Torvalds 已提交
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440

		if (ret) {
			read += (orig_count - ret);
			ret = -EFAULT;
			break;
		}
		buf += orig_count;
		pos += orig_count;
		read += orig_count;
		count -= orig_count;
	}
	*ppos += read;
	if (read)
		ret = read;
unlock_out:
441
	console_unlock();
442
	free_page((unsigned long) con_buf);
L
Linus Torvalds 已提交
443 444 445 446 447 448
	return ret;
}

static ssize_t
vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
A
Al Viro 已提交
449
	struct inode *inode = file_inode(file);
L
Linus Torvalds 已提交
450 451
	struct vc_data *vc;
	long pos;
452
	long attr, size, written;
L
Linus Torvalds 已提交
453
	char *con_buf0;
454
	int col, maxcol, viewed;
L
Linus Torvalds 已提交
455 456
	u16 *org0 = NULL, *org = NULL;
	size_t ret;
457
	char *con_buf;
L
Linus Torvalds 已提交
458

459 460 461
	con_buf = (char *) __get_free_page(GFP_KERNEL);
	if (!con_buf)
		return -ENOMEM;
L
Linus Torvalds 已提交
462 463 464 465 466 467

	pos = *ppos;

	/* Select the proper current console and verify
	 * sanity of the situation under the console lock.
	 */
468
	console_lock();
L
Linus Torvalds 已提交
469

470
	attr = use_attributes(inode);
L
Linus Torvalds 已提交
471
	ret = -ENXIO;
472 473
	vc = vcs_vc(inode, &viewed);
	if (!vc)
L
Linus Torvalds 已提交
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
		goto unlock_out;

	size = vcs_size(inode);
	ret = -EINVAL;
	if (pos < 0 || pos > size)
		goto unlock_out;
	if (count > size - pos)
		count = size - pos;
	written = 0;
	while (count) {
		long this_round = count;
		size_t orig_count;
		long p;

		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.
		 */
494
		console_unlock();
L
Linus Torvalds 已提交
495
		ret = copy_from_user(con_buf, buf, this_round);
496
		console_lock();
L
Linus Torvalds 已提交
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515

		if (ret) {
			this_round -= ret;
			if (!this_round) {
				/* Abort loop if no data were copied. Otherwise
				 * fail with -EFAULT.
				 */
				if (written)
					break;
				ret = -EFAULT;
				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);
J
Jiri Olsa 已提交
516 517 518 519 520 521
		if (size < 0) {
			if (written)
				break;
			ret = size;
			goto unlock_out;
		}
L
Linus Torvalds 已提交
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
		if (pos >= size)
			break;
		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 = vc->vc_cols;
		p = pos;
		if (!attr) {
			org0 = org = screen_pos(vc, p, viewed);
			col = p % maxcol;
			p += maxcol - col;

			while (this_round > 0) {
				unsigned char c = *con_buf0++;

				this_round--;
				vcs_scr_writew(vc,
					       (vcs_scr_readw(vc, org) & 0xff00) | c, org);
				org++;
				if (++col == maxcol) {
					org = screen_pos(vc, p, viewed);
					col = 0;
					p += maxcol;
				}
			}
		} else {
			if (p < HEADER_SIZE) {
				char header[HEADER_SIZE];

				getconsxy(vc, header + 2);
				while (p < HEADER_SIZE && this_round > 0) {
					this_round--;
					header[p++] = *con_buf0++;
				}
				if (!viewed)
					putconsxy(vc, header + 2);
			}
			p -= HEADER_SIZE;
			col = (p/2) % maxcol;
			if (this_round > 0) {
				org0 = org = screen_pos(vc, p/2, viewed);
				if ((p & 1) && this_round > 0) {
					char c;

					this_round--;
					c = *con_buf0++;
#ifdef __BIG_ENDIAN
					vcs_scr_writew(vc, c |
					     (vcs_scr_readw(vc, org) & 0xff00), org);
#else
					vcs_scr_writew(vc, (c << 8) |
					     (vcs_scr_readw(vc, org) & 0xff), org);
#endif
					org++;
					p++;
					if (++col == maxcol) {
						org = screen_pos(vc, p/2, viewed);
						col = 0;
					}
				}
				p /= 2;
				p += maxcol - col;
			}
			while (this_round > 1) {
				unsigned short w;

594
				w = get_unaligned(((unsigned short *)con_buf0));
L
Linus Torvalds 已提交
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
				vcs_scr_writew(vc, w, org++);
				con_buf0 += 2;
				this_round -= 2;
				if (++col == maxcol) {
					org = screen_pos(vc, p, viewed);
					col = 0;
					p += maxcol;
				}
			}
			if (this_round > 0) {
				unsigned char c;

				c = *con_buf0++;
#ifdef __BIG_ENDIAN
				vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff) | (c << 8), org);
#else
				vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff00) | c, org);
#endif
			}
		}
		count -= orig_count;
		written += orig_count;
		buf += orig_count;
		pos += orig_count;
		if (org0)
			update_region(vc, (unsigned long)(org0), org - org0);
	}
	*ppos += written;
	ret = written;
624 625
	if (written)
		vcs_scr_updated(vc);
L
Linus Torvalds 已提交
626 627

unlock_out:
628
	console_unlock();
629
	free_page((unsigned long) con_buf);
L
Linus Torvalds 已提交
630 631 632
	return ret;
}

633
static __poll_t
N
Nicolas Pitre 已提交
634 635 636
vcs_poll(struct file *file, poll_table *wait)
{
	struct vcs_poll_data *poll = vcs_poll_data_get(file);
637
	__poll_t ret = DEFAULT_POLLMASK|EPOLLERR;
N
Nicolas Pitre 已提交
638 639 640

	if (poll) {
		poll_wait(file, &poll->waitq, wait);
641 642 643 644 645 646 647 648
		switch (poll->event) {
		case VT_UPDATE:
			ret = DEFAULT_POLLMASK|EPOLLPRI;
			break;
		case VT_DEALLOCATE:
			ret = DEFAULT_POLLMASK|EPOLLHUP|EPOLLERR;
			break;
		case 0:
649
			ret = DEFAULT_POLLMASK;
650 651
			break;
		}
N
Nicolas Pitre 已提交
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
	}
	return ret;
}

static int
vcs_fasync(int fd, struct file *file, int on)
{
	struct vcs_poll_data *poll = file->private_data;

	if (!poll) {
		/* don't allocate anything if all we want is disable fasync */
		if (!on)
			return 0;
		poll = vcs_poll_data_get(file);
		if (!poll)
			return -ENOMEM;
	}

	return fasync_helper(fd, file, on, &poll->fasync);
}

L
Linus Torvalds 已提交
673 674 675
static int
vcs_open(struct inode *inode, struct file *filp)
{
676 677 678
	unsigned int currcons = console(inode);
	bool attr = use_attributes(inode);
	bool uni_mode = use_unicode(inode);
J
Jonathan Corbet 已提交
679
	int ret = 0;
680 681 682 683 684

	/* we currently don't support attributes in unicode mode */
	if (attr && uni_mode)
		return -EOPNOTSUPP;

685
	console_lock();
L
Linus Torvalds 已提交
686
	if(currcons && !vc_cons_allocated(currcons-1))
J
Jonathan Corbet 已提交
687
		ret = -ENXIO;
688
	console_unlock();
J
Jonathan Corbet 已提交
689
	return ret;
L
Linus Torvalds 已提交
690 691
}

N
Nicolas Pitre 已提交
692 693 694 695 696 697 698 699 700
static int vcs_release(struct inode *inode, struct file *file)
{
	struct vcs_poll_data *poll = file->private_data;

	if (poll)
		vcs_poll_data_free(poll);
	return 0;
}

701
static const struct file_operations vcs_fops = {
L
Linus Torvalds 已提交
702 703 704
	.llseek		= vcs_lseek,
	.read		= vcs_read,
	.write		= vcs_write,
N
Nicolas Pitre 已提交
705 706
	.poll		= vcs_poll,
	.fasync		= vcs_fasync,
L
Linus Torvalds 已提交
707
	.open		= vcs_open,
N
Nicolas Pitre 已提交
708
	.release	= vcs_release,
L
Linus Torvalds 已提交
709 710
};

711
static struct class *vc_class;
L
Linus Torvalds 已提交
712

713
void vcs_make_sysfs(int index)
L
Linus Torvalds 已提交
714
{
715 716
	device_create(vc_class, NULL, MKDEV(VCS_MAJOR, index + 1), NULL,
		      "vcs%u", index + 1);
717 718
	device_create(vc_class, NULL, MKDEV(VCS_MAJOR, index + 65), NULL,
		      "vcsu%u", index + 1);
719 720
	device_create(vc_class, NULL, MKDEV(VCS_MAJOR, index + 129), NULL,
		      "vcsa%u", index + 1);
L
Linus Torvalds 已提交
721
}
722

723
void vcs_remove_sysfs(int index)
L
Linus Torvalds 已提交
724
{
725
	device_destroy(vc_class, MKDEV(VCS_MAJOR, index + 1));
726
	device_destroy(vc_class, MKDEV(VCS_MAJOR, index + 65));
727
	device_destroy(vc_class, MKDEV(VCS_MAJOR, index + 129));
L
Linus Torvalds 已提交
728 729 730 731
}

int __init vcs_init(void)
{
732 733
	unsigned int i;

L
Linus Torvalds 已提交
734 735
	if (register_chrdev(VCS_MAJOR, "vcs", &vcs_fops))
		panic("unable to get major %d for vcs device", VCS_MAJOR);
736
	vc_class = class_create(THIS_MODULE, "vc");
L
Linus Torvalds 已提交
737

738
	device_create(vc_class, NULL, MKDEV(VCS_MAJOR, 0), NULL, "vcs");
739
	device_create(vc_class, NULL, MKDEV(VCS_MAJOR, 64), NULL, "vcsu");
740
	device_create(vc_class, NULL, MKDEV(VCS_MAJOR, 128), NULL, "vcsa");
741 742
	for (i = 0; i < MIN_NR_CONSOLES; i++)
		vcs_make_sysfs(i);
L
Linus Torvalds 已提交
743 744
	return 0;
}