Files

497 lines
13 KiB
NASM

; loader.asm -- COFF loader for the VT1200 / TMS34010.
;
; Input: a TI GSP COFF image (GSPLNK `-a` output) already in RAM, and a
; 16-byte descriptor (offsets in BITS, since all pointers here are bit
; addresses; the byte offsets are 0, 2, 4, 8 and 12):
;
; DESC + 0x00 bits u16 0x4C44 "LD" ) magic; both words must match
; DESC + 0x10 bits u16 0x5231 "R1" )
; DESC + 0x20 bits u32 buf bit address of the COFF image
; DESC + 0x40 bits u32 len length in bytes
; DESC + 0x60 bits u32 mode 1 = return to the caller (rets)
; 0 = debug resume at LD_ORIG_PC
;
; The descriptor pointer is A8. If A8 is zero, or does not point at a valid
; descriptor, the fixed descriptor at 0x01882C00 is used. A transport that
; receives a COFF over the network only has to write {buf,len,mode} there and
; call the loader entry; it does not choose a load address. The loader takes
; the addresses from the COFF section headers.
;
; Result block at 0x01882D00 (32-bit cells, bit addresses):
;
; +0x000 status 0 ok, nonzero = error (see the ld_err_* labels)
; +0x020 entry bit address passed to create_process
; +0x040 text addr .text paddr from the COFF (bits)
; +0x060 text size .text size (bits)
; +0x080 data addr .data paddr
; +0x0A0 data size .data size (bits)
; +0x0C0 bss addr .bss paddr
; +0x0E0 bss size .bss size (bits)
; +0x100 relocs relocation entries seen in the output COFF
; +0x120 pcb create_process() return value (0 on failure)
; +0x140 nscns section count from the file header
; +0x160 coflen length from the descriptor (bytes)
; +0x180 version loader base address, 0x01880000
;
; The image is POSITION-DEPENDENT: GSPLNK resolves every reference and emits
; nreloc = 0, so each section is copied to the bit address in its header and
; no relocation is applied. A COFF that still carries relocation records is
; refused (status 7) rather than guessed at.
;
; Safety checks before any copy:
; * size is a whole number of 16-bit words;
; * the section bytes lie inside the descriptor's (buf,len);
; * paddr..paddr+size is inside main RAM (bits 0x100..0x2000000);
; * it does not overlap the loader page [01880000,01883200) or the COFF
; buffer;
; * it lies inside one block of the firmware's global free list
; (head at bit 0x1E8AC0, block header {u32 size_bits; ...; u32 next@+0x20},
; user data at block+0x40). This is a check, not a reservation: the
; loader cannot make malloc hand out a chosen address.
;
; Delivered debugger-assisted for now: poke the raw .bin at 0x01880000, write
; the COFF at buf, write the descriptor, then set PC = 0x01880000 (A8 = 0 is
; fine). mode = 0 resumes at LD_ORIG_PC (the harness saves the interrupted
; PC); mode = 1 returns to the caller and passes the status in A8.
;
; Linked at bit 0x01880000; position-dependent itself.
.def _start
LOADER_BASE equ 01880000h
LD_SCRATCH equ 01882800h
DESC_ADDR equ 01882C00h
RESULT_BASE equ 01882D00h
ARG_AREA equ 01882B00h
RETURN_STUB equ 01883100h
LD_SP_AFTER equ LD_SCRATCH+000h
LD_A14 equ LD_SCRATCH+020h
LD_ENTRY equ LD_SCRATCH+040h
LD_BUF equ LD_SCRATCH+060h
LD_LEN equ LD_SCRATCH+080h
LD_MODE equ LD_SCRATCH+0A0h
LD_NSCNS equ LD_SCRATCH+0C0h
LD_RELOCS equ LD_SCRATCH+0E0h
LD_SEC_PADDR equ LD_SCRATCH+100h
LD_SEC_SIZE equ LD_SCRATCH+120h
LD_SEC_SCNPTR equ LD_SCRATCH+140h
LD_TEXT_ADDR equ LD_SCRATCH+160h
LD_TEXT_SIZE equ LD_SCRATCH+180h
LD_DATA_ADDR equ LD_SCRATCH+1A0h
LD_DATA_SIZE equ LD_SCRATCH+1C0h
LD_BSS_ADDR equ LD_SCRATCH+1E0h
LD_BSS_SIZE equ LD_SCRATCH+200h
LD_ORIG_PC equ LD_SCRATCH+220h
RESULT_STATUS equ RESULT_BASE+000h
RESULT_ENTRY equ RESULT_BASE+020h
RESULT_TEXT_A equ RESULT_BASE+040h
RESULT_TEXT_S equ RESULT_BASE+060h
RESULT_DATA_A equ RESULT_BASE+080h
RESULT_DATA_S equ RESULT_BASE+0A0h
RESULT_BSS_A equ RESULT_BASE+0C0h
RESULT_BSS_S equ RESULT_BASE+0E0h
RESULT_RELOCS equ RESULT_BASE+100h
RESULT_PCB equ RESULT_BASE+120h
RESULT_NSCNS equ RESULT_BASE+140h
RESULT_LEN equ RESULT_BASE+160h
RESULT_VERSION equ RESULT_BASE+180h
.text
_start:
mmtm sp, a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13
move sp, @LD_SP_AFTER, 1
move a14, @LD_A14, 1
setf 16, 0, 0
setf 32, 0, 1
; ---- descriptor -----------------------------------------------------
; A8 points at it only if both magic words match; else use the fixed one.
move a8, a6
move a8, a8
jreq ld_fixed_desc
move *a6, a0, 0
cmpi 4C44h, a0
jrne ld_fixed_desc
move *a6(10h), a0, 0
cmpi 5231h, a0
jreq ld_have_desc
ld_fixed_desc:
movi DESC_ADDR, a6, L
move *a6, a0, 0
cmpi 4C44h, a0
jrne ld_bad_desc
move *a6(10h), a0, 0
cmpi 5231h, a0
jrne ld_bad_desc
ld_have_desc:
move *a6(20h), a7, 1 ; buf
move *a6(40h), a8, 1 ; len
move *a6(60h), a12, 1 ; mode
move a7, @LD_BUF, 1
move a8, @LD_LEN, 1
move a12, @LD_MODE, 1
move a12, @RESULT_VERSION, 1 ; harmless; overwritten below
movi LOADER_BASE, a0, L
move a0, @RESULT_VERSION, 1
; clear the counters we report; status stays FF until this loader writes it
movi 0FFFFFFFFh, a0, L
move a0, @RESULT_STATUS, 1
sub a0, a0
move a0, @LD_RELOCS, 1
move a0, @LD_TEXT_ADDR, 1
move a0, @LD_TEXT_SIZE, 1
move a0, @LD_DATA_ADDR, 1
move a0, @LD_DATA_SIZE, 1
move a0, @LD_BSS_ADDR, 1
move a0, @LD_BSS_SIZE, 1
move a0, @RESULT_ENTRY, 1
move a0, @RESULT_TEXT_A, 1
move a0, @RESULT_TEXT_S, 1
move a0, @RESULT_DATA_A, 1
move a0, @RESULT_DATA_S, 1
move a0, @RESULT_BSS_A, 1
move a0, @RESULT_BSS_S, 1
move a0, @RESULT_RELOCS, 1
move a0, @RESULT_PCB, 1
; ---- file header ----------------------------------------------------
movi 20, a0, L
cmp a0, a8
jrlo ld_err_short
move *a7, a0, 0 ; magic
cmpi 0090h, a0
jrne ld_err_magic
move *a7(10h), a9, 0 ; nscns (+2 bytes)
move a9, @LD_NSCNS, 1
move *a7(10h), a0, 0
move a9, @RESULT_NSCNS, 1
move a8, @RESULT_LEN, 1
; section headers = buf + (20 + opthdr + nscns*40) bytes; must fit in len
move *a7(80h), a1, 0 ; opthdr (+16 bytes)
move a9, a2
sll 5, a2 ; nscns*32
move a9, a3
sll 3, a3 ; nscns*8
add a3, a2 ; nscns*40
add a1, a2
addi 20, a2 ; byte offset of section headers end
cmp a2, a8 ; a8 - a2
jrlo ld_err_short
; secbase = buf + (20 + opthdr)*8 bits
sll 3, a1 ; opthdr in bits
addi 160, a1 ; 20 bytes in bits
add a7, a1
move a1, a10
; ---- entry point ----------------------------------------------------
; optional header at buf+20 bytes (0x0A0 bits): magic 0x0108; entry at
; +16 bytes (0x080 bits further); textstart at +20 bytes.
move *a7(0A0h), a0, 0
cmpi 0108h, a0
jrne ld_no_opt
move *a7(120h), a5, 1 ; entry (bits)
move a5, a5
jrne ld_entry_set
move *a7(140h), a5, 1 ; textstart (bits)
jruc ld_entry_set
ld_no_opt:
sub a5, a5
ld_entry_set:
move a5, @LD_ENTRY, 1
move a5, @RESULT_ENTRY, 1
; ---- section loop ---------------------------------------------------
sub a11, a11 ; section index
ld_sec_loop:
cmp a9, a11 ; a11 - a9
jrhs ld_sec_done
move *a10(40h), a0, 1 ; paddr (bits) +8 bytes
move *a10(80h), a1, 1 ; size (bits) +16
move *a10(0A0h), a2, 1 ; scnptr (bytes) +20
move *a10(100h), a3, 0 ; nreloc +32
move *a10(120h), a4, 1 ; flags +36
move a0, @LD_SEC_PADDR, 1
move a1, @LD_SEC_SIZE, 1
move a2, @LD_SEC_SCNPTR, 1
; record the first text/data/bss section for the result block
move a4, a5
andi 20h, a5
move a5, a5
jreq ld_not_text
move @LD_TEXT_ADDR, a5, 1
move a5, a5
jrne ld_not_text
move a0, @LD_TEXT_ADDR, 1
move a1, @LD_TEXT_SIZE, 1
ld_not_text:
move a4, a5
andi 40h, a5
move a5, a5
jreq ld_not_data
move @LD_DATA_ADDR, a5, 1
move a5, a5
jrne ld_not_data
move a0, @LD_DATA_ADDR, 1
move a1, @LD_DATA_SIZE, 1
ld_not_data:
move a4, a5
andi 80h, a5
move a5, a5
jreq ld_not_bss
move @LD_BSS_ADDR, a5, 1
move a5, a5
jrne ld_not_bss
move a0, @LD_BSS_ADDR, 1
move a1, @LD_BSS_SIZE, 1
ld_not_bss:
; relocation records: count, then refuse (the linked image has none)
move a3, a3
jreq ld_no_reloc
move @LD_RELOCS, a5, 1
add a3, a5
move a5, @LD_RELOCS, 1
jruc ld_err_reloc
ld_no_reloc:
; size 0: nothing to place; size must be whole 16-bit words
move a1, a1
jreq ld_sec_next
move a1, a5
andi 0Fh, a5
move a5, a5
jrne ld_err_size
; source range: scnptr + size/8 <= len (when there are bytes)
move a2, a2
jreq ld_no_src
move a1, a5
srl 3, a5 ; size in bytes
add a5, a2 ; scnptr + nbytes
cmp a8, a2 ; a8 - a2
jrhi ld_err_short
ld_no_src:
; target range: 0x100 <= paddr and paddr+size <= 0x2000000 bits
move @LD_SEC_PADDR, a0, 1
cmpi 100h, a0
jrlo ld_err_bounds
move @LD_SEC_SIZE, a1, 1
move a0, a3
add a1, a3 ; target end
movi 2000000h, a5, L
cmp a5, a3
jrhi ld_err_bounds
; overlap with the loader page [01880000,01883200)
movi 01883200h, a5, L
cmp a5, a0
jrhs ld_ov1_done
movi 01880000h, a5, L
cmp a3, a5
jrhs ld_ov1_done
jruc ld_err_overlap
ld_ov1_done:
; overlap with the COFF buffer [buf, buf+len*8)
move a8, a5
sll 3, a5
add a7, a5 ; buffer end in bits
cmp a5, a0
jrhs ld_ov2_done
cmp a3, a7
jrhs ld_ov2_done
jruc ld_err_overlap
ld_ov2_done:
; ---- global free-list check ----------------------------------------
; a block is {u32 size_bits; ...; u32 next@+0x20}; data at block+0x40.
move a0, @LD_SEC_PADDR, 1 ; (unchanged, kept for clarity)
move a3, @LD_SEC_SIZE, 1 ; temporarily hold target end
move @1E8AC0h, a6, 1 ; global free-list head (block address)
movi 64, a5, L
ld_free_walk:
move a6, a6
jreq ld_err_notfree
move *a6, a1, 1 ; block size in bits
move *a6(20h), a2, 1 ; next block
move a6, a0
addi 40h, a0 ; user start
move @LD_SEC_PADDR, a3, 1
cmp a0, a3
jrlo ld_free_next
move a6, a4
add a1, a4 ; block end
move @LD_SEC_SIZE, a1, 1 ; target end (stashed above)
cmp a4, a1
jrhi ld_free_next
jruc ld_free_ok
ld_free_next:
move a2, a6
dsjs a5, ld_free_walk
jruc ld_err_notfree
ld_free_ok:
; restore size in LD_SEC_SIZE (the stash overwrote it)
; target end - paddr = size
move @LD_SEC_SIZE, a1, 1
move @LD_SEC_PADDR, a0, 1
sub a0, a1 ; a1 = size again
move a1, @LD_SEC_SIZE, 1
; ---- place the section ---------------------------------------------
move @LD_SEC_SCNPTR, a0, 1
move a0, a0
jreq ld_zero_sec
sll 3, a0 ; file offset -> bit offset
add a7, a0 ; source address
move @LD_SEC_PADDR, a2, 1
move @LD_SEC_SIZE, a1, 1
srl 4, a1 ; number of 16-bit words
ld_copy_words:
move *a0+, a4, 0
move a4, *a2+, 0
dsjs a1, ld_copy_words
jruc ld_sec_next
ld_zero_sec:
move @LD_SEC_PADDR, a2, 1
move @LD_SEC_SIZE, a1, 1
srl 4, a1
sub a4, a4
ld_zero_words:
move a4, *a2+, 0
dsjs a1, ld_zero_words
ld_sec_next:
addi 140h, a10 ; 40-byte section header = 320 bits
addi 1, a11
jruc ld_sec_loop
ld_sec_done:
move @LD_TEXT_ADDR, a0, 1
move a0, @RESULT_TEXT_A, 1
move @LD_TEXT_SIZE, a1, 1
move a1, @RESULT_TEXT_S, 1
move @LD_DATA_ADDR, a0, 1
move a0, @RESULT_DATA_A, 1
move @LD_DATA_SIZE, a1, 1
move a1, @RESULT_DATA_S, 1
move @LD_BSS_ADDR, a0, 1
move a0, @RESULT_BSS_A, 1
move @LD_BSS_SIZE, a1, 1
move a1, @RESULT_BSS_S, 1
move @LD_RELOCS, a0, 1
move a0, @RESULT_RELOCS, 1
; ---- create_process(entry, name, stack, 1, arg) --------------------
move @LD_ENTRY, a0, 1
move a0, a0
jrne ld_have_entry
jruc ld_err_noentry
ld_have_entry:
move @LD_A14, a14, 1
move @LD_SP_AFTER, sp, 1
move a14, -*sp, 1 ; the slot the firmware callee restores
movi ARG_AREA, a14, L
movi 0, a0, L
move a0, *a14+, 1 ; arg4 = 0
movi 1, a0, L
move a0, *a14+, 1 ; arg3 = 1
movi 6000h, a0, L
move a0, *a14+, 1 ; arg2 = stack bytes
movi ld_appname, a0, L
move a0, *a14+, 1 ; arg1 = name string
move @LD_ENTRY, a0, 1
move a0, *a14+, 1 ; arg0 = entry (bit address)
calla 3F706A40h ; create_process
move @LD_SP_AFTER, sp, 1
move @LD_A14, a14, 1
setf 16, 0, 0
setf 32, 0, 1
move a8, @RESULT_PCB, 1
move a8, a8
jreq ld_err_create
sub a0, a0
move a0, @RESULT_STATUS, 1
jruc ld_finish
; ---- errors ---------------------------------------------------------
ld_bad_desc:
movi 1, a0, L
jruc ld_error
ld_err_short:
movi 2, a0, L
jruc ld_error
ld_err_magic:
movi 3, a0, L
jruc ld_error
ld_err_size:
movi 4, a0, L
jruc ld_error
ld_err_bounds:
movi 5, a0, L
jruc ld_error
ld_err_overlap:
movi 6, a0, L
jruc ld_error
ld_err_reloc:
movi 7, a0, L
jruc ld_error
ld_err_notfree:
movi 8, a0, L
jruc ld_error
ld_err_noentry:
movi 9, a0, L
jruc ld_error
ld_err_create:
movi 10, a0, L
ld_error:
move a0, @RESULT_STATUS, 1
move @LD_NSCNS, a0, 1
move a0, @RESULT_NSCNS, 1
move @LD_RELOCS, a0, 1
move a0, @RESULT_RELOCS, 1
ld_finish:
move @LD_ENTRY, a0, 1
move a0, @RESULT_ENTRY, 1
move @LD_NSCNS, a0, 1
move a0, @RESULT_NSCNS, 1
move @LD_LEN, a0, 1
move a0, @RESULT_LEN, 1
movi LOADER_BASE, a0, L
move a0, @RESULT_VERSION, 1
; ---- return or spin -------------------------------------------------
move @LD_MODE, a0, 1
move a0, a0
jrne ld_return
; mode 0 (debugger entry): restore the interrupted state and resume at
; the PC the harness hijacked. Returning to the OS idle loop lets the
; scheduler run the created child.
move @LD_SP_AFTER, sp, 1
move @LD_A14, a14, 1
mmfm sp, a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13
move @LD_ORIG_PC, a0, 1
jump a0
ld_return:
move @LD_SP_AFTER, sp, 1
move @LD_A14, a14, 1
mmfm sp, a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13
move @RESULT_STATUS, a8, 1 ; 0 = ok, else the error code
rets
ld_appname:
.byte "LOADER", 0
.end