From: Alan Cox Date: Sat, 5 Jan 2019 21:05:24 +0000 (+0000) Subject: sc114: first draft of a real boot loader for Fuzix X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=d7851c53e5024be10a2545c0464f2fdeb4709aae;p=FUZIX.git sc114: first draft of a real boot loader for Fuzix --- diff --git a/Kernel/platform-sc114/loader.s b/Kernel/platform-sc114/loader.s new file mode 100644 index 00000000..9984f731 --- /dev/null +++ b/Kernel/platform-sc114/loader.s @@ -0,0 +1,605 @@ +; +; We have 12K loaded D000-FFFF. We actually keep ourselves highish up +; to leave lots of room to load code +; + +I_ADDR .equ 0x18 +I_SIZE .equ 0x08 +I_TYPE .equ 0x00 + +loadbuf .equ 0x0100 + +DIRTYPE .equ 0x40 +FILETYPE .equ 0x80 + + .area _CODE + +; +; The main loop. We wait for about 2 seconds looking for input. If we +; see input then we go to command line mode, if not we load the +; default image name. If the default fails we then drop into command +; line mode. +; +; Right now we treat anything as a filename, except * which indicates +; platform specific helpers. We may add other commands later (eg +; single letters to switch drive). +; +boot_begin: + ; Run anything system specific + call preboot + ; Load the partition table and find our partition + call partitions +boot_up: + ld hl,#prompt + call con_write + ld b,#200 ; 2 seconds +boot_wait: + push bc + call con_check + jr nz, con_prompt + call delay10ms + pop bc + djnz boot_wait +boot_default: + ld hl,#newline + call con_write + ld hl,#defname +boot_name: + ld de,#loadname + ld bc,#31 + ldir + call load_kernel + ld hl,#failed + call con_write +con_next: + ld hl,#prompt + call con_write +con_prompt: + call con_readbuf + jr z, con_next + ld a,(hl) + cp #'*' + jr nz, boot_name + call system + jr con_next + +prompt: + .asciz 'FILO: ' +failed: + .ascii 'Load failed.' +newline: + .asciz '\n' +defname: + .asciz 'fuzix' + + .area _DATA + +loadname: + .ds 31 +ibuf: + .ds 64 +dbuf: + .ds 512 +partbase_l: + .dw 0 +partbase_h: + .dw 0 + + .area _CODE + +partitions: + ; No partitions found (hack for now) + ld hl,#0 + ld (partbase_l),hl + ld (partbase_h),hl + ret +; +; We have set the base according to the partition table, work relative +; to it +; +; The key to this being small is that a directory is a special kind of +; file. We therefore only need one routine which loads a file at the +; load address to do all the work. We load inode 1 (/) and find the +; file, then we use the same function to load the actual file. +; +; We could easily add subdirectories if we wanted. +; +; The data loading is kept compact by having a routine to load an +; array of block numbers. That same logic works for both the direct +; blocks and the first indirect (which is all we care about). +; +load_kernel: + ; + ; Load the super block + ; + ld de,#1 ; 0 is the boot block space, 1 the superblock + ld hl,#dbuf + call bread + ret nz + ; + ; Check the superblock is valid + ; + ld hl,(dbuf) + ld de,#12742 ; file system + or a + sbc hl,de + ret nz ; not valid + ; + ; It's a filesystem so we can load inode 1 + ; + ld hl,#1 + ld a,#DIRTYPE ; want a directory + + call loadi ; load / into memory + ; returns DE = size + ret nz ; / failed to load or too large + ld ix,#loadbuf ; start pointer into IX + ; + ; Turn the size into a record count of 32 byte records + ; + ld b,#5 +shift5: + srl d + srl e + djnz shift5 + ; DE is now record count + + ; + ; Scan the loaded file (directory) for the wanted name entry + ; +find_by_name: + push de + ld hl,#loadname + push ix + call cmpname + pop ix + jr nz, next_name + ld l,30(ix) + ld h,31(ix) + ld a,h ; empty slot even if matched + or l + jr nz, found_name +next_name: + ; + ; Move on a record + ; + ld de,#32 + add ix,de + pop de + dec de + ld a,d + or e + jr nz, find_by_name + ; + ; Not found + ; + inc a ; force NZ + ret ; not found + ; + ; Name match found + ; +found_name: + pop af ; Discard top of stack + ld a,#FILETYPE ; must be a file + call loadi ; replace the directory load with the target + ret nz + ; + ; And launch + ; + jp loadbuf + +; +; Load a file or directory if not too big +; +loadi: + ; A is the required type, HL the inode + call iget ; get the relevant inode into the ibuf + ret nz + ld de, (ibuf+I_SIZE+2) + ld a,d + or e + ret nz ; over 64K + ld de, (ibuf+I_SIZE) + ld a,#0xE0 + cp d + jr nc, retnz ; too big too load + + ld a,e + or a + jr z, directs ; exact blocks + inc d ; load the partial + ; + ; Now it's load time + ; +directs: + push de + ld a,d ; number of blocks + cp #18 + jr z, shortfile + ld a,#18 ; direct blocks +shortfile: + ld b,a + ld hl,#loadbuf + ld ix,#ibuf + I_ADDR + call breadset ; read b blocks from the list in ix + pop de + ret nz + ; See what indirects are needed if any + ld a,d + cp #19 + jr c, load_done + push de + push hl + ld e,(ix) + inc ix + ld d,(ix) + inc ix + ld hl,#dbuf + call bread ; load the indirect block + pop de + pop hl + ld a,d + sub #18 ; direct blocks + ld b,a ; remainder + ld ix,#dbuf + call breadset + ret nz +load_done: + ; File now loaded + xor a + ret +retnz: + or a + ret + +; +; Load inode HL into ibuf +; +; Entry: HL = inode number, A = type bits +; +; Return: NZ = error, Z = ok, data in ibuf +; +; Destroys: AF/BC/DE/HL +; +iget: + ; Turn an inode into a block + push af + push hl + ex de,hl + srl d ; Divide inode by 8 (64 bytes per inode) + srl e + srl d + srl e + srl d + srl e + ld hl,#dbuf + call bread + pop hl + jr nz, badiget + ld a,l + and #0x07 ; inode offset in block + add a,a ; x2 + add a,a ; x4 + add a,a ; x8 + add a,a ; x16 + add a,a ; x32 + ld h,#0 + ld l,a + add hl,hl ; x64 + ld de,#dbuf + add hl,de ; valid pointer + ex de,hl + ld hl,#I_TYPE+1 + add hl,de ; hl is now the type bits + ld a,(hl) + and #0xF0 ; type bits + ld l,a + pop af + cp l + ret nz ; wrong type of file + ex de,hl ; get pointer back into hl + ld de,#ibuf + ld bc,#64 + ldir + ret ; Z +badiget: + pop af + ret + +; +; Read a set of blocks listed in the array at ix. Any zero entry means +; a sparse block (zero the memory). +; +; Entry: IX = table, B = count, HL = load address +; +; Return: Z = success, HL = next load address +; +; Destroys: AF BC DE IX +; +; +breadset: +reader_loop: + ld e,(ix) + inc ix + ld d,(ix) + inc ix + push bc + call bread ; read block DE to HL and move HL on + pop bc + ret nz + djnz reader_loop + ret + +; +; bread +; +; Load a disk block (512 bytes) or zero block +; +; HL = buffer to read into +; DE = block relative to partbase +; +; return +; NZ = fail, Z = ok +; HL = next read address +; BC, DE, AF destroyed +; IX, IY preserved +; +bread: + ld a,d + or e + jr nz, bread_disk + ld d,h + ld e,l + ld bc,#511 + ld (hl),#0 + inc de + ldir + inc hl + ret + +; +; Compare the name at IX with the one in HL +; +; Returns Z = matched +; +; Destroys BC,HL,IX +; +; +cmpname: ; check name in IX matches HL for 30 chars + ld b,#30 +cmploop: + ld a,(ix) + cp (hl) + ret nz + or a + ret z ; First matched \0 matches all + inc hl + inc ix + djnz cmploop + ret ; Z + + +; +; **************************************************************************** +; +; System specific code begins here +; +; **************************************************************************** + +DATA .equ 0x10 +ERROR .equ 0x11 +FEATURES .equ 0x11 +COUNT .equ 0x12 +LBA_0 .equ 0x13 +LBA_1 .equ 0x14 +LBA_2 .equ 0x15 +LBA_3 .equ 0x16 +STATUS .equ 0x17 +CMD .equ 0x17 + +ERR .equ 0 +DRQ .equ 3 +READY .equ 6 +BUSY .equ 7 + +ATA_READ .equ 0x10 + +drive .equ 0xE0 ; Drive 0 for now + + +SC114 .equ 8 +SC108 .equ 0 ; until assigned + +; +; bread_disk +; +; Load a disk block (512 bytes) +; +; HL = buffer to read into +; DE = block relative to partbase +; +; return +; NZ = fail, Z = ok +; HL = next read address +; BC, DE, AF destroyed +; IX, IY preserved +; +; For the SCM systems we have to hit the disk directly as the SCM +; doesn't expose disk functionality at this point. For CP/M or ROMWBW +; we could go via the monitor. +; +bread_disk: + push hl + ld hl,(partbase_l) + add hl,de + ld de,(partbase_h) + jr nc, nocarry_lba + inc de +nocarry_lba: + call ide_ready + ; Loading block DEHL + ld a,d + and #0x0F + ld d,a + ld a,(drive) + or d + out (LBA_3),a + ld a,e + out (LBA_2),a + ld a,h + out (LBA_1),a + ld a,l + out (LBA_0),a + call ide_ready + ret nz + pop hl + ld a,#1 + out (COUNT),a + ld a,#ATA_READ + out (CMD),a + nop + call ide_wait_drq + ret nz + ld bc,#DATA + inir + inir + xor a + ret +; +; Timeout might be a good idea +; +ide_ready: + in a,(STATUS) + bit ERR,a + ret nz + bit READY,a + jr z, ide_ready + xor a + ret +ide_wait_drq: + in a,(STATUS) + bit ERR,a + ret nz + bit DRQ,a + jr z,ide_wait_drq + xor a + ret + +; +; Console I/O +; +; Read the name buffer into a buffer +; +; Destroys AF, BC, DE +; +; Returns length of buffer in A and sets Z/NZ accordingly +; Buffer pointer is returned in HL +; +; We implement the console via the SCM monitor helpers +; +con_readbuf: + ld c,#0x05 + call mcall + or a + ex de,hl + ret +; +; Check for input +; +; Returns NZ if data +; Destroys AF,BC,DE,HL +; +con_check: + ld c,#0x03 + jp mcall +; +; +; Write the string at HL +; +; Destroys AF,HL +; +con_write: + push bc + push de +con_writel: + ld a,(hl) + cp #0x0A + jr z, con_writenl + or a + jr z, con_writedone + ld c,#0x02 +con_wop: + push hl + call mcall + pop hl + inc hl + jr con_writel +con_writenl: + ld c,#0x07 + jr con_wop +con_writedone: + pop de + pop bc + ret +; +; Pre boot. Run before anything else kicks off +; +preboot: + ld c,#0x08 + call mcall + ld a,h + cp #SC108 + ret z + cp #SC114 + ret z + ld hl,#unsupported + call con_write + ld c,#0x00 + jp mcall + +unsupported: + .asciz 'Unsupported platform.\n' + +; +; System. +; Input HL = command string +; +; Return: none +; +; Destroys: AF/BC/DE/HL +; +; Do anything with a command beginning *. In our case we feed it to +; the monitor +; +system: + inc hl ; Skip the * + ex de,hl + ld c,#0x22 ; Monitor call + ; + ; Call monitor + ; +mcall: + ex af,af' + ld a,#1 + out (0x38),a + ex af,af' + rst 0x30 + ex af,af' + xor a + out (0x38),a + ex af,af' + ret +; +; Delay approximately 10ms +; +; Destroys AF/BC/DE/HL +; +; SCM has a helper for this. +; +delay10ms: + ld c,#0x0a + ld de,#0x10 + jr mcall + + +