1/* $NetBSD: pbr.S,v 1.22 2016/01/04 18:17:31 christos Exp $ */ 2 3/*- 4 * Copyright (c) 2003,2004 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 * 35 * This code resides in sector zero of the netbsd partition, or sector 36 * zero of an unpartitioned disk (eg a floppy). 37 * Sector 1 is assumed to contain the netbsd disklabel. 38 * Sectors 2 until the end of the track contain the next phase of bootstrap. 39 * Which know how to read the interactive 'boot' program from filestore. 40 * The job of this code is to read in the phase 1 bootstrap. 41 * 42 * Makefile supplies: 43 * PRIMARY_LOAD_ADDRESS: Address we load code to (0x1000). 44 * BOOTXX_SECTORS: Number of sectors we load (15). 45 * X86_BOOT_MAGIC_1: A random magic number. 46 * 47 * Although this code is executing at 0x7c00, it is linked to address 0x1000. 48 * All data references MUST be fixed up using R(). 49 */ 50 51#include <machine/asm.h> 52#include <sys/bootblock.h> 53 54#define OURADDR 0x7c00 /* our address */ 55#define BOOTADDR PRIMARY_LOAD_ADDRESS 56 57#define R(a) (a - BOOTADDR + OURADDR) 58 59#define lba_info R(_lba_info) 60#define lba_sector R(_lba_sector) 61#define errtxt R(_errtxt) 62#define errcod R(_errcod) 63#define newline R(_newline) 64 65#define TABENTRYSIZE (MBR_BS_PARTNAMESIZE + 1) 66#define NAMETABSIZE (4 * TABENTRYSIZE) 67 68#ifdef BOOT_FROM_FAT 69#define MBR_AFTERBPB 90 /* BPB size in FAT32 partition BR */ 70#else 71#define MBR_AFTERBPB 62 /* BPB size in floppy master BR */ 72#endif 73 74#ifdef TERSE_ERROR 75/* 76 * Error codes. Done this way to save space. 77 */ 78#define ERR_READ '2' /* Read error */ 79#define ERR_NO_BOOTXX 'B' /* No bootxx_xfs in 3rd sector */ 80#define ERR_PTN 'P' /* partition not defined */ 81#define ERR_NO_LBA 'L' /* sector above chs limit */ 82 83#define set_err(err) movb $err, %al 84 85#else 86#define set_err(err) mov $R(err), %ax 87#endif 88 89/* 90 * This code is loaded to addresss 0:7c00 by either the system BIOS 91 * (for a floppy) or the mbr boot code. Since the boot program will 92 * be loaded to address 1000:0, we don't need to relocate ourselves 93 * and can load the subsequent blocks (that load boot) to an address 94 * of our choosing. 0:1000 is a not unreasonable choice. 95 * 96 * On entry the BIOS drive number is in %dl and %esi may contain the 97 * sector we were loaded from (if we were loaded by NetBSD mbr code). 98 * In any case we have to re-read sector zero of the disk and hunt 99 * through the BIOS partition table for the NetBSD partition. 100 * 101 * Or, we may have been loaded by a GPT hybrid MBR, handoff state is 102 * specified in T13 EDD-4 annex A. 103 */ 104 105 .text 106 .code16 107ENTRY(start) 108 /* 109 * The PC BIOS architecture defines a Boot Parameter Block (BPB) here. 110 * The actual format varies between different MS-DOS versions, but 111 * apparently some system BIOS insist on patching this area 112 * (especially on LS120 drives - which I thought had an MBR...). 113 * The initial jmp and nop are part of the standard and may be 114 * tested for by the system BIOS. 115 */ 116 jmp start0 117 nop 118 .ascii "NetBSD60" /* oemname (8 bytes) */ 119 120 . = start + MBR_BPB_OFFSET /* move to start of BPB */ 121 /* (ensures oemname doesn't overflow) */ 122 123 . = start + MBR_AFTERBPB /* skip BPB */ 124start0: 125 xor %cx, %cx /* don't trust values of ds, es or ss */ 126 mov %cx, %ss 127 mov %cx, %sp 128 mov %cx, %es 129#ifndef BOOT_FROM_FAT 130 cmpl $0x54504721, %eax /* did a GPT hybrid MBR start us? */ 131 je boot_gpt 132#endif 133 mov %cx, %ds 134 xor %ax, %ax 135 136 /* A 'reset disk system' request is traditional here... */ 137 push %dx /* some BIOS zap %dl here :-( */ 138 int $0x13 /* ah == 0 from code above */ 139 pop %dx 140 141 /* Read from start of disk */ 142 incw %cx /* track zero sector 1 */ 143 movb %ch, %dh /* dh = head = 0 */ 144 call chs_read 145 146/* See if this is our code, if so we have already loaded the next stage */ 147 148 xorl %ebp, %ebp /* pass sector 0 to next stage */ 149 movl (%bx), %eax /* MBR code shouldn't even have ... */ 150 cmpl R(start), %eax /* ... a jmp at the start. */ 151 je pbr_read_ok1 152 153/* Now scan the MBR partition table for a netbsd partition */ 154 155 xorl %ebx, %ebx /* for base extended ptn chain */ 156scan_ptn_tbl: 157 xorl %ecx, %ecx /* for next extended ptn */ 158 movw $BOOTADDR + MBR_PART_OFFSET, %di 1591: movb 4(%di), %al /* mbrp_type */ 160 movl 8(%di), %ebp /* mbrp_start == LBA sector */ 161 addl lba_sector, %ebp /* add base of extended partition */ 162#ifdef BOOT_FROM_FAT 163 cmpb $MBR_PTYPE_FAT12, %al 164 je 5f 165 cmpb $MBR_PTYPE_FAT16S, %al 166 je 5f 167 cmpb $MBR_PTYPE_FAT16B, %al 168 je 5f 169 cmpb $MBR_PTYPE_FAT32, %al 170 je 5f 171 cmpb $MBR_PTYPE_FAT32L, %al 172 je 5f 173 cmpb $MBR_PTYPE_FAT16L, %al 174 je 5f 175#else 176 cmpb $MBR_PTYPE_NETBSD, %al 177#endif 178 jne 10f 1795: testl %esi, %esi /* looking for a specific sector? */ 180 je boot 181 cmpl %ebp, %esi /* ptn we wanted? */ 182 je boot 183 /* check for extended partition */ 18410: cmpb $MBR_PTYPE_EXT, %al 185 je 15f 186 cmpb $MBR_PTYPE_EXT_LBA, %al 187 je 15f 188 cmpb $MBR_PTYPE_EXT_LNX, %al 189 jne 20f 19015: movl 8(%di), %ecx /* sector of next ext. ptn */ 19120: add $0x10, %di 192 cmp $BOOTADDR + MBR_MAGIC_OFFSET, %di 193 jne 1b 194 195 /* not in base partitions, check extended ones */ 196 jecxz no_netbsd_ptn 197 testl %ebx, %ebx 198 jne 30f 199 xchgl %ebx, %ecx /* save base of ext ptn chain */ 20030: addl %ebx, %ecx /* address this ptn */ 201 movl %ecx, lba_sector /* sector to read */ 202 call read_lba 203 jmp scan_ptn_tbl 204 205no_netbsd_ptn: 206 /* Specific sector not found: try again looking for first NetBSD ptn */ 207 testl %esi, %esi 208 set_err(ERR_PTN) 209 jz error 210 xorl %esi, %esi 211 movl %esi, lba_sector 212 jmp start 213 214/* 215 * Sector below CHS limit 216 * Do a cylinder-head-sector read instead 217 * I believe the BIOS should do reads that cross track boundaries. 218 * (but the read should start at the beginning of a track...) 219 */ 220read_chs: 221 movb 1(%di), %dh /* head */ 222 movw 2(%di), %cx /* ch=cyl, cl=sect */ 223 call chs_read 224pbr_read_ok1: 225 jmp pbr_read_ok 226 227/* 228 * Active partition pointed to by di. 229 * 230 * We can either do a CHS (Cylinder Head Sector) or an LBA (Logical 231 * Block Address) read. Always doing the LBA one 232 * would be nice - unfortunately not all systems support it. 233 * Also some may contain a separate (eg SCSI) BIOS that doesn't 234 * support it even when the main BIOS does. 235 * 236 * The safest thing seems to be to find out whether the sector we 237 * want is inside the CHS sector count. If it is we use CHS, if 238 * outside we use LBA. 239 * 240 * Actually we check that the CHS values reference the LBA sector, 241 * if not we assume that the LBA sector is above the limit, or that 242 * the geometry used (by fdisk) isn't correct. 243 */ 244boot: 245 movl %ebp, lba_sector /* to control block */ 246 testl %ebx, %ebx /* was it an extended ptn? */ 247 jnz boot_lba /* yes - boot with LBA reads */ 248 249/* get CHS values from BIOS */ 250 push %dx /* save drive number */ 251 movb $8, %ah 252 int $0x13 /* chs info */ 253 254/* 255 * Validate geometry, if the CHS sector number doesn't match the LBA one 256 * we'll do an LBA read. 257 * calc: (cylinder * number_of_heads + head) * number_of_sectors + sector 258 * and compare against LBA sector number. 259 * Take a slight 'flier' and assume we can just check 16bits (very likely 260 * to be true because the number of sectors per track is 63). 261 */ 262 movw 2(%di), %ax /* cylinder + sector */ 263 push %ax /* save for sector */ 264 shr $6, %al 265 xchgb %al, %ah /* 10 bit cylinder number */ 266 shr $8, %dx /* last head */ 267 inc %dx /* number of heads */ 268 mul %dx 269 mov 1(%di), %dl /* head we want */ 270 add %dx, %ax 271 and $0x3f, %cx /* number of sectors */ 272 mul %cx 273 pop %dx /* recover sector we want */ 274 and $0x3f, %dx 275 add %dx, %ax 276 dec %ax 277 pop %dx /* recover drive nmber */ 278 279 cmp %bp, %ax 280 je read_chs 281 282check_lba: 283#ifdef NO_LBA_CHECK 284 jmp boot_lba 285#else 286/* 287 * Determine whether we have int13-extensions, by calling 288 * int 13, function 41. Check for the magic number returned, 289 * and the disk packet capability. 290 * 291 * This is actually relatively pointless: 292 * 1) we only use LBA reads if CHS ones would fail 293 * 2) the MBR code managed to read the same sectors 294 * 3) the BIOS will (ok should) reject the LBA read as a bad BIOS call 295 */ 296 movw $0x55aa, %bx 297 movb $0x41, %ah 298 int $0x13 299 jc 1f /* no int13 extensions */ 300 cmpw $0xaa55, %bx 301 jnz 1f 302 testb $1, %cl 303 jnz boot_lba 3041: set_err(ERR_NO_LBA) 305#endif /* NO_LBA_CHECK */ 306 307/* 308 * Something went wrong, 309 * Output error code, 310 */ 311 312error: 313#ifdef TERSE_ERROR 314 movb %al, errcod 315 movw $errtxt, %si 316 call message 317#else 318 push %ax 319 movw $errtxt, %si 320 call message 321 pop %si 322 call message 323 movw $newline, %si 324 call message 325#endif 3261: sti 327 hlt 328 jmp 1b 329 330boot_lba: 331 call read_lba 332 333/* 334 * Check magic number for valid stage 2 bootcode 335 * then jump into it. 336 */ 337pbr_read_ok: 338 cmpl $X86_BOOT_MAGIC_1, bootxx_magic 339 set_err(ERR_NO_BOOTXX) 340 jnz error 341 342 movl %ebp, %esi /* %esi ptn base, %dl disk id */ 343 movl lba_sector + 4, %edi /* %edi ptn base high */ 344 jmp $0, $bootxx /* our %cs may not be zero */ 345 346/* Read disk using int13-extension parameter block */ 347read_lba: 348 pusha 349 movw $lba_info, %si /* ds:si is ctl block */ 350 movb $0x42, %ah 351do_read: 352 int $0x13 353 popa 354 355 set_err(ERR_READ) 356 jc error 357 ret 358 359/* Read using CHS */ 360 361chs_read: 362 movw $BOOTADDR, %bx /* es:bx is buffer */ 363 pusha 364 movw $0x200 + BOOTXX_SECTORS, %ax /* command 2, xx sectors */ 365 jmp do_read 366 367#ifndef BOOT_FROM_FAT 368boot_gpt: 369 movl (20+32+0)(%si), %ebp 370 movl (20+32+4)(%si), %edi 371 movw %cx, %ds 372 movl %ebp, lba_sector + 0 373 movl %edi, lba_sector + 4 374 movl %ebp, %esi 375 jmp boot_lba 376#endif 377 378_errtxt: .ascii "Error " /* runs into newline... */ 379_errcod: .byte 0 /* ... if errcod set */ 380_newline: 381 .asciz "\r\n" 382 383#ifndef TERSE_ERROR 384ERR_READ: .asciz "read" 385ERR_NO_BOOTXX: .asciz "no magic" 386ERR_PTN: .asciz "no slice" 387#ifndef NO_LBA_CHECK 388ERR_NO_LBA: .asciz "need LBA" 389#endif 390#endif 391 392/* 393 * I hate #including source files, but pbr_magic below has to be at 394 * the correct absolute address. 395 * Clearly this could be done with a linker script. 396 */ 397 398#include <message.S> 399#if 0 400#include <dump_eax.S> 401#endif 402 403/* Control block for int-13 LBA read. */ 404_lba_info: 405 .word 0x10 /* control block length */ 406 .word BOOTXX_SECTORS /* sector count */ 407 .word BOOTADDR /* offset in segment */ 408 .word 0 /* segment */ 409_lba_sector: 410 .quad 0 /* sector # goes here... */ 411 412/* Drive Serial Number */ 413 . = _C_LABEL(start) + MBR_DSN_OFFSET 414 .long 0 415 416/* mbr_bootsel_magic (not used here) */ 417 . = _C_LABEL(start) + MBR_BS_MAGIC_OFFSET 418 .word 0 419 420/* 421 * Provide empty MBR partition table. 422 * If this is installed as an MBR, the user can use fdisk(8) to create 423 * the correct partition table ... 424 */ 425 . = _C_LABEL(start) + MBR_PART_OFFSET 426_pbr_part0: 427 .byte 0, 0, 0, 0, 0, 0, 0, 0 428 .long 0, 0 429_pbr_part1: 430 .byte 0, 0, 0, 0, 0, 0, 0, 0 431 .long 0, 0 432_pbr_part2: 433 .byte 0, 0, 0, 0, 0, 0, 0, 0 434 .long 0, 0 435_pbr_part3: 436 .byte 0, 0, 0, 0, 0, 0, 0, 0 437 .long 0, 0 438 439/* 440 * The magic comes last 441 */ 442 . = _C_LABEL(start) + MBR_MAGIC_OFFSET 443pbr_magic: 444 .word MBR_MAGIC 445