TJ
2009-Mar-30 21:39 UTC
[syslinux] [PATCH 1/1] v3: Add Diagnostic MBR for trouble-shooting BIOS boot-order problems.
--- mbr/Makefile | 6 +- mbr/mbr-diag.S | 377 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 382 insertions(+), 1 deletions(-) create mode 100644 mbr/mbr-diag.S diff --git a/mbr/Makefile b/mbr/Makefile index 0bdf7e3..b9d743d 100644 --- a/mbr/Makefile +++ b/mbr/Makefile @@ -17,7 +17,7 @@ topdir = .. include $(topdir)/MCONFIG.embedded -all: mbr.bin gptmbr.bin isohdpfx.bin +all: mbr.bin gptmbr.bin isohdpfx.bin mbr-diag.bin .PRECIOUS: %.o %.o: %.S @@ -31,6 +31,10 @@ mbr.bin: mbr.elf checksize.pl $(OBJCOPY) -O binary $< $@ $(PERL) checksize.pl $@ 440 +mbr-diag.bin: mbr-diag.elf checksize.pl + $(OBJCOPY) -O binary $< $@ + $(PERL) checksize.pl mbr-diag.bin 440 + isohdpfx.bin: isohdpfx.elf checksize.pl $(OBJCOPY) -O binary $< $@ $(PERL) checksize.pl $@ 432 diff --git a/mbr/mbr-diag.S b/mbr/mbr-diag.S new file mode 100644 index 0000000..eba4192 --- /dev/null +++ b/mbr/mbr-diag.S @@ -0,0 +1,377 @@ +/* + Diagnostic Master Boot Record + Copyright 2009 TJ <linux at tjworld.net> <ubuntu at tjworld.net> + Licensed on the terms of the GNU General Public License, version 2 + + This is a diagnsotic master boot record (MBR) with the sole purpose of + reporting the BIOS values received at boot-time for addressing mode, drive-number, + device geometry, active partition and magic bytes of active partition boot sector. + + It is an aid to determining why some devices fail to boot correctly when + using the 'default' MBR. + + NOTE: If a shift key is held down at boot, CHS addressing mode is forced + If Ctrl key is held down, drive number 0x80 is forced + + It is also an educational tool and commented in a way which should increase + a user's understanding of both assembly programming and the boot process. + + The jump-off web page for the GNU documentation is http://www.gnu.org/software/binutils/ + The current (binutils 2.19.1) GNU 'as' documentation is at: http://sourceware.org/binutils/docs-2.19/as/index.html + + In comments 'doc:' refers to the binutils 'as' documentation section. + + This code uses the 'AT & T' form of instructions: op [[source] [destination]] doc: 9.13.3 + Ops explicitly use the size suffix to prevent GNU 'as' creating 32-bit op-codes. doc: 9.13.12 + + Due to the severe space constraints the output uses 1-character description codes to prefix each printed value. + Values are in hexadecimal. + + Description Codes: + L | C LBA or CHS addressing mode + D drive number BIOS-reported drive number + C cylinders Geometry of drive according to BIOS + H heads + S sectors + P partition active partition number (first partition flagged active). '?' if no active partition + O offset absolute sector offset of active partition . '????????' if no active partition + M magic magic bytes of active partition boot sector (sector <offset> as read by BIOS). + '????' if no active partition. Value is reset to 0xDEAD before the sector is read + to avoid inheriting the MBR magic on error + E error error code returned by BIOS 'read sector' interrupt (0x02 or 0x42, int 0x13). + '??' if no active partition. + + Examples: + L D81 C3D9 HFF S3F P1 O00000020 MAA55 E00 + C D80 C3D9 HFF S3F P1 O00000020 M0000 E04 + + Usage: + # set device to write diagnostic MBR to + DEV=/dev/sdc + # back-up existing MBR + sudo dd if=$DEV of=mbr-backup.bin bs=440 count=1 + # write diagnostic MBR to device + sudo dd if=mbr-diag.bin of=$DEV + # example: test in a virtual machine (uses sudo because it is accessing raw device) + sudo kvm -m 128 -hda $DEV + # restore original MBR + sudo dd if=mbr-backup.bin of=$DEV + +*/ + + .code16 /* generate 16-bit code. doc: 9.13.12 */ + .text /* sub-section (default 0). doc: 7.109 */ + +video_page = 0x462 /* I/O port for video controller page selection (bitmask 0x07) */ +flag_active = 0x80 /* partition table entry active (bootable) flag */ + +stack = 0x7C00 /* top of stack is immediately below where the BIOS loaded this MBR */ +pnp_header = (stack-4) /* The Plug'n'Play header received from BIOS in ES:DI */ +drive_number = (stack-6) /* The boot device as reported by BIOS in DL */ +cylinders = (stack-8) +heads = (stack-10) +sectors = (stack-12) +sectors_cylinder= (stack-16) + + .section ".bootsec", "a", @nobits /* assemble into; ELF: allocatable, no data. doc: 7.95 */ + .global bootsec /* externally visible. doc: 7.56 */ +bootsec: + .space 512 /* set 512 bytes to zero. doc: 7.102 */ + + .text + .global _start +_start: + + /* All code from here until the relocation operation does nothing that relies */ + /* on the value of the instruction pointer */ + cli /* disable interrupts */ + xorw %ax, %ax /* reset to 0 */ + movw %ax, %ds /* reset data segment base */ + movw %ax, %ss /* reset stack segment base */ + movw $stack, %sp /* set stack pointer */ + movw %sp, %si /* address where BIOS loaded MBR, used as source address for relocation */ + movw %sp, %bp /* needed later if drive number is to be forced due to Ctrl key being pressed */ + pushw %es /* save Plug'n'Play header provided by BIOS in ES:DI */ + pushw %di + pushw %dx /* save drive number provided by BIOS in DL */ + movw %ax, %es /* reset extra segment base */ + sti /* enable interrupts */ + cld /* increment direction for relocation operation */ +relocate: + movw $_start, %di /* destination address for relocation */ + movw $(512/2), %cx /* number of words to relocate */ + rep; movsw /* repeat the move CX times, incrementing SI and DI after each move */ + + ljmpw $0, $get_shift_keys /* far jump to ensure CS:IP are set correctly to 0000:0600+get_shift_keys */ + /* rather than the current CS:IP which will be based on 0x7C00 */ + +get_shift_keys: /* Provide a way for the user to force CHS addressing mode by pressing either Shift key */ + testb $0x04, 0x0417 /* I/O memory-mapped keyboard shift flags: control key pressed? */ + jz shift_test +force_first_hard_disk: + movb $0x80, -6(%bp) /* offset from top-of-stack to drive number; force it to be first hard disk */ + movb $0x80, %dl /* change the drive number about to be used in detect_bios_type */ +shift_test: + testb $0x03, 0x0417 /* left or right shift-keys pressed? */ + jnz use_chs_addressing /* if so, skip detection of BIOS type */ + +detect_bios_type: + /* try to use the BIOS extension for LBA (Logical Block Addressing). */ + /* inherit drive number in DL from BIOS */ + movw $0x55aa, %bx /* required */ + movb $0x41, %ah /* query presence of extended BIOS functions (EBIOS) */ + int $0x13 /* call BIOS */ + jc use_chs_addressing /* carry flag will be set on return if extensions are NOT supported */ + cmpb $0x55, %bl /* bytes returned swapped if successful */ + jne use_chs_addressing + shrw %cx /* Shift right once into carry flag. Bit 0: extended (LBA) disk functions available */ + jnc use_chs_addressing + jmp use_lba_addressing + +use_chs_addressing: + movb $0x02, read_op /* set the BIOS function to use when reading CHS sectors */ + movb $'C', (msg_address_mode) /* over-write 'L' with 'C' in boot message */ + +use_lba_addressing: + /* convert drive-number to two hex characters and insert them into the boot message */ + movw (drive_number), %dx + pushw %dx /* preserve for use by calculate_geometry */ + movw $msg_drive_number, %di + movb $0x02, %cl /* write two nibbles */ + call write_hex_value + +calculate_geometry: + popw %dx /* retrieve drive number */ + movb $0x08, %ah + int $0x13 /* get drive parameters; returns CH LSB of max cyl., CL(7-6) MSb max cyl., */ + /* CL(5-0) max sector, DH max head, DL drive qty, ES:DI floppy parameter table */ + movb %ch, %bl /* transfer maximum cylinder number */ + movb %cl, %bh + shrb $6, %bh /* move bits into correct positions */ + pushw %bx /* preserve maximum cylinder number */ + incb %dh /* convert zero-based maximum to quantity */ + movzbw %dh, %ax /* DH = maximum head number */ + pushw %ax /* preserve maximum head number */ + andw $0x3f, %cx /* mask to enforce maximum sector number 63 */ + pushw %cx /* preserve sector count */ + incw %ax /* convert zero-based maximum head number to quantity */ + mulw %cx /* sectors per cylinder = heads*sectors */ + pushw %dx /* preserve sectors per cylinder DX:AX */ + pushw %ax + +msg_format_cylinders: + movw $msg_cylinders, %di + movw (cylinders), %dx + movb $0x03, %cl /* write three nibbles */ + call write_hex_value + +msg_format_heads: + movw $msg_heads, %di + movw (heads), %dx + movb $0x02, %cl /* write two nibbles */ + push %cx /* preserve for sectors */ + call write_hex_value + +msg_format_sectors: + movw $msg_sectors, %di + movw (sectors), %dx + popw %cx /* write two nibbles */ + call write_hex_value + +partition_table_scan: + movw $partition_table, %si + movw $0x04, %cx /* number of table entries */ + +partition_entry_next: + testb $flag_active, (%si) /* is active (bootable) flag set? */ + jnz msg_partition_active + + addw $16, %si /* next entry */ + loopw partition_entry_next + + jmp print_boot_message /* no active parition, so don't try to read a sector */ + +msg_partition_active: + movb $0x05, %ch /* convert CL countdown to partition number */ + subb %cl, %ch /* CL:partition translations 4:1, 3:2, 2:3, 1:4 */ + movb %ch, %dl /* value to write is partition number [1-4] */ + movw $msg_partition, %di + movb $0x01, %cl /* one nibble */ + call write_hex_value + +partition_get_offset: + movl 8(%si), %edx /* absolute starting sector (DWORD: partition_table[8]) */ + pushl %edx /* preserve for use in partition_read_sector */ + rorl $16, %edx /* swap low and high WORDs - high WORD needed in DX first */ + movw $msg_partition_offset_high, %di /* high WORD of DWORD */ + movb $0x04, %cl /* four nibbles */ + pushw %cx /* use again for low WORD */ + call write_hex_value + + /* at this point DI should be pointing to first character of low WORD in the string */ + shrl $16, %edx /* shift high WORD into low WORD */ + popw %cx /* use again for low WORD */ + call write_hex_value + +partition_read_sector: + movw $0xDEAD, %cx /* over-write current 'magic' indicator in sector buffer */ + /* this ensures that if read_sector fails the MBR magic isn't still there */ + movw %cx, (bootsec + 510) + popl %eax /* retrieve boot sector of active partition into buffer */ + call read_sector + pushw %ax /* preserve read error code (0 = success) */ + +msg_partition_magic: + movw $msg_boot_sector_magic, %di + movb $0x04, %cl /* four nibbles */ + movw (bootsec + 510), %dx /* get last two bytes of sector */ + call write_hex_value + +msg_read_error_code: + movw $msg_read_error, %di + movb $0x02, %cl /* two nibbles */ + popw %dx /* retrieve read error code */ + shrw $8, %dx /* error code in high byte needs to be in low byte for writing */ + call write_hex_value + +print_boot_message: + movw $msg_boot, %si + call print /* print the boot message */ + +stop: + hlt /* stop nicely; don't burn the CPU up */ + jmp stop + +/* + * read_sector: read a single sector pointed to by %eax to 0x7c00. + * CF is set on error. All registers saved. + */ +read_sector: + xorl %edx, %edx /* reset to 0 */ + /* create a disk address packet structure on the stack */ + pushl %edx /* most significant double-word of LBA (upper part of 48-bit LBAs) */ + pushl %eax /* least significant double-word of LBA */ + pushw %es /* buffer segment */ + movw $bootsec, %bx /* address of buffer that will receive the sector (should be 0x7C00) */ + pushw %bx /* buffer offset */ + pushw $1 /* sector count always 1 */ + pushw $16 /* size of disk address packet */ + movw %sp, %si /* address of disk address packet (on stack) */ + + testb $0x42, read_op /* using LBA addressing? */ + jz read_common /* Use LBA extended addressing */ + +read_sector_chs: + divl (sectors_cylinder) /* LBA in DX:AX inherited from above */ + shlb $6, %ah /* quotient is the cylinder number */ + movb %ah, %cl /* cylinder high bits (7-6) */ + movb %al, %ch /* cylinder low bits (7-0) */ + xchgw %dx, %ax /* remainder is the sector-offset into the cylinder */ + divb (sectors) + movb %al, %dh /* head number */ + orb %ah, %cl /* sector offset ORed into CL since CL(7-6) already contain two high bits of cylinder number */ + incw %cx /* sector counts start at 1 not 0 */ + +read_common: +read_op = .+2 /* address of operand to be moved into AH (0x42 in the following instruction) */ + movw $0x4201, %ax /* default is 0x42 (LBA) but may be over-written with 0x02 (CHS). AL = number of sectors */ + movb (drive_number), %dl + int $0x13 /* read disk */ + + addw $16, %sp /* discard disk address packet */ + ret + +/* write value as hex. + writes CL (max 4) hex digits representing the value of DX starting at DI + The choice of nibbles is right-aligned so the least-significant will always be processed. +*/ + +write_hex_value: + xorb %ch, %ch /* reset to 0 to ensure CX loop uses only CL value passed in */ + +write_hex_next: + pushw %dx /* preserve value */ + cmpb $0x02, %cl /* is this the high byte? */ + jle write_hex_byte + movb %dh, %dl /* move high-byte into low byte for processing */ + +write_hex_byte: + btw $0x00, %cx /* low nibble? (CX = 4,2: high nibble. CX = 3,1: low nibble) */ + jc write_hex_nibble + +write_hex_high_nibble: + shrb $4, %dl /* high nibble required; shift into low nibble */ + +write_hex_nibble: + andb $0x0F, %dl /* mask to retain only the low nibble */ + call hex_ascii_format + movb %dl, (%di) /* over-write character in string */ + incw %di /* next character */ + pop %dx /* retrieve value for next iteration */ + loopw write_hex_next + + ret + +/* low nibble of DL contains value to be converted. returns ASCII code in DL */ +hex_ascii_format: + addb $'0', %dl /* offset into ASCII table; value 0 == ASCII "0" */ + cmpb $'9', %dl + jle nibble_set /* value is 0-9; no need to adjust */ + addb $('A' - ':'), %dl /* adjust 0x0a-0x0f to map to ASCII characters 'A' to 'F' */ +nibble_set: + ret + +/* print a zero-terminated string of characters whose address is in SI */ +print: + cld /* move left-to-right in string */ + movb (video_page), %bh /* get current page from video controller's memory-mapped I/O register */ + movb $0x07, %bl /* colour attribute */ + movb $0x0e, %ah /* BIOS function: display char */ + +print_next: + lodsb /* load a character into AL */ + orb %al, %al /* is it the string zero terminator? */ + jz print_end /* finished */ + + call print_char + jmp print_next /* next character */ + +print_char: + int $0x10 /* display */ + ret + +print_end: + ret + +msg_boot: +msg_address_mode: + .ascii "L" /* These entries make up ONE zero-terminated string */ + .ascii " D" +msg_drive_number: + .ascii "??" /* ?? will be over-written with drive number */ + .ascii " C" +msg_cylinders: + .ascii "???" + .ascii " H" +msg_heads: + .ascii "??" + .ascii " S" +msg_sectors: + .ascii "??" + .ascii " P" +msg_partition: + .ascii "?" + .ascii " O" +msg_partition_offset_high: + .ascii "????" +msg_partition_offset_low: + .ascii "????" + .ascii " M" +msg_boot_sector_magic: + .ascii "????" + .ascii " E" +msg_read_error: + .asciz "??" + +partition_table = _start + 446 /* location of partition table */ + -- 1.6.0.4
Miller, Shao
2009-Jul-24 19:43 UTC
[syslinux] [PATCH 1/1] v3: Add Diagnostic MBR for trouble-shooting BIOS boot-order problems.
Just curious if T. J.'s mbr-diag.S might be possible to include in Syslinux. - Shao -----Original Message----- From: syslinux-bounces at zytor.com [mailto:syslinux-bounces at zytor.com] On Behalf Of TJ Sent: Monday, March 30, 2009 17:39 To: Syslinux Subject: [syslinux] [PATCH 1/1] v3: Add Diagnostic MBR for trouble-shooting BIOS boot-order problems.
H. Peter Anvin
2009-Jul-30 14:38 UTC
[syslinux] [PATCH 1/1] v3: Add Diagnostic MBR for trouble-shooting BIOS boot-order problems.
On 03/30/2009 02:39 PM, TJ wrote:> diff --git a/mbr/mbr-diag.S b/mbr/mbr-diag.S > new file mode 100644 > index 0000000..eba4192 > --- /dev/null > +++ b/mbr/mbr-diag.S > @@ -0,0 +1,377 @@ > +/* > + Diagnostic Master Boot Record > + Copyright 2009 TJ <linux at tjworld.net> <ubuntu at tjworld.net> > + Licensed on the terms of the GNU General Public License, version 2 > + > + This is a diagnsotic master boot record (MBR) with the sole purpose of > + reporting the BIOS values received at boot-time for addressing mode, drive-number, > + device geometry, active partition and magic bytes of active partition boot sector. > + > + It is an aid to determining why some devices fail to boot correctly when > + using the 'default' MBR. > +Hi TJ, This message had fallen off my radar, I'm sorry for that. The other MBRs in the Syslinux distribution are licensed under the MIT license, would that be acceptable for this as well? -hpa -- H. Peter Anvin, Intel Open Source Technology Center I work for Intel. I don't speak on their behalf.