Home | History | Annotate | Line # | Download | only in fatboot
      1 /*	$NetBSD: fatboot.S,v 1.5 2025/02/28 09:07:12 andvar 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
     78 ENTRY(start)
     79 	jmp	start0
     80 	nop
     81 oem_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 */
    108 start0:
    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
    171 scan_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
    182 1:	set_err(ERR_NO_BOOT)
    183 
    184 error:
    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
    198 1:	sti
    199 	hlt
    200 	jmp	1b
    201 
    202 found_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 */
    207 1:	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 
    216 read_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
    242 1:	add	%ax, %bx
    243 	mov	fat_buffer(%bx), %ax
    244 	shr	$4, %ax
    245 2:
    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 chunk 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 */
    269 lookup_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)
    282 err1:	jmp	error
    283 
    284 /* Set parameters expected by /boot */
    285 magic_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 */
    294 read_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>
    318 dump_eax_buff = start
    319 #endif
    320 
    321 errtxt: .ascii	"Error "		/* runs into newline... */
    322 errcod: .byte	0			/* ... if errcod set */
    323 newline:
    324 	.asciz	"\r\n"
    325 
    326 #ifndef TERSE_ERROR
    327 ERR_READ:		.asciz	"Disk read"
    328 ERR_NO_BOOT:		.asciz	"No /boot"
    329 ERR_NOT_FAT16:		.ascii	"Not FAT"
    330 			.word	FAT_SIZE_STR
    331 			.asciz	" ptn"
    332 ERR_NO_BOOT_MAGIC_2:	.asciz	"No magic in /boot"
    333 #endif
    334 
    335 boot_filename: .ascii	"BOOT       "
    336 
    337 space:
    338 pbr_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
    348 boot_params:
    349 	.long	X86_BOOT_MAGIC_FAT
    350 	.long	1f - .
    351 1:
    352 fat_cache:	.word	0xffff		/* Sector number in buffer */
    353 	. = _C_LABEL(start) + 0x1fe
    354 	.word	0xaa55
    355 fat_buffer:				/* 2 sectors worth of FAT table */
    356 					/* 6k for FAT12 */
    357