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