diff options
| author | Arseney300 <Arseney300@gmail.com> | 2026-03-29 02:07:17 +0700 |
|---|---|---|
| committer | Arseney300 <Arseney300@gmail.com> | 2026-04-08 01:19:07 +0700 |
| commit | 4912796d2e88c6eb5d02fbf0fb9c39f8c9f7cd4c (patch) | |
| tree | 9ee8110e2c090c888f23797cda56c7e2df531695 /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-x | scripts/configure.py | 290 | ||||
| -rwxr-xr-x | scripts/create_disk.sh | 88 | ||||
| -rwxr-xr-x | scripts/run_qemu.sh | 110 |
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 |
