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