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