xref: /openbmc/linux/arch/x86/boot/video.c (revision 97873a3d)
197873a3dSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
296ae6ea0SThomas Gleixner /* -*- linux-c -*- ------------------------------------------------------- *
396ae6ea0SThomas Gleixner  *
496ae6ea0SThomas Gleixner  *   Copyright (C) 1991, 1992 Linus Torvalds
596ae6ea0SThomas Gleixner  *   Copyright 2007 rPath, Inc. - All Rights Reserved
6cf06de7bSH. Peter Anvin  *   Copyright 2009 Intel Corporation; author H. Peter Anvin
796ae6ea0SThomas Gleixner  *
896ae6ea0SThomas Gleixner  * ----------------------------------------------------------------------- */
996ae6ea0SThomas Gleixner 
1096ae6ea0SThomas Gleixner /*
1196ae6ea0SThomas Gleixner  * Select video mode
1296ae6ea0SThomas Gleixner  */
1396ae6ea0SThomas Gleixner 
1470f15287SKirill A. Shutemov #include <uapi/asm/boot.h>
1570f15287SKirill A. Shutemov 
1696ae6ea0SThomas Gleixner #include "boot.h"
1796ae6ea0SThomas Gleixner #include "video.h"
1896ae6ea0SThomas Gleixner #include "vesa.h"
1996ae6ea0SThomas Gleixner 
2079287cf8SAlexander Kuleshov static u16 video_segment;
2179287cf8SAlexander Kuleshov 
store_cursor_position(void)2296ae6ea0SThomas Gleixner static void store_cursor_position(void)
2396ae6ea0SThomas Gleixner {
24cf06de7bSH. Peter Anvin 	struct biosregs ireg, oreg;
2596ae6ea0SThomas Gleixner 
26cf06de7bSH. Peter Anvin 	initregs(&ireg);
27cf06de7bSH. Peter Anvin 	ireg.ah = 0x03;
28cf06de7bSH. Peter Anvin 	intcall(0x10, &ireg, &oreg);
2996ae6ea0SThomas Gleixner 
30cf06de7bSH. Peter Anvin 	boot_params.screen_info.orig_x = oreg.dl;
31cf06de7bSH. Peter Anvin 	boot_params.screen_info.orig_y = oreg.dh;
32d9b26352SMatthew Garrett 
33d9b26352SMatthew Garrett 	if (oreg.ch & 0x20)
34d9b26352SMatthew Garrett 		boot_params.screen_info.flags |= VIDEO_FLAGS_NOCURSOR;
35d9b26352SMatthew Garrett 
36d9b26352SMatthew Garrett 	if ((oreg.ch & 0x1f) > (oreg.cl & 0x1f))
37d9b26352SMatthew Garrett 		boot_params.screen_info.flags |= VIDEO_FLAGS_NOCURSOR;
3896ae6ea0SThomas Gleixner }
3996ae6ea0SThomas Gleixner 
store_video_mode(void)4096ae6ea0SThomas Gleixner static void store_video_mode(void)
4196ae6ea0SThomas Gleixner {
42cf06de7bSH. Peter Anvin 	struct biosregs ireg, oreg;
4396ae6ea0SThomas Gleixner 
4496ae6ea0SThomas Gleixner 	/* N.B.: the saving of the video page here is a bit silly,
4596ae6ea0SThomas Gleixner 	   since we pretty much assume page 0 everywhere. */
46cf06de7bSH. Peter Anvin 	initregs(&ireg);
47cf06de7bSH. Peter Anvin 	ireg.ah = 0x0f;
48cf06de7bSH. Peter Anvin 	intcall(0x10, &ireg, &oreg);
4996ae6ea0SThomas Gleixner 
5096ae6ea0SThomas Gleixner 	/* Not all BIOSes are clean with respect to the top bit */
51cf06de7bSH. Peter Anvin 	boot_params.screen_info.orig_video_mode = oreg.al & 0x7f;
52cf06de7bSH. Peter Anvin 	boot_params.screen_info.orig_video_page = oreg.bh;
5396ae6ea0SThomas Gleixner }
5496ae6ea0SThomas Gleixner 
5596ae6ea0SThomas Gleixner /*
5696ae6ea0SThomas Gleixner  * Store the video mode parameters for later usage by the kernel.
5796ae6ea0SThomas Gleixner  * This is done by asking the BIOS except for the rows/columns
5896ae6ea0SThomas Gleixner  * parameters in the default 80x25 mode -- these are set directly,
5996ae6ea0SThomas Gleixner  * because some very obscure BIOSes supply insane values.
6096ae6ea0SThomas Gleixner  */
store_mode_params(void)6196ae6ea0SThomas Gleixner static void store_mode_params(void)
6296ae6ea0SThomas Gleixner {
6396ae6ea0SThomas Gleixner 	u16 font_size;
6496ae6ea0SThomas Gleixner 	int x, y;
6596ae6ea0SThomas Gleixner 
6696ae6ea0SThomas Gleixner 	/* For graphics mode, it is up to the mode-setting driver
6796ae6ea0SThomas Gleixner 	   (currently only video-vesa.c) to store the parameters */
6896ae6ea0SThomas Gleixner 	if (graphic_mode)
6996ae6ea0SThomas Gleixner 		return;
7096ae6ea0SThomas Gleixner 
7196ae6ea0SThomas Gleixner 	store_cursor_position();
7296ae6ea0SThomas Gleixner 	store_video_mode();
7396ae6ea0SThomas Gleixner 
7496ae6ea0SThomas Gleixner 	if (boot_params.screen_info.orig_video_mode == 0x07) {
7596ae6ea0SThomas Gleixner 		/* MDA, HGC, or VGA in monochrome mode */
7696ae6ea0SThomas Gleixner 		video_segment = 0xb000;
7796ae6ea0SThomas Gleixner 	} else {
7896ae6ea0SThomas Gleixner 		/* CGA, EGA, VGA and so forth */
7996ae6ea0SThomas Gleixner 		video_segment = 0xb800;
8096ae6ea0SThomas Gleixner 	}
8196ae6ea0SThomas Gleixner 
8296ae6ea0SThomas Gleixner 	set_fs(0);
8396ae6ea0SThomas Gleixner 	font_size = rdfs16(0x485); /* Font size, BIOS area */
8496ae6ea0SThomas Gleixner 	boot_params.screen_info.orig_video_points = font_size;
8596ae6ea0SThomas Gleixner 
8696ae6ea0SThomas Gleixner 	x = rdfs16(0x44a);
8796ae6ea0SThomas Gleixner 	y = (adapter == ADAPTER_CGA) ? 25 : rdfs8(0x484)+1;
8896ae6ea0SThomas Gleixner 
8996ae6ea0SThomas Gleixner 	if (force_x)
9096ae6ea0SThomas Gleixner 		x = force_x;
9196ae6ea0SThomas Gleixner 	if (force_y)
9296ae6ea0SThomas Gleixner 		y = force_y;
9396ae6ea0SThomas Gleixner 
9496ae6ea0SThomas Gleixner 	boot_params.screen_info.orig_video_cols  = x;
9596ae6ea0SThomas Gleixner 	boot_params.screen_info.orig_video_lines = y;
9696ae6ea0SThomas Gleixner }
9796ae6ea0SThomas Gleixner 
get_entry(void)9896ae6ea0SThomas Gleixner static unsigned int get_entry(void)
9996ae6ea0SThomas Gleixner {
10096ae6ea0SThomas Gleixner 	char entry_buf[4];
10196ae6ea0SThomas Gleixner 	int i, len = 0;
10296ae6ea0SThomas Gleixner 	int key;
10396ae6ea0SThomas Gleixner 	unsigned int v;
10496ae6ea0SThomas Gleixner 
10596ae6ea0SThomas Gleixner 	do {
10696ae6ea0SThomas Gleixner 		key = getchar();
10796ae6ea0SThomas Gleixner 
10896ae6ea0SThomas Gleixner 		if (key == '\b') {
10996ae6ea0SThomas Gleixner 			if (len > 0) {
11096ae6ea0SThomas Gleixner 				puts("\b \b");
11196ae6ea0SThomas Gleixner 				len--;
11296ae6ea0SThomas Gleixner 			}
11396ae6ea0SThomas Gleixner 		} else if ((key >= '0' && key <= '9') ||
11496ae6ea0SThomas Gleixner 			   (key >= 'A' && key <= 'Z') ||
11596ae6ea0SThomas Gleixner 			   (key >= 'a' && key <= 'z')) {
1160e96f31eSJordan Borgner 			if (len < sizeof(entry_buf)) {
11796ae6ea0SThomas Gleixner 				entry_buf[len++] = key;
11896ae6ea0SThomas Gleixner 				putchar(key);
11996ae6ea0SThomas Gleixner 			}
12096ae6ea0SThomas Gleixner 		}
12196ae6ea0SThomas Gleixner 	} while (key != '\r');
12296ae6ea0SThomas Gleixner 	putchar('\n');
12396ae6ea0SThomas Gleixner 
12496ae6ea0SThomas Gleixner 	if (len == 0)
12596ae6ea0SThomas Gleixner 		return VIDEO_CURRENT_MODE; /* Default */
12696ae6ea0SThomas Gleixner 
12796ae6ea0SThomas Gleixner 	v = 0;
12896ae6ea0SThomas Gleixner 	for (i = 0; i < len; i++) {
12996ae6ea0SThomas Gleixner 		v <<= 4;
13096ae6ea0SThomas Gleixner 		key = entry_buf[i] | 0x20;
13196ae6ea0SThomas Gleixner 		v += (key > '9') ? key-'a'+10 : key-'0';
13296ae6ea0SThomas Gleixner 	}
13396ae6ea0SThomas Gleixner 
13496ae6ea0SThomas Gleixner 	return v;
13596ae6ea0SThomas Gleixner }
13696ae6ea0SThomas Gleixner 
display_menu(void)13796ae6ea0SThomas Gleixner static void display_menu(void)
13896ae6ea0SThomas Gleixner {
13996ae6ea0SThomas Gleixner 	struct card_info *card;
14096ae6ea0SThomas Gleixner 	struct mode_info *mi;
14196ae6ea0SThomas Gleixner 	char ch;
14296ae6ea0SThomas Gleixner 	int i;
1431cac5004SH. Peter Anvin 	int nmodes;
1441cac5004SH. Peter Anvin 	int modes_per_line;
1451cac5004SH. Peter Anvin 	int col;
14696ae6ea0SThomas Gleixner 
1471cac5004SH. Peter Anvin 	nmodes = 0;
1481cac5004SH. Peter Anvin 	for (card = video_cards; card < video_cards_end; card++)
1491cac5004SH. Peter Anvin 		nmodes += card->nmodes;
15096ae6ea0SThomas Gleixner 
1511cac5004SH. Peter Anvin 	modes_per_line = 1;
1521cac5004SH. Peter Anvin 	if (nmodes >= 20)
1531cac5004SH. Peter Anvin 		modes_per_line = 3;
1541cac5004SH. Peter Anvin 
1551cac5004SH. Peter Anvin 	for (col = 0; col < modes_per_line; col++)
1561cac5004SH. Peter Anvin 		puts("Mode: Resolution:  Type: ");
1571cac5004SH. Peter Anvin 	putchar('\n');
1581cac5004SH. Peter Anvin 
1591cac5004SH. Peter Anvin 	col = 0;
16096ae6ea0SThomas Gleixner 	ch = '0';
16196ae6ea0SThomas Gleixner 	for (card = video_cards; card < video_cards_end; card++) {
16296ae6ea0SThomas Gleixner 		mi = card->modes;
16396ae6ea0SThomas Gleixner 		for (i = 0; i < card->nmodes; i++, mi++) {
1641cac5004SH. Peter Anvin 			char resbuf[32];
16596ae6ea0SThomas Gleixner 			int visible = mi->x && mi->y;
16696ae6ea0SThomas Gleixner 			u16 mode_id = mi->mode ? mi->mode :
16796ae6ea0SThomas Gleixner 				(mi->y << 8)+mi->x;
16896ae6ea0SThomas Gleixner 
16996ae6ea0SThomas Gleixner 			if (!visible)
17096ae6ea0SThomas Gleixner 				continue; /* Hidden mode */
17196ae6ea0SThomas Gleixner 
1721cac5004SH. Peter Anvin 			if (mi->depth)
1731cac5004SH. Peter Anvin 				sprintf(resbuf, "%dx%d", mi->y, mi->depth);
1741cac5004SH. Peter Anvin 			else
1751cac5004SH. Peter Anvin 				sprintf(resbuf, "%d", mi->y);
1761cac5004SH. Peter Anvin 
1771cac5004SH. Peter Anvin 			printf("%c %03X %4dx%-7s %-6s",
1781cac5004SH. Peter Anvin 			       ch, mode_id, mi->x, resbuf, card->card_name);
1791cac5004SH. Peter Anvin 			col++;
1801cac5004SH. Peter Anvin 			if (col >= modes_per_line) {
1811cac5004SH. Peter Anvin 				putchar('\n');
1821cac5004SH. Peter Anvin 				col = 0;
1831cac5004SH. Peter Anvin 			}
18496ae6ea0SThomas Gleixner 
18596ae6ea0SThomas Gleixner 			if (ch == '9')
18696ae6ea0SThomas Gleixner 				ch = 'a';
18796ae6ea0SThomas Gleixner 			else if (ch == 'z' || ch == ' ')
18896ae6ea0SThomas Gleixner 				ch = ' '; /* Out of keys... */
18996ae6ea0SThomas Gleixner 			else
19096ae6ea0SThomas Gleixner 				ch++;
19196ae6ea0SThomas Gleixner 		}
19296ae6ea0SThomas Gleixner 	}
1931cac5004SH. Peter Anvin 	if (col)
1941cac5004SH. Peter Anvin 		putchar('\n');
19596ae6ea0SThomas Gleixner }
19696ae6ea0SThomas Gleixner 
19796ae6ea0SThomas Gleixner #define H(x)	((x)-'a'+10)
19896ae6ea0SThomas Gleixner #define SCAN	((H('s')<<12)+(H('c')<<8)+(H('a')<<4)+H('n'))
19996ae6ea0SThomas Gleixner 
mode_menu(void)20096ae6ea0SThomas Gleixner static unsigned int mode_menu(void)
20196ae6ea0SThomas Gleixner {
20296ae6ea0SThomas Gleixner 	int key;
20396ae6ea0SThomas Gleixner 	unsigned int sel;
20496ae6ea0SThomas Gleixner 
20596ae6ea0SThomas Gleixner 	puts("Press <ENTER> to see video modes available, "
20696ae6ea0SThomas Gleixner 	     "<SPACE> to continue, or wait 30 sec\n");
20796ae6ea0SThomas Gleixner 
20896ae6ea0SThomas Gleixner 	kbd_flush();
20996ae6ea0SThomas Gleixner 	while (1) {
21096ae6ea0SThomas Gleixner 		key = getchar_timeout();
21196ae6ea0SThomas Gleixner 		if (key == ' ' || key == 0)
21296ae6ea0SThomas Gleixner 			return VIDEO_CURRENT_MODE; /* Default */
21396ae6ea0SThomas Gleixner 		if (key == '\r')
21496ae6ea0SThomas Gleixner 			break;
21596ae6ea0SThomas Gleixner 		putchar('\a');	/* Beep! */
21696ae6ea0SThomas Gleixner 	}
21796ae6ea0SThomas Gleixner 
21896ae6ea0SThomas Gleixner 
21996ae6ea0SThomas Gleixner 	for (;;) {
22096ae6ea0SThomas Gleixner 		display_menu();
22196ae6ea0SThomas Gleixner 
22296ae6ea0SThomas Gleixner 		puts("Enter a video mode or \"scan\" to scan for "
22396ae6ea0SThomas Gleixner 		     "additional modes: ");
22496ae6ea0SThomas Gleixner 		sel = get_entry();
22596ae6ea0SThomas Gleixner 		if (sel != SCAN)
22696ae6ea0SThomas Gleixner 			return sel;
22796ae6ea0SThomas Gleixner 
22896ae6ea0SThomas Gleixner 		probe_cards(1);
22996ae6ea0SThomas Gleixner 	}
23096ae6ea0SThomas Gleixner }
23196ae6ea0SThomas Gleixner 
23296ae6ea0SThomas Gleixner /* Save screen content to the heap */
233a1a00b58SHannes Eder static struct saved_screen {
23496ae6ea0SThomas Gleixner 	int x, y;
23596ae6ea0SThomas Gleixner 	int curx, cury;
23696ae6ea0SThomas Gleixner 	u16 *data;
23796ae6ea0SThomas Gleixner } saved;
23896ae6ea0SThomas Gleixner 
save_screen(void)23996ae6ea0SThomas Gleixner static void save_screen(void)
24096ae6ea0SThomas Gleixner {
24196ae6ea0SThomas Gleixner 	/* Should be called after store_mode_params() */
24296ae6ea0SThomas Gleixner 	saved.x = boot_params.screen_info.orig_video_cols;
24396ae6ea0SThomas Gleixner 	saved.y = boot_params.screen_info.orig_video_lines;
24496ae6ea0SThomas Gleixner 	saved.curx = boot_params.screen_info.orig_x;
24596ae6ea0SThomas Gleixner 	saved.cury = boot_params.screen_info.orig_y;
24696ae6ea0SThomas Gleixner 
247e6e1ace9SH. Peter Anvin 	if (!heap_free(saved.x*saved.y*sizeof(u16)+512))
24896ae6ea0SThomas Gleixner 		return;		/* Not enough heap to save the screen */
24996ae6ea0SThomas Gleixner 
25096ae6ea0SThomas Gleixner 	saved.data = GET_HEAP(u16, saved.x*saved.y);
25196ae6ea0SThomas Gleixner 
25296ae6ea0SThomas Gleixner 	set_fs(video_segment);
25396ae6ea0SThomas Gleixner 	copy_from_fs(saved.data, 0, saved.x*saved.y*sizeof(u16));
25496ae6ea0SThomas Gleixner }
25596ae6ea0SThomas Gleixner 
restore_screen(void)25696ae6ea0SThomas Gleixner static void restore_screen(void)
25796ae6ea0SThomas Gleixner {
25896ae6ea0SThomas Gleixner 	/* Should be called after store_mode_params() */
25996ae6ea0SThomas Gleixner 	int xs = boot_params.screen_info.orig_video_cols;
26096ae6ea0SThomas Gleixner 	int ys = boot_params.screen_info.orig_video_lines;
26196ae6ea0SThomas Gleixner 	int y;
26296ae6ea0SThomas Gleixner 	addr_t dst = 0;
26396ae6ea0SThomas Gleixner 	u16 *src = saved.data;
264cf06de7bSH. Peter Anvin 	struct biosregs ireg;
26596ae6ea0SThomas Gleixner 
26696ae6ea0SThomas Gleixner 	if (graphic_mode)
26796ae6ea0SThomas Gleixner 		return;		/* Can't restore onto a graphic mode */
26896ae6ea0SThomas Gleixner 
26996ae6ea0SThomas Gleixner 	if (!src)
27096ae6ea0SThomas Gleixner 		return;		/* No saved screen contents */
27196ae6ea0SThomas Gleixner 
27296ae6ea0SThomas Gleixner 	/* Restore screen contents */
27396ae6ea0SThomas Gleixner 
27496ae6ea0SThomas Gleixner 	set_fs(video_segment);
27596ae6ea0SThomas Gleixner 	for (y = 0; y < ys; y++) {
27696ae6ea0SThomas Gleixner 		int npad;
27796ae6ea0SThomas Gleixner 
27896ae6ea0SThomas Gleixner 		if (y < saved.y) {
27996ae6ea0SThomas Gleixner 			int copy = (xs < saved.x) ? xs : saved.x;
28096ae6ea0SThomas Gleixner 			copy_to_fs(dst, src, copy*sizeof(u16));
28196ae6ea0SThomas Gleixner 			dst += copy*sizeof(u16);
28296ae6ea0SThomas Gleixner 			src += saved.x;
28396ae6ea0SThomas Gleixner 			npad = (xs < saved.x) ? 0 : xs-saved.x;
28496ae6ea0SThomas Gleixner 		} else {
28596ae6ea0SThomas Gleixner 			npad = xs;
28696ae6ea0SThomas Gleixner 		}
28796ae6ea0SThomas Gleixner 
28896ae6ea0SThomas Gleixner 		/* Writes "npad" blank characters to
28996ae6ea0SThomas Gleixner 		   video_segment:dst and advances dst */
29096ae6ea0SThomas Gleixner 		asm volatile("pushw %%es ; "
29196ae6ea0SThomas Gleixner 			     "movw %2,%%es ; "
29296ae6ea0SThomas Gleixner 			     "shrw %%cx ; "
29396ae6ea0SThomas Gleixner 			     "jnc 1f ; "
29496ae6ea0SThomas Gleixner 			     "stosw \n\t"
29596ae6ea0SThomas Gleixner 			     "1: rep;stosl ; "
29696ae6ea0SThomas Gleixner 			     "popw %%es"
29796ae6ea0SThomas Gleixner 			     : "+D" (dst), "+c" (npad)
29896ae6ea0SThomas Gleixner 			     : "bdS" (video_segment),
29996ae6ea0SThomas Gleixner 			       "a" (0x07200720));
30096ae6ea0SThomas Gleixner 	}
30196ae6ea0SThomas Gleixner 
30296ae6ea0SThomas Gleixner 	/* Restore cursor position */
303f1f6baf8SH. Peter Anvin 	if (saved.curx >= xs)
304f1f6baf8SH. Peter Anvin 		saved.curx = xs-1;
305f1f6baf8SH. Peter Anvin 	if (saved.cury >= ys)
306f1f6baf8SH. Peter Anvin 		saved.cury = ys-1;
307f1f6baf8SH. Peter Anvin 
308cf06de7bSH. Peter Anvin 	initregs(&ireg);
309cf06de7bSH. Peter Anvin 	ireg.ah = 0x02;		/* Set cursor position */
310cf06de7bSH. Peter Anvin 	ireg.dh = saved.cury;
311cf06de7bSH. Peter Anvin 	ireg.dl = saved.curx;
312cf06de7bSH. Peter Anvin 	intcall(0x10, &ireg, NULL);
313f1f6baf8SH. Peter Anvin 
314f1f6baf8SH. Peter Anvin 	store_cursor_position();
31596ae6ea0SThomas Gleixner }
31696ae6ea0SThomas Gleixner 
set_video(void)31796ae6ea0SThomas Gleixner void set_video(void)
31896ae6ea0SThomas Gleixner {
31996ae6ea0SThomas Gleixner 	u16 mode = boot_params.hdr.vid_mode;
32096ae6ea0SThomas Gleixner 
32196ae6ea0SThomas Gleixner 	RESET_HEAP();
32296ae6ea0SThomas Gleixner 
32396ae6ea0SThomas Gleixner 	store_mode_params();
32496ae6ea0SThomas Gleixner 	save_screen();
32596ae6ea0SThomas Gleixner 	probe_cards(0);
32696ae6ea0SThomas Gleixner 
32796ae6ea0SThomas Gleixner 	for (;;) {
32896ae6ea0SThomas Gleixner 		if (mode == ASK_VGA)
32996ae6ea0SThomas Gleixner 			mode = mode_menu();
33096ae6ea0SThomas Gleixner 
33196ae6ea0SThomas Gleixner 		if (!set_mode(mode))
33296ae6ea0SThomas Gleixner 			break;
33396ae6ea0SThomas Gleixner 
33496ae6ea0SThomas Gleixner 		printf("Undefined video mode number: %x\n", mode);
33596ae6ea0SThomas Gleixner 		mode = ASK_VGA;
33696ae6ea0SThomas Gleixner 	}
337e44b7b75SPavel Machek 	boot_params.hdr.vid_mode = mode;
33896ae6ea0SThomas Gleixner 	vesa_store_edid();
33996ae6ea0SThomas Gleixner 	store_mode_params();
34096ae6ea0SThomas Gleixner 
34196ae6ea0SThomas Gleixner 	if (do_restore)
34296ae6ea0SThomas Gleixner 		restore_screen();
34396ae6ea0SThomas Gleixner }
344