My penguin avatar

Running SerenityOS on Bare Metal

Published on 2022-02-19.

In a previous post, I mentioned:

I haven't managed to do a full successful boot on bare metal yet though, so that will definitely have to join this list one day.

Well, five months later I finally did it! :^)

View tweet: https://twitter.com/linusgroh/status/1486111320181149697

This is a post explaining what it took to get there, both as documentation for myself and hopefully to help someone else to get Serenity up and running on their hardware!

Hardware

While I also tried running SerenityOS on my laptop in the past (ThinkPad T450S), all serious attempts were on my desktop machine, as the availability of a serial port is highly important for debugging — especially when the kernel panics before even setting up a framebuffer and console for graphical output, and you only end up with a blank screen after booting. The alternative to this is beeping the PC speaker…

This is also the machine I do recording and some development on, so I didn't want to mess with the Linux partitions and ended up just giving Serenity its own SSD.

Setup

Note that I won't explain the general bare metal installation process here, that's all outlined in Documentation/BareMetalInstallation.md.

I usually keep my local checkout up to date, to get the latest kernel and userspace changes. This can mean that things that used to work suddenly stop working, and another investigation is required. That's ok though, as things improve for the most part. :^)

A recent example of this is an update to the i8042 initialisation process that would fail on my machine (I8042: Trying to probe for existence of controller failed), causing the mouse and keyboard to not work. Fixed in #12641.

For the record though: I'm using commit eca8208 as of writing this. I also tested i686 and x86_64, and they both boot.

Building & Creating a Bootable Disk

Basically: apply patches (discussed later), do a regular build, create GRUB disk image, dd to disk, reboot.

Serial Console

I use an adapter cable that's permanently connected to the 9-pin JCOM1 connector on the motherboard and has an RS-232 connector on the other end. Then I plug in an RS-232 to USB cable whenever I need to and connect from my laptop by running cu -s 57600 -l /dev/ttyUSB0 in a terminal, where I will then see exactly what QEMU would show on the debug console.

Input Devices

While trying to get Serenity to boot I already knew I wouldn't be able to actually use it once successful — USB HID devices aren't working in the kernel yet. The recommended first step is checking if the BIOS has an option to emulate PS/2 input devices; mine doesn't, but luckily the computer has a PS/2 port, so I ordered a cheap PS/2 mouse and keyboard. In an ironic twist, both the USB mouse and keyboard start working on Serenity once their PS/2 counterparts are plugged in, so the BIOS does have some sort of emulation, just doesn't seem to expose an option for it. I haven't investigated further.

Required Tweaks

As mentioned before, I still have to do some small modifications for Serenity to actually boot.

Additional Kernel Options

While all of these could be provided in GRUB on demand, I think it makes sense to hardcode them if the boot is known to fail without them.

So the patch to add all of them to the first GRUB menu entry looks as follows:

--- a/Meta/grub-mbr.cfg
+++ b/Meta/grub-mbr.cfg
@@ -2,7 +2,7 @@ timeout=1

 menuentry 'SerenityOS (normal)' {
   root=hd0,1
-  multiboot /boot/Prekernel root=/dev/hda1
+  multiboot /boot/Prekernel root=/dev/hda1 serial_debug enable_ioapic=off ahci_reset_mode=aggressive
   module /boot/Kernel
 }

Note that in many circumstances you will also need to specify a different value for the root option, especially in a dual boot environment or when having multiple disks. I simply reordered the connected disks so that Serenity is connected to SATA1 and the default option (root=/dev/hda1) just works. :^)

Increasing INITIAL_KMALLOC_MEMORY_SIZE

With the default value of 2 MiB, the kernel panics:

[Kernel]: ASSERTION FAILED: m_has_value
[Kernel]: ././AK/Optional.h:157 in T& AK::Optional::value() & [with T = KmallocGlobalData::ExpansionData]
[Kernel]: KERNEL PANIC! :^(
[Kernel]: Aborted
[Kernel]: at ./Kernel/Arch/x86/common/CPU.cpp:35 in void abort()

Various kernel changes have been made over time to reduce the amount of initial kmalloc memory that's needed, but it's still not enough in my case.

Increasing it to 8 MiB fixes the panic:

--- a/Kernel/Heap/kmalloc.cpp
+++ b/Kernel/Heap/kmalloc.cpp
@@ -23,7 +23,7 @@ static constexpr size_t CHUNK_SIZE = 32;
 static constexpr size_t CHUNK_SIZE = 64;
 #endif

-static constexpr size_t INITIAL_KMALLOC_MEMORY_SIZE = 2 * MiB;
+static constexpr size_t INITIAL_KMALLOC_MEMORY_SIZE = 8 * MiB;

 // Treat the heap as logically separate from .bss
 __attribute__((section(".heap"))) static u8 initial_kmalloc_memory[INITIAL_KMALLOC_MEMORY_SIZE];

Enabling the MULTIBOOT_VIDEO_MODE Flag

This is needed as I rely on Multiboot (which we use when booting in combination with GRUB) to prepare a framebuffer for the kernel. As far as I understand, QEMU & co. use a different approach, so it's not enabled by default.

--- a/Kernel/Prekernel/Arch/x86/multiboot.S
+++ b/Kernel/Prekernel/Arch/x86/multiboot.S
@@ -3,7 +3,7 @@
 .set MULTIBOOT_PAGE_ALIGN,    0x1
 .set MULTIBOOT_MEMORY_INFO,   0x2
 .set MULTIBOOT_VIDEO_MODE,    0x4
-.set multiboot_flags,         MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO
+.set multiboot_flags,         MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_VIDEO_MODE
 .set multiboot_checksum,      -(MULTIBOOT_MAGIC + multiboot_flags)

 .section .multiboot, "a"

Changing the WindowServer Screen Resolution

The default WindowServer screen resolution is 1024x768. However, the framebuffer we ask Multiboot to set up for us has a resolution of 1280x1024 by default:

/* for MULTIBOOT_VIDEO_MODE */
.long 0x00000000    /* mode_type */
.long 1280          /* width */
.long 1024          /* height */
.long 32            /* depth */

While it's possible to change these values (which would be great, as the monitor resolution is much larger), any attempts to do so resulted in a 640x480 framebuffer. Checking vbeinfo in a GRUB console revealed that no larger resolution is supported by this particular VBE setup:

grub> vbeinfo
List of supported video modes:
Legend: mask/position=red/green/blue/reserved
Adapter `VESA BIOS Extension Video Driver':
  VBE info:   version: 3.0  OEM software rev: 144.6
              total memory: 16384 KiB
  Ox101  640 x  480 x  8 ( 640)  Paletted
  0x103  800 x  600 x  8 (1024)  Paletted
  Ox105 1024 x  768 x  8 (1024)  Paletted
  Ox107 1280 x 1024 x  8 (1280)  Paletted
  Ox111  640 x  480 x 16 (1280)  Direct color, mask: 5/6/5/0  pos: 11/5/0/0
  Ox112  640 x  480 x 32 (2560)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  Ox114  800 x  600 x 16 (2048)  Direct color, mask: 5/6/5/0  pos: 11/5/0/0
  0x115  800 x  600 x 32 (4096)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  Ox117 1024 x  768 x 16 (2048)  Direct color, mask: 5/6/5/0  pos: 11/5/0/0
  Ox118 1024 x  768 x 32 (4096)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  Ox11a 1280 x 1024 x 16 (2560)  Direct color, mask: 5/6/5/0  pos: 11/5/0/0
  Ox11b 1280 x 1024 x 32 (5120)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
grub>

Changing the resolution in DisplaySettings doesn't do anything either (see #4414).

This situation will likely improve in the future with more native graphics drivers (we already have one for Intel graphics), but for now I set the monitor to a 5:4 aspect ratio and apply this patch to make use of the full available resolution:

--- a/Base/etc/WindowServer.ini
+++ b/Base/etc/WindowServer.ini
@@ -5,8 +5,8 @@ MainScreen=0
 Device=/dev/fb0
 Left=0
 Top=0
-Width=1024
-Height=768
+Width=1280
+Height=1024
 ScaleFactor=1

 [Fonts]

Demo

With all of these changes, it finally boots! The only major issue I found is that time advances way too quickly — we'd have to adjust it via NTP every now and then. Everything else seems to work just fine, including networking :^)

Thanks!

None of this would've been possible without Idan, Liav, Luke & all the other folks in the #bare-metal channel on our Discord server. I needed quite a bit of hand holding to understand the various panics and find workarounds or fixes for them!


Loading posts...