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