xref: /netbsd-src/sys/arch/i386/stand/fatboot/fatboot.S (revision 34a326a457d92407165c54b35235165701b21722)
1/*	$NetBSD: fatboot.S,v 1.4 2012/03/10 23:59:36 dsl Exp $	*/
2
3/*-
4 * Copyright (c) 2007 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by David Laight.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/*
33 * i386 partition boot code
34 * This version reads boot directly from a FAT16 filesystem on LBA
35 * Addressable media - eg USB media.
36 *
37 * The code is read to address 0:7c00 by the mbr code (in sector zero).
38 *
39 * We assume that the partition contains a 'boot parameter block' that
40 * correctly identifies the filesystem.
41 *
42 * On entry and exit the BIOS drive number is in %dl.
43 */
44
45#include <machine/asm.h>
46#include <sys/bootblock.h>
47
48#ifndef FAT_ENTRY_SIZE
49#error FAT_ENTRY_SIZE not defined
50#endif
51
52/* Support for FAT32 could be added - but hasn't been yet. */
53#if FAT_ENTRY_SIZE != 16 && FAT_ENTRY_SIZE != 12
54#error Unsupported FAT_ENTRY_SIZE value
55#endif
56
57#define FAT_SIZE_STR (('0'+ FAT_ENTRY_SIZE / 10) | ('0' + FAT_ENTRY_SIZE % 10) << 8)
58
59#define PBR_AFTERBPB	62		/* BPB size in floppy master BR */
60
61#ifdef TERSE_ERROR
62/*
63 * Error codes. Done this way to save space.
64 */
65#define ERR_READ		'R'	/* Read error */
66#define ERR_NO_BOOT		'B'	/* No /boot */
67#define ERR_NOT_FAT16		'F'	/* Not a FAT16 filesystem */
68#define	ERR_NO_BOOT_MAGIC_2	'M'	/* No magic in loaded /boot */
69
70#define	set_err(err)	movb	$err, %al
71
72#else
73#define	set_err(err)	mov	$err, %ax
74#endif
75
76	.text
77	.code16
78ENTRY(start)
79	jmp	start0
80	nop
81oem_name:               .ascii	"NetBSD40"	/* 8 bytes */
82/* FAT16 BIOS/BOOT Parameter Block - see struct mbr_bpbFAT16 in bootblock.h */
83#define	bpb_bytes_per_sec   /* .word 0	*/ 0x0b /* bytes per sector */
84#define	bpb_sec_per_clust   /* .byte 0	*/ 0x0d /* sectors per cluster */
85#define	bpb_res_sectors     /* .word 0	*/ 0x0e /* number of reserved sectors */
86#define	bpb_FATs            /* .byte 0	*/ 0x10 /* number of FATs */
87#define	bpb_root_dir_ents   /* .word 0	*/ 0x11 /* number of root dir entries */
88#define	bpb_sectors         /* .word 0	*/ 0x13 /* total number of sectors */
89#define	bpb_media           /* .byte 0	*/ 0x15 /* media descriptor */
90#define	bpb_FAT_secs        /* .word 0	*/ 0x16 /* number of sectors per FAT */
91#define	bpb_sec_per_track   /* .word 0	*/ 0x18 /* sectors per track */
92#define	bpb_heads           /* .word 0	*/ 0x1a /* number of heads */
93#define	bpb_hidden_secs     /* .long 0	*/ 0x1c /* # of hidden sectors */
94#define	bpb_huge_sectors    /* .long 0	*/ 0x20 /* # of sectors if !bpbSectors*/
95/* Extended boot area */
96#define	bs_drive_number     /* .byte 0	*/ 0x24 /* but we believe the BIOS ! */
97#define	bs_reserved_1       /* .byte 0	*/ 0x25 /* */
98#define	bs_boot_sig         /* .byte 0	*/ 0x26 /* */
99#define	bs_volume_id        /* .long 0	*/ 0x27 /* Volume ID number */
100#define	bs_volume_label     /* .space 11*/ 0x2b /* Volume label */
101#define	bs_file_sys_type    /* .space 8	*/ 0x36 /* "FAT16   " */
102
103/* Some locals overlaying the end of the above */
104#define sec_p_cl_w	0x2c			/* 16bit bpb_sec_per_clust */
105#define	fat_sector	0x30			/* start of FAT in sectors */
106
107	. = start + PBR_AFTERBPB	/* skip BPB */
108start0:
109	xor	%eax, %eax		/* don't trust values of ds, es or ss */
110	mov	%ax, %ds
111	mov	%ax, %es
112	mov	%ax, %ss
113	mov	$start, %sp
114	mov	%sp, %bp		/* to access the pbp */
115	push	%dx			/* save drive at -2(%bp) */
116
117/* We put the LBA bios command block on stack.
118 * Since we only want a 32bit sector number, stack a zero */
119	push	%cs			/* %cs is zero */
120	push	%cs			/* 64-bit for LBA read */
121
122	set_err(ERR_NOT_FAT16)
123	cmpl	$'A'|'T'<<8|FAT_SIZE_STR<<16, bs_file_sys_type+1(%bp)
124	jne	error
125
126/* Add 'reserved' (inside ptn) to 'hidden' (ptn offset) */
127	mov	bpb_res_sectors(%bp), %ax
128	addl	bpb_hidden_secs(%bp), %eax
129	mov	%eax, fat_sector(%bp)	/* To get first sector of FAT */
130
131#if FAT_ENTRY_SIZE == 12
132/* Read the entire FAT */
133	push	%eax
134	push	%ds
135	push	$fat_buffer
136	push	$12			/* 12 sectors is assumed 6k */
137	call	read_lba
138#endif
139
140/* Determine base of root directory */
141	movzbw	bpb_FATs(%bp), %ax	/* Count of FATs */
142	mulw	bpb_FAT_secs(%bp)	/* FAT size in %dx:%ax */
143	shl	$16,%edx
144	xchg	%ax,%dx			/* FAT size now in %edx */
145	addl	fat_sector(%bp), %edx	/* Directory is after FATs */
146	pushl	%edx			/* Sector number of root dir */
147
148	push	$0x1000			/* Read to 0x10000:0 */
149	pop	%es			/* Which we need in %es later */
150	push	%es
151	push	%cs			/* Offset zero */
152
153/* Convert the root directory size to sectors */
154	push	%dx
155	mov	bpb_root_dir_ents(%bp), %ax
156	mov	$0x20, %dx
157	mul	%dx
158	divw	bpb_bytes_per_sec(%bp)
159	add	$0xffff, %dx		/* Set carry if remainder non-zero */
160	adc	$0, %ax			/* and round up the division */
161	pop	%dx
162
163/* Read in the entire root directory */
164	push	%ax			/* Sectors in root directory */
165	cwtl
166	addl	%eax, %edx		/* %edx now sector of first cluster */
167	call	read_lba		/* Read entire directory */
168
169/* Scan directory for our file */
170	xor	%di, %di
171scan_dir:
172	mov	$boot_filename, %si
173	mov	$11, %cx
174	repz cmpsb
175	je	found_boot
176	or	$31,%di
177	inc	%di
178	cmp	%ch, %es:(%di)		/* %ch is zero - test end of dir */
179	jz	1f
180	decw	bpb_root_dir_ents(%bp)
181	jnz	scan_dir
1821:	set_err(ERR_NO_BOOT)
183
184error:
185#ifdef TERSE_ERROR
186	movb	%al, errcod
187	movw	$errtxt, %si
188	call	message
189#else
190	push	%ax
191	movw	$errtxt, %si
192	call	message
193	pop	%si
194	call	message
195	movw	$newline, %si
196	call	message
197#endif
1981:	sti
199	hlt
200	jmp	1b
201
202found_boot:
203	movzbl	bpb_sec_per_clust(%bp), %eax
204	movw	%ax, sec_p_cl_w(%bp)
205	add	%ax, %ax
206	subl	%eax, %edx		/* 1st file sector is cluster 2 */
2071:	inc	%cl			/* Convert power of 2 ... */
208	shr	$1, %ax			/* ... to shift */
209	jnz	1b
210	dec %cx
211	dec %cx
212	movw	%es:(26-11)(%di), %ax	/* Cluster number for file start */
213	push	%es			/* We increment the 'segment' ... */
214	pop	%di			/* ... after each read, offset is 0 */
215
216read_data_block:
217	mov	%ax, %bx		/* Save cluster number */
218	shl	%cl, %eax		/* Convert to sector number */
219	jz	error			/* Sanity bail-out */
220	add	%edx, %eax
221	pushl	%eax			/* Sector to read */
222	push	%di			/* Target address segment! */
223	push	$0
224	push	sec_p_cl_w(%bp)
225	call	read_lba		/* Read a cluster */
226
227/* Update read ptr for next cluster */
228	mov	bpb_bytes_per_sec(%bp), %ax
229	shr	$4, %ax			/* x86 segment count */
230	shl	%cl, %ax		/* for a cluster */
231	add	%ax, %di
232
233/* Lookup FAT slot number in FAT table */
234	mov	%bx, %ax		/* Recover cluster number */
235#if FAT_ENTRY_SIZE == 12
236	shr	$1, %ax
237	jc	1f
238	add	%ax, %bx
239	mov	fat_buffer(%bx), %ax
240	and	$0xf,%ah
241	jmp	2f
2421:	add	%ax, %bx
243	mov	fat_buffer(%bx), %ax
244	shr	$4, %ax
2452:
246	cmp	$0x0fff, %ax
247	jb	read_data_block
248#else
249	push	%dx
250	xor	%dx, %dx
251	divw	bpb_bytes_per_sec(%bp)
252	mov	%dx, %bx		/* Entry in FAT block */
253	pop	%dx
254	cmp	%ax, fat_cache
255	je	lookup_fat
256
257/* We must read a different chuck of the FAT */
258	mov	%ax, fat_cache
259	cwtl
260	shl	$1, %ax
261	addl	fat_sector(%bp), %eax
262	push	%eax
263	push	%ds
264	push	$fat_buffer
265	push	$2			/* Always read 2 sectors of FAT */
266	call	read_lba
267
268/* Now use low part of cluster number to index FAT sector */
269lookup_fat:
270	add	%bx, %bx		/* 2 bytes per entry... */
271	movzwl	fat_buffer(%bx), %eax	/* Next FAT slot */
272	cmp	$0xfff0, %ax
273	jb	read_data_block
274#endif
275
276/* Found end of FAT chain - must be EOF  - leap into loaded code */
277	mov	$0x1000, %ax
278	mov	%ax, %es
279	cmpl	$X86_BOOT_MAGIC_2, %es:4
280	je	magic_ok
281	set_err(ERR_NO_BOOT_MAGIC_2)
282err1:	jmp	error
283
284/* Set parameters expected by /boot */
285magic_ok:
286	mov	bpb_hidden_secs(%bp), %ebx	/* ptn base sector */
287	movb	-2(%bp), %dl		/* disk number */
288	mov	$boot_params + 4, %si
289	push	%es
290	push	$0
291	lret
292
293/* Read disk using on-stack int13-extension parameter block */
294read_lba:
295	pop	%ax			/* Save rtn addr */
296	pushw	$16			/* Stack ctl block length */
297	mov	%sp, %si		/* Address ctl block */
298	push	%ax			/* restack rtn addr */
299	pushal				/* Save everything except %si and %ax*/
300	mov	-2(%bp), %dl		/* Disk # saved on entry */
301	movb	$0x42, %ah
302	int	$0x13
303	popal
304
305	set_err(ERR_READ)
306	jc	err1
307	ret	$12			/* Discard all except high LBA zeros */
308
309/*
310 * I hate #including source files, but pbr_magic below has to be at
311 * the correct absolute address.
312 * Clearly this could be done with a linker script.
313 */
314
315#include <message.S>
316#if 0
317#include <dump_eax.S>
318dump_eax_buff = start
319#endif
320
321errtxt: .ascii	"Error "		/* runs into newline... */
322errcod: .byte	0			/* ... if errcod set */
323newline:
324	.asciz	"\r\n"
325
326#ifndef TERSE_ERROR
327ERR_READ:		.asciz	"Disk read"
328ERR_NO_BOOT:		.asciz	"No /boot"
329ERR_NOT_FAT16:		.ascii	"Not FAT"
330			.word	FAT_SIZE_STR
331			.asciz	" ptn"
332ERR_NO_BOOT_MAGIC_2:	.asciz	"No magic in /boot"
333#endif
334
335boot_filename: .ascii	"BOOT       "
336
337space:
338pbr_space = boot_params - .
339
340/*
341 * Add magic number, with a zero sized patchable area - just in case something
342 * finds it and tries to update the area.
343 * Boot options can be set using 'installboot -e boot' so we don't need to
344 * use any of our valuable bytes.
345 */
346
347	. = _C_LABEL(start) + 0x1fe - 2 - 4 - 4
348boot_params:
349	.long	X86_BOOT_MAGIC_FAT
350	.long	1f - .
3511:
352fat_cache:	.word	0xffff		/* Sector number in buffer */
353	. = _C_LABEL(start) + 0x1fe
354	.word	0xaa55
355fat_buffer:				/* 2 sectors worth of FAT table */
356					/* 6k for FAT12 */
357