1| $NetBSD: boot.S,v 1.13 2020/08/16 06:43:43 isaki Exp $
2
3|
4| (1) IPL (or previous stage loader) loads first 1KB of this primary
5|     bootloader to (*).  (*) is 0x2000 (from FD) or 0x2400 (from SASI/SCSI).
6|
7| (2) The first 1KB loads full primary bootloader (including first 1KB) from
8|     the boot partition to 0x3000.  And jump to there.
9|
10| (3) The full primary bootloader loads the secondary bootloader known as
11|     /boot from its filesystem to 0x6000.  And jump to there.
12|
13|       (1)         ->        (2)         ->        (3)
14|  +------------+        +------------+        +------------+    0x000000
15|  :            :        :            :        :            :
16|  +------------+        +------------+        +------------+    (*)
17|  | first 1KB  |        | first 1KB  |        | first 1KB  |
18|  +------------+        +------------+        +------------+    (*)+0x400
19|  :            :        :            :        :            :
20|  :            :        +------------+        +------------+    0x003000
21|  :            :        |full primary|        |full primary|
22|  :            :        |boot loader |        |boot loader |
23|  :            :        |(text+data) |        |(text+data) |
24|  :            :        +------------+        +------------+    0x005000
25|  :            :        |(startregs) |        |(startregs) |
26|  :            :        |(bss)       |        |(bss)       |
27|  :            :        +------------+        +------------+    0x006000
28|  :            :        :            :        | /boot      |
29|  :            :        :            :        +------------+
30|  :            :        :            :        :            :
31|  ~            ~        ~            ~        ~            ~
32|  :            :        :            :<-SP    :            :<-SP
33|  + - - - - - -+        + - - - - - -+        + - - - - - -+    0x100000
34|  :            :        :(heap)      :        :(heap)      :
35|  :            :        :            :        :            :
36|
37| The program code before first_kbyte
38| - must not access any text/data labels after first_kbyte
39|   (because it may not be loaded yet).
40| - must access any labels before first_kbyte by PC relative addressing
41|   (because this loader is assembled as starting from 0x3000 but is loaded
42|   at 0x2000 or 0x2400).
43| - must use RELOC() macro to access bss section (and first_kbyte as a jump
44|   destination address).
45|
46| The program code after first_kbyte can access any labels in all sections
47| directly.
48
49#include <machine/asm.h>
50#include "iocscall.h"
51
52#define RELOC(var)	%a5@(((var)-top):W)
53
54#define BOOT_ERROR(s)	jbsr boot_error; .asciz s; .even
55
56#define minN	(0)
57#define minC	(1)
58#define minH	(2)
59#define minR	(3)
60#define maxN	(4)
61#define maxC	(5)
62#define maxH	(6)
63#define maxR	(7)
64
65		.globl	_C_LABEL(bootmain)
66		.globl	_C_LABEL(startregs)
67		.text
68
69ASENTRY_NOPROFILE(start)
70ASENTRY_NOPROFILE(top)
71		bras	entry
72		.ascii	"SHARP/"
73		.ascii	"X680x0"
74		.word	0x8199,0x94e6,0x82ea,0x82bd
75		.word	0x8e9e,0x82c9,0x82cd,0x8cbb
76		.word	0x8ec0,0x93a6,0x94f0,0x8149
77msg_progname:
78		| This will be printed on boot_error.  And it also roles
79		| a signature in binary dump.
80		| Max length of PROG(without \0) is 14 ("fdboot_ustarfs").
81		.ascii	"\r\n\n"		| 3
82		.ascii	PROG			| 14
83		.asciz	": "			| 2+1
84		.org	msg_progname + 20
85entry:
86		jbra	disklabel_end
87
88		| Disklabel must be placed at 0x40 and the size is 404 bytes.
89		| (See LABELOFFSET in <machine/disklabel.h>)
90		.org	0x40
91disklabel:
92		.space	404
93disklabel_end:
94		| At first save all initial registers for observing traces
95		| of the IPL (or the previous bootloader).  At this point
96		| we cannot use RELOC() yet so that use absolute addressing.
97		| To prevent startregs from being cleared by subsequent bss
98		| initialization, we place it out of bss area.
99		moveml	%d0-%d7/%a0-%a7,startregs:W
100
101		| Initialize the screen.  Some IPL (060turbo ROM or genuine
102		| boot selector) don't initialize the screen.  It should be
103		| done as early as possible.
104		moveql	#0x10,%d1
105		IOCS(__CRTMOD)
106
107		| Set system stack
108		swap	%d1			| %d1 = 0x0010_0000
109		moveal	%d1,%sp
110
111		| Set base pointer.  Now we can use RELOC() macro.
112		leal	TEXTADDR:W,%a5
113
114		| Initialize bss.
115		| This code limits bss less than 64KB but it's no matter.
116		| The bss cannot grow more than 4KB.  See xxboot.ldscript.
117		leal	RELOC(__bss_start),%a1
118		movew	#_end - 1,%d0		| bss end
119
120		subw	%a1,%d0			| don't change this op!!
121clrbss:						|  see chkmpu below
122		clrb	%a1@+
123		dbra	%d0,clrbss
124
125		| If it boots from SCSI, %d4 has SCSI ID.
126		movel	%d4,RELOC(SCSI_ID)
127
128chkmpu:
129		| Check MPU beforehand since we want to use 68020 instructions.
130		| Here the above "subw %a1,%d0" = 0x9049 and %d0.w = -1 at this
131		| point, so that subsequent moveb loads
132		|   0x49 if MPU <= 010 (clrbss + %d0.w)
133		|   0x90 if MPU >= 020 (clrbss + %d0.w*2).
134		| This is a MOVE op, not a TST op because TST with pc-relative
135		| is not available on 000/010.
136		moveb	%pc@(clrbss-chkmpu-2:B,%d0:W:2),%d0
137		jmi	mpuok
138		BOOT_ERROR("MPU 68000?");
139mpuok:
140		|
141		| Check where did I boot from.
142		|
143		IOCS(__BOOTINF)
144		movel	%d0,RELOC(BOOT_INFO)	| save whole result
145
146		| %d0 = 0xHHWWWWWW
147		|
148		| HH:     how did I boot (powersw or alarm etc)
149		| WWWWWW: where did I boot from
150		|  0x80...0x8f		SASI
151		|  0x90...0x93		Floppy
152		|  0xed0000...0xed3ffe	SRAM
153		|  others		ROM (maybe SCSI)
154
155		bfextu	%d0{#8:#8},%d1
156		jne	boot_rom_ram		| ROM or SRAM
157		| FALLTHROUGH			| SASI or Floppy
158
159boot_sasi_floppy:
160		| Floppy or SASI
161		cmpiw	#0x90,%d0
162		jlt	boot_dev_not_supp	| SASI
163
164		|
165		| Boot from floppy
166		|
167boot_floppy:
168		| Make PDA+MODE
169		lslw	#8,%d0			| %d0=$00009X00 (X is unit#)
170		moveql	#0x70,%d1
171		orw	%d0,%d1			| %d1=$00009X70 = (PDA<<8)+MODE
172		movel	%d1,RELOC(FDMODE)
173check_fd_format:
174		| Check fd format.
175		| Obtain min & max sector # of track(cylinder) 0.
176		| On x68k, we can consider only double-sided floppy.
177		moveql	#0,%d2
178init_loop:
179		| On 1st time, clear %d3-%d5 with zero.
180		| On 2nd time, initialize %d3-%d5 with first %d2.
181		movel	%d2,%d3			| %d3: initial NCHR
182		movel	%d2,%d4			| %d4: minimum NCHR
183		movel	%d2,%d5			| %d5: maximum NCHR
184loop:
185		| B_READID with MSB of %d2 set obtains detected CHRN to %d2.
186		moveql	#1,%d2			| %d2 = 0x00000001
187		rorl	#1,%d2			| %d2 = 0x80000000
188		IOCS(__B_READID)
189						| %d2 = 0xCCHHRRNN
190		rorl	#8,%d2			| %d2 = 0xNNCCHHRR
191
192		| On 1st time, goto init_loop with %d2 (%d2 is not zero).
193		| On 2nd time, fall through because %d3 is not zero.
194		tstl	%d3
195		jeq	init_loop
196
197		cmpl	%d4,%d2			| if (%d2 < %d4)
198		jge	1f			|
199		movel	%d2,%d4			|  min = %d2
2001:
201		cmpl	%d5,%d2			| if (%d2 > %d5)
202		jle	1f			|
203		movel	%d2,%d5			|  max = %d2
2041:
205		cmpl	%d3,%d2			| if (%d2 == %d3) break
206		jne	loop
207
208		| Assume 2HD
209		oriw	#0x0100,%d5		| FDSEC.maxsec.H = 1
210		moveml	%d4-%d5,RELOC(FDSEC)	| Store
211		| end of check_fd_format
212
213		| read 8KB myself from floppy
214						| %d1: (PDA<<8)+MODE already
215		movel	%d4,%d2			| %d2: read pos = first sector
216		moveql	#0x20,%d3		| %d3: read bytes = (0x20 << 8)
217		lsll	#8,%d3			|  = 0x2000 = 8192
218		leal	%a5@,%a1		| %a1: dest buffer
219		IOCS(__B_READ)
220
221		| Jump to full parimary loader
222		jmp	RELOC(first_kbyte)
223
224boot_rom_ram:
225		| ROM(SCSI) or SRAM
226		cmpib	#0xed,%d1
227		jeq	boot_dev_not_supp	| SRAM
228
229		|
230		| Boot from SCSI
231		|
232boot_scsi:
233		| get block length of the SCSI disk
234		leal	RELOC(SCSI_CAP),%a1
235		SCSIIOCS(__S_READCAP)
236		tstl	%d0
237		jeq	boot_scsi1
238		BOOT_ERROR("READCAP failed")
239boot_scsi1:
240		movel	RELOC(SCSI_CAP+4),%d0	| %d0 = blocksize in bytes
241		lsrl	#2,%d0			| %d0 = blocksize in longword
242		moveql	#25,%d5
243		bfffo	%d0{#0:#32},%d1		| 25:256 24:512 23:1024 22:2048
244		subl	%d1,%d5			|  0:256  1:512  2:1024  3:2048
245		movel	%d5,RELOC(SCSI_BLKLEN)	| %d5 = sector length index
246
247		| Find out the start position of the boot partition.
248		| There seems to be no interface or consensus about this and
249		| so that we would have to do it heuristicly.
250		|
251		| ROM firmware:
252		|	pass read pos (in block #, aka sector #) in %d2.
253		|	Human68k-style partition table does not exist.
254		|	%d2 is 4 at the maximum.
255		| SCSI IPLs (genuine and SxSI):
256		|	pass read pos (in kilobytes) in %d2.
257		|	%d2 is bigger than 0x20.
258		|	partition table on the memory is destroyed.
259		| BOOT MENU Ver.2.22:
260		|	passes partition table entry address in %a0.
261		|	%d2 is cleared to zero
262		| No other IPLs are supported.
263
264		tstl	%d2
265		jne	1f
266		| If no information in %d2, probably from BOOT MENU.
267		| %a0 points the on-memory partition table entry.
268		movel	%a0@(0x0008),%d2	| %d2 = pos in kbyte
2691:
270		moveql	#0x20,%d3
271		cmpl	%d3,%d2
272		jcs	1f			| jump if %d2 > 0x20
273		| SCSI IPL or BOOT MENU.
274		| At this point, %d2 is pos in kbyte in all cases.
275		lsll	#8,%d2			| %d2 = pos in longword
276		divul	%d0,%d2			| %d2 = pos in sector
2771:
278		| At this point, %d2 is pos in sector in all cases.
279		| TDSIZE = 8192, TDSIZE / 4 = 0x800 = (0x20 << 6).
280		lsll	#6,%d3			| %d3 = TDSIZE in longword
281		divul	%d0,%d3			| %d0 = TDSIZE in sector
282		| Read full primary bootloader
283		moveal	%a5,%a1			| %a1 = dest buffer
284		jbsr	scsiread
285
286		| Selected start sector should not <= 4.  There should be
287		| partition table.  If so, repoints to zero(?).
288		moveql	#5,%d0
289		cmpl	%d0,%d2
290		bcc	1f
291		moveql	#0,%d2
2921:
293		movel	%d2,RELOC(SCSI_PARTTOP)
294
295		| Jump to full parimary loader
296		jmp	RELOC(first_kbyte)
297
298|
299| scsiread
300|	Read SCSI disk using __S_READ as possible.  If __S_READ cannot be
301|	used (due to read length or offset), use __S_READEXT instead.
302| input:
303|	%d2.l: pos in sector
304|	%d3.l: len in sector (must be < 65536)
305|	%d4.l: target SCSI ID
306|	%d5.l: sector length index (0:256, 1:512, 2:1024, 3:2048, ...)
307|	%a1.l: buffer address
308| destroy:
309|	%d0,%d1
310scsiread:
311		| if (len >= 256 || pos + len >= 0x200000)
312		|   use READEXT
313		| else
314		|   use READ
315
316		moveql	#__S_READEXT,%d1
317
318		cmpiw	#256,%d3
319		jge	scsiread_core		| if (d3 >= 256) use READEXT
320
321		movel	%d2,%d0
322		addl	%d3,%d0			| %d0 = pos + len
323		jcs	scsiread_core		| if overflow, use READEXT
324		bftst	%d0{#0:#11}		| if (pos + len >= 0x200000)
325		jne	scsiread_core		|  use REAEXT
326
327		moveql	#__S_READ,%d1		| else use READ
328scsiread_core:
329		IOCS(__SCSIDRV)
330		rts
331
332boot_dev_not_supp:
333		BOOT_ERROR("not supported device");
334
335|
336| void __dead BOOT_ERROR(const char *msg);
337|	Print an error message, wait for key press, and reboot.
338|	Called from C.
339ENTRY_NOPROFILE(BOOT_ERROR)
340		addql	#4,%sp			| throw away return address
341		| FALLTHROUGH
342|
343| BOOT_ERROR(msg)
344|	Print an error message, wait for key press, and reboot.
345|	Called from asm.
346boot_error:
347		leal	%pc@(msg_progname),%a1
348		IOCS(__B_PRINT)
349		moveal	%sp@+,%a1
350		IOCS(__B_PRINT)
351ENTRY_NOPROFILE(exit)
352ENTRY_NOPROFILE(_rtt)
353		leal	%pc@(msg_reboot),%a1
354		IOCS(__B_PRINT)
355
356		| wait for a key press (or release of a modifier)
357		IOCS(__B_KEYINP)
358
359		| issue software reset
360		trap	#10
361		| NOTREACHED
362msg_reboot:
363		.asciz	"\r\n[Hit key to reboot]"
364		.even
365
366		.globl	first_kbyte
367first_kbyte:
368|--------------------------------------------------------------------------
369|
370#if defined(SELFTEST)
371		jbsr	selftest_ashldi3
372		jbsr	selftest_ashrdi3
373		jbsr	selftest_memcmp
374		jbsr	selftest_memmove
375		jbsr	selftest_memset
376#endif
377
378		jmp	_C_LABEL(bootmain)
379		| NOTREACHED
380
381|
382| uint32_t badbadd(void *addr)
383|	returns 1 if reading addr occurs bus error.  Otherwise it returns 0.
384ENTRY_NOPROFILE(badbaddr)
385		leal	0x0008:W,%a1		| bus error vector
386		moveql	#1,%d0
387		leal	%pc@(badbaddr1),%a0
388		movew	%sr,%sp@-
389		oriw	#0x0700,%sr		| keep out interrupts
390		movel	%a1@,%sp@-
391		movel	%a0,%a1@		| set bus error vector
392		movel	%sp,%d1			| save sp
393		moveal	%sp@(10),%a0
394		tstb	%a0@			| try read...
395		moveql	#0,%d0			| this is skipped on bus error
396badbaddr1:
397		moveal	%d1,%sp			| restore sp
398		movel	%sp@+,%a1@
399		movew	%sp@+,%sr
400		rts
401
402|
403| int raw_read(uint32_t blkpos, uint32_t bytelen, void *buf)
404|	blkpos:  read start position in 512 byte block unit (always?).
405|	bytelen: read length in bytes.
406|	         caller already avoids bytelen == 0 so that no checks here.
407|	         must be a multiple of sector size on scsi.
408|	buf:     destination buffer address
409|
410ENTRY_NOPROFILE(raw_read)
411		moveal	%sp,%a1
412		moveml	%d2-%d7/%a2-%a6,%sp@-
413		moveml	%a1@,%d0/%d2-%d3/%a1	| %d0 (return address)
414						| %d2 blkpos
415						| %d3 bytelen
416						| %a1 buf
417		| At this point boot device is either floppy or SCSI.
418		tstb	%pc@(BOOT_INFO+1)
419		jeq	raw_read_floppy
420		| FALLTHROUGH
421
422raw_read_scsi:
423						| %d2 = pos from device top
424						|  in 512 bytes/block
425		lsll	#1,%d2			| %d2 = in 256 bytes/block
426		movel	%pc@(SCSI_BLKLEN),%d5	| %d5 = sector length index
427		lsrl	%d5,%d2			| %d2 = pos from device top
428						|  in media sector size
429
430		divull	%pc@(SCSI_CAP+4),%d0:%d3| %d3 = bytelen / sectsize
431						| %d0 = bytelen % sectsize
432		tstl	%d0
433		jeq	.Lraw1
434		BOOT_ERROR("Err1")		| ASSERT(bytelen%sectsize==0)
435.Lraw1:
436		movel	%pc@(SCSI_ID),%d4	| %d4 = SCSI ID
437		jbsr	scsiread
438
439raw_read_exit:
440		moveml	%sp@+,%d2-%d7/%a2-%a6
441		rts
442
443raw_read_floppy:
444		| nhead = FDSEC.maxsec.H - FDSEC.minsec.H + 1
445		|       = 2;
446		| nsect = FDSEC.maxsec.R - FDSEC.minsec.R + 1;
447		|
448		| sect = (blkpos % nsect) + FDSEC.minsec.R;
449		| head = ((blkpos / nsect) % nhead) + FDSEC.minsec.H;
450		| cyl  = ((blkpos / nsect) / nhead) + FDSEC.minsec.C;
451		|
452		| NCHR = (FDSEC.minsec.N << 24) |
453		|      (cyl << 16) |
454		|      (head << 8) |
455		|      sect;
456
457		| calc nsect.
458		moveql	#1,%d0			| %d0 = 1
459		addb	%pc@(FDSEC+maxR),%d0	| %d0 = 1 + maxsec.R
460		subb	%pc@(FDSEC+minR),%d0	| %d0 = 1 + maxsec.R - minsec.R
461						|     = nsect
462
463		| Convert blkpos to N/C/H/R.
464		divuw	%d0,%d2			| %d2.hw = blkpos % nsect
465						| %d2.lw = blkpos / nsect
466		| Here, %d2.hw becomes sector number and .lw becomes cyl+head.
467		| %d2.lw = %0000_0000_CCCC_CCCH in binary form.  LSB of
468		| (blkpos / nsect) is head number because we support only
469		| double-sided floppy here.
470						| %d2.w = %0000_0000_CCCC_CCCH
471		lslw	#7,%d2			| %d2.w = %0CCC_CCCC_H000_0000
472		lsrb	#7,%d2			| %d2.w = %0CCC_CCCC_0000_000H
473						| i.e,
474						| %d2 = $00rrCCHH
475		swap	%d2			| %d2 = $CCHH00rr
476		lslw	#8,%d2			| %d2 = $CCHHrr00
477		| two bytes from odd FDSEC+minR is (minR << 8 | maxN) and
478		| minN == maxN always.
479		addw	%pc@(FDSEC+minR),%d2	| %d2 = $CCHHRRNN
480		rorl	#8,%d2			| %d2 = $NNCCHHRR
481
482		movel	%pc@(FDMODE),%d1	| %d1 = PDA+MODE
483		IOCS(__B_READ)
484		andil	#0xf8ffff00,%d0		| Check status (must be zero)
485		jeq	raw_read_exit
486		BOOT_ERROR("B_READ failed");
487
488|
489| BSS
490|
491		BSS(BOOT_INFO, 4)	| whole result of IOCS BOOTINF
492
493		BSS(FDMODE, 4)
494		BSS(FDSEC, 8)		| +0: (minN) sector length
495					| +1: (minC) track number
496					| +2: (minH) head
497					| +3: (minR) sector number
498					| +4: (maxN) sector length
499					| +5: (maxC) track number
500					| +6: (maxH) head
501					| +7: (maxR) sector number
502
503		BSS(SCSI_ID, 4)		| SCSI ID, if booted from SCSI
504		BSS(SCSI_CAP, 8)	| result of SCSI READCAP
505					|  +0.L: total number of logical blocks
506					|  +4.L: block length in bytes
507		BSS(SCSI_PARTTOP, 4)	| top sector # of this partition
508		BSS(SCSI_BLKLEN ,4)	| sector length index
509					|  0:256, 1:512, 2:1024, 3:2048, ..
510