psx_rt/bios.rs
1//! The runtime's one remaining BIOS entry point: TTY `putchar`.
2//!
3//! Normal operation talks straight to the hardware; the i-cache flush
4//! that used to go through `A(44h) FlushCache` is now
5//! [`crate::cache::flush_instruction_cache`]. The single service still routed
6//! through the BIOS dispatch convention is `A(3Ch) putchar`, kept
7//! because it is the canonical PS1 debug channel: the emulator's HLE
8//! prints it to the host terminal, and on a real console the BIOS TTY
9//! device swallows it harmlessly. It is reached only from panic
10//! reporting and debug prints ([`crate::tty`]), never from a frame
11//! loop.
12//!
13//! The trampoline is a 3-instruction stub against the publicly
14//! documented A-table dispatch convention: load the table address into
15//! `$t0`, jump to it, put the function index into `$t1` in the branch
16//! delay slot.
17
18core::arch::global_asm!(
19 ".set noreorder",
20 ".section .text.bios.putchar",
21 ".globl __bios_putchar",
22 "__bios_putchar:",
23 " la $8, 0xA0",
24 " jr $8",
25 " li $9, 0x3C",
26 ".set reorder",
27);
28
29extern "C" {
30 fn __bios_putchar(ch: u32);
31}
32
33/// Write one byte to TTY via BIOS `putchar`.
34#[inline(always)]
35#[doc(alias = "putchar")]
36pub fn write_char(ch: u8) {
37 // SAFETY: `__bios_putchar` tail-jumps into the BIOS A-table dispatcher at 0xA0 with function
38 // 0x3C (putchar), which takes the byte in $a0 and clobbers only O32 caller-saved registers.
39 // Assumes the program has not overwritten kernel RAM (the first 64 KiB), where the A-table
40 // lives.
41 unsafe { __bios_putchar(ch as u32) }
42}