xref: /freebsd/stand/i386/cdboot/cdboot.S (revision cfd6422a5217410fbd66f7a7a8a64d9d85e61229)
1#
2# Copyright (c) 2001 John Baldwin <jhb@FreeBSD.org>
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions
6# are met:
7# 1. Redistributions of source code must retain the above copyright
8#    notice, this list of conditions and the following disclaimer.
9# 2. Redistributions in binary form must reproduce the above copyright
10#    notice, this list of conditions and the following disclaimer in the
11#    documentation and/or other materials provided with the distribution.
12#
13# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23# SUCH DAMAGE.
24#
25
26# $FreeBSD$
27
28#
29# This program is a freestanding boot program to load an a.out binary
30# from a CD-ROM booted with no emulation mode as described by the El
31# Torito standard.  Due to broken BIOSen that do not load the desired
32# number of sectors, we try to fit this in as small a space as possible.
33#
34# Basically, we first create a set of boot arguments to pass to the loaded
35# binary.  Then we attempt to load /boot/loader from the CD we were booted
36# off of.
37#
38
39#include <bootargs.h>
40
41#
42# Memory locations.
43#
44		.set MEM_PAGE_SIZE,0x1000	# memory page size, 4k
45		.set MEM_ARG,0x900		# Arguments at start
46		.set MEM_ARG_BTX,0xa100		# Where we move them to so the
47						#  BTX client can see them
48		.set MEM_ARG_SIZE,0x18		# Size of the arguments
49		.set MEM_BTX_ADDRESS,0x9000	# where BTX lives
50		.set MEM_BTX_ENTRY,0x9010	# where BTX starts to execute
51		.set MEM_BTX_OFFSET,MEM_PAGE_SIZE # offset of BTX in the loader
52		.set MEM_BTX_CLIENT,0xa000	# where BTX clients live
53#
54# a.out header fields
55#
56		.set AOUT_TEXT,0x04		# text segment size
57		.set AOUT_DATA,0x08		# data segment size
58		.set AOUT_BSS,0x0c		# zero'd BSS size
59		.set AOUT_SYMBOLS,0x10		# symbol table
60		.set AOUT_ENTRY,0x14		# entry point
61		.set AOUT_HEADER,MEM_PAGE_SIZE	# size of the a.out header
62#
63# Segment selectors.
64#
65		.set SEL_SDATA,0x8		# Supervisor data
66		.set SEL_RDATA,0x10		# Real mode data
67		.set SEL_SCODE,0x18		# PM-32 code
68		.set SEL_SCODE16,0x20		# PM-16 code
69#
70# BTX constants
71#
72		.set INT_SYS,0x30		# BTX syscall interrupt
73#
74# Constants for reading from the CD.
75#
76		.set ERROR_TIMEOUT,0x80		# BIOS timeout on read
77		.set NUM_RETRIES,3		# Num times to retry
78		.set SECTOR_SIZE,0x800		# size of a sector
79		.set SECTOR_SHIFT,11		# number of place to shift
80		.set BUFFER_LEN,0x100		# number of sectors in buffer
81		.set MAX_READ,0x10000		# max we can read at a time
82		.set MAX_READ_SEC,MAX_READ >> SECTOR_SHIFT
83		.set MEM_READ_BUFFER,0x9000	# buffer to read from CD
84		.set MEM_VOLDESC,MEM_READ_BUFFER # volume descriptor
85		.set MEM_DIR,MEM_VOLDESC+SECTOR_SIZE # Lookup buffer
86		.set VOLDESC_LBA,0x10		# LBA of vol descriptor
87		.set VD_PRIMARY,1		# Primary VD
88		.set VD_END,255			# VD Terminator
89		.set VD_ROOTDIR,156		# Offset of Root Dir Record
90		.set DIR_LEN,0			# Offset of Dir Record length
91		.set DIR_EA_LEN,1		# Offset of EA length
92		.set DIR_EXTENT,2		# Offset of 64-bit LBA
93		.set DIR_SIZE,10		# Offset of 64-bit length
94		.set DIR_NAMELEN,32		# Offset of 8-bit name len
95		.set DIR_NAME,33		# Offset of dir name
96#
97# We expect to be loaded by the BIOS at 0x7c00 (standard boot loader entry
98# point)
99#
100		.code16
101		.globl start
102		.org 0x0, 0x0
103#
104# Program start.
105#
106start:		cld				# string ops inc
107		xor %ax,%ax			# zero %ax
108		mov %ax,%ss			# setup the
109		mov $start,%sp			#  stack
110		mov %ax,%ds			# setup the
111		mov %ax,%es			#  data segments
112		mov %dl,drive			# Save BIOS boot device
113		mov $msg_welcome,%si		# %ds:(%si) -> welcome message
114		call putstr			# display the welcome message
115#
116# Setup the arguments that the loader is expecting from boot[12]
117#
118		mov $msg_bootinfo,%si		# %ds:(%si) -> boot args message
119		call putstr			# display the message
120		mov $MEM_ARG,%bx		# %ds:(%bx) -> boot args
121		mov %bx,%di			# %es:(%di) -> boot args
122		xor %eax,%eax			# zero %eax
123		mov $(MEM_ARG_SIZE/4),%cx	# Size of arguments in 32-bit
124						#  dwords
125		rep				# Clear the arguments
126		stosl				#  to zero
127		mov drive,%dl			# Store BIOS boot device
128		mov %dl,0x4(%bx)		#  in kargs->bootdev
129		orb $KARGS_FLAGS_CD,0x8(%bx)	# kargs->bootflags |=
130						#  KARGS_FLAGS_CD
131#
132# Load Volume Descriptor
133#
134		mov $VOLDESC_LBA,%eax		# Set LBA of first VD
135load_vd:	push %eax			# Save %eax
136		mov $1,%dh			# One sector
137		mov $MEM_VOLDESC,%ebx		# Destination
138		call read			# Read it in
139		cmpb $VD_PRIMARY,(%bx)		# Primary VD?
140		je have_vd			# Yes
141		pop %eax			# Prepare to
142		inc %eax			#  try next
143		cmpb $VD_END,(%bx)		# Last VD?
144		jne load_vd			# No, read next
145		mov $msg_novd,%si		# No VD
146		jmp error			# Halt
147have_vd:					# Have Primary VD
148#
149# Try to look up the loader binary using the paths in the loader_paths
150# array.
151#
152		mov $loader_paths,%si		# Point to start of array
153lookup_path:	push %si			# Save file name pointer
154		call lookup			# Try to find file
155		pop %di				# Restore file name pointer
156		jnc lookup_found		# Found this file
157		xor %al,%al			# Look for next
158		mov $0xffff,%cx			#  path name by
159		repnz				#  scanning for
160		scasb				#  nul char
161		mov %di,%si			# Point %si at next path
162		mov (%si),%al			# Get first char of next path
163		or %al,%al			# Is it double nul?
164		jnz lookup_path			# No, try it.
165		mov $msg_failed,%si		# Failed message
166		jmp error			# Halt
167lookup_found:					# Found a loader file
168#
169# Load the binary into the buffer.  Due to real mode addressing limitations
170# we have to read it in 64k chunks.
171#
172		mov DIR_SIZE(%bx),%eax		# Read file length
173		add $SECTOR_SIZE-1,%eax		# Convert length to sectors
174		shr $SECTOR_SHIFT,%eax
175		cmp $BUFFER_LEN,%eax
176		jbe load_sizeok
177		mov $msg_load2big,%si		# Error message
178		call error
179load_sizeok:	movzbw %al,%cx			# Num sectors to read
180		mov DIR_EXTENT(%bx),%eax	# Load extent
181		xor %edx,%edx
182		mov DIR_EA_LEN(%bx),%dl
183		add %edx,%eax			# Skip extended
184		mov $MEM_READ_BUFFER,%ebx	# Read into the buffer
185load_loop:	mov %cl,%dh
186		cmp $MAX_READ_SEC,%cl		# Truncate to max read size
187		jbe load_notrunc
188		mov $MAX_READ_SEC,%dh
189load_notrunc:	sub %dh,%cl			# Update count
190		push %eax			# Save
191		call read			# Read it in
192		pop %eax			# Restore
193		add $MAX_READ_SEC,%eax		# Update LBA
194		add $MAX_READ,%ebx		# Update dest addr
195		jcxz load_done			# Done?
196		jmp load_loop			# Keep going
197load_done:
198#
199# Turn on the A20 address line
200#
201		call seta20			# Turn A20 on
202#
203# Relocate the loader and BTX using a very lazy protected mode
204#
205		mov $msg_relocate,%si		# Display the
206		call putstr			#  relocation message
207		mov MEM_READ_BUFFER+AOUT_ENTRY,%edi # %edi is the destination
208		mov $(MEM_READ_BUFFER+AOUT_HEADER),%esi	# %esi is
209						#  the start of the text
210						#  segment
211		mov MEM_READ_BUFFER+AOUT_TEXT,%ecx # %ecx = length of the text
212						#  segment
213		push %edi			# Save entry point for later
214		lgdt gdtdesc			# setup our own gdt
215		cli				# turn off interrupts
216		mov %cr0,%eax			# Turn on
217		or $0x1,%al			#  protected
218		mov %eax,%cr0			#  mode
219		ljmp $SEL_SCODE,$pm_start	# long jump to clear the
220						#  instruction pre-fetch queue
221		.code32
222pm_start:	mov $SEL_SDATA,%ax		# Initialize
223		mov %ax,%ds			#  %ds and
224		mov %ax,%es			#  %es to a flat selector
225		rep				# Relocate the
226		movsb				#  text segment
227		add $(MEM_PAGE_SIZE - 1),%edi	# pad %edi out to a new page
228		and $~(MEM_PAGE_SIZE - 1),%edi #  for the data segment
229		mov MEM_READ_BUFFER+AOUT_DATA,%ecx # size of the data segment
230		rep				# Relocate the
231		movsb				#  data segment
232		mov MEM_READ_BUFFER+AOUT_BSS,%ecx # size of the bss
233		xor %eax,%eax			# zero %eax
234		add $3,%cl			# round %ecx up to
235		shr $2,%ecx			#  a multiple of 4
236		rep				# zero the
237		stosl				#  bss
238		mov MEM_READ_BUFFER+AOUT_ENTRY,%esi # %esi -> relocated loader
239		add $MEM_BTX_OFFSET,%esi	# %esi -> BTX in the loader
240		mov $MEM_BTX_ADDRESS,%edi	# %edi -> where BTX needs to go
241		movzwl 0xa(%esi),%ecx		# %ecx -> length of BTX
242		rep				# Relocate
243		movsb				#  BTX
244		ljmp $SEL_SCODE16,$pm_16	# Jump to 16-bit PM
245		.code16
246pm_16:		mov $SEL_RDATA,%ax		# Initialize
247		mov %ax,%ds			#  %ds and
248		mov %ax,%es			#  %es to a real mode selector
249		mov %cr0,%eax			# Turn off
250		and $~0x1,%al			#  protected
251		mov %eax,%cr0			#  mode
252		ljmp $0,$pm_end			# Long jump to clear the
253						#  instruction pre-fetch queue
254pm_end:		sti				# Turn interrupts back on now
255#
256# Copy the BTX client to MEM_BTX_CLIENT
257#
258		xor %ax,%ax			# zero %ax and set
259		mov %ax,%ds			#  %ds and %es
260		mov %ax,%es			#  to segment 0
261		mov $MEM_BTX_CLIENT,%di		# Prepare to relocate
262		mov $btx_client,%si		#  the simple btx client
263		mov $(btx_client_end-btx_client),%cx # length of btx client
264		rep				# Relocate the
265		movsb				#  simple BTX client
266#
267# Copy the boot[12] args to where the BTX client can see them
268#
269		mov $MEM_ARG,%si		# where the args are at now
270		mov $MEM_ARG_BTX,%di		# where the args are moving to
271		mov $(MEM_ARG_SIZE/4),%cx	# size of the arguments in longs
272		rep				# Relocate
273		movsl				#  the words
274#
275# Save the entry point so the client can get to it later on
276#
277		pop %eax			# Restore saved entry point
278		stosl				#  and add it to the end of
279						#  the arguments
280#
281# Now we just start up BTX and let it do the rest
282#
283		mov $msg_jump,%si		# Display the
284		call putstr			#  jump message
285		ljmp $0,$MEM_BTX_ENTRY		# Jump to the BTX entry point
286
287#
288# Lookup the file in the path at [SI] from the root directory.
289#
290# Trashes: All but BX
291# Returns: CF = 0 (success), BX = pointer to record
292#          CF = 1 (not found)
293#
294lookup:		mov $VD_ROOTDIR+MEM_VOLDESC,%bx	# Root directory record
295		push %si
296		mov $msg_lookup,%si		# Display lookup message
297		call putstr
298		pop %si
299		push %si
300		call putstr
301		mov $msg_lookup2,%si
302		call putstr
303		pop %si
304lookup_dir:	lodsb				# Get first char of path
305		cmp $0,%al			# Are we done?
306		je lookup_done			# Yes
307		cmp $'/',%al			# Skip path separator.
308		je lookup_dir
309		dec %si				# Undo lodsb side effect
310		call find_file			# Lookup first path item
311		jnc lookup_dir			# Try next component
312		mov $msg_lookupfail,%si		# Not found message
313		call putstr
314		stc				# Set carry
315		ret
316		jmp error
317lookup_done:	mov $msg_lookupok,%si		# Success message
318		call putstr
319		clc				# Clear carry
320		ret
321
322#
323# Lookup file at [SI] in directory whose record is at [BX].
324#
325# Trashes: All but returns
326# Returns: CF = 0 (success), BX = pointer to record, SI = next path item
327#          CF = 1 (not found), SI = preserved
328#
329find_file:	mov DIR_EXTENT(%bx),%eax	# Load extent
330		xor %edx,%edx
331		mov DIR_EA_LEN(%bx),%dl
332		add %edx,%eax			# Skip extended attributes
333		mov %eax,rec_lba		# Save LBA
334		mov DIR_SIZE(%bx),%eax		# Save size
335		mov %eax,rec_size
336		xor %cl,%cl			# Zero length
337		push %si			# Save
338ff.namelen:	inc %cl				# Update length
339		lodsb				# Read char
340		cmp $0,%al			# Nul?
341		je ff.namedone			# Yes
342		cmp $'/',%al			# Path separator?
343		jnz ff.namelen			# No, keep going
344ff.namedone:	dec %cl				# Adjust length and save
345		mov %cl,name_len
346		pop %si				# Restore
347ff.load:	mov rec_lba,%eax		# Load LBA
348		mov $MEM_DIR,%ebx		# Address buffer
349		mov $1,%dh			# One sector
350		call read			# Read directory block
351		incl rec_lba			# Update LBA to next block
352ff.scan:	mov %ebx,%edx			# Check for EOF
353		sub $MEM_DIR,%edx
354		cmp %edx,rec_size
355		ja ff.scan.1
356		stc				# EOF reached
357		ret
358ff.scan.1:	cmpb $0,DIR_LEN(%bx)		# Last record in block?
359		je ff.nextblock
360		push %si			# Save
361		movzbw DIR_NAMELEN(%bx),%si	# Find end of string
362ff.checkver:	cmpb $'0',DIR_NAME-1(%bx,%si)	# Less than '0'?
363		jb ff.checkver.1
364		cmpb $'9',DIR_NAME-1(%bx,%si)	# Greater than '9'?
365		ja ff.checkver.1
366		dec %si				# Next char
367		jnz ff.checkver
368		jmp ff.checklen			# All numbers in name, so
369						#  no version
370ff.checkver.1:	movzbw DIR_NAMELEN(%bx),%cx
371		cmp %cx,%si			# Did we find any digits?
372		je ff.checkdot			# No
373		cmpb $';',DIR_NAME-1(%bx,%si)	# Check for semicolon
374		jne ff.checkver.2
375		dec %si				# Skip semicolon
376		mov %si,%cx
377		mov %cl,DIR_NAMELEN(%bx)	# Adjust length
378		jmp ff.checkdot
379ff.checkver.2:	mov %cx,%si			# Restore %si to end of string
380ff.checkdot:	cmpb $'.',DIR_NAME-1(%bx,%si)	# Trailing dot?
381		jne ff.checklen			# No
382		decb DIR_NAMELEN(%bx)		# Adjust length
383ff.checklen:	pop %si				# Restore
384		movzbw name_len,%cx		# Load length of name
385		cmp %cl,DIR_NAMELEN(%bx)	# Does length match?
386		je ff.checkname			# Yes, check name
387ff.nextrec:	add DIR_LEN(%bx),%bl		# Next record
388		adc $0,%bh
389		jmp ff.scan
390ff.nextblock:	subl $SECTOR_SIZE,rec_size	# Adjust size
391		jnc ff.load			# If subtract ok, keep going
392		ret				# End of file, so not found
393ff.checkname:	lea DIR_NAME(%bx),%di		# Address name in record
394		push %si			# Save
395		repe cmpsb			# Compare name
396		je ff.match			# We have a winner!
397		pop %si				# Restore
398		jmp ff.nextrec			# Keep looking.
399ff.match:	add $2,%sp			# Discard saved %si
400		clc				# Clear carry
401		ret
402
403#
404# Load DH sectors starting at LBA EAX into [EBX].
405#
406# Trashes: EAX
407#
408read:		push %si			# Save
409		push %cx			# Save since some BIOSs trash
410		mov %eax,edd_lba		# LBA to read from
411		mov %ebx,%eax			# Convert address
412		shr $4,%eax			#  to segment
413		mov %ax,edd_addr+0x2		#  and store
414read.retry:	call twiddle			# Entertain the user
415		push %dx			# Save
416		mov $edd_packet,%si		# Address Packet
417		mov %dh,edd_len			# Set length
418		mov drive,%dl			# BIOS Device
419		mov $0x42,%ah			# BIOS: Extended Read
420		int $0x13			# Call BIOS
421		pop %dx				# Restore
422		jc read.fail			# Worked?
423		pop %cx				# Restore
424		pop %si
425		ret				# Return
426read.fail:	cmp $ERROR_TIMEOUT,%ah		# Timeout?
427		je read.retry			# Yes, Retry.
428read.error:	mov %ah,%al			# Save error
429		mov $hex_error,%di		# Format it
430		call hex8			#  as hex
431		mov $msg_badread,%si		# Display Read error message
432
433#
434# Display error message at [SI] and halt.
435#
436error:		call putstr			# Display message
437halt:		hlt
438		jmp halt			# Spin
439
440#
441# Display a null-terminated string.
442#
443# Trashes: AX, SI
444#
445putstr:		push %bx			# Save
446putstr.load:	lodsb				# load %al from %ds:(%si)
447		test %al,%al			# stop at null
448		jnz putstr.putc			# if the char != null, output it
449		pop %bx				# Restore
450		ret				# return when null is hit
451putstr.putc:	call putc			# output char
452		jmp putstr.load			# next char
453
454#
455# Display a single char.
456#
457putc:		mov $0x7,%bx			# attribute for output
458		mov $0xe,%ah			# BIOS: put_char
459		int $0x10			# call BIOS, print char in %al
460		ret				# Return to caller
461
462#
463# Output the "twiddle"
464#
465twiddle:	push %ax			# Save
466		push %bx			# Save
467		mov twiddle_index,%al		# Load index
468		mov $twiddle_chars,%bx		# Address table
469		inc %al				# Next
470		and $3,%al			#  char
471		mov %al,twiddle_index		# Save index for next call
472		xlat				# Get char
473		call putc			# Output it
474		mov $8,%al			# Backspace
475		call putc			# Output it
476		pop %bx				# Restore
477		pop %ax				# Restore
478		ret
479
480#
481# Enable A20. Put an upper limit on the amount of time we wait for the
482# keyboard controller to get ready (65K x ISA access time). If
483# we wait more than that amount, the hardware is probably
484# legacy-free and simply doesn't have a keyboard controller.
485# Thus, the A20 line is already enabled.
486#
487seta20: 	cli				# Disable interrupts
488		xor %cx,%cx			# Clear
489seta20.1:	inc %cx				# Increment, overflow?
490		jz seta20.3			# Yes
491		in $0x64,%al			# Get status
492		test $0x2,%al			# Busy?
493		jnz seta20.1			# Yes
494		mov $0xd1,%al			# Command: Write
495		out %al,$0x64			#  output port
496seta20.2:	in $0x64,%al			# Get status
497		test $0x2,%al			# Busy?
498		jnz seta20.2			# Yes
499		mov $0xdf,%al			# Enable
500		out %al,$0x60			#  A20
501seta20.3:	sti				# Enable interrupts
502		ret				# To caller
503
504#
505# Convert AL to hex, saving the result to [EDI].
506#
507hex8:		pushl %eax			# Save
508		shrb $0x4,%al			# Do upper
509		call hex8.1			#  4
510		popl %eax			# Restore
511hex8.1: 	andb $0xf,%al			# Get lower 4
512		cmpb $0xa,%al			# Convert
513		sbbb $0x69,%al			#  to hex
514		das				#  digit
515		orb $0x20,%al			# To lower case
516		stosb				# Save char
517		ret				# (Recursive)
518
519#
520# BTX client to start btxldr
521#
522		.code32
523btx_client:	mov $(MEM_ARG_BTX-MEM_BTX_CLIENT+MEM_ARG_SIZE-4), %esi
524						# %ds:(%esi) -> end
525						#  of boot[12] args
526		mov $(MEM_ARG_SIZE/4),%ecx	# Number of words to push
527		std				# Go backwards
528push_arg:	lodsl				# Read argument
529		push %eax			# Push it onto the stack
530		loop push_arg			# Push all of the arguments
531		cld				# In case anyone depends on this
532		pushl MEM_ARG_BTX-MEM_BTX_CLIENT+MEM_ARG_SIZE # Entry point of
533						#  the loader
534		push %eax			# Emulate a near call
535		mov $0x1,%eax			# 'exec' system call
536		int $INT_SYS			# BTX system call
537btx_client_end:
538		.code16
539
540		.p2align 4
541#
542# Global descriptor table.
543#
544gdt:		.word 0x0,0x0,0x0,0x0		# Null entry
545		.word 0xffff,0x0,0x9200,0xcf	# SEL_SDATA
546		.word 0xffff,0x0,0x9200,0x0	# SEL_RDATA
547		.word 0xffff,0x0,0x9a00,0xcf	# SEL_SCODE (32-bit)
548		.word 0xffff,0x0,0x9a00,0x8f	# SEL_SCODE16 (16-bit)
549gdt.1:
550#
551# Pseudo-descriptors.
552#
553gdtdesc:	.word gdt.1-gdt-1		# Limit
554		.long gdt			# Base
555#
556# EDD Packet
557#
558edd_packet:	.byte 0x10			# Length
559		.byte 0				# Reserved
560edd_len:	.byte 0x0			# Num to read
561		.byte 0				# Reserved
562edd_addr:	.word 0x0,0x0			# Seg:Off
563edd_lba:	.quad 0x0			# LBA
564
565drive:		.byte 0
566
567#
568# State for searching dir
569#
570rec_lba:	.long 0x0			# LBA (adjusted for EA)
571rec_size:	.long 0x0			# File size
572name_len:	.byte 0x0			# Length of current name
573
574twiddle_index:	.byte 0x0
575
576msg_welcome:	.asciz	"CD Loader 1.2\r\n\n"
577msg_bootinfo:	.asciz	"Building the boot loader arguments\r\n"
578msg_relocate:	.asciz	"Relocating the loader and the BTX\r\n"
579msg_jump:	.asciz	"Starting the BTX loader\r\n"
580msg_badread:	.ascii  "Read Error: 0x"
581hex_error:	.asciz	"00\r\n"
582msg_novd:	.asciz  "Could not find Primary Volume Descriptor\r\n"
583msg_lookup:	.asciz  "Looking up "
584msg_lookup2:	.asciz  "... "
585msg_lookupok:	.asciz  "Found\r\n"
586msg_lookupfail:	.asciz  "File not found\r\n"
587msg_load2big:	.asciz  "File too big\r\n"
588msg_failed:	.asciz	"Boot failed\r\n"
589twiddle_chars:	.ascii	"|/-\\"
590loader_paths:	.asciz  "/BOOT/LOADER"
591		.asciz	"/boot/loader"
592		.byte 0
593
594