| /* |
| * BIOS Enhanced Disk Drive support |
| * Copyright (C) 2002, 2003, 2004 Dell, Inc. |
| * by Matt Domsch <Matt_Domsch@dell.com> October 2002 |
| * conformant to T13 Committee www.t13.org |
| * projects 1572D, 1484D, 1386D, 1226DT |
| * disk signature read by Matt Domsch <Matt_Domsch@dell.com> |
| * and Andrew Wilks <Andrew_Wilks@dell.com> September 2003, June 2004 |
| * legacy CHS retrieval by Patrick J. LoPresti <patl@users.sourceforge.net> |
| * March 2004 |
| * Command line option parsing, Matt Domsch, November 2004 |
| */ |
| |
| #include <linux/edd.h> |
| #include <asm/setup.h> |
| |
| #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE) |
| |
| # It is assumed that %ds == INITSEG here |
| |
| movb $0, (EDD_MBR_SIG_NR_BUF) |
| movb $0, (EDDNR) |
| |
| # Check the command line for options: |
| # edd=of disables EDD completely (edd=off) |
| # edd=sk skips the MBR test (edd=skipmbr) |
| # edd=on re-enables EDD (edd=on) |
| |
| pushl %esi |
| movw $edd_mbr_sig_start, %di # Default to edd=on |
| |
| movl %cs:(cmd_line_ptr), %esi |
| andl %esi, %esi |
| jz old_cl # Old boot protocol? |
| |
| # Convert to a real-mode pointer in fs:si |
| movl %esi, %eax |
| shrl $4, %eax |
| movw %ax, %fs |
| andw $0xf, %si |
| jmp have_cl_pointer |
| |
| # Old-style boot protocol? |
| old_cl: |
| push %ds # aka INITSEG |
| pop %fs |
| |
| cmpw $0xa33f, (0x20) |
| jne done_cl # No command line at all? |
| movw (0x22), %si # Pointer relative to INITSEG |
| |
| # fs:si has the pointer to the command line now |
| have_cl_pointer: |
| |
| # Loop through kernel command line one byte at a time. Just in |
| # case the loader is buggy and failed to null-terminate the command line |
| # terminate if we get close enough to the end of the segment that we |
| # cannot fit "edd=XX"... |
| cl_atspace: |
| cmpw $-5, %si # Watch for segment wraparound |
| jae done_cl |
| movl %fs:(%si), %eax |
| andb %al, %al # End of line? |
| jz done_cl |
| cmpl $EDD_CL_EQUALS, %eax |
| jz found_edd_equals |
| cmpb $0x20, %al # <= space consider whitespace |
| ja cl_skipword |
| incw %si |
| jmp cl_atspace |
| |
| cl_skipword: |
| cmpw $-5, %si # Watch for segment wraparound |
| jae done_cl |
| movb %fs:(%si), %al # End of string? |
| andb %al, %al |
| jz done_cl |
| cmpb $0x20, %al |
| jbe cl_atspace |
| incw %si |
| jmp cl_skipword |
| |
| found_edd_equals: |
| # only looking at first two characters after equals |
| # late overrides early on the command line, so keep going after finding something |
| movw %fs:4(%si), %ax |
| cmpw $EDD_CL_OFF, %ax # edd=of |
| je do_edd_off |
| cmpw $EDD_CL_SKIP, %ax # edd=sk |
| je do_edd_skipmbr |
| cmpw $EDD_CL_ON, %ax # edd=on |
| je do_edd_on |
| jmp cl_skipword |
| do_edd_skipmbr: |
| movw $edd_start, %di |
| jmp cl_skipword |
| do_edd_off: |
| movw $edd_done, %di |
| jmp cl_skipword |
| do_edd_on: |
| movw $edd_mbr_sig_start, %di |
| jmp cl_skipword |
| |
| done_cl: |
| popl %esi |
| jmpw *%di |
| |
| # Read the first sector of each BIOS disk device and store the 4-byte signature |
| edd_mbr_sig_start: |
| movb $0x80, %dl # from device 80 |
| movw $EDD_MBR_SIG_BUF, %bx # store buffer ptr in bx |
| edd_mbr_sig_read: |
| movl $0xFFFFFFFF, %eax |
| movl %eax, (%bx) # assume failure |
| pushw %bx |
| movb $READ_SECTORS, %ah |
| movb $1, %al # read 1 sector |
| movb $0, %dh # at head 0 |
| movw $1, %cx # cylinder 0, sector 0 |
| pushw %es |
| pushw %ds |
| popw %es |
| movw $EDDBUF, %bx # disk's data goes into EDDBUF |
| pushw %dx # work around buggy BIOSes |
| stc # work around buggy BIOSes |
| int $0x13 |
| sti # work around buggy BIOSes |
| popw %dx |
| popw %es |
| popw %bx |
| jc edd_mbr_sig_done # on failure, we're done. |
| cmpb $0, %ah # some BIOSes do not set CF |
| jne edd_mbr_sig_done # on failure, we're done. |
| movl (EDDBUF+EDD_MBR_SIG_OFFSET), %eax # read sig out of the MBR |
| movl %eax, (%bx) # store success |
| incb (EDD_MBR_SIG_NR_BUF) # note that we stored something |
| incb %dl # increment to next device |
| addw $4, %bx # increment sig buffer ptr |
| cmpb $EDD_MBR_SIG_MAX, (EDD_MBR_SIG_NR_BUF) # Out of space? |
| jb edd_mbr_sig_read # keep looping |
| edd_mbr_sig_done: |
| |
| # Do the BIOS Enhanced Disk Drive calls |
| # This consists of two calls: |
| # int 13h ah=41h "Check Extensions Present" |
| # int 13h ah=48h "Get Device Parameters" |
| # int 13h ah=08h "Legacy Get Device Parameters" |
| # |
| # A buffer of size EDDMAXNR*(EDDEXTSIZE+EDDPARMSIZE) is reserved for our use |
| # in the boot_params at EDDBUF. The first four bytes of which are |
| # used to store the device number, interface support map and version |
| # results from fn41. The next four bytes are used to store the legacy |
| # cylinders, heads, and sectors from fn08. The following 74 bytes are used to |
| # store the results from fn48. Starting from device 80h, fn41, then fn48 |
| # are called and their results stored in EDDBUF+n*(EDDEXTSIZE+EDDPARMIZE). |
| # Then the pointer is incremented to store the data for the next call. |
| # This repeats until either a device doesn't exist, or until EDDMAXNR |
| # devices have been stored. |
| # The one tricky part is that ds:si always points EDDEXTSIZE bytes into |
| # the structure, and the fn41 and fn08 results are stored at offsets |
| # from there. This removes the need to increment the pointer for |
| # every store, and leaves it ready for the fn48 call. |
| # A second one-byte buffer, EDDNR, in the boot_params stores |
| # the number of BIOS devices which exist, up to EDDMAXNR. |
| # In setup.c, copy_edd() stores both boot_params buffers away |
| # for later use, as they would get overwritten otherwise. |
| # This code is sensitive to the size of the structs in edd.h |
| edd_start: |
| # %ds points to the bootsector |
| # result buffer for fn48 |
| movw $EDDBUF+EDDEXTSIZE, %si # in ds:si, fn41 results |
| # kept just before that |
| movb $0x80, %dl # BIOS device 0x80 |
| |
| edd_check_ext: |
| movb $CHECKEXTENSIONSPRESENT, %ah # Function 41 |
| movw $EDDMAGIC1, %bx # magic |
| int $0x13 # make the call |
| jc edd_done # no more BIOS devices |
| |
| cmpw $EDDMAGIC2, %bx # is magic right? |
| jne edd_next # nope, next... |
| |
| movb %dl, %ds:-8(%si) # store device number |
| movb %ah, %ds:-7(%si) # store version |
| movw %cx, %ds:-6(%si) # store extensions |
| incb (EDDNR) # note that we stored something |
| |
| edd_get_device_params: |
| movw $EDDPARMSIZE, %ds:(%si) # put size |
| movw $0x0, %ds:2(%si) # work around buggy BIOSes |
| movb $GETDEVICEPARAMETERS, %ah # Function 48 |
| int $0x13 # make the call |
| # Don't check for fail return |
| # it doesn't matter. |
| edd_get_legacy_chs: |
| xorw %ax, %ax |
| movw %ax, %ds:-4(%si) |
| movw %ax, %ds:-2(%si) |
| # Ralf Brown's Interrupt List says to set ES:DI to |
| # 0000h:0000h "to guard against BIOS bugs" |
| pushw %es |
| movw %ax, %es |
| movw %ax, %di |
| pushw %dx # legacy call clobbers %dl |
| movb $LEGACYGETDEVICEPARAMETERS, %ah # Function 08 |
| int $0x13 # make the call |
| jc edd_legacy_done # failed |
| movb %cl, %al # Low 6 bits are max |
| andb $0x3F, %al # sector number |
| movb %al, %ds:-1(%si) # Record max sect |
| movb %dh, %ds:-2(%si) # Record max head number |
| movb %ch, %al # Low 8 bits of max cyl |
| shr $6, %cl |
| movb %cl, %ah # High 2 bits of max cyl |
| movw %ax, %ds:-4(%si) |
| |
| edd_legacy_done: |
| popw %dx |
| popw %es |
| movw %si, %ax # increment si |
| addw $EDDPARMSIZE+EDDEXTSIZE, %ax |
| movw %ax, %si |
| |
| edd_next: |
| incb %dl # increment to next device |
| cmpb $EDDMAXNR, (EDDNR) # Out of space? |
| jb edd_check_ext # keep looping |
| |
| edd_done: |
| #endif |