ZXFL Barebones

From OSDev Wiki
Jump to navigation Jump to search

WAIT! Have you read Getting Started, Beginner Mistakes, and some of the related OS theory?

Difficulty level
Difficulty 2.png
Medium

About

A minimal kernel targeting IBM z/Architecture (s390x), using ZXFoundationLoader (ZXFL) dual-stage loader.

Note: This is a minimal barebones intended to get you to a booting kernel as quickly as possible. It deliberately omits explanation of z/Architecture concepts (IPL, DASD, PSW, boot protocol internals, etc.). See the IBM Principle Of Operations or ZXFoundation API documentation for a thorough treatment of all of these.

Why ZXFoundationLoader?

Realistically, this is one of the few and only hobbyist IPL loader for the s390x architecture. There are some existing projects like FLATBOOT here are its limitation:

  • Adapts (and awkwardly implements) x86 protocol stivale2 on a mainframe architecture
  • Full EBCDIC end to end (which not all compiler installation supports)
  • Doesn't even support ELF64, 32-bit ELFs only
  • Abandoned/Inactive (?)


Prerequisites

  • s390x-ibm-linux-gnu-gcc cross-compiler (or any s390x-*-gcc)
  • s390x-ibm-linux-gnu-ld
  • dasdload (Hercules tools)
  • ZXFoundation binaries: core.zxfoundationloader00.sys, core.zxfoundationloader01.sys and zxsign

Compiling ZXFoundation

git clone https://github.com/assembler-0/ZXFoundation.git
cd ZXFoundation
mkdir build && cd build
cmake .. -DZX_TOOLCHAIN_FILE=../cmake/toolchain/zxfoundation-gcc.cmake -DZX_CONFIG_FILE=../cmake/configs/base.cmake -GNinja
ninja
ls -l core.zxfoundationloader00.sys core.zxfoundationloader01.sys zxsign
# now, you can copy the listed files to the barebones sources (i.e. ZXFoundation/docs/barebones/)

Source Files

  • main.c - kernel main entry point
#include <stdint.h>
#include "zxfl.h"

[[noreturn]] void zxfoundation_global_initialize(zxfl_boot_protocol_t *boot) {
    while (1) __asm__ volatile("nop");
}
  • head64.S - assembly stub
// arch/s390x/init/head64.S (ZXFoundation)

#include <arch/s390x/init/zxfl/zxvl.h>
#include <arch/s390x/init/zxfl/zxfl.h>

#define KERNEL_BOOT_STACK_SIZE 0x4000

    .section .text.handshake, "ax"
    .globl __zxfl_handshake_stub
__zxfl_handshake_stub:
    llihf   %r0, ZXVL_SEED_HI
    iilf    %r0, ZXVL_SEED_LO
    xgr     %r2, %r0
    lgr     %r0, %r2
    sllg    %r0, %r0, 17
    srlg    %r1, %r2, 47
    ogr     %r0, %r1
    llihf   %r1, ZXVL_HS_RESPONSE_HI
    iilf    %r1, ZXVL_HS_RESPONSE_LO
    lgr     %r2, %r0
    agr     %r2, %r1
    br      %r14

    .section .text.__zx_start_kernel64, "ax", @progbits
    .globl __zx_start_kernel64
__zx_start_kernel64:
    lgr     %r11, %r2

    larl    %r2, __bss_start
    larl    %r3, __bss_end
    sgr     %r3, %r2                       // R3 = BSS size in bytes
    jz      .Lbss_done
    lgr     %r4, %r2                       // R4 = source (same as dest)
    sgr     %r5, %r5                       // R5 = 0 (source length 0 + pad byte 0x00)
.Lmvcl_retry:
    mvcl    %r2, %r4
    jo      .Lmvcl_retry
.Lbss_done:

    lgr     %r2, %r11
    larl    %r15, __kernel_boot_stack_top
    aghi    %r15, -160                     // 160-byte register save area

    // Enable AFP (bit 45) and Vector (bit 46) for BSP before entering C++ (you can remove this if you dont need these features. though compilers may assume this feature)
    stctg   %c0, %c0, 0(%r15)
    lg      %r1, 0(%r15)
    iilf    %r0, 0x00060000
    ogr     %r1, %r0
    stg     %r1, 0(%r15)
    lctlg   %c0, %c0, 0(%r15)

    // Zero out backchain and r14 save area to terminate unwinder gracefully
    sgr     %r0, %r0
    stg     %r0, 0(%r15)
    stg     %r0, 112(%r15)

    lgr     %r2, %r11
    brasl   %r14, zxfoundation_global_initialize

.Lhalt:
    larl    %r1, .Ldisabled_wait_psw
    lpswe   0(%r1)
    j       .Lhalt

    .align 8
.Ldisabled_wait_psw:
    .quad   PSW_MASK_DISABLED_WAIT, 0x0000000000000000

    // BSP boot stack: kernel-owned, in .bss, zeroed by the entry path above.
    .section .bss.boot_stack, "aw", @nobits
    .align 16
    .globl __kernel_boot_stack
__kernel_boot_stack:
    .skip   KERNEL_BOOT_STACK_SIZE
    .globl __kernel_boot_stack_top
__kernel_boot_stack_top:

    .section .note.GNU-stack, "", @progbits
  • link.ld - linker script
/* arch/s390x/init/link.ld — ZXFoundation Kernel Linker Script */

ENTRY(__zx_start_kernel64)

PHDRS {
    hs_seg        PT_LOAD FLAGS(0x00400005);  /* ZXVL_PFLAGS_HS:    PF_X|PF_R + bit22 */
    entry_seg     PT_LOAD FLAGS(0x00800005);  /* ZXVL_PFLAGS_ENTRY: PF_X|PF_R + bit23 */
    nucleus_text  PT_LOAD FLAGS(0x00000005);  /* PF_X|PF_R  — kernel .text             */
    nucleus_ro    PT_LOAD FLAGS(0x00000004);  /* PF_R       — kernel .rodata            */
    nucleus_data  PT_LOAD FLAGS(0x00000006);  /* PF_R|PF_W  — kernel .data             */
    nucleus_bss   PT_LOAD FLAGS(0x00000006);  /* PF_R|PF_W  — kernel .bss              */
    lock_seg      PT_LOAD FLAGS(0x00100006);  /* ZXVL_PFLAGS_LOCK:  PF_R|PF_W + bit20 */
    checksums_seg PT_LOAD FLAGS(0x00200004);  /* ZXVL_PFLAGS_CKSUM: PF_R + bit21      */
}

nucleus_vma_base = 0xFFFF800000100000;

SECTIONS {
    . = nucleus_vma_base;
    __zx_vma_start = .;

    .text.handshake : ALIGN(8) {
        KEEP(*(.text.handshake))
    } :hs_seg

    .text.entry : ALIGN(8) {
        KEEP(*(.text.__zx_start_kernel64))
    } :entry_seg

    .text : ALIGN(8) {
        *(.text .text.*)
    } :nucleus_text

    .rodata : ALIGN(4096) {
        __rodata_start = .;
        *(.rodata .rodata.*)
        __rodata_end = .;
    } :nucleus_ro

    .data : ALIGN(4096) {
        __data_start = .;
        *(.data .data.*)
        __data_end = .;
    } :nucleus_data

    .bss : ALIGN(4096) {
        __bss_start = .;
        *(COMMON)
        *(.bss .bss.*)
        __bss_end = .;
    } :nucleus_bss

    .zxfl_lock : ALIGN(4096) {
        LONG(0xCCBBCC35)
        LONG(0x5A58464C)
        . += 0x1000 - 8;
        LONG(0xE5664311)
    } :lock_seg

    .zxvl_checksums : ALIGN(4096) {
        KEEP(*(.zxvl_checksums))
    } :checksums_seg

    __zx_vma_end = .;

    /DISCARD/ : { *(.comment) *(.eh_frame) *(.note.GNU-stack) }
}
  • zxvl_cksum.c - SHA256 kernel checksum
#include "zxvl.h"

__attribute__((section(".zxvl_checksums"), used))
zxvl_checksum_table_t zxvl_kernel_checksum_table = { 0 };
  • sysres.conf - dasdload control file
sysres 3390-2 * core.zxfoundationloader00.sys
core.zxfoundationloader01.sys SEQ core.zxfoundationloader01.sys TRK 5 0 0 PS FB 4096 4096 0
core.zxfoundation.nucleus SEQ core.zxfoundation.nucleus TRK 300 0 0 PS FB 4096 4096 0
etc.zxfoundation.parm SEQ etc.zxfoundation.parm TRK 2 0 0 PS FB 4096 4096 0
  • hercules.conf - hercules script
ARCHLVL      z/Arch
CPUSERIAL    000666
CPUMODEL     $(z16)
LPARNUM      1
LPARNAME     ZXF
PLANT        ZZ
MANUFACTURER IBM
MAINSIZE     512M
XPNDSIZE     0
NUMCPU       4
MAXCPU       8
DIAG8CMD     ENABLE
CNSLPORT     3270
0009         3215-C  /
001C         SYSG
001F         3270

0100        3390    sysres.3390

IPL 0100
  • etc.zxfoundation.parm - parameter file (cmdline)
syssize=512M sysmodule=

Using with C++ (modules)

ZXFL is capable of loading a C++ kernel (like most other loader). With traditional C++, ZXFL protocol (the C header) can easily be adapted. For C++ modules, the protocol module is not entirely freestanding-compatible, and depends on several simple dependencies:

  • ZXFoundation fundamental types (rust-like) (can be trivially replaced by standard C/C++ types)
  • STFLE_MAX_DWORDS (just a number)
  • CONFIG_ZX_MAX_CPUS (your configuration)
  • SHA256_DIGEST_SIZE (also a number)
  • string_view-like implementation (can be swapped out for const char*)

Though, is possible to adapt/hard-code for your kernel.

Environment and Compilation (from scratch)

# navigate to your source dir
# copy your ZXFoundation binaries here

# get header files
wget https://raw.githubusercontent.com/assembler-0/ZXFoundation/refs/heads/master/arch/s390x/init/zxfl/include/arch/s390x/init/zxfl/zxfl.h
wget https://raw.githubusercontent.com/assembler-0/ZXFoundation/refs/heads/master/arch/s390x/init/zxfl/include/arch/s390x/init/zxfl/zxvl.h

# make sure your sources are here

export CFLAGS="-ffreestanding -nostdlib -msoft-float -mno-vx -m64 -march=z13 -mtune=z13 \
        -pipe -fno-stack-protector -std=c23 -mzarch -static-libgcc -O2 -g0 -I."
export LDFLAGS="-nostdlib -static --no-dynamic-linker -ztext -zmax-page-size=0x1000 \
         --no-pie -g -melf64_s390"

# It is possible to use clang with target triple s390x-{unknown-none,linux-gnu}-elf
s390x-ibm-linux-gnu-gcc $CFLAGS -c head64.S     -o head64.o
s390x-ibm-linux-gnu-gcc $CFLAGS -c main.c       -o main.o
s390x-ibm-linux-gnu-gcc $CFLAGS -c zxvl_cksum.c -o zxvl_cksum.o
s390x-ibm-linux-gnu-ld  -T link.ld $LDFLAGS head64.o main.o zxvl_cksum.o \
        -o core.zxfoundation.nucleus

# Embed checksums required by the ZXFL ZXVerifiedLoad protocol
./zxsign core.zxfoundation.nucleus

# Build the DASD image
dasdload -z sysres.conf sysres.3390

The output is sysres.3390, a CKD 3390 disk image bootable under Hercules.

Running

$ hercules -f hercules.cnf

Further Reading

For loader internals, boot protocol layout, memory map, and kernel subsystems see the full documentation.