feat(stream): payload skeleton with one startup malloc

This commit is contained in:
opencode.energia.arti.ee committed 2026-10-30 22:40:23 +00:00
1 parent 1a68630534
commit 4f6b0bf1a6
4 files changed
+571

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
#!/bin/sh
# run-vidstream.sh -- build the streaming payload and run its MAME check.
#
# harness/mame/run-vidstream.sh # build, then mode alloc
# BENCH=0 harness/mame/run-vidstream.sh # use the existing .bin
# VT1200_MODE=rx harness/mame/run-vidstream.sh # Task 4's mode
# STREAM_POOL_BYTES=0x7FFF000 harness/mame/run-vidstream.sh # force fallback
#
# The run uses VT1200_SKIP_LEVEL1=1: the payload's heap is only initialised in
# the OS phase. In the diagnostics menu the free counter (0x1E8BA0) and the
# free-list head (0x1E8AC0) both read 0, so the malloc success path cannot be
# tested there. The skip is a documented divergence (harness/mame/README.md).
#
# Exit status: 0 only if vidstream.lua printed "VID: RESULT PASS".
set -eu
HERE=$(cd "$(dirname "$0")" && pwd)
ROOT=$(cd "$HERE/../.." && pwd)
MAME=${MAME:-/home/arti/code/k-space/mame0289/vt1200}
BENCH=${BENCH:-1}
MODE=${VT1200_MODE:-alloc}
LOGDIR=${LOGDIR:-/tmp/opencode}
LOG="$LOGDIR/vidstream-mame.log"
if [ ! -x "$MAME" ]; then
echo "run-vidstream.sh: no MAME binary at $MAME (set MAME=...)" >&2
exit 1
fi
if [ "$BENCH" != "0" ]; then
STREAM_POOL_BYTES=${STREAM_POOL_BYTES:-249856} \
"$ROOT/harness/udpstream/build-stream.sh"
fi
if [ ! -f "$ROOT/harness/udpstream/build/vidstream.bin" ]; then
echo "run-vidstream.sh: no build/vidstream.bin (run without BENCH=0)" >&2
exit 1
fi
# POOL_BYTES / STATIC_PTR come from the image that was just built, so the check
# compares against the linked fallback address rather than a guess.
POOL_BYTES=249856
STATIC_PTR=0
ENTRY_PTR=0x018C0000
ENVFILE="$ROOT/harness/udpstream/build/vidstream.env"
if [ -f "$ENVFILE" ]; then
# shellcheck disable=SC1090
. "$ENVFILE"
fi
mkdir -p "$LOGDIR"
set +e
VT1200_SKIP_LEVEL1=1 \
VT1200_MODE="$MODE" \
VID_POOL_BYTES="$POOL_BYTES" \
VID_STATIC_PTR="$STATIC_PTR" \
VID_ENTRY_PTR="$ENTRY_PTR" \
"$MAME" vt1200 -rompath "$HERE/roms" -video none -sound none -nothrottle \
-skip_gameinfo -debug -debugger none \
-seconds_to_run "${VID_SECONDS:-120}" \
-autoboot_script "$HERE/vidstream.lua" -autoboot_delay 0 \
> "$LOG" 2>&1
STATUS=$?
set -e
grep '^VID:' "$LOG" || true
if [ "$STATUS" -ne 0 ]; then
echo "run-vidstream.sh: MAME exited $STATUS; log $LOG" >&2
tail -20 "$LOG" >&2
exit "$STATUS"
fi
if ! grep -q '^VID: RESULT PASS' "$LOG"; then
echo "run-vidstream.sh: check failed (log $LOG)" >&2
exit 1
fi
exit 0
+228
View File
@@ -0,0 +1,228 @@
-- vidstream.lua -- load the streaming payload and check its startup allocation.
--
-- MECHANISM: debugger entry, not a delivery mechanism. The firmware is run
-- with VT1200_SKIP_LEVEL1=1 (a documented divergence) to the OS, because the
-- level-0 diagnostics menu's heap is NOT initialised (both the free counter at
-- 0x1E8BA0 and the free-list head at 0x1E8AC0 read 0 there -- measured; see
-- the task-3 report). The payload is poked into RAM at VID_AT and entered at
-- the OS idle loop by a breakpoint, the same way harness/mame/hello.lua enters
-- its payload at the diagnostics idle loop.
--
-- Mode "alloc" (Task 3) asserts:
-- * POOL_PTR is non-zero and its BYTE address is in 0x100000..0x400000;
-- * the heap free counter dropped by POOL_BYTES (plus allocator header);
-- * on a failed malloc, POOL_PTR is the linked static buffer and the low bit
-- of POOL_FREE (before) marks the fallback, i.e. the payload keeps running.
--
-- Marker page: bit 0x01810000. A marker named 0x018100NN in the plan is a
-- BYTE offset into that page (0x01810024 is four bits after 0x01810020 --
-- inside the same 32-bit cell -- so the literal reading cannot work). The C
-- side uses the same rule: bit = 0x01810000 + NN * 8.
--
-- Env:
-- VT1200_MODE alloc (default; rx/draw arrive in later tasks)
-- VID_AT load/entry frame (default 2400, the OS phase)
-- VID_END inspection/exit frame (default VID_AT + 60)
-- VID_PAYLOAD raw .text image (default harness/udpstream/build/vidstream.bin)
-- VID_ORIGIN payload load (poke) bit address (default 0x018C0000)
-- VID_ENTRY_PTR _start bit address from vidstream.env (default VID_ORIGIN)
-- VID_ENTRY_PC breakpoint PC used to enter (default 0x3F579970, OS idle)
-- VID_POOL_BYTES POOL_BYTES the image was built with (default 249856)
-- VID_STATIC_PTR linked address of the fallback pool, from vidstream.env
local function hex(v, w) return string.format("%0" .. (w or 8) .. "X", v) end
local machine = manager.machine
local cpu = machine.devices[':maincpu']
local dbg = machine.debugger
local prog = cpu.spaces['program']
if not dbg then
print("VID ERROR: debugger not enabled; pass -debug")
machine:exit()
return
end
local MODE = os.getenv("VT1200_MODE") or "alloc"
local AT = tonumber(os.getenv("VID_AT") or "2400")
local STOP_AT = tonumber(os.getenv("VID_END") or tostring(AT + 60))
local ORIGIN = tonumber(os.getenv("VID_ORIGIN") or "0x018C0000")
local ENTRY_PC = tonumber(os.getenv("VID_ENTRY_PC") or "0x3F579970")
local PATH = os.getenv("VID_PAYLOAD") or "harness/udpstream/build/vidstream.bin"
local POOL_BYTES = tonumber(os.getenv("VID_POOL_BYTES") or "249856")
local STATIC_PTR = tonumber(os.getenv("VID_STATIC_PTR") or "0")
-- _start is at the linked entry point, not at the section origin: GSPLNK
-- places ENTRY.obj after the C object (see the map). build-stream.sh
-- publishes the address; ORIGIN is only where the raw .text is poked.
local ENTRY_PTR = tonumber(os.getenv("VID_ENTRY_PTR") or "0x018C0000")
local MARK_PAGE = 0x01810000
local POOL_PTR = MARK_PAGE + 0x20 * 8
local POOL_FREEB = MARK_PAGE + 0x24 * 8
local POOL_FREEA = MARK_PAGE + 0x28 * 8
local VID_ALIVE = MARK_PAGE + 0x2c * 8
if MODE ~= "alloc" then
print("VID ERROR: mode '" .. MODE .. "' is not implemented in Task 3")
machine:exit()
return
end
local payload = nil
do
local f, err = io.open(PATH, "rb")
if not f then
print("VID ERROR: cannot open " .. PATH .. ": " .. tostring(err))
machine:exit()
return
end
payload = f:read("*a")
f:close()
print(string.format("VID: payload %s bytes=%d origin=%s", PATH, #payload, hex(ORIGIN)))
end
local frames = 0
local loaded = false
local entered = false
local armed = false
local direct = false
local alive0 = nil
local alive_last = nil
local function load_payload()
local n = math.floor(#payload / 2)
local words = {}
local bad = 0
for i = 0, n - 1 do
local lo = payload:byte(i * 2 + 1)
local hi = payload:byte(i * 2 + 2)
words[i] = lo | (hi << 8)
prog:write_u16(ORIGIN + i * 16, words[i])
end
for i = 0, n - 1 do
if prog:read_u16(ORIGIN + i * 16) ~= words[i] then
bad = bad + 1
end
end
print(string.format("VID: loaded %d words at bit %s; readback %s (%d bad)",
n, hex(ORIGIN), bad == 0 and "OK" or "FAILED", bad))
-- Enter through the idle loop, exactly like hello.lua. The action only
-- assigns PC and resumes; the payload proves itself.
dbg:command("bpset " .. hex(ENTRY_PC) .. ",,{ pc = " .. hex(ENTRY_PTR) .. " ; g }")
armed = true
end
local function in_payload(pc)
return pc >= ORIGIN and pc < ORIGIN + #payload * 8
end
local function enter_direct(reason)
dbg:command("bpclear " .. hex(ENTRY_PC))
cpu.state['PC'].value = ENTRY_PTR
direct = true
print(string.format("VID: frame %d: %s; entered payload directly at %s",
frames, reason, hex(ENTRY_PTR)))
end
local function inspect()
local ptr = prog:read_u32(POOL_PTR)
local freeb = prog:read_u32(POOL_FREEB)
local freea = prog:read_u32(POOL_FREEA)
local alive = prog:read_u32(VID_ALIVE)
local pbyte = math.floor(ptr / 8)
local ok = true
print(string.format("VID: marker page %s: POOL_PTR=%s POOL_FREE(before)=%d POOL_FREE(after)=%d",
hex(MARK_PAGE), hex(ptr), freeb, freea))
print(string.format("VID: heartbeat %d (was %s) -- payload %s",
alive, alive_last and tostring(alive_last) or "unset",
(in_payload(cpu.state['PC'].value) or direct) and "still running" or "NOT RUNNING"))
if alive_last ~= nil and alive == alive_last then
ok = false
print("VID: heartbeat did not advance -> FAIL")
end
if ptr == 0 then
print(string.format("VID: pool pointer %s -> FAIL", hex(ptr)))
ok = false
elseif pbyte < 0x100000 or pbyte > 0x400000 then
print(string.format("VID: pool pointer %s (byte %s) outside 100000..400000 -> FAIL",
hex(ptr), hex(pbyte, 6)))
ok = false
else
print(string.format("VID: pool pointer in range: %s (byte %s) -> PASS",
hex(ptr), hex(pbyte, 6)))
end
if (freeb & 1) == 1 then
-- malloc failed: the low bit of the before value is the static flag.
local before = freeb - 1
if STATIC_PTR ~= 0 and ptr == STATIC_PTR then
print(string.format("VID: static fallback at %s, flag set, heap untouched (%d -> %d bytes) -> PASS",
hex(ptr), before, freea))
else
print(string.format("VID: static fallback wrong: pointer %s, expected %s -> FAIL",
hex(ptr), hex(STATIC_PTR)))
ok = false
end
else
local drop = freeb - freea
local delta = drop - POOL_BYTES
if delta < 0 then delta = -delta end
if drop > 0 and delta <= 64 then
print(string.format("VID: heap free before/after differ by %d bytes (POOL_BYTES=%d, header delta=%d) -> PASS",
drop, POOL_BYTES, drop - POOL_BYTES))
else
print(string.format("VID: heap free before/after differ by %d bytes, expected ~%d -> FAIL",
drop, POOL_BYTES))
ok = false
end
end
print(ok and "VID: RESULT PASS" or "VID: RESULT FAIL")
end
emu.register_frame_done(function()
frames = frames + 1
local pcv = cpu.state['PC'].value
if frames == AT and not loaded then
loaded = true
load_payload()
end
if loaded and not entered and in_payload(pcv) then
entered = true
dbg:command("bpclear " .. hex(ENTRY_PC))
alive0 = prog:read_u32(VID_ALIVE)
print(string.format("VID: payload entered by frame %d (PC=%s, heartbeat=%d)",
frames, hex(pcv), alive0))
end
if loaded and not entered and armed and frames == AT + 10 then
enter_direct("breakpoint at " .. hex(ENTRY_PC) .. " did not fire")
entered = true
alive0 = prog:read_u32(VID_ALIVE)
end
if entered and frames % 10 == 0 and frames < STOP_AT then
alive_last = prog:read_u32(VID_ALIVE)
end
if frames >= STOP_AT then
if not entered then
print("VID: payload never entered -> FAIL")
print("VID: RESULT FAIL")
else
local ok2, err = pcall(inspect)
if not ok2 then
print("VID: ERROR " .. tostring(err))
print("VID: RESULT FAIL")
end
end
machine:exit()
end
end, "vt1200-vidstream")
dbg.visible_cpu = cpu
dbg.execution_state = 'run'
+178
View File
@@ -0,0 +1,178 @@
#!/bin/sh
# Build the video-stream payload (C + ROM-API thunks).
#
# harness/udpstream/build-stream.sh
# STREAM_POOL_BYTES=0x7FFF000 harness/udpstream/build-stream.sh # fail the malloc
#
# .text at bit 0x018C0000, .bss at 0x01C00000 (both inside the firmware's large
# free block), entry _start, no .cinit (the runtime's autoinit pass does not
# run -- see harness/c-hello/README.md).
#
# POOL_BYTES is compile-time: the script writes poolcfg.h from
# STREAM_POOL_BYTES (default 249856 = 244 KB: two 24 KB slots + 160 KB
# framebuffer + 32 KB dictionary + 4 KB slack).
#
# Outputs: build/vidstream.bin (raw .text, what vidstream.lua loads),
# build/vidstream.coff, vidstream.map, vidstream.lst, entry.lst,
# vidstream.env (POOL_BYTES, the linked entry point and the address of
# the static fallback pool, which the MAME check asserts against).
set -eu
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
DIR=$(cd "$(dirname "$0")" && pwd)
WORK="$DIR/work"
OUT="$DIR/build"
IMG="$ROOT/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img"
DOSBOX_CONF="$ROOT/harness/sdk-scratch/dosbox-bench.conf"
TEXT=018C0000
BSS=01C00000
POOL_BYTES=${STREAM_POOL_BYTES:-249856}
mkdir -p "$WORK" "$OUT"
if [ ! -x "$WORK/GSPCG.EXE" ]; then
mcopy -s -n -i "$IMG" "::/A2/*" "$WORK/"
mcopy -s -n -i "$IMG" "::/C2/*" "$WORK/"
fi
SRC=${STREAM_SRC:-$DIR/vidstream.c}
cp "$SRC" "$WORK/VIDSTRM.C"
cp "$DIR/romapi.h" "$WORK/ROMAPI.H"
cp "$DIR/entry.asm" "$WORK/ENTRY.ASM"
cp "$DIR/vram.asm" "$WORK/VRAM.ASM"
# vram_b.asm is Task 5's PIXBLT B,XY primitive. Link it when it exists so the
# build does not have to be edited again that day; a Task 3 tree has only
# vram.asm and links exactly what it uses.
VRAMB_OBJ=
VRAMB_LST=
if [ -f "$DIR/vram_b.asm" ]; then
cp "$DIR/vram_b.asm" "$WORK/VRAMB.ASM"
VRAMB_OBJ=VRAMB.obj
VRAMB_LST=VRAMB.LST
fi
# The allocator request size is a compile-time constant (the 1987 compiler has
# no -D we rely on). One generated header keeps the default in one place.
printf '#define POOL_BYTES %s\n' "$POOL_BYTES" > "$WORK/POOLCFG.H"
{
printf '%s\n' '-a'
printf '%s\n' '-o VIDSTRM.out'
printf '%s\n' '-m VIDSTRM.map'
printf '%s\n' '-e _start'
printf '%s\n' 'VIDSTRM.obj'
printf '%s\n' 'ENTRY.obj'
printf '%s\n' 'VRAM.obj'
[ -z "$VRAMB_OBJ" ] || printf '%s\n' "$VRAMB_OBJ"
printf '%s\n' '-l RTS.LIB'
printf '%s\n' 'MEMORY'
printf '%s\n' '{'
printf '%s\n' " prog: origin = ${TEXT}h, length = 20000h"
printf '%s\n' " ram: origin = ${BSS}h, length = 200000h"
printf '%s\n' '}'
printf '%s\n' 'SECTIONS'
printf '%s\n' '{'
printf '%s\n' ' .text: {} > prog'
printf '%s\n' ' .data: {} > prog'
printf '%s\n' ' .cinit: {} > prog'
printf '%s\n' ' .bss: {} > ram'
printf '%s\n' '}'
} > "$WORK/VIDSTRM.CMD"
sed -i 's/$/\r/' "$WORK/VIDSTRM.CMD"
rm -f "$WORK"/VIDSTRM.OBJ "$WORK"/VIDSTRM.OUT "$WORK"/VIDSTRM.LST \
"$WORK"/VIDSTRM.MAP "$WORK"/VIDSTRM.ASM "$WORK"/VIDSTRM.CPP \
"$WORK"/VIDSTRM.IF "$WORK"/ENTRY.OBJ "$WORK"/ENTRY.LST \
"$WORK"/VRAM.OBJ "$WORK"/VRAM.LST "$WORK"/VRAMB.OBJ "$WORK"/VRAMB.LST
run_dosbox() {
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout 240 dosbox -conf "$DOSBOX_CONF" -noprimaryconf -nolocalconf \
-c "mount c $WORK" -c "c:" \
-c "gspcpp VIDSTRM" \
-c "gspcc VIDSTRM" \
-c "gspcg VIDSTRM" \
-c "gspa VIDSTRM VIDSTRM.obj VIDSTRM.lst -l" \
-c "gspa ENTRY ENTRY.obj ENTRY.lst -l" \
-c "gspa VRAM VRAM.obj VRAM.lst -l" \
"$@" \
-c "gsplnk VIDSTRM.cmd" \
-c "exit" -exit > "$WORK/dosbox.log" 2>&1
}
if [ -n "$VRAMB_OBJ" ]; then
run_dosbox -c "gspa VRAMB VRAMB.obj VRAMB.lst -l"
else
run_dosbox
fi
if [ ! -f "$WORK/VIDSTRM.OUT" ]; then
echo "build-stream.sh: GSPLNK produced no VIDSTRM.OUT" >&2
sed 's/\r$//' "$WORK/VIDSTRM.MAP" 2>/dev/null | tail -20 >&2 || true
for f in VIDSTRM.LST ENTRY.LST VRAM.LST $VRAMB_LST; do
sed 's/\r$//' "$WORK/$f" 2>/dev/null | grep -i 'error' | grep -vi 'no errors' >&2 || true
done
exit 1
fi
for f in VIDSTRM.LST ENTRY.LST VRAM.LST $VRAMB_LST; do
if sed 's/\r$//' "$WORK/$f" | grep -i 'error' | grep -qvi 'no errors'; then
echo "build-stream.sh: $f diagnostics:" >&2
sed 's/\r$//' "$WORK/$f" | grep -in 'error' | grep -vi 'no errors' >&2
exit 1
fi
done
cp "$WORK/VIDSTRM.OUT" "$OUT/vidstream.coff"
sed 's/\r$//' "$WORK/VIDSTRM.MAP" > "$OUT/vidstream.map"
sed 's/\r$//' "$WORK/VIDSTRM.LST" > "$OUT/vidstream.lst"
sed 's/\r$//' "$WORK/ENTRY.LST" > "$OUT/entry.lst"
sed 's/\r$//' "$WORK/VRAM.LST" > "$OUT/vram.lst"
if [ -n "$VRAMB_LST" ]; then
sed 's/\r$//' "$WORK/VRAMB.LST" > "$OUT/vramb.lst"
fi
# Guard: the marker block (MARK in the source) must sit clear of .text, or the
# payload overwrites its own code with marker writes (an illegal-opcode crash).
python3 - "$SRC" "$OUT/vidstream.map" <<'PYCHK'
import re, sys
src, mp = sys.argv[1], sys.argv[2]
m = re.search(r'#define\s+MARK\s+(0x[0-9A-Fa-f]+)', open(src).read())
if m:
mark = int(m.group(1), 16)
for ln in open(mp):
f = ln.split()
if len(f) >= 4 and f[0] == '.text':
start, size = int(f[2], 16), int(f[3], 16)
end = start + size
if end > mark:
sys.stderr.write("build: ERROR .text ends at 0x%X, past MARK 0x%X\n" % (end, mark))
sys.exit(1)
print("build: .text ends 0x%X, MARK 0x%X (%d bytes clear)" % (end, mark, (mark - end) // 8))
PYCHK
# The raw .text image is what the debugger loads (vidstream.lua).
python3 "$ROOT/harness/hello-world/cofextract.py" \
"$WORK/VIDSTRM.OUT" "$OUT/vidstream.bin"
# The static-fallback address and the entry point are linked, not guessed:
# publish them (bits) for the MAME check, which enters at _start and asserts
# that a failed malloc lands exactly on the static buffer.
STATIC_PTR=$(sed 's/\r$//' "$WORK/VIDSTRM.MAP" \
| awk '$2 == "_pool_static" { print $1; exit }')
if [ -z "$STATIC_PTR" ]; then
echo "build-stream.sh: _pool_static missing from the map (fallback cannot be checked)" >&2
exit 1
fi
ENTRY_PTR=$(sed 's/\r$//' "$WORK/VIDSTRM.MAP" \
| awk '/ENTRY POINT SYMBOL/ { print $NF; exit }')
if [ -z "$ENTRY_PTR" ]; then
echo "build-stream.sh: no entry point in the map" >&2
exit 1
fi
printf 'POOL_BYTES=%s\nSTATIC_PTR=0x%s\nENTRY_PTR=0x%s\n' \
"$POOL_BYTES" "$STATIC_PTR" "$ENTRY_PTR" > "$OUT/vidstream.env"
echo "build-stream.sh: $OUT/vidstream.bin ($(wc -c < "$OUT/vidstream.bin") bytes)"
grep -E "ENTRY POINT|^\.(text|cinit|bss)" "$OUT/vidstream.map" | head -8
echo "build-stream.sh: POOL_BYTES=$POOL_BYTES entry bit 0x$ENTRY_PTR static fallback bit 0x$STATIC_PTR"
+90
View File
@@ -0,0 +1,90 @@
/*
* vidstream.c -- terminal side of the VT1200 video stream (startup skeleton).
*
* Task 3 only: claim the working set with ONE malloc at startup and publish
* where it landed. The receive ring (Task 4) and the blit loop (Task 5) build
* on the pointer this file leaves behind.
*
* Marker layout. The design plan names markers as 0x01810020, 0x01810024,
* 0x01810030, ... Those cannot be read literally as bit addresses: the
* TMS34010 is bit-addressed, so 0x01810024 is FOUR BITS after 0x01810020,
* inside the same 32-bit cell. They are byte offsets into the marker page
* (bit 0x01810000, byte 0x00302000), which is the only reading under which
* the plan's 4-byte spacing holds. So a named marker 0x018100NN lives at
*
* bit 0x01810000 + NN * 8
*
* (POOL_PTR = 0x01810100, POOL_FREE before/after = 0x01810120/0x01810140).
* vidstream.lua uses the same rule. See the task-3 report.
*
* K&R C only: no prototypes, no '//', declarations before statements.
*/
#include "romapi.h"
#include "poolcfg.h" /* POOL_BYTES, written by build-stream.sh */
#define MARK_PAGE 0x01810000 /* bit address of the marker page */
#define POOL_PTR (MARK_PAGE + 0x20 * 8) /* u32 bit pointer to the pool */
#define POOL_FREEB (MARK_PAGE + 0x24 * 8) /* u32 heap free bytes, before */
#define POOL_FREEA (MARK_PAGE + 0x28 * 8) /* u32 heap free bytes, after */
#define VID_ALIVE (MARK_PAGE + 0x2c * 8) /* u32 heartbeat while spinning */
#define HEAP_FREE 0x1E8BA0 /* firmware free-byte counter, IN BITS */
#define MARK 0x01A00000 /* .text must end before this bit address */
#define STATIC_BYTES 249856 /* the designed working set, 244 KB */
/*
* Static fallback. pool_static is global so the linker map names it and the
* MAME check can assert the exact address a failed malloc falls back to.
* stack_cushion and the five VRAM words below sit between it and ENTRY.ASM's
* _cstack: TMS34010 pushes grow downward, so the 4 KB stack spills into those
* 4 KB, never into the pool.
*/
unsigned int pool_static[STATIC_BYTES / 4];
unsigned int stack_cushion[1024];
/*
* VRAM.ASM's externals. vram.asm is linked into every payload in this
* directory; the C side owns the values. Task 5 fills the blit_* words.
*/
unsigned int vram_pattern;
unsigned int blit_src, blit_sptch, blit_daddr, blit_dydx;
/*
* The firmware's free-space counter is a 32-bit count IN BITS at
* 0x1E8BA0 (dwt/notes/HEAP.md). POOL_FREE reports bytes, because the
* marker is for humans and the check compares against POOL_BYTES.
*/
static unsigned int heap_bytes()
{
return *(unsigned int *)HEAP_FREE / 8;
}
int main()
{
unsigned int before, after, ptr;
*(unsigned int *)VID_ALIVE = 0;
*(unsigned int *)POOL_PTR = 0;
*(unsigned int *)POOL_FREEB = 0;
*(unsigned int *)POOL_FREEA = 0;
before = heap_bytes();
ptr = malloc_(POOL_BYTES); /* malloc_ takes BYTES, returns BITS */
after = heap_bytes();
if (ptr == 0) {
/* The fallback is the 244 KB static pool by definition; a larger
* request still reports the failure through the low-bit flag. */
ptr = (unsigned int)pool_static;
*(unsigned int *)POOL_FREEB = before | 1;
} else {
*(unsigned int *)POOL_FREEB = before;
}
*(unsigned int *)POOL_FREEA = after;
*(unsigned int *)POOL_PTR = ptr; /* publish last: readers see a complete record */
for (;;) {
*(unsigned int *)VID_ALIVE = *(unsigned int *)VID_ALIVE + 1;
}
}