root/sys/arch/x68k/stand/xxboot/boot.S
| $NetBSD: boot.S,v 1.13 2020/08/16 06:43:43 isaki Exp $

|
| (1) IPL (or previous stage loader) loads first 1KB of this primary
|     bootloader to (*).  (*) is 0x2000 (from FD) or 0x2400 (from SASI/SCSI).
|
| (2) The first 1KB loads full primary bootloader (including first 1KB) from
|     the boot partition to 0x3000.  And jump to there.
|
| (3) The full primary bootloader loads the secondary bootloader known as
|     /boot from its filesystem to 0x6000.  And jump to there.
|
|       (1)         ->        (2)         ->        (3)
|  +------------+        +------------+        +------------+    0x000000
|  :            :        :            :        :            :
|  +------------+        +------------+        +------------+    (*)
|  | first 1KB  |        | first 1KB  |        | first 1KB  |
|  +------------+        +------------+        +------------+    (*)+0x400
|  :            :        :            :        :            :
|  :            :        +------------+        +------------+    0x003000
|  :            :        |full primary|        |full primary|
|  :            :        |boot loader |        |boot loader |
|  :            :        |(text+data) |        |(text+data) |
|  :            :        +------------+        +------------+    0x005000
|  :            :        |(startregs) |        |(startregs) |
|  :            :        |(bss)       |        |(bss)       |
|  :            :        +------------+        +------------+    0x006000
|  :            :        :            :        | /boot      |
|  :            :        :            :        +------------+
|  :            :        :            :        :            :
|  ~            ~        ~            ~        ~            ~
|  :            :        :            :<-SP    :            :<-SP
|  + - - - - - -+        + - - - - - -+        + - - - - - -+    0x100000
|  :            :        :(heap)      :        :(heap)      :
|  :            :        :            :        :            :
|
| The program code before first_kbyte
| - must not access any text/data labels after first_kbyte
|   (because it may not be loaded yet).
| - must access any labels before first_kbyte by PC relative addressing
|   (because this loader is assembled as starting from 0x3000 but is loaded
|   at 0x2000 or 0x2400).
| - must use RELOC() macro to access bss section (and first_kbyte as a jump
|   destination address).
|
| The program code after first_kbyte can access any labels in all sections
| directly.

#include <machine/asm.h>
#include "iocscall.h"

#define RELOC(var)      %a5@(((var)-top):W)

#define BOOT_ERROR(s)   jbsr boot_error; .asciz s; .even

#define minN    (0)
#define minC    (1)
#define minH    (2)
#define minR    (3)
#define maxN    (4)
#define maxC    (5)
#define maxH    (6)
#define maxR    (7)

                .globl  _C_LABEL(bootmain)
                .globl  _C_LABEL(startregs)
                .text

ASENTRY_NOPROFILE(start)
ASENTRY_NOPROFILE(top)
                bras    entry
                .ascii  "SHARP/"
                .ascii  "X680x0"
                .word   0x8199,0x94e6,0x82ea,0x82bd
                .word   0x8e9e,0x82c9,0x82cd,0x8cbb
                .word   0x8ec0,0x93a6,0x94f0,0x8149
msg_progname:
                | This will be printed on boot_error.  And it also roles
                | a signature in binary dump.
                | Max length of PROG(without \0) is 14 ("fdboot_ustarfs").
                .ascii  "\r\n\n"                | 3
                .ascii  PROG                    | 14
                .asciz  ": "                    | 2+1
                .org    msg_progname + 20
entry:
                jbra    disklabel_end

                | Disklabel must be placed at 0x40 and the size is 404 bytes.
                | (See LABELOFFSET in <machine/disklabel.h>)
                .org    0x40
disklabel:
                .space  404
disklabel_end:
                | At first save all initial registers for observing traces
                | of the IPL (or the previous bootloader).  At this point
                | we cannot use RELOC() yet so that use absolute addressing.
                | To prevent startregs from being cleared by subsequent bss
                | initialization, we place it out of bss area.
                moveml  %d0-%d7/%a0-%a7,startregs:W

                | Initialize the screen.  Some IPL (060turbo ROM or genuine
                | boot selector) don't initialize the screen.  It should be
                | done as early as possible.
                moveql  #0x10,%d1
                IOCS(__CRTMOD)

                | Set system stack
                swap    %d1                     | %d1 = 0x0010_0000
                moveal  %d1,%sp

                | Set base pointer.  Now we can use RELOC() macro.
                leal    TEXTADDR:W,%a5

                | Initialize bss.
                | This code limits bss less than 64KB but it's no matter.
                | The bss cannot grow more than 4KB.  See xxboot.ldscript.
                leal    RELOC(__bss_start),%a1
                movew   #_end - 1,%d0           | bss end

                subw    %a1,%d0                 | don't change this op!!
clrbss:                                         |  see chkmpu below
                clrb    %a1@+
                dbra    %d0,clrbss

                | If it boots from SCSI, %d4 has SCSI ID.
                movel   %d4,RELOC(SCSI_ID)

chkmpu:
                | Check MPU beforehand since we want to use 68020 instructions.
                | Here the above "subw %a1,%d0" = 0x9049 and %d0.w = -1 at this
                | point, so that subsequent moveb loads
                |   0x49 if MPU <= 010 (clrbss + %d0.w)
                |   0x90 if MPU >= 020 (clrbss + %d0.w*2).
                | This is a MOVE op, not a TST op because TST with pc-relative
                | is not available on 000/010.
                moveb   %pc@(clrbss-chkmpu-2:B,%d0:W:2),%d0
                jmi     mpuok
                BOOT_ERROR("MPU 68000?");
mpuok:
                |
                | Check where did I boot from.
                |
                IOCS(__BOOTINF)
                movel   %d0,RELOC(BOOT_INFO)    | save whole result

                | %d0 = 0xHHWWWWWW
                |
                | HH:     how did I boot (powersw or alarm etc)
                | WWWWWW: where did I boot from
                |  0x80...0x8f          SASI
                |  0x90...0x93          Floppy
                |  0xed0000...0xed3ffe  SRAM
                |  others               ROM (maybe SCSI)

                bfextu  %d0{#8:#8},%d1
                jne     boot_rom_ram            | ROM or SRAM
                | FALLTHROUGH                   | SASI or Floppy

boot_sasi_floppy:
                | Floppy or SASI
                cmpiw   #0x90,%d0
                jlt     boot_dev_not_supp       | SASI

                |
                | Boot from floppy
                |
boot_floppy:
                | Make PDA+MODE
                lslw    #8,%d0                  | %d0=$00009X00 (X is unit#)
                moveql  #0x70,%d1
                orw     %d0,%d1                 | %d1=$00009X70 = (PDA<<8)+MODE
                movel   %d1,RELOC(FDMODE)
check_fd_format:
                | Check fd format.
                | Obtain min & max sector # of track(cylinder) 0.
                | On x68k, we can consider only double-sided floppy.
                moveql  #0,%d2
init_loop:
                | On 1st time, clear %d3-%d5 with zero.
                | On 2nd time, initialize %d3-%d5 with first %d2.
                movel   %d2,%d3                 | %d3: initial NCHR
                movel   %d2,%d4                 | %d4: minimum NCHR
                movel   %d2,%d5                 | %d5: maximum NCHR
loop:
                | B_READID with MSB of %d2 set obtains detected CHRN to %d2.
                moveql  #1,%d2                  | %d2 = 0x00000001
                rorl    #1,%d2                  | %d2 = 0x80000000
                IOCS(__B_READID)
                                                | %d2 = 0xCCHHRRNN
                rorl    #8,%d2                  | %d2 = 0xNNCCHHRR

                | On 1st time, goto init_loop with %d2 (%d2 is not zero).
                | On 2nd time, fall through because %d3 is not zero.
                tstl    %d3
                jeq     init_loop

                cmpl    %d4,%d2                 | if (%d2 < %d4)
                jge     1f                      |
                movel   %d2,%d4                 |  min = %d2
1:
                cmpl    %d5,%d2                 | if (%d2 > %d5)
                jle     1f                      |
                movel   %d2,%d5                 |  max = %d2
1:
                cmpl    %d3,%d2                 | if (%d2 == %d3) break
                jne     loop

                | Assume 2HD
                oriw    #0x0100,%d5             | FDSEC.maxsec.H = 1
                moveml  %d4-%d5,RELOC(FDSEC)    | Store
                | end of check_fd_format

                | read 8KB myself from floppy
                                                | %d1: (PDA<<8)+MODE already
                movel   %d4,%d2                 | %d2: read pos = first sector
                moveql  #0x20,%d3               | %d3: read bytes = (0x20 << 8)
                lsll    #8,%d3                  |  = 0x2000 = 8192
                leal    %a5@,%a1                | %a1: dest buffer
                IOCS(__B_READ)

                | Jump to full parimary loader
                jmp     RELOC(first_kbyte)

boot_rom_ram:
                | ROM(SCSI) or SRAM
                cmpib   #0xed,%d1
                jeq     boot_dev_not_supp       | SRAM

                |
                | Boot from SCSI
                |
boot_scsi:
                | get block length of the SCSI disk
                leal    RELOC(SCSI_CAP),%a1
                SCSIIOCS(__S_READCAP)
                tstl    %d0
                jeq     boot_scsi1
                BOOT_ERROR("READCAP failed")
boot_scsi1:
                movel   RELOC(SCSI_CAP+4),%d0   | %d0 = blocksize in bytes
                lsrl    #2,%d0                  | %d0 = blocksize in longword
                moveql  #25,%d5
                bfffo   %d0{#0:#32},%d1         | 25:256 24:512 23:1024 22:2048
                subl    %d1,%d5                 |  0:256  1:512  2:1024  3:2048
                movel   %d5,RELOC(SCSI_BLKLEN)  | %d5 = sector length index

                | Find out the start position of the boot partition.
                | There seems to be no interface or consensus about this and
                | so that we would have to do it heuristicly.
                |
                | ROM firmware:
                |       pass read pos (in block #, aka sector #) in %d2.
                |       Human68k-style partition table does not exist.
                |       %d2 is 4 at the maximum.
                | SCSI IPLs (genuine and SxSI):
                |       pass read pos (in kilobytes) in %d2.
                |       %d2 is bigger than 0x20.
                |       partition table on the memory is destroyed.
                | BOOT MENU Ver.2.22:
                |       passes partition table entry address in %a0.
                |       %d2 is cleared to zero
                | No other IPLs are supported.

                tstl    %d2
                jne     1f
                | If no information in %d2, probably from BOOT MENU.
                | %a0 points the on-memory partition table entry.
                movel   %a0@(0x0008),%d2        | %d2 = pos in kbyte
1:
                moveql  #0x20,%d3
                cmpl    %d3,%d2
                jcs     1f                      | jump if %d2 > 0x20
                | SCSI IPL or BOOT MENU.
                | At this point, %d2 is pos in kbyte in all cases.
                lsll    #8,%d2                  | %d2 = pos in longword
                divul   %d0,%d2                 | %d2 = pos in sector
1:
                | At this point, %d2 is pos in sector in all cases.
                | TDSIZE = 8192, TDSIZE / 4 = 0x800 = (0x20 << 6).
                lsll    #6,%d3                  | %d3 = TDSIZE in longword
                divul   %d0,%d3                 | %d0 = TDSIZE in sector
                | Read full primary bootloader
                moveal  %a5,%a1                 | %a1 = dest buffer
                jbsr    scsiread

                | Selected start sector should not <= 4.  There should be
                | partition table.  If so, repoints to zero(?).
                moveql  #5,%d0
                cmpl    %d0,%d2
                bcc     1f
                moveql  #0,%d2
1:
                movel   %d2,RELOC(SCSI_PARTTOP)

                | Jump to full parimary loader
                jmp     RELOC(first_kbyte)

|
| scsiread
|       Read SCSI disk using __S_READ as possible.  If __S_READ cannot be
|       used (due to read length or offset), use __S_READEXT instead.
| input:
|       %d2.l: pos in sector
|       %d3.l: len in sector (must be < 65536)
|       %d4.l: target SCSI ID
|       %d5.l: sector length index (0:256, 1:512, 2:1024, 3:2048, ...)
|       %a1.l: buffer address
| destroy:
|       %d0,%d1
scsiread:
                | if (len >= 256 || pos + len >= 0x200000)
                |   use READEXT
                | else
                |   use READ

                moveql  #__S_READEXT,%d1

                cmpiw   #256,%d3
                jge     scsiread_core           | if (d3 >= 256) use READEXT

                movel   %d2,%d0
                addl    %d3,%d0                 | %d0 = pos + len
                jcs     scsiread_core           | if overflow, use READEXT
                bftst   %d0{#0:#11}             | if (pos + len >= 0x200000)
                jne     scsiread_core           |  use REAEXT

                moveql  #__S_READ,%d1           | else use READ
scsiread_core:
                IOCS(__SCSIDRV)
                rts

boot_dev_not_supp:
                BOOT_ERROR("not supported device");

|
| void __dead BOOT_ERROR(const char *msg);
|       Print an error message, wait for key press, and reboot.
|       Called from C.
ENTRY_NOPROFILE(BOOT_ERROR)
                addql   #4,%sp                  | throw away return address
                | FALLTHROUGH
|
| BOOT_ERROR(msg)
|       Print an error message, wait for key press, and reboot.
|       Called from asm.
boot_error:
                leal    %pc@(msg_progname),%a1
                IOCS(__B_PRINT)
                moveal  %sp@+,%a1
                IOCS(__B_PRINT)
ENTRY_NOPROFILE(exit)
ENTRY_NOPROFILE(_rtt)
                leal    %pc@(msg_reboot),%a1
                IOCS(__B_PRINT)

                | wait for a key press (or release of a modifier)
                IOCS(__B_KEYINP)

                | issue software reset
                trap    #10
                | NOTREACHED
msg_reboot:
                .asciz  "\r\n[Hit key to reboot]"
                .even

                .globl  first_kbyte
first_kbyte:
|--------------------------------------------------------------------------
|
#if defined(SELFTEST)
                jbsr    selftest_ashldi3
                jbsr    selftest_ashrdi3
                jbsr    selftest_memcmp
                jbsr    selftest_memmove
                jbsr    selftest_memset
#endif

                jmp     _C_LABEL(bootmain)
                | NOTREACHED

|
| uint32_t badbadd(void *addr)
|       returns 1 if reading addr occurs bus error.  Otherwise it returns 0.
ENTRY_NOPROFILE(badbaddr)
                leal    0x0008:W,%a1            | bus error vector
                moveql  #1,%d0
                leal    %pc@(badbaddr1),%a0
                movew   %sr,%sp@-
                oriw    #0x0700,%sr             | keep out interrupts
                movel   %a1@,%sp@-
                movel   %a0,%a1@                | set bus error vector
                movel   %sp,%d1                 | save sp
                moveal  %sp@(10),%a0
                tstb    %a0@                    | try read...
                moveql  #0,%d0                  | this is skipped on bus error
badbaddr1:
                moveal  %d1,%sp                 | restore sp
                movel   %sp@+,%a1@
                movew   %sp@+,%sr
                rts

|
| int raw_read(uint32_t blkpos, uint32_t bytelen, void *buf)
|       blkpos:  read start position in 512 byte block unit (always?).
|       bytelen: read length in bytes.
|                caller already avoids bytelen == 0 so that no checks here.
|                must be a multiple of sector size on scsi.
|       buf:     destination buffer address
|
ENTRY_NOPROFILE(raw_read)
                moveal  %sp,%a1
                moveml  %d2-%d7/%a2-%a6,%sp@-
                moveml  %a1@,%d0/%d2-%d3/%a1    | %d0 (return address)
                                                | %d2 blkpos
                                                | %d3 bytelen
                                                | %a1 buf
                | At this point boot device is either floppy or SCSI.
                tstb    %pc@(BOOT_INFO+1)
                jeq     raw_read_floppy
                | FALLTHROUGH

raw_read_scsi:
                                                | %d2 = pos from device top
                                                |  in 512 bytes/block
                lsll    #1,%d2                  | %d2 = in 256 bytes/block
                movel   %pc@(SCSI_BLKLEN),%d5   | %d5 = sector length index
                lsrl    %d5,%d2                 | %d2 = pos from device top
                                                |  in media sector size

                divull  %pc@(SCSI_CAP+4),%d0:%d3| %d3 = bytelen / sectsize
                                                | %d0 = bytelen % sectsize
                tstl    %d0
                jeq     .Lraw1
                BOOT_ERROR("Err1")              | ASSERT(bytelen%sectsize==0)
.Lraw1:
                movel   %pc@(SCSI_ID),%d4       | %d4 = SCSI ID
                jbsr    scsiread

raw_read_exit:
                moveml  %sp@+,%d2-%d7/%a2-%a6
                rts

raw_read_floppy:
                | nhead = FDSEC.maxsec.H - FDSEC.minsec.H + 1
                |       = 2;
                | nsect = FDSEC.maxsec.R - FDSEC.minsec.R + 1;
                |
                | sect = (blkpos % nsect) + FDSEC.minsec.R;
                | head = ((blkpos / nsect) % nhead) + FDSEC.minsec.H;
                | cyl  = ((blkpos / nsect) / nhead) + FDSEC.minsec.C;
                |
                | NCHR = (FDSEC.minsec.N << 24) |
                |      (cyl << 16) |
                |      (head << 8) |
                |      sect;

                | calc nsect.
                moveql  #1,%d0                  | %d0 = 1
                addb    %pc@(FDSEC+maxR),%d0    | %d0 = 1 + maxsec.R
                subb    %pc@(FDSEC+minR),%d0    | %d0 = 1 + maxsec.R - minsec.R
                                                |     = nsect

                | Convert blkpos to N/C/H/R.
                divuw   %d0,%d2                 | %d2.hw = blkpos % nsect
                                                | %d2.lw = blkpos / nsect
                | Here, %d2.hw becomes sector number and .lw becomes cyl+head.
                | %d2.lw = %0000_0000_CCCC_CCCH in binary form.  LSB of
                | (blkpos / nsect) is head number because we support only
                | double-sided floppy here.
                                                | %d2.w = %0000_0000_CCCC_CCCH
                lslw    #7,%d2                  | %d2.w = %0CCC_CCCC_H000_0000
                lsrb    #7,%d2                  | %d2.w = %0CCC_CCCC_0000_000H
                                                | i.e,
                                                | %d2 = $00rrCCHH
                swap    %d2                     | %d2 = $CCHH00rr
                lslw    #8,%d2                  | %d2 = $CCHHrr00
                | two bytes from odd FDSEC+minR is (minR << 8 | maxN) and
                | minN == maxN always.
                addw    %pc@(FDSEC+minR),%d2    | %d2 = $CCHHRRNN
                rorl    #8,%d2                  | %d2 = $NNCCHHRR

                movel   %pc@(FDMODE),%d1        | %d1 = PDA+MODE
                IOCS(__B_READ)
                andil   #0xf8ffff00,%d0         | Check status (must be zero)
                jeq     raw_read_exit
                BOOT_ERROR("B_READ failed");

|
| BSS
|
                BSS(BOOT_INFO, 4)       | whole result of IOCS BOOTINF

                BSS(FDMODE, 4)
                BSS(FDSEC, 8)           | +0: (minN) sector length
                                        | +1: (minC) track number
                                        | +2: (minH) head
                                        | +3: (minR) sector number
                                        | +4: (maxN) sector length
                                        | +5: (maxC) track number
                                        | +6: (maxH) head
                                        | +7: (maxR) sector number

                BSS(SCSI_ID, 4)         | SCSI ID, if booted from SCSI
                BSS(SCSI_CAP, 8)        | result of SCSI READCAP
                                        |  +0.L: total number of logical blocks
                                        |  +4.L: block length in bytes
                BSS(SCSI_PARTTOP, 4)    | top sector # of this partition
                BSS(SCSI_BLKLEN ,4)     | sector length index
                                        |  0:256, 1:512, 2:1024, 3:2048, ..