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