xref: /dragonfly/stand/boot/pc32/boot0/boot0.S (revision 7d3e9a5b)
1/*
2 * Copyright (c) 2003,2004 The DragonFly Project.  All rights reserved.
3 *
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in
15 *    the documentation and/or other materials provided with the
16 *    distribution.
17 * 3. Neither the name of The DragonFly Project nor the names of its
18 *    contributors may be used to endorse or promote products derived
19 *    from this software without specific, prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 *
34 * Copyright (c) 1998 Robert Nordier
35 * All rights reserved.
36 *
37 * Redistribution and use in source and binary forms are freely
38 * permitted provided that the above copyright notice and this
39 * paragraph and the following disclaimer are duplicated in all
40 * such forms.
41 *
42 * This software is provided "AS IS" and without any express or
43 * implied warranties, including, without limitation, the implied
44 * warranties of merchantability and fitness for a particular
45 * purpose.
46 *
47 *
48 * $FreeBSD: src/sys/boot/i386/boot0/boot0.s,v 1.26 2003/06/01 20:41:04 obrien Exp $
49 */
50
51#include "../bootasm.h"
52
53#ifdef SIO
54/* ... using a serial console on COM1. */
55#endif
56
57		/*
58		 * A 512-byte boot manager.
59		 */
60		.set PRT_OFF,0x1be		# Partition table
61
62		.set TBL0SZ,0x3 		# Table 0 size
63		.set TBL1SZ,0xb 		# Table 1 size
64
65		.set MAGIC,0xaa55		# Magic: bootable
66		.set B0MAGIC,0xbb66		# Identification
67
68		.set KEY_ENTER,0x1c		# Enter key scan code
69		.set KEY_F1,0x3b		# F1 key scan code
70		.set KEY_1,0x02			# #1 key scan code
71
72		.set ASCII_BEL,0x07		# ASCII code for <BEL>
73		.set ASCII_CR,0x0D		# ASCII code for <CR>
74
75		/*
76		 * Addresses in the sector of embedded data values.  Accessed
77		 * with negative offsets from the end of the relocated sector
78		 * (%ebp).
79		 *
80		 * Note that %ebp is the base of our variable space and
81		 * points at the end of the sector (base + 0x200).  The
82		 * fake partition and menu option is thus stored in the
83		 * memory just after the boot0 sector.
84		 */
85		.set _NXTDRV,-0x48		# Next drive
86		.set _OPT,-0x47 		# Default option
87		.set _SETDRV,-0x46		# Drive to force
88		.set _FLAGS,-0x45		# Flags
89		.set _TICKS,-0x44		# Timeout ticks
90		.set _FAKE,0x0			# Fake partition entry
91		.set _MNUOPT,0xc		# Menu options
92
93		.set _SECTOR_FIELD_OFF,-0xe	# Offset to Sector field
94						# in the fake partition
95		.set _DATA_OFF,PRT_OFF+_SECTOR_FIELD_OFF
96						# Offset to boot0 variables
97						# from partition table
98
99		.globl start			# Entry point
100		.code16				# This runs in real mode
101
102		/*
103		 * Initialise segments and registers to known values.
104		 * segments start at 0.  The stack is immediately below the
105		 * address we were loaded to.
106		 */
107start:		cld				# String ops inc
108		xorw %ax,%ax			# Zero
109		movw %ax,%es			# Address
110		movw %ax,%ds			#  data
111		movw %ax,%ss			# Set up
112		movw $MEM_BIOS_LADDR,%sp	#  stack
113
114		/*
115		 * Copy this code to the address it was linked for.
116		 * Base address after relocation is - 0x600.
117		 */
118		movw %sp,%si			# Source
119		movw $start,%di			# Destination
120		movw $0x100,%cx			# Word count
121		rep				# Relocate
122		movsw				#  code
123
124		/*
125		 * Set address for variable space beyond code, and clear it.
126		 * Notice that this is also used to point to the values
127		 * embedded in the block, by using negative offsets.
128		 */
129		movw %di,%bp			# Address variables
130		movb $0x8,%cl			# Words to clear
131		rep				# Zero
132		stosw				#  them
133
134		/*
135		 * Set C:H:S to 0:0:1 and relocate to the new copy of
136		 * the code.  Do not make assumptions with regard to
137		 * a relative-PC near jump capability.
138		 */
139		incb _SECTOR_FIELD_OFF(%di)	# Sector number
140		pushw $main			# Jump to relocated code
141		retw
142
143main:
144#if defined(SIO) && COMSPEED != 0
145		/*
146		 * Initialize the serial port.
147		 * bioscom preserves the drive number in DX.
148		 */
149		movw $COMSPEED,%ax		# defined by Makefile
150		callw bioscom
151#endif
152		/*
153		 * Check what flags were loaded with us, specifically, Use a
154		 * predefined Drive.  If what the bios gives us is bad, use
155		 * the '0' in the block instead, as well.
156		 */
157		testb $0x20,_FLAGS(%bp)		# Set number drive?
158		jnz main.1			# Yes
159		testb %dl,%dl			# Drive number valid?
160		js main.2			# Possibly (0x80 set)
161
162main.1: 	movb _SETDRV(%bp),%dl		# Drive number to use
163
164		/*
165		 * Whatever we decided to use, now store it into the fake
166		 * partition entry that lives in the data space above us.
167		 */
168main.2: 	movb %dl,_FAKE(%bp)		# Save drive number
169		callw putn			# To new line
170		pushw %dx			# Save drive number
171
172		/*
173		 * Start out with a pointer to the 4th byte of the first
174		 * table entry so that after 4 iterations it's beyond the
175		 * end of the sector.  and beyond a 256 byte boundary and
176		 * has overflowed 8 bits (see next comment).  (remember
177		 * that the table starts 2 bytes earlier than you would
178		 * expect as the bootable flag is after it in the block)
179		 */
180		movw $(partbl+0x4),%bx		# Partition table (+4 - type)
181		xorw %dx,%dx			# Partition entry item number
182
183		/*
184		 * Loop around on the partition table, printing values until
185		 * we pass a 256 (4x 64 byte partitions) byte boundary. The
186		 * end of loop test is at
187		 * main.5.
188		 */
189main.3: 	movb %ch,-0x4(%bx)		# Zero active flag (ch == 0)
190		btw %dx,_FLAGS(%bp)		# Entry enabled?
191		jnc main.5			# No
192
193		/*
194		 * If any of the entries in the table are the same as the
195		 * 'type' in the slice table entry, then this is an empty
196		 * or non bootable partition. Skip it.
197		 */
198		movb (%bx),%al			# Load type
199		movw $tables,%di		# Lookup tables
200		movb $TBL0SZ,%cl		# Number of entries
201		repne				# Exclude
202		scasb				#  partition?
203		je main.5			# Yes
204
205		/*
206		 * Now scan the table of known types
207		 */
208		movb $TBL1SZ,%cl		# Number of entries
209		repne				# Known
210		scasb				#  type?
211		jne main.4			# No
212
213		/*
214		 * If it matches get the matching element in the
215		 * next array. if it doesn't, we are already
216		 * pointing at its first element which points to a "?".
217		 */
218		addw $TBL1SZ,%di		# Adjust
219main.4: 	movb (%di),%cl			# Partition
220		addw %cx,%di			#  description
221		callw putx			# Display it
222main.5: 	incw %dx			# Next item
223		addb $0x10,%bl			# Next entry
224		jnc main.3			# Till done
225
226		/*
227		 * Passed a 256 byte boundary..
228		 * table is finished.
229		 * Add one to the drive number and check it is valid,
230		 */
231		popw %ax			# Drive number
232		subb $0x80-0x1,%al		# Does next
233		cmpb BDA_NHRDRV,%al		#  drive exist? (from BIOS?)
234		jb main.6			# Yes
235
236		/*
237		 * If not then if there is only one drive,
238		 * Don't display drive as an option.
239		 */
240		decw %ax			# Already drive 0?
241		jz main.7			# Yes
242
243		/*
244		 * If it was illegal or we cycled through them,
245		 * then go back to drive 0.
246		 */
247		xorb %al,%al			# Drive 0
248
249		/*
250		 * Whatever drive we selected, make it an ascii digit and
251		 * save it back to the "next drive" location in the loaded
252		 * block in case we want to save it for next time.  This also
253		 * is part of the printed drive string so add 0x80 to indicate
254		 * end of string.
255		 */
256main.6: 	addb $'0'|0x80,%al		# Save next
257		movb %al,_NXTDRV(%bp)		#  drive number
258		movw $drive,%di			# Display
259		callw putx			#  item
260
261		/*
262		 * Now that we've printed the drive (if we needed to),
263		 * display a prompt.  Get ready for the input by noting the
264		 * time.
265		 */
266main.7: 	movw $prompt,%si		# Display
267		callw putstr			#  prompt
268		movb _OPT(%bp),%dl		# Display
269		decw %si			#  default
270		callw putkey			#  key
271		xorb %ah,%ah			# BIOS: Get
272		int $0x1a			#  system time
273		movw %dx,%di			# Ticks when
274		addw _TICKS(%bp),%di	 	#  timeout
275
276		/*
277		 * Busy loop, looking for keystrokes but
278		 * keeping one eye on the time.
279		 */
280main.8:
281#ifndef SIO
282		movb $0x1,%ah			# BIOS: Check
283		int $0x16			#  for keypress
284		jnz main.11			# Have one
285#else  /* SIO */
286		movb $0x03,%ah			# BIOS: Read COM
287		call bioscom
288		testb $0x01,%ah			# Check line status
289		jnz main.11			# (bit 1 indicates input)
290#endif /* SIO */
291		xorb %ah,%ah			# BIOS: Get
292		int $0x1a			#  system time
293		cmpw %di,%dx			# Timeout?
294		jb main.8			# No
295
296		/*
297		 * If timed out or defaulting, come here.
298		 */
299main.9: 	movb _OPT(%bp),%al		# Load default
300		jmp main.12			# Join common code
301
302		/*
303		 * User's last try was bad, beep in displeasure.
304		 * Since nothing was printed, just continue on as if the
305		 * user hadn't done anything. This gives the effect of the
306		 * user getting a beep for all bad keystrokes but no action
307		 * until either the timeout occurs or the user hits a good
308		 * key.
309		 */
310main.10:	movb $ASCII_BEL,%al		# Signal
311		callw putchr			#  error
312
313		/*
314		 * Get the keystroke.
315		 */
316main.11:
317#ifndef SIO
318		xorb %ah,%ah			# BIOS: Get
319		int $0x16			#  keypress
320		movb %ah,%al			# Scan code
321#else
322		movb $0x02,%ah			# BIOS: Receive
323		call bioscom
324#endif
325
326		/*
327		 * If it's CR act as if timed out.
328		 */
329#ifndef SIO
330		cmpb $KEY_ENTER,%al		# Enter pressed?
331#else
332		cmpb $ASCII_CR,%al		# Enter pressed?
333#endif
334		je main.9			# Yes
335
336		/*
337		 * Otherwise check if legal
338		 * If not ask again.
339		 */
340#ifndef SIO
341		subb $KEY_F1,%al		# Less F1 scan code
342		cmpb $0x4,%al			# F1..F5?
343		jna main.12			# Yes
344		subb $(KEY_1 - KEY_F1),%al	# Less #1 scan code
345#else
346		subb $'1',%al			# Less '1' ascii character
347#endif
348		cmpb $0x4,%al			# #1..#5?
349		ja main.10			# No
350
351		/*
352		 * We have a selection.  But if it's a bad selection go back
353		 * to complain.  The bits in MNUOPT were set when the options
354		 * were printed.  Anything not printed is not an option.
355		 */
356main.12:	cbtw				# Option
357		btw %ax,_MNUOPT(%bp)	 	#  enabled?
358		jnc main.10			# No
359
360		/*
361		 * Save the info in the original tables
362		 * for rewriting to the disk.
363		 */
364		movb %al,_OPT(%bp)		# Save option
365		lea _FAKE(%bp),%si		# Partition for write
366		movb (%si),%dl			# Drive number
367		movw %si,%bx			# Partition for read
368		cmpb $0x4,%al			# F5 pressed?
369		pushf				# Save
370		je main.13			# Yes
371		shlb $0x4,%al			# Point to
372		addw $partbl,%ax		#  selected
373		xchgw %bx,%ax	 		#  partition
374		movb $0x80,(%bx)		# Flag active
375
376		/*
377		 * If not asked to do a write-back (flags 0x40) don't do one.
378		 */
379main.13:	pushw %bx			# Save
380		testb $0x40,_FLAGS(%bp)		# No updates?
381		jnz main.14			# Yes
382		movw $start,%bx			# Data to write
383		movb $0x3,%ah			# Write sector
384		callw intx13			#  to disk
385main.14:	popw %si			# Restore partition
386		popf				# Restore eflags
387
388		/*
389		 * If going to next drive, replace drive with selected one.
390		 * Remember to un-ascii it. Hey 0x80 is already set, cool!
391		 */
392		jne main.15			# If not F5
393		movb _NXTDRV(%bp),%dl		# Next drive
394		subb $'0',%dl			#  number
395
396		/*
397		 * load  selected bootsector to the MEM_BIOS_LADDR location
398		 * in RAM.  If it fails to read or isn't marked bootable,
399		 * treat it as a bad selection.
400		 * XXX what does %si carry?
401		 */
402main.15:	movw $MEM_BIOS_LADDR,%bx	# Address for read
403		movb $0x2,%ah			# Read sector
404		callw intx13			#  from disk
405		jc main.10			# If error
406		cmpw $MAGIC,0x1fe(%bx)		# Bootable?
407		jne main.10			# No
408		pushw %si			# Save
409		movw $crlf,%si			# Leave some
410		callw puts			#  space
411		popw %si			# Restore
412		jmp *%bx			# Invoke bootstrap
413
414		/*
415		 * Display routines
416		 */
417putkey:
418#ifndef SIO
419		movb $'F',%al			# Display
420		callw putchr			#  'F'
421#endif
422		movb $'1',%al			# Prepare
423		addb %dl,%al			#  digit
424		jmp putstr.1			# Display the rest
425
426		/*
427		 * Display the option and note that it is a valid option.
428		 * That last point is a bit tricky..
429		 */
430putx:		btsw %dx,_MNUOPT(%bp)		# Enable menu option
431		movw $item,%si			# Display
432		callw putkey			#  key
433		movw %di,%si			# Display the rest
434
435puts:		callw putstr			# Display string
436
437putn:		movw $crlf,%si			# To next line
438
439putstr: 	lodsb				# Get byte
440		testb $0x80,%al 		# End of string?
441		jnz putstr.2			# Yes
442putstr.1:	callw putchr			# Display char
443		jmp putstr			# Continue
444putstr.2:	andb $~0x80,%al 		# Clear MSB
445
446#ifndef SIO
447putchr:
448		pusha 				# Save
449		movw $0x7,%bx	 		# Page:attribute
450		movb $0xe,%ah			# BIOS: Display
451		int $0x10			#  character
452		popa 				# Restore
453		retw				# To caller
454#else /* SIO */
455putchr:
456		movb $0x01,%ah			# BIOS: Send
457bioscom:
458		pusha				# Save drive number
459		xorw %dx,%dx			# Use COM1
460		int $0x14			#  Character
461		popa				# Restore drive number
462		retw				# To caller
463#endif
464
465
466		/*
467		 * One-sector disk I/O routine
468		 *
469		 * Setup for both the packet and non-packet interfaces
470		 * then select one or the other.
471		 *
472		 * Don't trust the BIOS to keep registers intact across
473		 * the call, use pusha/popa.
474		 */
475intx13: 	movb 0x1(%si),%dh		# (nonpkt) head
476		movw 0x2(%si),%cx		# (nonpkt) cylinder:sector
477		movb $0x1,%al			# (nonpkt) Sector count
478		pusha 				# save: do not trust the bios
479		pushl $0x0			# (pkt) LBA address
480		pushl 0x8(%si)			# (pkt)
481		pushw %es			# (pkt) xfer buffer address
482		pushw %bx			# (pkt)
483		pushw $1			# (pkt) Block count
484		pushw $16			# (pkt) Packet size
485		testb $0x80,_FLAGS(%bp)		# Use packet interface?
486		jz intx13.1
487		movw %sp,%si			# Yes, set packet pointer
488		decw %ax			# Verify off
489		orb $0x40,%ah			# Set pkt mode in command
490intx13.1:	int $0x13			# BIOS: Disk I/O
491		# WARNING: RETAIN CARRY AFTER BIOS CALL
492		movw %sp,%si
493		lea 16(%si),%sp			# cleanup the stack
494		popa 				# Restore registers
495		retw
496
497		/*
498		 * Menu strings
499		 */
500
501item:		.ascii "  ";	     .byte ' '|0x80
502prompt: 	.ascii "\nDefault:"; .byte ' '|0x80
503crlf:		.ascii "\r";	     .byte '\n'|0x80
504
505		/*
506		 * Partition type tables
507		 */
508
509tables:
510		/*
511		 * These entries identify invalid or NON BOOT types and
512		 * partitions.
513		 */
514		.byte 0x0, 0x5, 0xf
515
516		/*
517		 * These values indicate bootable types we know the names of.
518		 */
519		.byte 0x1, 0x6, 0x7, 0xc, 0xe, 0x6c, 0x83, 0x9f
520		.byte 0xa5, 0xa6, 0xa9
521
522		/*
523		 * These are offsets that match the known names above and
524		 * point to the strings that will be printed.
525		 */
526		.byte os_misc-. 		# Unknown
527		.byte os_dos-.			# DOS
528		.byte os_dos-.			# DOS
529		.byte os_dos-.			# Windows
530		.byte os_dos-.			# Windows
531		.byte os_dos-.			# Windows
532		.byte os_dfbsd-.		# DragonFly (new)
533		.byte os_linux-.		# Linux
534		.byte os_bsd-.			# BSD/OS
535		.byte os_dfbsd-.		# DragonFly (old) / FreeBSD
536		.byte os_bsd-.			# OpenBSD
537		.byte os_bsd-.			# NetBSD
538
539		/*
540		 * And here are the strings themselves. 0x80 or'd into a
541		 * byte indicates the end of the string. (not so great for
542		 * Russians but...)
543		 */
544os_misc:	.ascii "?";    .byte '?'|0x80
545os_dos: 	.ascii "DO";   .byte 'S'|0x80
546os_linux:	.ascii "Linu"; .byte 'x'|0x80
547os_dfbsd:	.ascii "DF/F"
548os_bsd: 	.ascii "BS";   .byte 'D'|0x80
549
550		.org _DATA_OFF,0x90
551
552		.word B0MAGIC			# Magic number
553
554		/*
555		 * These values are sometimes changed before writing back
556		 * to the drive.  Be especially careful that nxtdrv: must
557		 * come after drive:, as it is part of the same string.
558		 */
559drive:		.ascii "Drive "
560nxtdrv: 	.byte 0x0			# Next drive number
561opt:		.byte 0x0			# Option
562setdrv: 	.byte 0x80			# Drive to force
563flags:		.byte FLAGS			# Flags
564ticks:		.word TICKS			# Delay
565
566		/*
567		 * here is the 64 byte partition table that fdisk would
568		 * fiddle with.
569		 */
570partbl: 	.fill 0x40,0x1,0x0		# Partition table
571		.word MAGIC			# Magic number
572