; 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