[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:
Justin C. Miller
2021-01-08 22:25:37 -08:00
parent 1ba44c99d1
commit 61845b8761
13 changed files with 457 additions and 293 deletions

View File

@@ -71,6 +71,7 @@ modules:
- src/boot/loader.cpp - src/boot/loader.cpp
- src/boot/memory.cpp - src/boot/memory.cpp
- src/boot/paging.cpp - src/boot/paging.cpp
- src/boot/status.cpp
- src/boot/support.cpp - src/boot/support.cpp
nulldrv: nulldrv:

View File

@@ -17,23 +17,7 @@ namespace boot {
size_t ROWS = 0; size_t ROWS = 0;
size_t COLS = 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; 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', 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'}; 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; 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 } // namespace boot

View File

@@ -1,5 +1,5 @@
/// \file console.h /// \file console.h
/// Text output and status message handling /// Text output handler
#pragma once #pragma once
#include <stdarg.h> #include <stdarg.h>
#include <stddef.h> #include <stddef.h>
@@ -42,46 +42,4 @@ private:
static console *s_console; 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 } // namespace boot

View File

@@ -1,11 +1,11 @@
#include "error.h" #include "error.h"
#include "console.h" #include "console.h"
#include "kernel_args.h"
#include "status.h"
namespace boot { namespace boot {
namespace error { namespace error {
handler *handler::s_current = nullptr;
struct error_code_desc { struct error_code_desc {
uefi::status code; uefi::status code;
const wchar_t *name; const wchar_t *name;
@@ -20,8 +20,8 @@ struct error_code_desc error_table[] = {
{ uefi::status::success, nullptr } { uefi::status::success, nullptr }
}; };
static const wchar_t * const wchar_t *
error_message(uefi::status status) message(uefi::status status)
{ {
int32_t i = -1; int32_t i = -1;
while (error_table[++i].name != nullptr) { while (error_table[++i].name != nullptr) {
@@ -34,56 +34,31 @@ error_message(uefi::status status)
return L"Unknown Warning"; return L"Unknown Warning";
} }
[[ noreturn ]] void [[ noreturn ]] static void
raise(uefi::status status, const wchar_t *message) cpu_assert(uefi::status s, 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)
{ {
asm volatile ( asm volatile (
"movq $0xeeeeeeebadbadbad, %%r8;" "movq $0xeeeeeeebadbadbad, %%r8;"
"movq %0, %%r9;" "movq %0, %%r9;"
"movq %1, %%r10;"
"movq $0, %%rdx;" "movq $0, %%rdx;"
"divq %%rdx;" "divq %%rdx;"
: :
: "r"((uint64_t)s) : "r"((uint64_t)s), "r"(message)
: "rax", "rdx", "r8", "r9"); : "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 error
} // namespace boot } // namespace boot

View File

@@ -14,48 +14,7 @@ namespace error {
/// Halt or exit the program with the given error status/message /// Halt or exit the program with the given error status/message
[[ noreturn ]] void raise(uefi::status status, const wchar_t *message); [[ noreturn ]] void raise(uefi::status status, const wchar_t *message);
/// Interface for error-handling functors const wchar_t * message(uefi::status status);
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;
};
} // namespace error } // namespace error
} // namespace boot } // namespace boot

View File

@@ -8,6 +8,7 @@
#include "console.h" #include "console.h"
#include "error.h" #include "error.h"
#include "memory.h" #include "memory.h"
#include "status.h"
namespace boot { namespace boot {
namespace fs { namespace fs {

View File

@@ -1,6 +1,7 @@
#include "hardware.h" #include "hardware.h"
#include "console.h" #include "console.h"
#include "error.h" #include "error.h"
#include "status.h"
namespace boot { namespace boot {
namespace hw { namespace hw {

View File

@@ -8,6 +8,7 @@
#include "fs.h" #include "fs.h"
#include "memory.h" #include "memory.h"
#include "paging.h" #include "paging.h"
#include "status.h"
namespace args = kernel::args; namespace args = kernel::args;

View File

@@ -14,6 +14,7 @@
#include "loader.h" #include "loader.h"
#include "memory.h" #include "memory.h"
#include "paging.h" #include "paging.h"
#include "status.h"
#include "kernel_args.h" #include "kernel_args.h"
@@ -38,7 +39,6 @@ const program_desc program_list[] = {
{L"kernel", L"jsix.elf"}, {L"kernel", L"jsix.elf"},
{L"null driver", L"nulldrv.elf"}, {L"null driver", L"nulldrv.elf"},
{L"fb driver", L"fb.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 /// 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_modules,
size_t max_programs) 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; 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 /// UEFI is still in control of the machine. (ie, while the loader still
/// has access to boot services. /// has access to boot services.
args::header * args::header *
bootloader_main_uefi( uefi_preboot(uefi::handle image, uefi::system_table *st)
uefi::handle image,
uefi::system_table *st,
console &con)
{ {
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::boot_services *bs = st->boot_services;
uefi::runtime_services *rs = st->runtime_services; uefi::runtime_services *rs = st->runtime_services;
@@ -128,8 +124,6 @@ bootloader_main_uefi(
memory::module_type); memory::module_type);
add_module(args, args::mod_type::symbol_table, symbols); add_module(args, args::mod_type::symbol_table, symbols);
args->video = con.fb();
for (auto &desc : program_list) { for (auto &desc : program_list) {
buffer buf = loader::load_file(disk, desc.name, desc.path); buffer buf = loader::load_file(disk, desc.name, desc.path);
args::program &program = args->programs[args->num_programs++]; args::program &program = args->programs[args->num_programs++];
@@ -144,35 +138,48 @@ bootloader_main_uefi(
return args; 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 } // namespace boot
/// The UEFI entrypoint for the loader. /// The UEFI entrypoint for the loader.
extern "C" uefi::status 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; using namespace boot;
error::cpu_assert_handler handler;
console con(st->boot_services, st->con_out); console con(st->boot_services, st->con_out);
args::header *args = args::header *args = uefi_preboot(image, st);
bootloader_main_uefi(image_handle, st, con); 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]; args::program &kernel = args->programs[0];
paging::map_pages(args, kernel.phys_addr, kernel.virt_addr, kernel.size); paging::map_pages(args, kernel.phys_addr, kernel.virt_addr, kernel.size);
kernel::entrypoint kentry = kernel::entrypoint kentry =
reinterpret_cast<kernel::entrypoint>(kernel.entrypoint); reinterpret_cast<kernel::entrypoint>(kernel.entrypoint);
status.next();
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");
memory::virtualize(args->pml4, map, st->runtime_services); memory::virtualize(args->pml4, map, st->runtime_services);
status.next();
change_pointer(args->pml4); change_pointer(args->pml4);
hw::setup_cr4(); hw::setup_cr4();
status.next();
kentry(args); kentry(args);
debug_break(); debug_break();

View File

@@ -7,6 +7,7 @@
#include "error.h" #include "error.h"
#include "memory.h" #include "memory.h"
#include "paging.h" #include "paging.h"
#include "status.h"
namespace boot { namespace boot {
namespace memory { namespace memory {
@@ -43,10 +44,12 @@ memory_type_name(uefi::memory_type t)
} }
switch(t) { switch(t) {
/*
case args_type: return L"jsix kernel args"; case args_type: return L"jsix kernel args";
case module_type: return L"jsix bootloader module"; case module_type: return L"jsix bootloader module";
case program_type: return L"jsix kernel or program code"; case program_type: return L"jsix kernel or program code";
case table_type: return L"jsix page tables"; case table_type: return L"jsix page tables";
*/
default: return L"Bad Type Value"; default: return L"Bad Type Value";
} }
} }
@@ -100,14 +103,15 @@ can_merge(mem_entry &prev, mem_type type, uefi::memory_descriptor *next)
void void
get_uefi_mappings(efi_mem_map *map, bool allocate, uefi::boot_services *bs) 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( 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) if (status != uefi::status::buffer_too_small)
error::raise(status, L"Error getting memory map size"); error::raise(status, L"Error getting memory map size");
map->length = length;
if (allocate) { if (allocate) {
map->length += 10*map->size; 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 efi_mem_map
build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs) 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; efi_mem_map map;
get_uefi_mappings(&efi_map, false, bs); 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; kernel::args::mem_entry *kernel_map = nullptr;
try_or_raise( 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"); L"Error allocating kernel memory map module space");
bs->set_mem(kernel_map, map_size, 0); 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; size_t i = 0;
bool first = true; bool first = true;
for (auto desc : efi_map) { for (auto desc : map) {
/* /*
console::print(L" Range %lx (%lx) %x(%s) [%lu]\r\n", 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); 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; type = mem_type::persistent;
break; break;
/*
case args_type: case args_type:
type = mem_type::args; type = mem_type::args;
break; break;
@@ -202,6 +207,7 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
case table_type: case table_type:
type = mem_type::table; type = mem_type::table;
break; break;
*/
default: default:
error::raise( error::raise(
@@ -235,7 +241,7 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
args->mem_map = kernel_map; args->mem_map = kernel_map;
args->map_count = i; args->map_count = i;
return efi_map; return map;
} }
void void

View File

@@ -6,6 +6,7 @@
#include "memory.h" #include "memory.h"
#include "paging.h" #include "paging.h"
#include "pointer_manipulation.h" #include "pointer_manipulation.h"
#include "status.h"
namespace boot { namespace boot {
namespace paging { namespace paging {

263
src/boot/status.cpp Normal file
View 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
View 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