summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorArseney300 <Arseney300@gmail.com>2026-03-29 02:07:17 +0700
committerArseney300 <Arseney300@gmail.com>2026-04-08 01:19:07 +0700
commit4912796d2e88c6eb5d02fbf0fb9c39f8c9f7cd4c (patch)
tree9ee8110e2c090c888f23797cda56c7e2df531695 /scripts
bastion: initial implementation
ready project skeleton dual-arch build system with Linux-config style configuration UEFI EFI stub loader (PE32+) for x86_64 and AArch64 ELF64 kernel parser Temporary framebuffer console freestanding string and c++ abi stubs For now, kernel boots, prints banner, memory map and go halt
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/configure.py290
-rwxr-xr-xscripts/create_disk.sh88
-rwxr-xr-xscripts/run_qemu.sh110
3 files changed, 488 insertions, 0 deletions
diff --git a/scripts/configure.py b/scripts/configure.py
new file mode 100755
index 0000000..053aac9
--- /dev/null
+++ b/scripts/configure.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python3
+"""
+configure.py — MyOS Kernel Configuration Generator
+
+Reads .config (or a specified config file), validates dependencies,
+and generates:
+ - include/kernel/config.h (C++ preprocessor defines)
+ - config_generated.mk (Makefile variables)
+
+Usage:
+ python3 scripts/configure.py # reads .config
+ python3 scripts/configure.py myconfig # reads myconfig
+ python3 scripts/configure.py --arch x86_64 # set arch, use default config
+ python3 scripts/configure.py --check # validate only, don't generate
+
+The ARCH variable is NOT set in .config — it comes from the build system.
+This script receives it via --arch and injects it into the generated output.
+"""
+
+import sys
+import os
+import argparse
+from pathlib import Path
+from datetime import datetime
+
+# ============================================================================
+# Dependency rules
+#
+# Format: (option, requires, description)
+# If 'option' is 'y', then 'requires' must also be 'y'
+# ============================================================================
+
+DEPENDENCIES = [
+ ("FS_EXT2", "DRIVER_VIRTIO_BLK", "ext2 requires a block device driver"),
+ ("FS_FAT32", "DRIVER_VIRTIO_BLK", "FAT32 requires a block device driver"),
+ ("FEATURE_NETWORK", "DRIVER_VIRTIO_NET", "networking requires a network driver"),
+ ("FEATURE_MODULES", "FS_INITRAMFS", "loadable modules require a filesystem"),
+ ("FEATURE_SMP", None, None), # no deps, just a marker
+]
+
+# ============================================================================
+# Conflict rules
+#
+# Format: (option_a, option_b, description)
+# Both cannot be 'y' at the same time
+# ============================================================================
+
+CONFLICTS = [
+ # None yet — add as needed
+]
+
+# ============================================================================
+# Valid values for non-boolean options
+# ============================================================================
+
+VALID_VALUES = {
+ "PMM_TYPE": ["buddy", "bitmap"],
+ "HEAP_TYPE": ["slab", "simple"],
+}
+
+# ============================================================================
+# Options that must be present (with defaults if missing)
+# ============================================================================
+
+DEFAULTS = {
+ "DEBUG_SERIAL": "n",
+ "DEBUG_VERBOSE_BOOT": "n",
+ "DEBUG_PAGE_ALLOC": "n",
+ "DEBUG_SCHEDULER": "n",
+ "DEBUG_SYSCALL_TRACE": "n",
+ "MAX_CPUS": "4",
+ "KERNEL_STACK_SIZE": "16384",
+ "PMM_TYPE": "buddy",
+ "HEAP_TYPE": "slab",
+ "DRIVER_FBCON": "y",
+ "DRIVER_UART_16550": "n",
+ "DRIVER_PL011_UART": "n",
+ "DRIVER_PS2KBD": "n",
+ "DRIVER_VIRTIO_BLK": "n",
+ "DRIVER_AHCI": "n",
+ "DRIVER_USB": "n",
+ "DRIVER_VIRTIO_NET": "n",
+ "FS_INITRAMFS": "y",
+ "FS_TMPFS": "y",
+ "FS_EXT2": "n",
+ "FS_FAT32": "n",
+ "FBCON_FG_COLOR": "0x00CCCCCC",
+ "FBCON_BG_COLOR": "0x001A1A2E",
+ "FEATURE_SMP": "n",
+ "FEATURE_MODULES": "n",
+ "FEATURE_NETWORK": "n",
+ "FEATURE_POSIX_SIGNALS": "n",
+ "KERNEL_FILE_NAME": "kernel.elf",
+}
+
+
+def parse_config(path: str) -> dict:
+ """Parse a .config file into a dict."""
+ config = {}
+ with open(path) as f:
+ for lineno, line in enumerate(f, 1):
+ line = line.strip()
+ if not line or line.startswith("#"):
+ continue
+ if "=" not in line:
+ print(f"WARNING: {path}:{lineno}: malformed line: {line}",
+ file=sys.stderr)
+ continue
+ key, val = line.split("=", 1)
+ key = key.strip()
+ val = val.strip()
+ # Strip surrounding quotes from string values
+ if len(val) >= 2 and val[0] == '"' and val[-1] == '"':
+ val = val[1:-1]
+ config[key] = val
+ return config
+
+
+def validate(config: dict) -> list:
+ """Validate config. Returns list of error strings (empty = ok)."""
+ errors = []
+
+ # Check dependencies
+ for opt, requires, desc in DEPENDENCIES:
+ if requires is None:
+ continue
+ if config.get(opt) == "y" and config.get(requires) != "y":
+ errors.append(f"{opt}=y requires {requires}=y ({desc})")
+
+ # Check conflicts
+ for opt_a, opt_b, desc in CONFLICTS:
+ if config.get(opt_a) == "y" and config.get(opt_b) == "y":
+ errors.append(f"{opt_a} conflicts with {opt_b} ({desc})")
+
+ # Check valid values
+ for key, valid in VALID_VALUES.items():
+ val = config.get(key)
+ if val is not None and val not in valid:
+ errors.append(f"{key}={val} is invalid. Must be one of: {valid}")
+
+ # Check numeric values
+ for key in ("MAX_CPUS", "KERNEL_STACK_SIZE"):
+ val = config.get(key)
+ if val is not None:
+ try:
+ int(val)
+ except ValueError:
+ errors.append(f"{key}={val} must be a number")
+
+ return errors
+
+
+def generate_config_h(config: dict, arch: str, output: str):
+ """Generate include/kernel/config.h from config dict."""
+ lines = [
+ "#pragma once",
+ "",
+ "// ============================================================================",
+ "// config.h — Auto-generated by scripts/configure.py",
+ f"// Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
+ f"// Architecture: {arch}",
+ "//",
+ "// DO NOT EDIT — edit .config and re-run make instead.",
+ "// ============================================================================",
+ "",
+ ]
+
+ # Group by prefix for readability
+ groups = {}
+ for key, val in sorted(config.items()):
+ prefix = key.split("_")[0]
+ if prefix not in groups:
+ groups[prefix] = []
+ groups[prefix].append((key, val))
+
+ for prefix, items in groups.items():
+ lines.append(f"// ── {prefix} " + "─" * (60 - len(prefix)))
+ for key, val in items:
+ if val == "y":
+ lines.append(f"#define CONFIG_{key} 1")
+ elif val == "n":
+ lines.append(f"/* #undef CONFIG_{key} */")
+ elif val.startswith("0x"):
+ # Hex constant
+ lines.append(f"#define CONFIG_{key} {val}")
+ else:
+ # Try numeric
+ try:
+ int(val)
+ lines.append(f"#define CONFIG_{key} {val}")
+ except ValueError:
+ # String value — quote it for some, raw for others
+ lines.append(f'#define CONFIG_{key} "{val}"')
+ # For enum-like options, also define a variant for preprocessor comparisons
+ if key in VALID_VALUES:
+ lines.append(f'#define CONFIG_{key}_{val.upper()} 1')
+
+ lines.append("")
+
+ os.makedirs(os.path.dirname(output), exist_ok=True)
+ with open(output, "w") as f:
+ f.write("\n".join(lines) + "\n")
+
+
+def generate_config_mk(config: dict, arch: str, output: str):
+ """Generate config_generated.mk from config dict."""
+ lines = [
+ "# ============================================================================",
+ "# config_generated.mk — Auto-generated by scripts/configure.py",
+ f"# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
+ "# DO NOT EDIT — edit .config and re-run make instead.",
+ "# ============================================================================",
+ "",
+ ]
+
+ for key, val in sorted(config.items()):
+ lines.append(f"CONFIG_{key} := {val}")
+
+ with open(output, "w") as f:
+ f.write("\n".join(lines) + "\n")
+
+
+def main():
+ parser = argparse.ArgumentParser(description="MyOS kernel configuration generator")
+ parser.add_argument("config_file", nargs="?", default=".config",
+ help="Path to config file (default: .config)")
+ parser.add_argument("--arch", default=None,
+ help="Target architecture (x86_64 or aarch64)")
+ parser.add_argument("--check", action="store_true",
+ help="Validate only, don't generate files")
+ parser.add_argument("--config-h", default="include/kernel/config.h",
+ help="Output path for config.h")
+ parser.add_argument("--config-mk", default="config_generated.mk",
+ help="Output path for config_generated.mk")
+ args = parser.parse_args()
+
+ # ── Read config ─────────────────────────────────────────────────────
+
+ if not os.path.exists(args.config_file):
+ print(f"ERROR: Config file '{args.config_file}' not found.",
+ file=sys.stderr)
+ print(f" Create one with: cp configs/default_x86_64 .config",
+ file=sys.stderr)
+ sys.exit(1)
+
+ config = parse_config(args.config_file)
+
+ # ── Apply defaults for missing keys ─────────────────────────────────
+
+ for key, default_val in DEFAULTS.items():
+ if key not in config:
+ config[key] = default_val
+
+ # ── Determine architecture ──────────────────────────────────────────
+
+ arch = args.arch or os.environ.get("ARCH", "x86_64")
+
+ # ── Validate ────────────────────────────────────────────────────────
+
+ errors = validate(config)
+ if errors:
+ print("Configuration errors:", file=sys.stderr)
+ for e in errors:
+ print(f" ERROR: {e}", file=sys.stderr)
+ sys.exit(1)
+
+ if args.check:
+ print("Configuration is valid.")
+ sys.exit(0)
+
+ # ── Generate ────────────────────────────────────────────────────────
+
+ generate_config_h(config, arch, args.config_h)
+ generate_config_mk(config, arch, args.config_mk)
+
+ # ── Summary ─────────────────────────────────────────────────────────
+
+ enabled = [k for k, v in config.items() if v == "y"]
+ disabled = [k for k, v in config.items() if v == "n"]
+ other = [k for k, v in config.items() if v not in ("y", "n")]
+
+ print(f"Configuration generated for {arch}:")
+ print(f" {len(enabled)} options enabled, {len(disabled)} disabled, "
+ f"{len(other)} custom values")
+ print(f" → {args.config_h}")
+ print(f" → {args.config_mk}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/create_disk.sh b/scripts/create_disk.sh
new file mode 100755
index 0000000..1e5d1f3
--- /dev/null
+++ b/scripts/create_disk.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+# ============================================================================
+# create_disk.sh — Create a bootable UEFI disk image
+#
+# Usage: create_disk.sh <arch> <efi_binary> <kernel_elf> <output_img>
+#
+# Creates a GPT disk image with a FAT32 EFI System Partition containing:
+# - EFI/BOOT/BOOTx64.EFI (or BOOTAA64.EFI) — our UEFI loader
+# - kernel.elf — the kernel binary
+# ============================================================================
+
+set -e
+
+ARCH="$1"
+EFI_BIN="$2"
+KERNEL_ELF="$3"
+OUTPUT="$4"
+
+if [ -z "$ARCH" ] || [ -z "$EFI_BIN" ] || [ -z "$KERNEL_ELF" ] || [ -z "$OUTPUT" ]; then
+ echo "Usage: $0 <arch> <efi_binary> <kernel_elf> <output_img>"
+ exit 1
+fi
+
+# Determine the default EFI boot path
+if [ "$ARCH" = "x86_64" ]; then
+ EFI_BOOT_NAME="BOOTX64.EFI"
+elif [ "$ARCH" = "aarch64" ]; then
+ EFI_BOOT_NAME="BOOTAA64.EFI"
+else
+ echo "Unknown architecture: $ARCH"
+ exit 1
+fi
+
+echo "Creating disk image for $ARCH..."
+
+# Image size: 64 MiB (more than enough)
+IMG_SIZE=$((64 * 1024 * 1024))
+
+# Create empty image
+dd if=/dev/zero of="$OUTPUT" bs=1M count=64 status=none
+
+# Create GPT partition table with one EFI System Partition
+# Use sgdisk if available, otherwise fall back to parted
+if command -v sgdisk &> /dev/null; then
+ sgdisk --clear \
+ --new=1:2048:131038 --typecode=1:ef00 --change-name=1:"EFI System" \
+ "$OUTPUT" > /dev/null 2>&1
+elif command -v parted &> /dev/null; then
+ parted -s "$OUTPUT" mklabel gpt
+ parted -s "$OUTPUT" mkpart "EFI System" fat32 1MiB 63MiB
+ parted -s "$OUTPUT" set 1 esp on
+else
+ echo "ERROR: Need sgdisk or parted to create GPT partition"
+ exit 1
+fi
+
+# Format the ESP partition as FAT32
+# Extract partition offset (sector 2048 * 512 = 1 MiB)
+PART_OFFSET=$((2048 * 512))
+PART_SIZE=$(((131038 - 2048 + 1) * 512))
+
+# Create a separate FAT32 filesystem image
+FAT_IMG="${OUTPUT}.fat"
+dd if=/dev/zero of="$FAT_IMG" bs=512 count=$((PART_SIZE / 512)) status=none
+mkfs.vfat -F 32 "$FAT_IMG" > /dev/null 2>&1
+
+# Copy files into the FAT filesystem using mtools
+# Set up mtools config for this image
+export MTOOLSRC="$(mktemp)"
+echo "drive c: file=\"$FAT_IMG\" offset=0" > "$MTOOLSRC"
+
+# Create EFI boot directory and copy files
+mmd -i "$FAT_IMG" ::EFI
+mmd -i "$FAT_IMG" ::EFI/BOOT
+mcopy -i "$FAT_IMG" "$EFI_BIN" "::EFI/BOOT/$EFI_BOOT_NAME"
+mcopy -i "$FAT_IMG" "$KERNEL_ELF" "::kernel.elf"
+
+# List contents for verification
+echo "ESP contents:"
+mdir -i "$FAT_IMG" ::/ -/ 2>/dev/null || true
+
+# Write the FAT image into the partition slot
+dd if="$FAT_IMG" of="$OUTPUT" bs=512 seek=2048 conv=notrunc status=none
+
+# Clean up
+rm -f "$FAT_IMG" "$MTOOLSRC"
+
+echo "Disk image created: $OUTPUT"
diff --git a/scripts/run_qemu.sh b/scripts/run_qemu.sh
new file mode 100755
index 0000000..f48f445
--- /dev/null
+++ b/scripts/run_qemu.sh
@@ -0,0 +1,110 @@
+#!/bin/bash
+# ============================================================================
+# run_qemu.sh — Launch QEMU with UEFI firmware
+#
+# Usage: run_qemu.sh <arch> <disk_image>
+#
+# Requirements:
+# x86_64: OVMF firmware (usually in /usr/share/OVMF/ or /usr/share/edk2/)
+# aarch64: AAVMF firmware (usually in /usr/share/AAVMF/ or qemu-efi-aarch64)
+# ============================================================================
+
+set -e
+
+ARCH="$1"
+DISK="$2"
+
+if [ -z "$ARCH" ] || [ -z "$DISK" ]; then
+ echo "Usage: $0 <arch> <disk_image>"
+ exit 1
+fi
+
+# ── Find UEFI firmware ─────────────────────────────────────────────────────
+
+find_firmware() {
+ local candidates=("$@")
+ for path in "${candidates[@]}"; do
+ if [ -f "$path" ]; then
+ echo "$path"
+ return 0
+ fi
+ done
+ return 1
+}
+
+if [ "$ARCH" = "x86_64" ]; then
+ QEMU=qemu-system-x86_64
+
+ # Common OVMF paths across distros
+ OVMF=$(find_firmware \
+ /usr/share/OVMF/OVMF_CODE.fd \
+ /usr/share/edk2/ovmf/OVMF_CODE.fd \
+ /usr/share/edk2-ovmf/x64/OVMF_CODE.fd \
+ /usr/share/qemu/OVMF_CODE.fd \
+ /usr/share/OVMF/OVMF_CODE_4M.fd \
+ ) || {
+ echo "ERROR: Cannot find OVMF firmware."
+ echo "Install it: apt install ovmf OR dnf install edk2-ovmf"
+ exit 1
+ }
+
+ echo "Using OVMF: $OVMF"
+ echo "Starting QEMU x86_64..."
+ echo "Press Ctrl+A, X to exit QEMU"
+ echo "─────────────────────────────────────"
+
+ exec $QEMU \
+ -machine q35 \
+ -cpu qemu64 \
+ -m 256M \
+ -drive if=pflash,format=raw,readonly=on,file="$OVMF" \
+ -drive format=raw,file="$DISK" \
+ -serial stdio \
+ -no-reboot \
+ -no-shutdown \
+ -d int,cpu_reset \
+ -D qemu_log.txt
+
+elif [ "$ARCH" = "aarch64" ]; then
+ QEMU=qemu-system-aarch64
+
+ # Common AAVMF / EDK2 paths
+ AAVMF=$(find_firmware \
+ /usr/share/AAVMF/AAVMF_CODE.fd \
+ /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \
+ /usr/share/edk2/aarch64/QEMU_EFI.fd \
+ /usr/share/edk2-aarch64/QEMU_EFI.fd \
+ ) || {
+ echo "ERROR: Cannot find AAVMF firmware."
+ echo "Install it: apt install qemu-efi-aarch64 OR dnf install edk2-aarch64"
+ exit 1
+ }
+
+ echo "Using AAVMF: $AAVMF"
+ echo "Starting QEMU aarch64..."
+ echo "Press Ctrl+A, X to exit QEMU"
+ echo "─────────────────────────────────────"
+
+ # Create a pflash variable store (AAVMF needs separate code + vars)
+ VARS_FILE="${DISK}.vars.fd"
+ if [ ! -f "$VARS_FILE" ]; then
+ dd if=/dev/zero of="$VARS_FILE" bs=1M count=64 status=none
+ fi
+
+ exec $QEMU \
+ -machine virt \
+ -cpu cortex-a72 \
+ -m 256M \
+ -drive if=pflash,format=raw,readonly=on,file="$AAVMF" \
+ -drive if=pflash,format=raw,file="$VARS_FILE" \
+ -drive format=raw,file="$DISK" \
+ -serial stdio \
+ -no-reboot \
+ -no-shutdown \
+ -d int,cpu_reset \
+ -D qemu_log.txt
+
+else
+ echo "Unknown architecture: $ARCH"
+ exit 1
+fi