sc114: first draft of a real boot loader for Fuzix
authorAlan Cox <alan@linux.intel.com>
Sat, 5 Jan 2019 21:05:24 +0000 (21:05 +0000)
committerAlan Cox <alan@linux.intel.com>
Sat, 5 Jan 2019 21:05:24 +0000 (21:05 +0000)
Kernel/platform-sc114/loader.s [new file with mode: 0644]

diff --git a/Kernel/platform-sc114/loader.s b/Kernel/platform-sc114/loader.s
new file mode 100644 (file)
index 0000000..9984f73
--- /dev/null
@@ -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
+
+
+