linux/arch/x86/boot/video.c
<<
>>
Prefs
   1/* -*- linux-c -*- ------------------------------------------------------- *
   2 *
   3 *   Copyright (C) 1991, 1992 Linus Torvalds
   4 *   Copyright 2007 rPath, Inc. - All Rights Reserved
   5 *   Copyright 2009 Intel Corporation; author H. Peter Anvin
   6 *
   7 *   This file is part of the Linux kernel, and is made available under
   8 *   the terms of the GNU General Public License version 2.
   9 *
  10 * ----------------------------------------------------------------------- */
  11
  12/*
  13 * Select video mode
  14 */
  15
  16#include <uapi/asm/boot.h>
  17
  18#include "boot.h"
  19#include "video.h"
  20#include "vesa.h"
  21
  22static u16 video_segment;
  23
  24static void store_cursor_position(void)
  25{
  26        struct biosregs ireg, oreg;
  27
  28        initregs(&ireg);
  29        ireg.ah = 0x03;
  30        intcall(0x10, &ireg, &oreg);
  31
  32        boot_params.screen_info.orig_x = oreg.dl;
  33        boot_params.screen_info.orig_y = oreg.dh;
  34
  35        if (oreg.ch & 0x20)
  36                boot_params.screen_info.flags |= VIDEO_FLAGS_NOCURSOR;
  37
  38        if ((oreg.ch & 0x1f) > (oreg.cl & 0x1f))
  39                boot_params.screen_info.flags |= VIDEO_FLAGS_NOCURSOR;
  40}
  41
  42static void store_video_mode(void)
  43{
  44        struct biosregs ireg, oreg;
  45
  46        /* N.B.: the saving of the video page here is a bit silly,
  47           since we pretty much assume page 0 everywhere. */
  48        initregs(&ireg);
  49        ireg.ah = 0x0f;
  50        intcall(0x10, &ireg, &oreg);
  51
  52        /* Not all BIOSes are clean with respect to the top bit */
  53        boot_params.screen_info.orig_video_mode = oreg.al & 0x7f;
  54        boot_params.screen_info.orig_video_page = oreg.bh;
  55}
  56
  57/*
  58 * Store the video mode parameters for later usage by the kernel.
  59 * This is done by asking the BIOS except for the rows/columns
  60 * parameters in the default 80x25 mode -- these are set directly,
  61 * because some very obscure BIOSes supply insane values.
  62 */
  63static void store_mode_params(void)
  64{
  65        u16 font_size;
  66        int x, y;
  67
  68        /* For graphics mode, it is up to the mode-setting driver
  69           (currently only video-vesa.c) to store the parameters */
  70        if (graphic_mode)
  71                return;
  72
  73        store_cursor_position();
  74        store_video_mode();
  75
  76        if (boot_params.screen_info.orig_video_mode == 0x07) {
  77                /* MDA, HGC, or VGA in monochrome mode */
  78                video_segment = 0xb000;
  79        } else {
  80                /* CGA, EGA, VGA and so forth */
  81                video_segment = 0xb800;
  82        }
  83
  84        set_fs(0);
  85        font_size = rdfs16(0x485); /* Font size, BIOS area */
  86        boot_params.screen_info.orig_video_points = font_size;
  87
  88        x = rdfs16(0x44a);
  89        y = (adapter == ADAPTER_CGA) ? 25 : rdfs8(0x484)+1;
  90
  91        if (force_x)
  92                x = force_x;
  93        if (force_y)
  94                y = force_y;
  95
  96        boot_params.screen_info.orig_video_cols  = x;
  97        boot_params.screen_info.orig_video_lines = y;
  98}
  99
 100static unsigned int get_entry(void)
 101{
 102        char entry_buf[4];
 103        int i, len = 0;
 104        int key;
 105        unsigned int v;
 106
 107        do {
 108                key = getchar();
 109
 110                if (key == '\b') {
 111                        if (len > 0) {
 112                                puts("\b \b");
 113                                len--;
 114                        }
 115                } else if ((key >= '0' && key <= '9') ||
 116                           (key >= 'A' && key <= 'Z') ||
 117                           (key >= 'a' && key <= 'z')) {
 118                        if (len < sizeof entry_buf) {
 119                                entry_buf[len++] = key;
 120                                putchar(key);
 121                        }
 122                }
 123        } while (key != '\r');
 124        putchar('\n');
 125
 126        if (len == 0)
 127                return VIDEO_CURRENT_MODE; /* Default */
 128
 129        v = 0;
 130        for (i = 0; i < len; i++) {
 131                v <<= 4;
 132                key = entry_buf[i] | 0x20;
 133                v += (key > '9') ? key-'a'+10 : key-'0';
 134        }
 135
 136        return v;
 137}
 138
 139static void display_menu(void)
 140{
 141        struct card_info *card;
 142        struct mode_info *mi;
 143        char ch;
 144        int i;
 145        int nmodes;
 146        int modes_per_line;
 147        int col;
 148
 149        nmodes = 0;
 150        for (card = video_cards; card < video_cards_end; card++)
 151                nmodes += card->nmodes;
 152
 153        modes_per_line = 1;
 154        if (nmodes >= 20)
 155                modes_per_line = 3;
 156
 157        for (col = 0; col < modes_per_line; col++)
 158                puts("Mode: Resolution:  Type: ");
 159        putchar('\n');
 160
 161        col = 0;
 162        ch = '0';
 163        for (card = video_cards; card < video_cards_end; card++) {
 164                mi = card->modes;
 165                for (i = 0; i < card->nmodes; i++, mi++) {
 166                        char resbuf[32];
 167                        int visible = mi->x && mi->y;
 168                        u16 mode_id = mi->mode ? mi->mode :
 169                                (mi->y << 8)+mi->x;
 170
 171                        if (!visible)
 172                                continue; /* Hidden mode */
 173
 174                        if (mi->depth)
 175                                sprintf(resbuf, "%dx%d", mi->y, mi->depth);
 176                        else
 177                                sprintf(resbuf, "%d", mi->y);
 178
 179                        printf("%c %03X %4dx%-7s %-6s",
 180                               ch, mode_id, mi->x, resbuf, card->card_name);
 181                        col++;
 182                        if (col >= modes_per_line) {
 183                                putchar('\n');
 184                                col = 0;
 185                        }
 186
 187                        if (ch == '9')
 188                                ch = 'a';
 189                        else if (ch == 'z' || ch == ' ')
 190                                ch = ' '; /* Out of keys... */
 191                        else
 192                                ch++;
 193                }
 194        }
 195        if (col)
 196                putchar('\n');
 197}
 198
 199#define H(x)    ((x)-'a'+10)
 200#define SCAN    ((H('s')<<12)+(H('c')<<8)+(H('a')<<4)+H('n'))
 201
 202static unsigned int mode_menu(void)
 203{
 204        int key;
 205        unsigned int sel;
 206
 207        puts("Press <ENTER> to see video modes available, "
 208             "<SPACE> to continue, or wait 30 sec\n");
 209
 210        kbd_flush();
 211        while (1) {
 212                key = getchar_timeout();
 213                if (key == ' ' || key == 0)
 214                        return VIDEO_CURRENT_MODE; /* Default */
 215                if (key == '\r')
 216                        break;
 217                putchar('\a');  /* Beep! */
 218        }
 219
 220
 221        for (;;) {
 222                display_menu();
 223
 224                puts("Enter a video mode or \"scan\" to scan for "
 225                     "additional modes: ");
 226                sel = get_entry();
 227                if (sel != SCAN)
 228                        return sel;
 229
 230                probe_cards(1);
 231        }
 232}
 233
 234/* Save screen content to the heap */
 235static struct saved_screen {
 236        int x, y;
 237        int curx, cury;
 238        u16 *data;
 239} saved;
 240
 241static void save_screen(void)
 242{
 243        /* Should be called after store_mode_params() */
 244        saved.x = boot_params.screen_info.orig_video_cols;
 245        saved.y = boot_params.screen_info.orig_video_lines;
 246        saved.curx = boot_params.screen_info.orig_x;
 247        saved.cury = boot_params.screen_info.orig_y;
 248
 249        if (!heap_free(saved.x*saved.y*sizeof(u16)+512))
 250                return;         /* Not enough heap to save the screen */
 251
 252        saved.data = GET_HEAP(u16, saved.x*saved.y);
 253
 254        set_fs(video_segment);
 255        copy_from_fs(saved.data, 0, saved.x*saved.y*sizeof(u16));
 256}
 257
 258static void restore_screen(void)
 259{
 260        /* Should be called after store_mode_params() */
 261        int xs = boot_params.screen_info.orig_video_cols;
 262        int ys = boot_params.screen_info.orig_video_lines;
 263        int y;
 264        addr_t dst = 0;
 265        u16 *src = saved.data;
 266        struct biosregs ireg;
 267
 268        if (graphic_mode)
 269                return;         /* Can't restore onto a graphic mode */
 270
 271        if (!src)
 272                return;         /* No saved screen contents */
 273
 274        /* Restore screen contents */
 275
 276        set_fs(video_segment);
 277        for (y = 0; y < ys; y++) {
 278                int npad;
 279
 280                if (y < saved.y) {
 281                        int copy = (xs < saved.x) ? xs : saved.x;
 282                        copy_to_fs(dst, src, copy*sizeof(u16));
 283                        dst += copy*sizeof(u16);
 284                        src += saved.x;
 285                        npad = (xs < saved.x) ? 0 : xs-saved.x;
 286                } else {
 287                        npad = xs;
 288                }
 289
 290                /* Writes "npad" blank characters to
 291                   video_segment:dst and advances dst */
 292                asm volatile("pushw %%es ; "
 293                             "movw %2,%%es ; "
 294                             "shrw %%cx ; "
 295                             "jnc 1f ; "
 296                             "stosw \n\t"
 297                             "1: rep;stosl ; "
 298                             "popw %%es"
 299                             : "+D" (dst), "+c" (npad)
 300                             : "bdS" (video_segment),
 301                               "a" (0x07200720));
 302        }
 303
 304        /* Restore cursor position */
 305        if (saved.curx >= xs)
 306                saved.curx = xs-1;
 307        if (saved.cury >= ys)
 308                saved.cury = ys-1;
 309
 310        initregs(&ireg);
 311        ireg.ah = 0x02;         /* Set cursor position */
 312        ireg.dh = saved.cury;
 313        ireg.dl = saved.curx;
 314        intcall(0x10, &ireg, NULL);
 315
 316        store_cursor_position();
 317}
 318
 319void set_video(void)
 320{
 321        u16 mode = boot_params.hdr.vid_mode;
 322
 323        RESET_HEAP();
 324
 325        store_mode_params();
 326        save_screen();
 327        probe_cards(0);
 328
 329        for (;;) {
 330                if (mode == ASK_VGA)
 331                        mode = mode_menu();
 332
 333                if (!set_mode(mode))
 334                        break;
 335
 336                printf("Undefined video mode number: %x\n", mode);
 337                mode = ASK_VGA;
 338        }
 339        boot_params.hdr.vid_mode = mode;
 340        vesa_store_edid();
 341        store_mode_params();
 342
 343        if (do_restore)
 344                restore_screen();
 345}
 346