metropolis/node/core/bios_bootcode: Add legacy bootcode
This change provides a legacy bootcode that shows the user that they
are using an invalid configuration, e.g. not use UEFI. This can be
tested with "qemu-system-i386 -hda bazel-bin/metropolis/node/image.img".
Closes monogon-dev/monogon#142
Change-Id: I3337a70125010aec110ad75647346310cac76d37
Reviewed-on: https://review.monogon.dev/c/monogon/+/3748
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/node/BUILD.bazel b/metropolis/node/BUILD.bazel
index 3eccd5f..1638083 100644
--- a/metropolis/node/BUILD.bazel
+++ b/metropolis/node/BUILD.bazel
@@ -1,8 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("@rules_pkg//:pkg.bzl", "pkg_zip")
load("//osbase/build:def.bzl", "erofs_image", "verity_image")
load("//osbase/build:efi.bzl", "efi_unified_kernel_image")
+load("//osbase/build/genosrelease:defs.bzl", "os_release")
load("//osbase/build/mkimage:def.bzl", "node_image")
-load("@rules_pkg//:pkg.bzl", "pkg_zip")
go_library(
name = "node",
@@ -127,6 +128,7 @@
node_image(
name = "image",
abloader = "//metropolis/node/core/abloader",
+ bios_bootcode = "//metropolis/node/core/bios_bootcode",
kernel = ":kernel_efi",
system = ":verity_rootfs",
visibility = [
@@ -136,8 +138,6 @@
],
)
-load("//osbase/build/genosrelease:defs.bzl", "os_release")
-
os_release(
name = "os-release-info",
os_id = "metropolis-node",
diff --git a/metropolis/node/core/bios_bootcode/BUILD.bazel b/metropolis/node/core/bios_bootcode/BUILD.bazel
new file mode 100644
index 0000000..4b02276
--- /dev/null
+++ b/metropolis/node/core/bios_bootcode/BUILD.bazel
@@ -0,0 +1,17 @@
+load("//metropolis/node/core/bios_bootcode/genlogo:def.bzl", "gen_logo")
+
+gen_logo(
+ name = "logo.asm",
+ logo = ":logo.png",
+)
+
+genrule(
+ name = "bios_bootcode",
+ srcs = [
+ ":boot.asm",
+ ":logo.asm",
+ ],
+ outs = ["boot.bin"],
+ cmd = "nasm -d LOGO=$(location :logo.asm) $(location :boot.asm) -f bin -o $@",
+ visibility = ["//visibility:public"],
+)
diff --git a/metropolis/node/core/bios_bootcode/README.md b/metropolis/node/core/bios_bootcode/README.md
new file mode 100644
index 0000000..4480344
--- /dev/null
+++ b/metropolis/node/core/bios_bootcode/README.md
@@ -0,0 +1,11 @@
+# BIOS Bootcode
+
+This package contains legacy bootcode which is displayed to non-UEFI users.
+It's sole purpose is to explain users their wrongdoing and tell them to use UEFI.
+It also shows a cute ascii-art logo.
+
+## Build
+
+Bazel generates the logo content with `genlogo`.
+It takes a black/white png-file and converts it to RLE encoded data,
+which is rendered as ascii-art at runtime.
diff --git a/metropolis/node/core/bios_bootcode/boot.asm b/metropolis/node/core/bios_bootcode/boot.asm
new file mode 100644
index 0000000..222e913
--- /dev/null
+++ b/metropolis/node/core/bios_bootcode/boot.asm
@@ -0,0 +1,89 @@
+org 7c00h
+
+start:
+ jmp main
+
+; si: string data, null terminated
+; di: start offset
+writestring:
+ mov al, [si]
+ or al, al
+ jz writestring_done
+ inc si
+ mov byte [fs:di], al
+ add di, 2
+ jmp writestring
+writestring_done:
+ ret
+
+; si: rle encoded data (high bit == color, lower 7: length)
+; di: start offset
+writegfx:
+ mov al, [si]
+ or al, al
+ jz writegfx_done
+ inc si
+
+ mov cl, al
+ and cx, 0b01111111
+ shr al, 7
+
+writegfx_nextinner:
+ or al, al
+ jz writegfx_space
+ mov byte [fs:di], 'M'
+writegfx_space:
+ add di, 2
+ sub cx, 1
+ jz writegfx
+ jmp writegfx_nextinner
+writegfx_done:
+ ret
+
+main:
+ xor ax, ax
+ mov ds, ax
+
+ ; set mode 3 (text 80x25, 16 color)
+ mov ax, 0x3
+ int 0x10
+
+ ; set up fs segment to point at framebuffer
+ mov ax, 0xb800
+ mov fs, ax
+
+ mov di, 4
+ mov si, logo
+ call writegfx
+
+ mov di, 3400
+ mov si, line1
+ call writestring
+
+ mov di, 3544
+ mov si, line2
+ call writestring
+
+end:
+ jmp end
+
+; Workaround to pass file as argument
+%macro incdef 1
+ %push _incdef_
+ %defstr %$file %{1}
+ %include %{$file}
+ %pop
+%endmacro
+
+incdef LOGO
+
+line1:
+ db "Hi there! Didn't see you coming in.", 0
+
+line2:
+ db "Unfortunately, Metropolis can only boot in UEFI mode.", 0
+
+db 0x55
+db 0xAA
+
+; We don't fill the rest with zeros, as this is done by mkimage and friends.
\ No newline at end of file
diff --git a/metropolis/node/core/bios_bootcode/genlogo/BUILD.bazel b/metropolis/node/core/bios_bootcode/genlogo/BUILD.bazel
new file mode 100644
index 0000000..eb31744
--- /dev/null
+++ b/metropolis/node/core/bios_bootcode/genlogo/BUILD.bazel
@@ -0,0 +1,14 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+ name = "genlogo_lib",
+ srcs = ["main.go"],
+ importpath = "source.monogon.dev/metropolis/node/core/bios_bootcode/genlogo",
+ visibility = ["//visibility:private"],
+)
+
+go_binary(
+ name = "genlogo",
+ embed = [":genlogo_lib"],
+ visibility = ["//visibility:public"],
+)
diff --git a/metropolis/node/core/bios_bootcode/genlogo/def.bzl b/metropolis/node/core/bios_bootcode/genlogo/def.bzl
new file mode 100644
index 0000000..11a56cd
--- /dev/null
+++ b/metropolis/node/core/bios_bootcode/genlogo/def.bzl
@@ -0,0 +1,34 @@
+def _build_logo_impl(ctx):
+ arguments = ctx.actions.args()
+
+ arguments.add_all(["--input"] + ctx.files.logo)
+ output = ctx.actions.declare_file("logo.asm")
+ arguments.add_all(["--output", output])
+
+ ctx.actions.run(
+ outputs = [output],
+ inputs = ctx.files.logo,
+ arguments = [arguments],
+ executable = ctx.executable._genlogo,
+ )
+
+ return DefaultInfo(
+ files = depset([output]),
+ )
+
+ pass
+
+gen_logo = rule(
+ implementation = _build_logo_impl,
+ attrs = {
+ "logo": attr.label(
+ allow_single_file = True,
+ ),
+ "_genlogo": attr.label(
+ default = Label(":genlogo"),
+ allow_single_file = True,
+ executable = True,
+ cfg = "exec",
+ ),
+ },
+)
diff --git a/metropolis/node/core/bios_bootcode/genlogo/main.go b/metropolis/node/core/bios_bootcode/genlogo/main.go
new file mode 100644
index 0000000..23686fd
--- /dev/null
+++ b/metropolis/node/core/bios_bootcode/genlogo/main.go
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Monogon Project Authors
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "image/color"
+ "image/png"
+ "log"
+ "os"
+)
+
+func main() {
+ input := flag.String("input", "", "")
+ output := flag.String("output", "", "")
+ flag.Parse()
+
+ if *input == "" || *output == "" {
+ log.Fatal("missing input or output flag")
+ }
+
+ inputFile, err := os.Open(*input)
+ if err != nil {
+ log.Fatal("Error opening image file:", err)
+ return
+ }
+ defer inputFile.Close()
+
+ img, err := png.Decode(inputFile)
+ if err != nil {
+ log.Fatal("Error decoding image:", err)
+ }
+
+ if img.Bounds().Dx() != 80 || img.Bounds().Dy() != 20 {
+ log.Fatal("Image dimensions must be 80x20")
+ }
+
+ var linear []uint8
+ for y := 0; y < img.Bounds().Dy(); y++ {
+ for x := 0; x < img.Bounds().Dx(); x++ {
+ gray := color.GrayModel.Convert(img.At(x, y)).(color.Gray).Y
+ linear = append(linear, gray)
+ }
+ }
+
+ // Perform RLE compression
+ var rle []uint8
+ for len(linear) > 0 {
+ val := linear[0]
+ l := uint8(1)
+ for i := 1; i < len(linear); i++ {
+ if linear[i] != val {
+ break
+ }
+ l++
+ }
+
+ L := l
+ for l > 0 {
+ block := l
+ if block > 127 {
+ block = 127
+ }
+ rle = append(rle, (val<<7)|block)
+ l -= block
+ }
+ linear = linear[L:]
+ }
+
+ rle = append(rle, 0)
+
+ outputFile, err := os.Create(*output)
+ if err != nil {
+ log.Fatalf("failed creating output file: %v", err)
+ }
+ defer outputFile.Close()
+
+ outputFile.WriteString("logo: db ")
+ for i, r := range rle {
+ if i > 0 {
+ outputFile.WriteString(", ")
+ }
+ fmt.Fprintf(outputFile, "0x%02x", r)
+ }
+ outputFile.WriteString("\n")
+}
diff --git a/metropolis/node/core/bios_bootcode/logo.png b/metropolis/node/core/bios_bootcode/logo.png
new file mode 100644
index 0000000..5f395df
--- /dev/null
+++ b/metropolis/node/core/bios_bootcode/logo.png
Binary files differ