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