toaruos/boot/video.c

278 lines
6.0 KiB
C

/**
* @brief Video mode management.
*
* Tries to abstract away differences between VESA mode setting
* on BIOS and GOP mode setting on UEFI. Also provides the
* video mode selection menu.
*
* @copyright
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2018-2021 K. Lange
*/
#include "text.h"
#include "util.h"
#include "kbd.h"
#ifdef EFI_PLATFORM
#include <efi.h>
extern EFI_SYSTEM_TABLE *ST;
#else
#include <stdint.h>
#endif
int platform_count_modes(int *);
int platform_list_modes(int sel, int select_this_mode);
extern void init_graphics(void);
void mode_selector(int sel, int ndx, char *str) {
set_attr(sel == ndx ? 0x70 : 0x07);
print_(str);
if (x < 26) {
while (x < 25) {
print_(" ");
}
x = 26;
} else if (x < 52) {
while (x < 51) {
print_(" ");
}
x = 52;
} else {
print_("\n");
}
}
static char * print_int_into(char * str, unsigned int value) {
unsigned int n_width = 1;
unsigned int i = 9;
while (value > i && i < UINT32_MAX) {
n_width += 1;
i *= 10;
i += 9;
}
char buf[n_width+1];
for (int i = 0; i < n_width + 1; i++) {
buf[i] = 0;
}
i = n_width;
while (i > 0) {
unsigned int n = value / 10;
int r = value % 10;
buf[i - 1] = r + '0';
i--;
value = n;
}
for (char * c = buf; *c; c++) {
*str++ = *c;
}
return str;
}
int video_menu(void) {
clear_();
int sel = 0;
int sel_max = platform_count_modes(&sel);
int select_this_mode = 0;
int s = 0;
do {
move_cursor(0,0);
set_attr(0x1f);
print_banner("Select Video Mode");
set_attr(0x07);
print_("\n");
if (platform_list_modes(sel,select_this_mode)) return 0;
read_again:
s = read_scancode(0);
if (s == 0x50) { /* DOWN */
if (sel >= 0 && sel < sel_max - 1) {
sel = (sel + 3) % sel_max;
} else {
sel = (sel + 1) % sel_max;
}
} else if (s == 0x48) { /* UP */
if (sel >= 1) {
sel = (sel_max + sel - 3) % sel_max;
} else {
sel = (sel_max + sel - 1) % sel_max;
}
} else if (s == 0x4B) { /* LEFT */
if (sel >= 0) {
if (sel % 3 != 0) {
sel = (sel - 1) % sel_max;
} else {
sel += 2;
}
}
} else if (s == 0x4D) { /* RIGHT */
if (sel >= 0) {
if (sel % 3 != 2) {
sel = (sel + 1) % sel_max;
} else {
sel -= 2;
}
}
} else if (s == 0x1c) {
select_this_mode = 1;
continue;
} else if (s == 0x01) {
return 0;
} else {
goto read_again;
}
} while (1);
}
#ifdef EFI_PLATFORM
extern EFI_GRAPHICS_OUTPUT_PROTOCOL * GOP;
int platform_list_modes(int sel, int select_this_mode) {
int index = 0;
for (int i = 0; i < GOP->Mode->MaxMode; ++i) {
EFI_STATUS status;
UINTN size;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION * info;
status = uefi_call_wrapper(GOP->QueryMode,
4, GOP, i, &size, &info);
if (EFI_ERROR(status) || info->PixelFormat != 1) {
continue;
}
if (select_this_mode && sel == index) {
uefi_call_wrapper(GOP->SetMode, 2, GOP, i);
init_graphics();
return 1;
}
char tmp[100];
char * t = tmp;
t = print_int_into(t, info->HorizontalResolution); *t = 'x'; t++;
t = print_int_into(t, info->VerticalResolution); *t = '\0';
mode_selector(sel, index, tmp);
index++;
}
return 0;
}
int platform_count_modes(int * current) {
int index = 0;
for (int i = 0; i < GOP->Mode->MaxMode; ++i) {
EFI_STATUS status;
UINTN size;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION * info;
status = uefi_call_wrapper(GOP->QueryMode,
4, GOP, i, &size, &info);
if (EFI_ERROR(status) || info->PixelFormat != 1) {
continue;
} else {
index++;
}
}
return index;
}
#else
extern void do_bios_call(uint32_t function, uint32_t arg1);
extern uint32_t vbe_cont_info_mode_off;
struct ColorFormat {
uint8_t mask;
uint8_t offset;
};
struct VbeMode {
uint16_t attributes;
uint16_t old_shit;
uint16_t granularity;
uint16_t window_size;
uint32_t segments;
uint32_t old_bank_switching_thing;
uint16_t pitch;
uint16_t width;
uint16_t height;
uint16_t w_y;
uint8_t planes;
uint8_t bpp;
uint8_t banks;
uint8_t memory_model;
uint8_t bank_size;
uint8_t pages;
uint8_t reserved;
struct ColorFormat red;
struct ColorFormat green;
struct ColorFormat blue;
struct ColorFormat alpha;
uint8_t color_attributes;
uint32_t framebuffer_addr;
uint32_t memory_offset;
uint32_t memory_size;
uint8_t other[206];
} __attribute__((packed));
extern volatile struct VbeMode vbe_info;
static struct VbeMode vbe_info_save;
static int qualified(void) {
if (!(vbe_info.attributes & (1 << 7))) return 0;
if (vbe_info.bpp < 24) return 0;
if (vbe_info.width < 640) return 0;
if (vbe_info.height < 480) return 0;
}
static char tmp[40];
int platform_list_modes(int sel, int select_this_mode) {
uint32_t vbe_addr = ((vbe_cont_info_mode_off & 0xFFFF0000) >> 12) + (vbe_cont_info_mode_off & 0xFFFF);
memcpy(&vbe_info_save, (char*)&vbe_info, sizeof(struct VbeMode));
int index = 0;
for (uint16_t * x = (uint16_t*)vbe_addr; *x != 0xFFFF; x++) {
do_bios_call(2, *x);
if (!qualified()) {
memcpy((char*)&vbe_info, &vbe_info_save, sizeof(struct VbeMode));
continue;
}
if (select_this_mode && sel == index) {
extern void bios_set_video(int);
bios_set_video(*x);
return 1;
}
char * t = tmp;
t = print_int_into(t, vbe_info.width); *t = 'x'; t++;
t = print_int_into(t, vbe_info.height); *t = 'x'; t++;
t = print_int_into(t, vbe_info.bpp); *t = '\0';
memcpy((char*)&vbe_info, &vbe_info_save, sizeof(struct VbeMode));
mode_selector(sel, index, tmp);
index++;
}
return 0;
}
extern int last_video_mode;
int platform_count_modes(int * current) {
uint32_t vbe_addr = ((vbe_cont_info_mode_off & 0xFFFF0000) >> 12) + (vbe_cont_info_mode_off & 0xFFFF);
int count = 0;
memcpy(&vbe_info_save, (char*)&vbe_info, sizeof(struct VbeMode));
for (uint16_t * x = (uint16_t*)vbe_addr; *x != 0xFFFF; x++) {
if (*x == last_video_mode) *current = count;
do_bios_call(2, *x);
if (!qualified()) continue;
count++;
}
memcpy((char*)&vbe_info, &vbe_info_save, sizeof(struct VbeMode));
return count;
}
#endif