Note: File paths in this document may be outdated after the source tree reorganization. See Source Tree Structure for the current layout.
Porting PPAP to the Sharp X68000 (MC68000 @ 10 MHz). The QEMU qemu_m68k
target already proves the m68k kernel works; this document describes the
X68000-specific boot sequence, ELF loading, and process model as implemented.
Produce a bootable PPAP system on the Sharp X68000 that:
- Boots from a 5.25" floppy disk (1.2 MB) with a two-stage bootstrap.
- Provides a console on the built-in TVRAM display via IOCS calls, mirrored to the RS-232C serial port.
- Mounts a UFS root filesystem from a RAM image loaded by stage2.
- Runs the PPAP userland test suite (
runtests) and passes it. - Executes Human68k
.xand.rbinaries natively (no CPU emulation). - Runs an interactive BusyBox shell on XEiJ emulator.
- Boot from SCSI HDD (larger capacity, faster iteration).
- Replace IOCS console with a direct TVRAM driver for full tty semantics.
- SRAM real-time clock persistence.
- Reuse community-written X68000 device drivers.
- Graphics, VRAM, or sprite-layer drivers.
- Sound (YM2151, ADPCM).
- X68030 MMU or 68030-specific instructions.
- Network drivers.
| Component | File(s) | Reuse |
|---|---|---|
| m68k kernel core | src/kernel/ |
100% |
| 68000 vector table and reset handler | src/arch/m68k/boot.S |
100% |
| Context switch (TRAP #1 + timer ISR) | src/arch/m68k/switch.S |
100% |
| TRAP #0 syscall handler | src/arch/m68k/trap.S |
100% |
| F-line (Human68k DOS) and TRAP #15 handlers | src/arch/m68k/boot.S |
See 4.3 |
| RAM size detection | src/arch/m68k/probe_ram.S |
100% (set RAM_END=0xC00000) |
| 32-bit multiply/divide (no libgcc) | src/arch/m68k/math.S |
100% |
| Human68k subsystem bridge | src/kernel/core/subsys/human68k/human68k_bridge.c |
100% |
| X-format / R-format binary loader | src/kernel/core/subsys/human68k/x_loader.c |
100% |
| UFS filesystem driver | src/kernel/fs/ufs.c |
100% |
| VFS, tmpfs, procfs, devfs | src/kernel/fs/, src/kernel/vfs/ |
100% |
| m68k user-space toolchain | ppap/m68k Docker image |
100% |
| Item | Value |
|---|---|
| CPU | MC68000 @ 10 MHz |
| Main RAM | 1 MB (minimum) -- 0x000000-0x0FFFFF |
| Extended RAM | Up to 11 MB optional -- 0x100000-0xBFFFFF |
| GVRAM | 1 MB graphics -- 0xC00000-0xCFFFFF |
| TVRAM | 512 KB text -- 0xE00000-0xE7FFFF |
| Sprite / PCG | 0xEB0000-0xEBFFFF |
| SRAM | 16 KB battery-backed -- 0xED0000-0xED3FFF |
| IPL ROM | 128 KB -- 0xFE0000-0xFFFFFF |
probe_ram must be bounded at RAM_END=0xC00000 to avoid clobbering
GVRAM (no bus error; reads return pixel data, writes produce artefacts).
SRAM at 0xED0000 is battery-backed and user-visible. The kernel and stage bootstrap must not write to SRAM under any circumstances.
| Address | Peripheral | PPAP Use |
|---|---|---|
| 0xE88001 | MFP (MC68901) -- odd bytes, 2B stride | UART console + timer |
| 0xE8A001 | RTC (Ricoh RP5C15) -- odd bytes | SRAM clock (extended) |
| 0xE94000 | FDC (uPD765A) | Floppy block driver |
| 0xE96020 | SCSI (MB89352A) | Extended goal |
| 0xE9A000 | PPI (uPD8255) | Keyboard (extended) |
| 0xFE0000 | IPL ROM | IOCS calls throughout boot |
All I/O registers are 8-bit, at odd addresses of a word-width bus.
Always use byte (.b) operations; a word access aliases two registers.
The IPL ROM code at 0xFE0000-0xFFFFFF remains mapped for the entire lifetime of the machine -- it is not overlaid after reset. The IPL ROM sets up the vector table at 0x000000 in RAM with TRAP #15 (vector 47) pointing to the IOCS handler inside the ROM.
As long as PPAP does not overwrite vector 47, IOCS is always callable
via trap #15.
CRITICAL: IOCS calls are non-reentrant. Functions like _B_PUTC
internally lower the CPU IPL to allow VSYNC synchronisation. If the MFP
Timer-C ISR fires during an IOCS call and re-enters IOCS (e.g., via the
scheduler calling a console function), the shared IOCS work area at
0x000400-0x0007FF is corrupted, causing an address error crash at ROM
0x00FF775E.
Mitigation: All IOCS calls in uart_x68k.c are wrapped with
ipl7_save()/ipl7_restore() to raise IPL to 7 before entry, preventing
timer ISR preemption. The scheduler's input poll callback uses
uart_rx_avail_hw() which reads the MFP USART RSR register directly
(0xE8802B bit 7) instead of calling _B_KEYSNS.
IOCS calls used by PPAP kernel:
| IOCS function | d0 value | Purpose |
|---|---|---|
_B_PUTC |
0x20 | Write one character (TTY + serial) |
_B_GETC |
0x21 | Read one character (keyboard/serial) |
_B_LOCATE |
0x17 | Set TVRAM cursor position |
_B_KEYSNS |
0x1C | Keyboard status (non-blocking) |
_B_CLR_ST |
0x22 | Clear entire screen |
_B_CLR_ED |
0x23 | Clear from cursor to end of screen |
_B_CLR_AL |
0x19 | Clear current line from cursor |
_B_READ |
0x46 | Read floppy sectors (CHS addressing) |
_OUT232C |
0x35 | Write one character to RS-232C serial |
Base: 0xE88000; register N at 0xE88000 + 1 + 2*N.
| N | Register | Purpose |
|---|---|---|
| 3 | IERA | Interrupt enable A |
| 4 | IERB | Interrupt enable B |
| 11 | VR | Vector base register (IPL ROM sets to 0x40) |
| 12 | TACR | Timer A control |
| 14 | TCDCR | Timers C and D control |
| 17 | TCDR | Timer C count |
| 20 | UCR | USART control |
| 21 | RSR | Receiver status |
| 22 | TSR | Transmitter status |
| 23 | UDR | USART data |
MFP clock input on X68000: 4 MHz.
TCDCR bits [6:4] = 0b111 -> prescaler /200
TCDR = 200 -> 4 000 000 / 200 / 200 = 100 Hz
MFP vectored interrupt mapping:
| Channel | Function | Vector = VR_base + channel |
|---|---|---|
| 5 | Timer-C | 0x40 + 5 = 69 (0x45) -- used for scheduler tick |
| 10 | USART RX Buffer Full | 0x40 + 10 = 74 (0x4A) -- keyboard input |
The MFP USART serves dual purpose on the X68000:
- RS-232C serial port (directly via
_OUT232Cfor output) - Keyboard input via the USART RX interrupt (vector 74)
The IPL ROM's keyboard interrupt handler at vector 74 reads scan codes from the USART and fills the IOCS keyboard buffer. This vector must be preserved -- silencing it disables keyboard input entirely.
On power-on the CPU reads from the ROM overlay mapped at 0x000000. The IPL:
- Initialises hardware and sets up the RAM vector table at 0x000000
(including
TRAP #15-> IOCS handler in ROM at 0xFExxxx). - Reads the first 1 KB of floppy track 0 (sector 0) into 0x002000.
- Unmaps the ROM overlay (RAM now visible at 0x000000; ROM remains at 0xFE0000).
- Jumps to 0x002000 (our Stage1).
At the moment Stage1 starts, the RAM vector table is fully functional and IOCS is available.
Location: Sector 0 of floppy, loaded by IPL ROM to 0x002000. Size limit: 1024 bytes (one floppy sector).
Stage1 contains a BPB (BIOS Parameter Block) at offset 2-35 for floppy format identification by emulators and Gotek drives.
Stage1 execution flow:
1. Set sp = 0x002000 (pre-decrement grows into free RAM 0x000400-0x001FFF)
2. Save IOCS vector: d5 = *(uint32_t *)0x0000BC (vector 47 = TRAP #15)
3. Diagnostic: IOCS _B_PUTC "Pi" to TVRAM
4. Read loop: IOCS _B_READ sectors 1-3 -> 0x003000 (Stage2)
- LBA-to-CHS conversion: cyl = lsec/16, head = (lsec%16)/8, sec = (lsec%16)%8+1
- Uses d4-d7/a3 (preserved across _B_READ)
5. Store IOCS vector: *(uint32_t *)0x002FF0 = d5 (handoff to stage2)
6. jmp 0x003000
The temporary stack grows downward from 0x002000 into the free RAM region
0x000400-0x001FFF. Interrupts must remain enabled -- _B_READ waits
for FDC interrupts internally.
On read error, Stage1 prints "E" followed by the hex FDC status code and
halts (stop #0x2700).
Location: Sectors 1-3 of floppy, loaded by Stage1 to 0x003000.
Size limit: 3072 bytes (three floppy sectors).
Language: C (compiled with -m68000 -nostdlib -ffreestanding).
Stage2 implements a minimal UFS parser sufficient to load two files from the floppy's UFS partition.
$000000-$0003FF CPU exception vectors
$000400-$0007FF IOCS work area (cursor, screen config, font data ...)
$000800-$000FFF OPM (YM2151) driver work area
$001000-$001FFF FDC / SCSI / DMA driver work area
Stage2 uses 0x003C00 (immediately after its own code) as its 4 KB scratch buffer, safely above all system work areas.
Stage2 execution flow:
1. Read UFS superblock (block 0 -> BUF at 0x003C00)
2. Diagnostic: IOCS _B_PUTC "PA" (after first _B_READ, not before)
3. Validate UFS magic (0x55465331)
4. Read inode table block -> BUF; extract root inode (inode 1)
5. Walk root directory -> find "boot" directory inode
6. Walk boot/ directory -> find "kernel" and "rootfs.ufs" inodes
7. Load kernel to 0x006000 (KERNEL_LOAD_ADDR)
- Direct blocks (up to 10 x 4 KB = 40 KB)
- Single indirect block (for files > 40 KB)
8. Load rootfs.ufs to ROOTFS_BASE (= __page_pool_start from kernel ELF)
9. Write handoff record:
- 0x002FF4: 'RAMD' magic (0x52414D44)
- 0x002FF8: rootfs RAM address
- 0x002FFC: rootfs size in bytes
10. stage2_final():
a. Raise IPL to 7 (mask all interrupts)
b. Save IPL ROM handlers:
- iocs_handler = *(uint32_t *)0x002FF0 (saved by stage1)
- kbd_handler = vector[74] (MFP USART RX = keyboard)
c. Copy 256 longwords from 0x006000 to 0x000000 (kernel vector table)
d. Restore preserved vectors:
- vector[47] = iocs_handler (TRAP #15: IOCS dispatch)
- vector[74] = kbd_handler (MFP ch.10: USART RX buffer full)
e. jmp 0x006400 (Reset_Handler)
The diagnostic banner is split across the boot chain: Stage1 prints "Pi", Stage2 prints "PA", and the kernel prints "Po" + " booting..." to spell "PiPAPo booting...".
mkx68kimg.sh reads __page_pool_start from the kernel ELF and passes
it via -DROOTFS_BASE=0x...u when compiling stage2. This ensures the
rootfs image lands above the kernel BSS (which Reset_Handler zeros at
boot before the kernel can read the rootfs).
The IOCS _B_READ handler uses the supervisor stack extensively for FDC
wait loops and interrupt handling. If the ufs_inode_t struct lives on
the same stack, IOCS can corrupt it. load_file() copies all needed
inode fields into local variables before any floppy reads to avoid
reading from corrupted memory.
Stage2 performs a selective vector table replacement:
- Save vector 47 (TRAP #15 / IOCS) from stage1's handoff at 0x002FF0.
- Save vector 74 (MFP USART RX / keyboard) from the live vector table.
- Copy all 256 entries (1 KB) from the kernel binary at 0x006000 to the RAM vector table at 0x000000.
- Restore vector 47 (IOCS dispatch) and vector 74 (keyboard input).
After this step, all PPAP exception handlers are live while IOCS and keyboard input remain functional.
The kernel's target_early_init() in target_x68k.c performs additional
patching:
- Autovectors 25-30 (OPM, MFP, reserved, SCC/VSYNC, FDC, DMA) are
set to
m68k_irq_ignore(barerte) to prevent halts from Default_Handler. - MFP vectors 64-79 are set to
m68k_irq_ignore, except vector 74 (keyboard) which is explicitly skipped to preserve the IPL handler. - Vector 69 (Timer-C) is later overwritten by
timer_init()withm68k_timer_isrfor the scheduler tick.
After stage2 jumps to 0x006400, the standard m68k Reset_Handler in
boot.S executes:
- Set SSP =
__stack_top(0x006400, grows down into freed boot area). - Copy
.data(no-op:__data_load == __data_startsince all RAM). - Zero
.bss(__bss_startto__bss_end). - Jump to
kmain().
kmain() calls target_early_init() which patches interrupt vectors and
prints the banner, then proceeds with MM init, VFS setup, and process
creation.
The X68000 target links the kernel at 0x006000 (linker script:
src/target/x68k/x68k.ld).
Address Range Size Contents
0x000000-0x0003FF 1 KB Active vector table (copied by stage2)
0x000400-0x005FFF 22.75 KB Freed boot area -> page pool
0x006000-0x0063FF 1 KB Kernel .vectors (reused as SSP headroom)
0x006400-~0x02xxxx ~120 KB Kernel .text + .rodata + .data + .bss
~0x02xxxx (aligned) __page_pool_start (rootfs.ufs loaded here)
~0x02xxxx+rootfs ... Page pool start (after rootfs reservation)
... ... Free pages for user processes
0x0FFFFF ... End of 1 MB main RAM
0x100000-0xBFFFFF (optional) Extended RAM (up to 11 MB)
The linker script sets __stack_top = 0x006400. The kernel SSP starts
there and grows downward through the .vectors region (0x006000-0x0063FF,
which was already copied to 0x000000 and is no longer needed) and into
the freed boot area (0x000400-0x005FFF), giving approximately 23.75 KB
of supervisor stack space -- far more than needed.
Stage2 loads boot/rootfs.ufs to __page_pool_start. The kernel
validates the UFS magic at that address and computes the size from the
superblock (block_count * block_size). Pages underlying the rootfs
image are reserved via page_alloc_at() so the allocator never hands
them out. The rootfs is mounted read-only as / via flatblk.
The floppy is formatted as a two-level UFS image built by mkx68kimg.sh:
Offset Size Contents
0 KB 1 KB Boot sector (Stage1 -- IPL ROM loads to 0x002000)
1 KB 3 KB Stage2 UFS loader (loaded by Stage1 to 0x003000)
4 KB ... Outer UFS filesystem (contains boot/ and romfs files)
The outer UFS contains:
boot/kernel-- flat binary kernel imageboot/rootfs.ufs-- inner UFS image (the actual root filesystem)
The inner UFS image is built by mkufs with BIG_ENDIAN mode (m68k is
big-endian). It contains the complete root filesystem:
/sbin/init First userland process
/bin/sh -> busybox Shell (symlink to BusyBox)
/bin/busybox BusyBox multi-call binary
/bin/runtests On-target test runner
/bin/test_* Test binaries
/etc/profile Shell profile (TERM=dumb, PS1, PATH)
mkufs pre-swaps i_direct[] bytes for big-endian mode so that
write_inode()'s w32() cancels out, preserving raw symlink target
bytes on disk without byte-order corruption.
ppap_generate_romfs(ppap_x68k BIG_ENDIAN
OVERLAY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/romfs
EXCLUDE_APPS hello trace pdb)The EXCLUDE_APPS list keeps the image small for floppy capacity.
The OVERLAY_DIR adds x68k-specific files like /etc/profile.
X68000 2HD format: 77 tracks x 2 heads x 8 sectors x 1024 bytes
= 1,232 KB total. The .xdf image must declare this geometry
for emulators and Gotek drives to accept it.
On the X68000 target, user binaries reside in a UFS filesystem (either
the rootfs RAM image or a floppy UFS). Unlike ARM targets where .text
can execute in place from flash (XIP), all m68k binaries are loaded
entirely into RAM before execution.
The loader (src/kernel/exec/exec.c) handles this via a unified path:
if (vn->xip_addr == NULL) {
// Non-XIP: allocate contiguous pages, read entire file to buffer
file_buf = alloc_contiguous(file_pages);
vn->mount->ops->read(vn, file_buf, file_size, 0);
// ... format detection (Human68k .x, .r, CP/M .com, ELF) ...
elf_buf = file_buf; // text runs in-place from these RAM pages
}
For a native m68k ELF binary from a non-XIP filesystem:
-
Read entire file into contiguous RAM pages allocated from the page pool. These pages are not freed -- the
.textsegment runs in-place from them, exactly as romfs XIP works (both are in RAM on m68k). -
Validate ELF header (
elf_validate): checks EI_MAG, EI_CLASS=32, EI_DATA=big-endian, e_machine=EM_68K. -
Extract PT_LOAD segments (
elf_load_segments): up to 4 segments. Identifies text (PF_X) and data (PF_W) segments. -
Compute text base:
text_base = file_buf + text_seg->p_offset(physical RAM address where text was loaded). -
Compute entry point:
entry = text_base + e_entry - text_seg->p_vaddr(relocates the ELF virtual address to the actual RAM location). -
Allocate data pages (if data segment exists):
data_pages = ceil(p_memsz / PAGE_SIZE)sram_page = alloc_contiguous(data_pages)- Copy initialised data from file buffer
- Zero BSS region (
p_memsz - p_filesz)
-
Relocate GOT (Global Offset Table):
- Find
.gotsection viaelf_find_got() - GOT entries pointing into text (below
data_seg->p_vaddr) are relocated by addingtext_base - GOT entries pointing into data (>=
data_seg->p_vaddr) are relocated tosram_page + offset - With
-msep-data, the a5 register holds the GOT base address (set by the kernel, not recalculated by the binary)
- Find
-
Apply relocations (
apply_relocations):- Processes
.rel.dyn/.rela.dynsections - Handles
R_68K_RELATIVE(data segment only -- no text relocs) - Handles
R_68K_JMP_SLOT(PLT GOT entries for dynamic calls) - Skips entries already handled by GOT relocation
- Processes
The m68k toolchain with -msep-data -fPIC generates position-independent
code that accesses all global data through the GOT via register a5.
Text and data locations are fully independent -- there is no PC-relative
GOT lookup as on ARM.
The kernel sets a5 (slot 13 in the software register frame on the SSP)
during execve:
if (got_sram_addr) {
uint32_t *sw = (uint32_t *)(uintptr_t)p->sp;
sw[13] = got_sram_addr; // a5 in the register frame
}
p->got_base = got_sram_addr; // saved for signal handler restorationWhen a signal handler runs, m68k_call_signal_handler() must restore a5
to p->got_base so the handler can access its global data correctly.
The MC68000 has two physically separate stack pointers:
- SSP (Supervisor Stack Pointer, a7 in supervisor mode): used by the kernel for exception handling, syscall dispatch, and context switch.
- USP (User Stack Pointer, a7 in user mode): used by user-space code.
The CPU automatically switches between SSP and USP based on the S bit in the Status Register (SR). On exception entry (trap, interrupt, bus error), the CPU pushes SR + PC onto the SSP and sets S=1. On RTE with the saved SR having S=0, the CPU switches back to the USP.
Each process gets two separate stack pages:
| Page | PCB field | Purpose |
|---|---|---|
stack_page |
p->stack_page |
Kernel stack (SSP target): exception frames, context switch save area, syscall handling |
user_stack_page |
p->user_stack_page |
User stack (USP target): argc/argv/envp/auxv, user function calls, local variables |
Allocation order in execve():
// Pre-allocate stack (SSP) page first to avoid LIFO brk conflict
void *stack = page_alloc(); // -> p->stack_page
void *user_stack = page_alloc(); // -> p->user_stack_pageThe stack page is allocated before data pages to prevent LIFO page-pool conflicts that would block brk expansion.
proc_setup_stack() builds the initial exception frame on the kernel
stack (stack_page), regardless of user_sp:
Kernel stack (SSP) layout, built by proc_setup_stack():
[SP+0 ] d0 <- pcb_t.sp points here
[SP+4 ] d1
[SP+8 ] d2
[SP+12] d3
[SP+16] d4
[SP+20] d5
[SP+24] d6
[SP+28] d7
[SP+32] a0
[SP+36] a1
[SP+40] a2
[SP+44] a3
[SP+48] a4
[SP+52] a5 <- GOT base (slot 13, patched by execve)
[SP+56] a6
[SP+60] SR (16-bit) <- SR_USER (S=0, IPL=0) -> switches to USP on RTE
[SP+62] PC (32-bit) <- entry point
Total: 15 registers x 4 bytes + 2 (SR) + 4 (PC) = 66 bytes.
The SR is set to SR_USER (S=0, IPL=0), so when switch.S executes
movem.l %sp@+,%d0-%d7/%a0-%a6; rte, the CPU pops all registers, then
pops SR+PC. Since SR has S=0, the CPU switches to user mode and the
hardware stack pointer becomes the USP.
The user stack (USP) is set up in execve():
// Build argc/argv/envp/auxv at top of user_stack page
uint32_t stack_top = (uint32_t)user_stack + PAGE_SIZE;
uint32_t sp = stack_top;
// ... push argv strings, auxv, envp, argv pointers, argc ...
p->usp = sp; // saved in PCBThe PCB's usp field is loaded into the hardware USP register by
switch.S during context restore:
move.l %a0@(PCB_USP_OFFSET),%a1
move.l %a1,%uspBoth m68k_trap1_handler (cooperative yield) and m68k_timer_isr
(preemptive tick) save and restore both stack pointers:
| Save
move.l %sp,%a0@(PCB_SP_OFFSET) | save SSP
move.l %usp,%a1
move.l %a1,%a0@(PCB_USP_OFFSET) | save USP
| ... sched_next() picks new process ...
| Restore
move.l %a0@(PCB_SP_OFFSET),%sp | restore SSP
move.l %a0@(PCB_USP_OFFSET),%a1
move.l %a1,%usp | restore USPDuring execve(), there is a critical window between constructing the
new process's exception frame and the trap return path restoring it. If
the timer ISR fires in this window and performs a context switch, it would
save the kernel's SSP into current->sp, overwriting the carefully
constructed exec frame.
The solution uses an exec_pending flag:
// In execve(), with interrupts disabled:
uint32_t irq_save = arch_irq_save();
proc_setup_stack(p, entry, m68k_user_sp);
p->usp = m68k_user_sp;
// Patch GOT base in frame...
if (p == current)
exec_pending[0] = 1; // protect the frame
arch_irq_restore(irq_save);The timer ISR in switch.S checks this flag:
tst.l exec_pending
bne.s .Lno_switch | skip context switch if exec in progressThe flag is cleared by trap.S's .Lexec_restore path after the new
process context has been successfully restored.
X68000 IOCS _B_PUTC has its own escape sequence parser that is not
VT100-compatible. Sending VT100 CSI sequences directly causes the IOCS
parser to enter a bad state, potentially crashing.
The console driver (src/target/x68k/drivers/uart_x68k.c) intercepts
all output through a VT100 converter (vt_feed()) that:
- Tracks cursor position in software (
cur_x,cur_y) - Converts VT100 CSI sequences to equivalent IOCS calls:
| VT100 Sequence | IOCS Equivalent |
|---|---|
| ESC[nA | Cursor up n (_B_LOCATE) |
| ESC[nB | Cursor down n (_B_LOCATE) |
| ESC[nC | Cursor forward n (_B_LOCATE) |
| ESC[nD | Cursor back n (_B_LOCATE) |
| ESC[row;colH | Cursor position (_B_LOCATE) |
| ESC[2J | Clear screen (_B_CLR_ST) |
| ESC[0J | Clear to end (_B_CLR_ED) |
| ESC[0K | Clear to EOL (_B_CLR_AL) |
| ESC[...m | SGR (silently ignored) |
| ESC[?... | Private modes (silently ignored) |
The console follows the pico1 mirror pattern: two outputs active simultaneously.
- TVRAM (TTY_DISPLAY):
uart_putc()-> VT100 converter -> IOCS calls - Serial (TTY_SERIAL):
uart_serial_putc()-> IOCS_OUT232C - klog mirror: all kernel log output also goes to serial
When the crash handler runs after an IOCS fault, calling _B_PUTC again
would cause a double bus/address error (CPU halt). The uart_tvram_inhibit
flag makes uart_putc a no-op for TVRAM; crash diagnostics go only via
the serial mirror (_OUT232C).
BusyBox hush's ask_terminal() sends ESC[6n (cursor position query)
before /etc/profile is sourced. Since the X68000 IOCS cannot respond
to this query, it causes garbage bytes on screen. The init process sets
TERM=dumb in the default environment to suppress this behaviour.
src/target/x68k/CMakeLists.txtwithRAM_END=0xC00000src/target/x68k/x68k.ldlinker script (ORIGIN = 0x006000)target_x68k.cwith IOCS-based console- VT100 escape sequence converter
- Crash-safe uart_tvram_inhibit mode
src/target/x68k/drivers/timer_x68k.c: MFP Timer-C at 100 Hz- Preemptive scheduling works
- Vector 69 (Timer-C) installed by
timer_init()
src/target/x68k/boot/stage1.S: IPL bootstrap with BPBsrc/target/x68k/boot/stage2.c: UFS kernel + rootfs loader in Ctools/mkx68kimg/mkx68kimg.sh: floppy image build tooltools/mkufs/mkufs.c: UFS image creator with big-endian symlink support- Rootfs loaded to RAM, mounted as
/via flatblk - Keyboard vector preservation (vector 74)
- IOCS reentrancy protection (IPL7 wrappers)
- Direct MFP USART RSR polling for input availability
- Shell launches and runs interactively on XEiJ emulator
- 19 tests pass on m68k (qemu_m68k)
- XEiJ serial-over-TCP for automated test capture (
scripts/run_xeij_tcp.sh) - Remaining: automated
runtestson XEiJ
./scripts/run.sh --test qemu_m68k # build + run m68k tests on QEMU
./scripts/run.sh --test qemu_arm # build + run ARM tests on QEMU
./scripts/run.sh --build x68k # build X68000 floppy image
./scripts/run.sh --run x68k # build + launch XEiJ emulator1. Build kernel -> build/x68k/ppap_x68k (ELF)
2. Build stage1 -> build/x68k/stage1.bin
3. Build stage2 -> build/x68k/stage2.elf (with -DROOTFS_BASE)
4. Build userland -> build/x68k/romfs_ppap_x68k/
5. mkufs (inner) -> build/x68k/rootfs.ufs (big-endian)
6. mkx68kimg (outer) -> build/x68k/ppap_x68k.xdf (bootable floppy)
| Target | Tests Pass | Notes |
|---|---|---|
| qemu_m68k | 19 | Full suite |
| qemu_arm | 17 | h68k_dos + x68k skipped (m68k-only) |
| x68k (XEiJ) | -- | Shell interactive, runtests pending |
src/target/x68k/
CMakeLists.txt Build rules (RAM_END, drivers, flags)
x68k.ld Linker script (ORIGIN = 0x006000)
target_x68k.c Target hooks, vector patching, rootfs mount
boot/
stage1.S IPL bootstrap (sector 0, <= 1024 bytes)
stage2.c UFS kernel + rootfs loader (sectors 1-3)
drivers/
uart_x68k.c IOCS console, VT100 converter, MFP polling
timer_x68k.c MFP Timer-C driver (100 Hz tick)
romfs/
etc/profile Shell startup (TERM=dumb, PS1, PATH)
src/arch/m68k/
boot.S Vector table, Reset_Handler, bus/address error
switch.S Context switch (TRAP #1 + timer ISR)
trap.S TRAP #0 syscall handler, exec_restore
m68k_common.c Crash handler (with uart_tvram_inhibit)
math.S 32-bit multiply/divide (no libgcc)
src/kernel/exec/
exec.c ELF loader (XIP + non-XIP, GOT relocation)
tools/
mkx68kimg/mkx68kimg.sh Floppy image build tool
mkufs/mkufs.c UFS image creator (big-endian support)
| Risk | Mitigation |
|---|---|
| Baud rate mismatch | Re-read MFP crystal; try 9600/19200/38400 |
| MFP vector base != 0x40 | Read VR at early_init before assuming |
| RAM probe GVRAM artefacts | Confirm RAM_END guard |
| SRAM corruption | No code writes to 0xED0000-0xED3FFF |
With PPAP_ENABLE_ECPU=OFF, PPAP_ENABLE_CPM=OFF, and excluded apps
(hello, trace, pdb), the current floppy image uses ~1068 of 1232 sectors,
leaving ~164 sectors (~164 KB) of headroom.
SCSI HDD support would remove the floppy capacity constraint and enable
running with eCPU and CP/M enabled. Requires implementing scsi_x68k.c
for the MB89352A SCSI controller.
- docs/targets/68000.md -- m68k architecture reference
- docs/subsystems/human68k.md -- Human68k subsystem design
- docs/ecpu/m68k.md -- eCPU m68k emulator
- docs/archive/history/target-68000-plan.md -- Original m68k planning document