---
mbr/Makefile | 6 +-
mbr/mbr-diag.S | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 376 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..5e12cbc
--- /dev/null
+++ b/mbr/mbr-diag.S
@@ -0,0 +1,371 @@
+/*
+ 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 drive-number,
+ device geometry, active partition and magic bytes are.
+
+ 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
+
+ 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.
+ Hex values are suffixed with 'h' (saves 1 byte against using 0x).
+
+ 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)
+ O offset absolute sector offset of active partition
+ M magic magic bytes of active partition boot sector (sector <offset>
as read by BIOS)
+ value is reset to 0000 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)
+
+ Examples:
+ L D80h C3D9h HFFh S3Fh P1 O00000020h MAA55h E00h
+ C D80h C3D9h HFFh S3Fh P1 O00000020h M0000h E04h
+
+ 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-diagnostic.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 */
+ 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 */
+ movb $0x02, %ah /* function: get keyboard shift flags */
+ int $0x16 /* read keys; shift flags returned in AL */
+ testb $0x03, %al /* 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). */
+ movb (drive_number), %dl
+ 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 */
+ cmpw $0xaa55, %bx /* 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 $53, (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 to quantity */
+ mulw %cx /* sectors per cylinder = heads*sectors */
+ pushw %dx /* preserve sectors per cylinder */
+ 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 */
+
+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:
+ /* inherit CX=0 from write_hex_value */
+ /* 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 /* start of result including error indicator
"!" */
+ 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:
+ mov $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:
+ .ascii "DIAG "
+msg_address_mode:
+ .ascii "L" /* These entries make up ONE zero-terminated string */
+ .ascii " D"
+msg_drive_number:
+ .ascii "??h" /* ?? will be over-written with drive number */
+ .ascii " C"
+msg_cylinders:
+ .ascii "???h"
+ .ascii " H"
+msg_heads:
+ .ascii "??h"
+ .ascii " S"
+msg_sectors:
+ .ascii "??h"
+ .ascii " P"
+msg_partition:
+ .ascii "?"
+ .ascii " O"
+msg_partition_offset_high:
+ .ascii "????"
+msg_partition_offset_low:
+ .ascii "????h"
+ .ascii " M"
+msg_boot_sector_magic:
+ .ascii "????h"
+ .ascii " E"
+msg_read_error:
+ .ascii "??h"
+ .asciz "\r\n"
+
+partition_table = _start + 446 /* location of partition table */
+
--
1.6.0.4