mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
497 lines
13 KiB
NASM
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
|