[boot] Add framebuffer progress bar
After exiting UEFI, the bootloader had no way of displaying status to the user. Now it will display a series of small boxes as a progress bar along the bottom of the screen if a framebuffer exists. Errors or warnings during a step will cause that step's box to turn red or orange, and display bars above it to signal the error code. This caused the simplification of the error handling system (which was mostly just calling status_line::fail) and added different types of status objects.
This commit is contained in:
@@ -17,23 +17,7 @@ namespace boot {
|
||||
size_t ROWS = 0;
|
||||
size_t COLS = 0;
|
||||
|
||||
static constexpr int level_ok = 0;
|
||||
static constexpr int level_warn = 1;
|
||||
static constexpr int level_fail = 2;
|
||||
|
||||
static const wchar_t *level_tags[] = {
|
||||
L" ok ",
|
||||
L" warn ",
|
||||
L"failed"
|
||||
};
|
||||
static const uefi::attribute level_colors[] = {
|
||||
uefi::attribute::green,
|
||||
uefi::attribute::brown,
|
||||
uefi::attribute::light_red
|
||||
};
|
||||
|
||||
console *console::s_console = nullptr;
|
||||
status_line *status_line::s_current = nullptr;
|
||||
|
||||
static const wchar_t digits[] = {u'0', u'1', u'2', u'3', u'4', u'5',
|
||||
u'6', u'7', u'8', u'9', u'a', u'b', u'c', u'd', u'e', u'f'};
|
||||
@@ -317,119 +301,4 @@ console::print(const wchar_t *fmt, ...)
|
||||
return result;
|
||||
}
|
||||
|
||||
status_line::status_line(const wchar_t *message, const wchar_t *context) :
|
||||
m_level(level_ok)
|
||||
{
|
||||
auto out = console::get().m_out;
|
||||
m_line = out->mode->cursor_row;
|
||||
m_depth = (s_current ? 1 + s_current->m_depth : 0);
|
||||
|
||||
int indent = 2 * m_depth;
|
||||
out->set_cursor_position(indent, m_line);
|
||||
out->set_attribute(uefi::attribute::light_gray);
|
||||
out->output_string(message);
|
||||
|
||||
if (context) {
|
||||
out->output_string(L": ");
|
||||
out->output_string(context);
|
||||
}
|
||||
|
||||
out->output_string(L"\r\n");
|
||||
|
||||
m_next = s_current;
|
||||
s_current = this;
|
||||
}
|
||||
|
||||
status_line::~status_line()
|
||||
{
|
||||
if (s_current != this)
|
||||
error::raise(uefi::status::unsupported, L"Destroying non-current status_line");
|
||||
|
||||
finish();
|
||||
if (m_next && m_level > m_next->m_level) {
|
||||
m_next->m_level = m_level;
|
||||
m_next->print_status_tag();
|
||||
}
|
||||
s_current = m_next;
|
||||
}
|
||||
|
||||
void
|
||||
status_line::print_status_tag()
|
||||
{
|
||||
auto out = console::get().m_out;
|
||||
int row = out->mode->cursor_row;
|
||||
int col = out->mode->cursor_column;
|
||||
|
||||
uefi::attribute color = level_colors[m_level];
|
||||
const wchar_t *tag = level_tags[m_level];
|
||||
|
||||
out->set_cursor_position(50, m_line);
|
||||
|
||||
out->set_attribute(uefi::attribute::light_gray);
|
||||
out->output_string(L"[");
|
||||
out->set_attribute(color);
|
||||
out->output_string(tag);
|
||||
out->set_attribute(uefi::attribute::light_gray);
|
||||
out->output_string(L"]\r\n");
|
||||
|
||||
out->set_cursor_position(col, row);
|
||||
}
|
||||
|
||||
void
|
||||
status_line::do_warn(const wchar_t *message, const wchar_t *error)
|
||||
{
|
||||
auto out = console::get().m_out;
|
||||
int row = out->mode->cursor_row;
|
||||
|
||||
if (m_level < level_warn) {
|
||||
m_level = level_warn;
|
||||
print_status_tag();
|
||||
}
|
||||
|
||||
int indent = 2 + 2 * m_depth;
|
||||
out->set_cursor_position(indent, row);
|
||||
out->set_attribute(uefi::attribute::yellow);
|
||||
out->output_string(message);
|
||||
|
||||
if (error) {
|
||||
out->output_string(L": ");
|
||||
out->output_string(error);
|
||||
}
|
||||
|
||||
out->set_attribute(uefi::attribute::light_gray);
|
||||
out->output_string(L"\r\n");
|
||||
}
|
||||
|
||||
void
|
||||
status_line::do_fail(const wchar_t *message, const wchar_t *error)
|
||||
{
|
||||
auto out = console::get().m_out;
|
||||
int row = out->mode->cursor_row;
|
||||
|
||||
if (s_current->m_level < level_fail) {
|
||||
m_level = level_fail;
|
||||
print_status_tag();
|
||||
}
|
||||
|
||||
int indent = 2 + 2 * m_depth;
|
||||
out->set_cursor_position(indent, row);
|
||||
out->set_attribute(uefi::attribute::red);
|
||||
out->output_string(message);
|
||||
|
||||
if (error) {
|
||||
out->output_string(L": ");
|
||||
out->output_string(error);
|
||||
}
|
||||
|
||||
out->set_attribute(uefi::attribute::light_gray);
|
||||
out->output_string(L"\r\n");
|
||||
}
|
||||
|
||||
void
|
||||
status_line::finish()
|
||||
{
|
||||
if (m_level <= level_ok)
|
||||
print_status_tag();
|
||||
}
|
||||
|
||||
} // namespace boot
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/// \file console.h
|
||||
/// Text output and status message handling
|
||||
/// Text output handler
|
||||
#pragma once
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
@@ -42,46 +42,4 @@ private:
|
||||
static console *s_console;
|
||||
};
|
||||
|
||||
/// Scoped status line reporter. Prints a message and an "OK" if no errors
|
||||
/// or warnings were reported before destruction, otherwise reports the
|
||||
/// error or warning.
|
||||
class status_line
|
||||
{
|
||||
public:
|
||||
/// Constructor.
|
||||
/// \arg message Description of the operation in progress
|
||||
/// \arg context If non-null, printed after `message` and a colon
|
||||
status_line(const wchar_t *message, const wchar_t *context = nullptr);
|
||||
~status_line();
|
||||
|
||||
/// Set the state to warning, and print a message. If the state is already at
|
||||
/// warning or error, the state is unchanged but the message is still printed.
|
||||
/// \arg message The warning message to print
|
||||
/// \arg error If non-null, printed after `message`
|
||||
inline static void warn(const wchar_t *message, const wchar_t *error = nullptr) {
|
||||
if (s_current) s_current->do_warn(message, error);
|
||||
}
|
||||
|
||||
/// Set the state to error, and print a message. If the state is already at
|
||||
/// error, the state is unchanged but the message is still printed.
|
||||
/// \arg message The error message to print
|
||||
/// \arg error If non-null, printed after `message`
|
||||
inline static void fail(const wchar_t *message, const wchar_t *error = nullptr) {
|
||||
if (s_current) s_current->do_fail(message, error);
|
||||
}
|
||||
|
||||
private:
|
||||
void print_status_tag();
|
||||
void do_warn(const wchar_t *message, const wchar_t *error);
|
||||
void do_fail(const wchar_t *message, const wchar_t *error);
|
||||
void finish();
|
||||
|
||||
size_t m_line;
|
||||
int m_level;
|
||||
int m_depth;
|
||||
|
||||
status_line *m_next;
|
||||
static status_line *s_current;
|
||||
};
|
||||
|
||||
} // namespace boot
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "error.h"
|
||||
#include "console.h"
|
||||
#include "kernel_args.h"
|
||||
#include "status.h"
|
||||
|
||||
namespace boot {
|
||||
namespace error {
|
||||
|
||||
handler *handler::s_current = nullptr;
|
||||
|
||||
struct error_code_desc {
|
||||
uefi::status code;
|
||||
const wchar_t *name;
|
||||
@@ -20,8 +20,8 @@ struct error_code_desc error_table[] = {
|
||||
{ uefi::status::success, nullptr }
|
||||
};
|
||||
|
||||
static const wchar_t *
|
||||
error_message(uefi::status status)
|
||||
const wchar_t *
|
||||
message(uefi::status status)
|
||||
{
|
||||
int32_t i = -1;
|
||||
while (error_table[++i].name != nullptr) {
|
||||
@@ -34,56 +34,31 @@ error_message(uefi::status status)
|
||||
return L"Unknown Warning";
|
||||
}
|
||||
|
||||
[[ noreturn ]] void
|
||||
raise(uefi::status status, const wchar_t *message)
|
||||
{
|
||||
if (handler::s_current) {
|
||||
handler::s_current->handle(status, message);
|
||||
}
|
||||
while (1) asm("hlt");
|
||||
}
|
||||
|
||||
handler::handler() :
|
||||
m_next(s_current)
|
||||
{
|
||||
s_current = this;
|
||||
}
|
||||
|
||||
handler::~handler()
|
||||
{
|
||||
if (s_current != this)
|
||||
raise(uefi::status::warn_stale_data,
|
||||
L"Non-current error handler destructing");
|
||||
s_current = m_next;
|
||||
}
|
||||
|
||||
uefi_handler::uefi_handler(console &con) :
|
||||
handler(),
|
||||
m_con(con)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
uefi_handler::handle(uefi::status s, const wchar_t *message)
|
||||
{
|
||||
status_line::fail(message, error_message(s));
|
||||
}
|
||||
|
||||
cpu_assert_handler::cpu_assert_handler() : handler() {}
|
||||
|
||||
void
|
||||
cpu_assert_handler::handle(uefi::status s, const wchar_t *message)
|
||||
[[ noreturn ]] static void
|
||||
cpu_assert(uefi::status s, const wchar_t *message)
|
||||
{
|
||||
asm volatile (
|
||||
"movq $0xeeeeeeebadbadbad, %%r8;"
|
||||
"movq %0, %%r9;"
|
||||
"movq %1, %%r10;"
|
||||
"movq $0, %%rdx;"
|
||||
"divq %%rdx;"
|
||||
:
|
||||
: "r"((uint64_t)s)
|
||||
: "rax", "rdx", "r8", "r9");
|
||||
: "r"((uint64_t)s), "r"(message)
|
||||
: "rax", "rdx", "r8", "r9", "r10");
|
||||
while (1) asm("hlt");
|
||||
}
|
||||
|
||||
[[ noreturn ]] void
|
||||
raise(uefi::status status, const wchar_t *message)
|
||||
{
|
||||
if(status_line::fail(message, status))
|
||||
while (1) asm("hlt");
|
||||
else
|
||||
cpu_assert(status, message);
|
||||
}
|
||||
|
||||
|
||||
} // namespace error
|
||||
} // namespace boot
|
||||
|
||||
|
||||
@@ -14,48 +14,7 @@ namespace error {
|
||||
/// Halt or exit the program with the given error status/message
|
||||
[[ noreturn ]] void raise(uefi::status status, const wchar_t *message);
|
||||
|
||||
/// Interface for error-handling functors
|
||||
class handler
|
||||
{
|
||||
public:
|
||||
/// Constructor must be called by implementing classes.
|
||||
handler();
|
||||
virtual ~handler();
|
||||
|
||||
/// Interface for implementations of error handling.
|
||||
virtual void handle(uefi::status, const wchar_t*) = 0;
|
||||
|
||||
private:
|
||||
friend void raise(uefi::status, const wchar_t *);
|
||||
|
||||
handler *m_next;
|
||||
static handler *s_current;
|
||||
};
|
||||
|
||||
/// Error handler using UEFI boot services. Integrates with `status_line`
|
||||
/// to print formatted error messages to the screen.
|
||||
class uefi_handler :
|
||||
public handler
|
||||
{
|
||||
public:
|
||||
uefi_handler(console &con);
|
||||
virtual ~uefi_handler() {}
|
||||
void handle(uefi::status, const wchar_t*) override;
|
||||
|
||||
private:
|
||||
console &m_con;
|
||||
};
|
||||
|
||||
/// Error handler that doesn't rely on UEFI. Sets status into CPU
|
||||
/// registers and then causes a CPU #DE exception.
|
||||
class cpu_assert_handler :
|
||||
public handler
|
||||
{
|
||||
public:
|
||||
cpu_assert_handler();
|
||||
virtual ~cpu_assert_handler() {}
|
||||
void handle(uefi::status, const wchar_t*) override;
|
||||
};
|
||||
const wchar_t * message(uefi::status status);
|
||||
|
||||
} // namespace error
|
||||
} // namespace boot
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "console.h"
|
||||
#include "error.h"
|
||||
#include "memory.h"
|
||||
#include "status.h"
|
||||
|
||||
namespace boot {
|
||||
namespace fs {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "hardware.h"
|
||||
#include "console.h"
|
||||
#include "error.h"
|
||||
#include "status.h"
|
||||
|
||||
namespace boot {
|
||||
namespace hw {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "fs.h"
|
||||
#include "memory.h"
|
||||
#include "paging.h"
|
||||
#include "status.h"
|
||||
|
||||
namespace args = kernel::args;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "loader.h"
|
||||
#include "memory.h"
|
||||
#include "paging.h"
|
||||
#include "status.h"
|
||||
|
||||
#include "kernel_args.h"
|
||||
|
||||
@@ -38,7 +39,6 @@ const program_desc program_list[] = {
|
||||
{L"kernel", L"jsix.elf"},
|
||||
{L"null driver", L"nulldrv.elf"},
|
||||
{L"fb driver", L"fb.elf"},
|
||||
//{L"terminal driver", L"terminal.elf"},
|
||||
};
|
||||
|
||||
/// Change a pointer to point to the higher-half linear-offset version
|
||||
@@ -57,7 +57,7 @@ allocate_args_structure(
|
||||
size_t max_modules,
|
||||
size_t max_programs)
|
||||
{
|
||||
status_line status(L"Setting up kernel args memory");
|
||||
status_line status {L"Setting up kernel args memory"};
|
||||
|
||||
args::header *args = nullptr;
|
||||
|
||||
@@ -98,13 +98,9 @@ add_module(args::header *args, args::mod_type type, buffer &data)
|
||||
/// UEFI is still in control of the machine. (ie, while the loader still
|
||||
/// has access to boot services.
|
||||
args::header *
|
||||
bootloader_main_uefi(
|
||||
uefi::handle image,
|
||||
uefi::system_table *st,
|
||||
console &con)
|
||||
uefi_preboot(uefi::handle image, uefi::system_table *st)
|
||||
{
|
||||
error::uefi_handler handler(con);
|
||||
status_line status(L"Performing UEFI pre-boot");
|
||||
status_line status {L"Performing UEFI pre-boot"};
|
||||
|
||||
uefi::boot_services *bs = st->boot_services;
|
||||
uefi::runtime_services *rs = st->runtime_services;
|
||||
@@ -128,8 +124,6 @@ bootloader_main_uefi(
|
||||
memory::module_type);
|
||||
add_module(args, args::mod_type::symbol_table, symbols);
|
||||
|
||||
args->video = con.fb();
|
||||
|
||||
for (auto &desc : program_list) {
|
||||
buffer buf = loader::load_file(disk, desc.name, desc.path);
|
||||
args::program &program = args->programs[args->num_programs++];
|
||||
@@ -144,35 +138,48 @@ bootloader_main_uefi(
|
||||
return args;
|
||||
}
|
||||
|
||||
memory::efi_mem_map
|
||||
uefi_exit(args::header *args, uefi::handle image, uefi::boot_services *bs)
|
||||
{
|
||||
status_line status {L"Exiting UEFI"};
|
||||
|
||||
memory::efi_mem_map map =
|
||||
memory::build_kernel_mem_map(args, bs);
|
||||
|
||||
try_or_raise(
|
||||
bs->exit_boot_services(image, map.key),
|
||||
L"Failed to exit boot services");
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
} // namespace boot
|
||||
|
||||
/// The UEFI entrypoint for the loader.
|
||||
extern "C" uefi::status
|
||||
efi_main(uefi::handle image_handle, uefi::system_table *st)
|
||||
efi_main(uefi::handle image, uefi::system_table *st)
|
||||
{
|
||||
using namespace boot;
|
||||
|
||||
error::cpu_assert_handler handler;
|
||||
console con(st->boot_services, st->con_out);
|
||||
|
||||
args::header *args =
|
||||
bootloader_main_uefi(image_handle, st, con);
|
||||
args::header *args = uefi_preboot(image, st);
|
||||
memory::efi_mem_map map = uefi_exit(args, image, st->boot_services);
|
||||
|
||||
args->video = con.fb();
|
||||
status_bar status {con.fb()}; // Switch to fb status display
|
||||
|
||||
args::program &kernel = args->programs[0];
|
||||
paging::map_pages(args, kernel.phys_addr, kernel.virt_addr, kernel.size);
|
||||
kernel::entrypoint kentry =
|
||||
reinterpret_cast<kernel::entrypoint>(kernel.entrypoint);
|
||||
|
||||
memory::efi_mem_map map =
|
||||
memory::build_kernel_mem_map(args, st->boot_services);
|
||||
|
||||
try_or_raise(
|
||||
st->boot_services->exit_boot_services(image_handle, map.key),
|
||||
L"Failed to exit boot services");
|
||||
status.next();
|
||||
|
||||
memory::virtualize(args->pml4, map, st->runtime_services);
|
||||
status.next();
|
||||
|
||||
change_pointer(args->pml4);
|
||||
hw::setup_cr4();
|
||||
status.next();
|
||||
|
||||
kentry(args);
|
||||
debug_break();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "error.h"
|
||||
#include "memory.h"
|
||||
#include "paging.h"
|
||||
#include "status.h"
|
||||
|
||||
namespace boot {
|
||||
namespace memory {
|
||||
@@ -43,10 +44,12 @@ memory_type_name(uefi::memory_type t)
|
||||
}
|
||||
|
||||
switch(t) {
|
||||
/*
|
||||
case args_type: return L"jsix kernel args";
|
||||
case module_type: return L"jsix bootloader module";
|
||||
case program_type: return L"jsix kernel or program code";
|
||||
case table_type: return L"jsix page tables";
|
||||
*/
|
||||
default: return L"Bad Type Value";
|
||||
}
|
||||
}
|
||||
@@ -100,14 +103,15 @@ can_merge(mem_entry &prev, mem_type type, uefi::memory_descriptor *next)
|
||||
void
|
||||
get_uefi_mappings(efi_mem_map *map, bool allocate, uefi::boot_services *bs)
|
||||
{
|
||||
status_line(L"Getting UEFI memory map");
|
||||
|
||||
size_t length = 0;
|
||||
uefi::status status = bs->get_memory_map(
|
||||
&map->length, nullptr, &map->key, &map->size, &map->version);
|
||||
&length, nullptr, &map->key, &map->size, &map->version);
|
||||
|
||||
if (status != uefi::status::buffer_too_small)
|
||||
error::raise(status, L"Error getting memory map size");
|
||||
|
||||
map->length = length;
|
||||
|
||||
if (allocate) {
|
||||
map->length += 10*map->size;
|
||||
|
||||
@@ -126,12 +130,12 @@ get_uefi_mappings(efi_mem_map *map, bool allocate, uefi::boot_services *bs)
|
||||
efi_mem_map
|
||||
build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
|
||||
{
|
||||
status_line(L"Creating kernel memory map");
|
||||
status_line status {L"Creating kernel memory map"};
|
||||
|
||||
efi_mem_map efi_map;
|
||||
get_uefi_mappings(&efi_map, false, bs);
|
||||
efi_mem_map map;
|
||||
get_uefi_mappings(&map, false, bs);
|
||||
|
||||
size_t map_size = efi_map.num_entries() * sizeof(mem_entry);
|
||||
size_t map_size = map.num_entries() * sizeof(mem_entry);
|
||||
|
||||
kernel::args::mem_entry *kernel_map = nullptr;
|
||||
try_or_raise(
|
||||
@@ -143,11 +147,11 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
|
||||
L"Error allocating kernel memory map module space");
|
||||
|
||||
bs->set_mem(kernel_map, map_size, 0);
|
||||
get_uefi_mappings(&efi_map, true, bs);
|
||||
get_uefi_mappings(&map, true, bs);
|
||||
|
||||
size_t i = 0;
|
||||
bool first = true;
|
||||
for (auto desc : efi_map) {
|
||||
for (auto desc : map) {
|
||||
/*
|
||||
console::print(L" Range %lx (%lx) %x(%s) [%lu]\r\n",
|
||||
desc->physical_start, desc->attribute, desc->type, memory_type_name(desc->type), desc->number_of_pages);
|
||||
@@ -187,6 +191,7 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
|
||||
type = mem_type::persistent;
|
||||
break;
|
||||
|
||||
/*
|
||||
case args_type:
|
||||
type = mem_type::args;
|
||||
break;
|
||||
@@ -202,6 +207,7 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
|
||||
case table_type:
|
||||
type = mem_type::table;
|
||||
break;
|
||||
*/
|
||||
|
||||
default:
|
||||
error::raise(
|
||||
@@ -235,7 +241,7 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
|
||||
args->mem_map = kernel_map;
|
||||
args->map_count = i;
|
||||
|
||||
return efi_map;
|
||||
return map;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "memory.h"
|
||||
#include "paging.h"
|
||||
#include "pointer_manipulation.h"
|
||||
#include "status.h"
|
||||
|
||||
namespace boot {
|
||||
namespace paging {
|
||||
|
||||
263
src/boot/status.cpp
Normal file
263
src/boot/status.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
#include <uefi/types.h>
|
||||
#include <uefi/graphics.h>
|
||||
|
||||
#include "console.h"
|
||||
#include "error.h"
|
||||
#include "kernel_args.h"
|
||||
#include "status.h"
|
||||
|
||||
constexpr int num_boxes = 30;
|
||||
|
||||
namespace boot {
|
||||
|
||||
static constexpr int level_ok = 0;
|
||||
static constexpr int level_warn = 1;
|
||||
static constexpr int level_fail = 2;
|
||||
|
||||
static const wchar_t *level_tags[] = {
|
||||
L" ok ",
|
||||
L" warn ",
|
||||
L"failed"
|
||||
};
|
||||
|
||||
static const uefi::attribute level_colors[] = {
|
||||
uefi::attribute::green,
|
||||
uefi::attribute::brown,
|
||||
uefi::attribute::light_red
|
||||
};
|
||||
|
||||
status *status::s_current = nullptr;
|
||||
unsigned status::s_current_type = 0;
|
||||
unsigned status_bar::s_count = 0;
|
||||
|
||||
status_line::status_line(const wchar_t *message, const wchar_t *context) :
|
||||
m_level(level_ok),
|
||||
m_depth(0),
|
||||
m_outer(nullptr)
|
||||
{
|
||||
if (status::s_current_type == status_line::type) {
|
||||
m_outer = static_cast<status_line*>(s_current);
|
||||
m_depth = (m_outer ? 1 + m_outer->m_depth : 0);
|
||||
}
|
||||
s_current = this;
|
||||
s_current_type = status_line::type;
|
||||
|
||||
auto out = console::get().m_out;
|
||||
m_line = out->mode->cursor_row;
|
||||
|
||||
int indent = 2 * m_depth;
|
||||
out->set_cursor_position(indent, m_line);
|
||||
out->set_attribute(uefi::attribute::light_gray);
|
||||
out->output_string(message);
|
||||
|
||||
if (context) {
|
||||
out->output_string(L": ");
|
||||
out->output_string(context);
|
||||
}
|
||||
|
||||
out->output_string(L"\r\n");
|
||||
print_status_tag();
|
||||
}
|
||||
|
||||
status_line::~status_line()
|
||||
{
|
||||
if (s_current != this)
|
||||
error::raise(uefi::status::unsupported, L"Destroying non-current status_line");
|
||||
|
||||
if (m_outer && m_level > m_outer->m_level) {
|
||||
m_outer->m_level = m_level;
|
||||
m_outer->print_status_tag();
|
||||
}
|
||||
s_current = m_outer;
|
||||
}
|
||||
|
||||
void
|
||||
status_line::print_status_tag()
|
||||
{
|
||||
auto out = console::get().m_out;
|
||||
int row = out->mode->cursor_row;
|
||||
int col = out->mode->cursor_column;
|
||||
|
||||
uefi::attribute color = level_colors[m_level];
|
||||
const wchar_t *tag = level_tags[m_level];
|
||||
|
||||
out->set_cursor_position(50, m_line);
|
||||
|
||||
out->set_attribute(uefi::attribute::light_gray);
|
||||
out->output_string(L"[");
|
||||
out->set_attribute(color);
|
||||
out->output_string(tag);
|
||||
out->set_attribute(uefi::attribute::light_gray);
|
||||
out->output_string(L"]\r\n");
|
||||
|
||||
out->set_cursor_position(col, row);
|
||||
}
|
||||
|
||||
void
|
||||
status_line::do_warn(const wchar_t *message, uefi::status status)
|
||||
{
|
||||
auto out = console::get().m_out;
|
||||
int row = out->mode->cursor_row;
|
||||
|
||||
if (m_level < level_warn) {
|
||||
m_level = level_warn;
|
||||
print_status_tag();
|
||||
}
|
||||
|
||||
int indent = 2 + 2 * m_depth;
|
||||
out->set_cursor_position(indent, row);
|
||||
out->set_attribute(uefi::attribute::yellow);
|
||||
out->output_string(message);
|
||||
|
||||
const wchar_t *error = error::message(status);
|
||||
if (error) {
|
||||
out->output_string(L": ");
|
||||
out->output_string(error);
|
||||
}
|
||||
|
||||
out->set_attribute(uefi::attribute::light_gray);
|
||||
out->output_string(L"\r\n");
|
||||
}
|
||||
|
||||
void
|
||||
status_line::do_fail(const wchar_t *message, uefi::status status)
|
||||
{
|
||||
auto out = console::get().m_out;
|
||||
int row = out->mode->cursor_row;
|
||||
|
||||
if (m_level < level_fail) {
|
||||
m_level = level_fail;
|
||||
print_status_tag();
|
||||
}
|
||||
|
||||
int indent = 2 + 2 * m_depth;
|
||||
out->set_cursor_position(indent, row);
|
||||
out->set_attribute(uefi::attribute::red);
|
||||
out->output_string(message);
|
||||
|
||||
const wchar_t *error = error::message(status);
|
||||
if (error) {
|
||||
out->output_string(L": ");
|
||||
out->output_string(error);
|
||||
}
|
||||
|
||||
out->set_attribute(uefi::attribute::light_gray);
|
||||
out->output_string(L"\r\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
status_bar::status_bar(kernel::args::framebuffer const &fb) :
|
||||
m_outer(nullptr)
|
||||
{
|
||||
m_size = (fb.vertical / num_boxes) - 1;
|
||||
m_top = fb.vertical - m_size;
|
||||
m_horiz = fb.horizontal;
|
||||
m_fb = reinterpret_cast<uint32_t*>(fb.phys_addr);
|
||||
m_type = static_cast<uint16_t>(fb.type);
|
||||
next();
|
||||
|
||||
if (status::s_current_type == status_bar::type)
|
||||
m_outer = static_cast<status_bar*>(s_current);
|
||||
s_current = this;
|
||||
s_current_type = status_bar::type;
|
||||
}
|
||||
|
||||
status_bar::~status_bar()
|
||||
{
|
||||
if (s_current != this)
|
||||
error::raise(uefi::status::unsupported, L"Destroying non-current status_bar");
|
||||
draw_box();
|
||||
s_current = m_outer;
|
||||
}
|
||||
|
||||
void
|
||||
status_bar::do_warn(const wchar_t *message, uefi::status status)
|
||||
{
|
||||
m_status = status;
|
||||
if (m_level < level_warn) {
|
||||
m_level = level_warn;
|
||||
draw_box();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
status_bar::do_fail(const wchar_t *message, uefi::status status)
|
||||
{
|
||||
m_status = status;
|
||||
if (m_level < level_fail) {
|
||||
m_level = level_fail;
|
||||
draw_box();
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
make_color(uint8_t r, uint8_t g, uint8_t b, uint16_t type)
|
||||
{
|
||||
switch (static_cast<kernel::args::fb_type>(type)) {
|
||||
case kernel::args::fb_type::bgr8:
|
||||
return
|
||||
(static_cast<uint32_t>(b) << 0) |
|
||||
(static_cast<uint32_t>(g) << 8) |
|
||||
(static_cast<uint32_t>(r) << 16);
|
||||
|
||||
case kernel::args::fb_type::rgb8:
|
||||
return
|
||||
(static_cast<uint32_t>(r) << 0) |
|
||||
(static_cast<uint32_t>(g) << 8) |
|
||||
(static_cast<uint32_t>(b) << 16);
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
status_bar::draw_box()
|
||||
{
|
||||
static const uint32_t colors[] = {0x909090, 0xf0f0f0};
|
||||
constexpr unsigned ncolors = sizeof(colors) / sizeof(uint32_t);
|
||||
|
||||
if (m_fb == nullptr)
|
||||
return;
|
||||
|
||||
unsigned x0 = m_current * m_size;
|
||||
unsigned x1 = x0 + m_size - 3;
|
||||
unsigned y0 = m_top;
|
||||
unsigned y1 = m_top + m_size - 3;
|
||||
|
||||
uint32_t color = 0;
|
||||
switch (m_level) {
|
||||
case level_ok:
|
||||
color = colors[m_current % ncolors];
|
||||
break;
|
||||
case level_warn:
|
||||
color = make_color(0xff, 0xb2, 0x34, m_type);
|
||||
break;
|
||||
case level_fail:
|
||||
color = make_color(0xfb, 0x0a, 0x1e, m_type);
|
||||
break;
|
||||
default:
|
||||
color = 0;
|
||||
}
|
||||
|
||||
for (unsigned y = y0; y < y1; ++y)
|
||||
for (unsigned x = x0; x < x1; ++x)
|
||||
m_fb[y * m_horiz + x] = color;
|
||||
|
||||
if (m_level > level_ok) {
|
||||
unsigned nbars = static_cast<uint64_t>(m_status) & 0xffff;
|
||||
constexpr unsigned bar_height = 4;
|
||||
|
||||
for (unsigned i = 1; i <= nbars; ++i) {
|
||||
y0 = m_top - 2 * i * bar_height;
|
||||
y1 = y0 + bar_height;
|
||||
|
||||
for (unsigned y = y0; y < y1; ++y)
|
||||
for (unsigned x = x0; x < x1; ++x)
|
||||
m_fb[y * m_horiz + x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace boot
|
||||
122
src/boot/status.h
Normal file
122
src/boot/status.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/// \file status.h
|
||||
/// Status message and indicator handling
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <uefi/types.h>
|
||||
|
||||
namespace kernel {
|
||||
namespace args {
|
||||
class framebuffer;
|
||||
}
|
||||
}
|
||||
|
||||
namespace boot {
|
||||
|
||||
// Abstract base class for status reporters.
|
||||
class status
|
||||
{
|
||||
public:
|
||||
virtual void do_warn(const wchar_t *message, uefi::status status) = 0;
|
||||
virtual void do_fail(const wchar_t *message, uefi::status status) = 0;
|
||||
|
||||
/// Set the state to warning, and print a message. If the state is already at
|
||||
/// warning or error, the state is unchanged but the message is still printed.
|
||||
/// \arg message The warning message to print, if text is supported
|
||||
/// \arg status If set, the error or warning code that should be represented
|
||||
/// \returns True if there was a status handler to display the warning
|
||||
inline static bool warn(const wchar_t *message, uefi::status status = uefi::status::success) {
|
||||
if (!s_current) return false;
|
||||
s_current->do_warn(message, status);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Set the state to error, and print a message. If the state is already at
|
||||
/// error, the state is unchanged but the message is still printed.
|
||||
/// \arg message The error message to print, if text is supported
|
||||
/// \arg status The error or warning code that should be represented
|
||||
/// \returns True if there was a status handler to display the failure
|
||||
inline static bool fail(const wchar_t *message, uefi::status status) {
|
||||
if (!s_current) return false;
|
||||
s_current->do_fail(message, status);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
static status *s_current;
|
||||
static unsigned s_current_type;
|
||||
};
|
||||
|
||||
/// Scoped status line reporter. Prints a message and an "OK" if no errors
|
||||
/// or warnings were reported before destruction, otherwise reports the
|
||||
/// error or warning.
|
||||
class status_line :
|
||||
public status
|
||||
{
|
||||
public:
|
||||
constexpr static unsigned type = 1;
|
||||
|
||||
/// Constructor.
|
||||
/// \arg message Description of the operation in progress
|
||||
/// \arg context If non-null, printed after `message` and a colon
|
||||
status_line(const wchar_t *message, const wchar_t *context = nullptr);
|
||||
~status_line();
|
||||
|
||||
virtual void do_warn(const wchar_t *message, uefi::status status) override;
|
||||
virtual void do_fail(const wchar_t *message, uefi::status status) override;
|
||||
|
||||
private:
|
||||
void print_status_tag();
|
||||
|
||||
size_t m_line;
|
||||
int m_level;
|
||||
int m_depth;
|
||||
|
||||
status_line *m_outer;
|
||||
};
|
||||
|
||||
/// Scoped status bar reporter. Draws a row of boxes along the bottom of
|
||||
/// the screen, turning one red if there's an error in that step.
|
||||
class status_bar :
|
||||
public status
|
||||
{
|
||||
public:
|
||||
constexpr static unsigned type = 2;
|
||||
|
||||
using framebuffer = kernel::args::framebuffer;
|
||||
|
||||
/// Constructor.
|
||||
/// \arg fb The framebuffer descriptor to draw to
|
||||
status_bar(kernel::args::framebuffer const &fb);
|
||||
~status_bar();
|
||||
|
||||
virtual void do_warn(const wchar_t *message, uefi::status status) override;
|
||||
virtual void do_fail(const wchar_t *message, uefi::status status) override;
|
||||
|
||||
inline void next() {
|
||||
m_current = s_count++;
|
||||
m_level = 0;
|
||||
m_status = uefi::status::success;
|
||||
draw_box();
|
||||
}
|
||||
|
||||
private:
|
||||
void draw_box();
|
||||
|
||||
uint32_t *m_fb;
|
||||
uint32_t m_size;
|
||||
uint32_t m_top;
|
||||
uint32_t m_horiz;
|
||||
|
||||
int m_level;
|
||||
uefi::status m_status;
|
||||
|
||||
uint16_t m_type;
|
||||
uint16_t m_current;
|
||||
|
||||
status_bar *m_outer;
|
||||
|
||||
static unsigned s_count;
|
||||
};
|
||||
|
||||
} // namespace boot
|
||||
Reference in New Issue
Block a user