From: Matt Domsch Patch below from Patrick J. LoPresti and myself. Patrick describes: Why this patch? The problem is that the legacy BIOS interface (INT13/AH=3D08) for querying the disk geometry returns different values than the extended INT13 interface which the EDD code currently uses. This is because the legacy interface only provides a 10-bit cylinder field, so modern BIOSes "lie" about the head/sector counts in order to make more of the disk visible within the first 1024 cylinders. Many non-Linux applications, including the stock Windows boot loader, DOS fdisk, etc., rely upon the legacy interface and geometry. So it is useful to be able to obtain the legacy values from a running Linux kernel. What this patch does is to add new entries under /sys/firmware/edd/int13_devXX named "legacy_cylinders", "legacy_heads", and "legacy_sectors". These provide the geometry given by the legacy INT13/AH=3D08 BIOS interface, just like the current "default_cylinders" etc. provide the the geometry given by the INT13/AH=3D48 interface. Without this patch, I cannot use Linux to partition a drive and install Windows, which happens to be my application. - Pat http://unattended.sourceforge.net/ In addition, this adds two buggy BIOS workarounds in the EDD int13 calls as suggested by Ralf Brown's interrupt list. I'm also interested in moving this code out of arch/i386/kernel/edd.c and include/asm-i386/edd.h, as I believe it is applicable on x86-64 as well. However, there's no good place under drivers/ to put edd.c when it's not tied to a bus, but to several CPU architectures and their firmwares... Maybe a new directory drivers/firmware? --- 25-akpm/Documentation/i386/zero-page.txt | 2 25-akpm/arch/i386/boot/setup.S | 76 +++++++++++++++++++------- 25-akpm/arch/i386/kernel/edd.c | 88 ++++++++++++++++++++++++++++--- 25-akpm/include/asm-i386/edd.h | 6 +- 4 files changed, 143 insertions(+), 29 deletions(-) diff -puN arch/i386/boot/setup.S~edd-legacy-parameters-fix arch/i386/boot/setup.S --- 25/arch/i386/boot/setup.S~edd-legacy-parameters-fix Tue Mar 9 15:02:19 2004 +++ 25-akpm/arch/i386/boot/setup.S Tue Mar 9 15:02:19 2004 @@ -51,6 +51,8 @@ * projects 1572D, 1484D, 1386D, 1226DT * disk signature read by Matt Domsch * and Andrew Wilks September 2003 + * legacy CHS retreival by Patrick J. LoPresti + * March 2004 */ #include @@ -592,7 +594,11 @@ done_apm_bios: pushw %ds popw %es movw $EDDBUF, %bx - int $0x13 + pushw %dx # work around buggy BIOSes + stc # work around buggy BIOSes + int $0x13 + sti # work around buggy BIOSes + popw %dx jc disk_sig_done movl (EDDBUF+MBR_SIG_OFFSET), %eax movl %eax, (DISK80_SIG_BUFFER) # store success @@ -603,32 +609,34 @@ disk_sig_done: # 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 empty_zero_page at EDDBUF. The first four bytes of which are # used to store the device number, interface support map and version -# results from fn41. The following 74 bytes are used to store -# the results from fn48. Starting from device 80h, fn41, then fn48 +# 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 four bytes into -# the structure, and the fn41 results are stored at offsets +# 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 empty_zero_page stores # the number of BIOS devices which exist, up to EDDMAXNR. # In setup.c, copy_edd() stores both empty_zero_page buffers away -# for later use, as they would get overwritten otherwise. +# for later use, as they would get overwritten otherwise. # This code is sensitive to the size of the structs in edd.h -edd_start: +edd_start: # %ds points to the bootsector # result buffer for fn48 - movw $EDDBUF+EDDEXTSIZE, %si # in ds:si, fn41 results - # kept just before that + movw $EDDBUF+EDDEXTSIZE, %si # in ds:si, fn41 results + # kept just before that movb $0, (EDDNR) # zero value at EDDNR - movb $0x80, %dl # BIOS device 0x80 + movb $0x80, %dl # BIOS device 0x80 edd_check_ext: movb $CHECKEXTENSIONSPRESENT, %ah # Function 41 @@ -636,30 +644,56 @@ edd_check_ext: int $0x13 # make the call jc edd_done # no more BIOS devices - cmpw $EDDMAGIC2, %bx # is magic right? + cmpw $EDDMAGIC2, %bx # is magic right? jne edd_next # nope, next... - movb %dl, %ds:-4(%si) # store device number - movb %ah, %ds:-3(%si) # store version - movw %cx, %ds:-2(%si) # store extensions + 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: + +edd_get_device_params: movw $EDDPARMSIZE, %ds:(%si) # put size - movb $GETDEVICEPARAMETERS, %ah # Function 48 + 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? + incb %dl # increment to next device + cmpb $EDDMAXNR, (EDDNR) # Out of space? jb edd_check_ext # keep looping - -edd_done: + +edd_done: #endif # Now we want to move to protected mode ... diff -puN arch/i386/kernel/edd.c~edd-legacy-parameters-fix arch/i386/kernel/edd.c --- 25/arch/i386/kernel/edd.c~edd-legacy-parameters-fix Tue Mar 9 15:02:19 2004 +++ 25-akpm/arch/i386/kernel/edd.c Tue Mar 9 15:02:19 2004 @@ -1,8 +1,9 @@ /* * linux/arch/i386/kernel/edd.c - * Copyright (C) 2002, 2003 Dell Inc. + * Copyright (C) 2002, 2003, 2004 Dell Inc. * by Matt Domsch * disk80 signature by Matt Domsch, Andrew Wilks, and Sandeep K. Shandilya + * legacy CHS by Patrick J. LoPresti * * BIOS Enhanced Disk Drive Services (EDD) * conformant to T13 Committee www.t13.org @@ -60,7 +61,7 @@ MODULE_AUTHOR("Matt Domsch params); if (!edev || !info || !buf) { return -EINVAL; } @@ -240,10 +241,10 @@ edd_show_raw_data(struct edd_device *ede len = info->params.length; /* In case of buggy BIOSs */ - if (len > (sizeof(*info) - 4)) - len = sizeof(*info) - 4; + if (len > (sizeof(info->params))) + len = sizeof(info->params); - memcpy(buf, ((char *)info) + 4, len); + memcpy(buf, &info->params, len); return len; } @@ -321,6 +322,45 @@ edd_show_info_flags(struct edd_device *e } static ssize_t +edd_show_legacy_cylinders(struct edd_device *edev, char *buf) +{ + struct edd_info *info = edd_dev_get_info(edev); + char *p = buf; + if (!edev || !info || !buf) { + return -EINVAL; + } + + p += snprintf(p, left, "0x%x\n", info->legacy_cylinders); + return (p - buf); +} + +static ssize_t +edd_show_legacy_heads(struct edd_device *edev, char *buf) +{ + struct edd_info *info = edd_dev_get_info(edev); + char *p = buf; + if (!edev || !info || !buf) { + return -EINVAL; + } + + p += snprintf(p, left, "0x%x\n", info->legacy_heads); + return (p - buf); +} + +static ssize_t +edd_show_legacy_sectors(struct edd_device *edev, char *buf) +{ + struct edd_info *info = edd_dev_get_info(edev); + char *p = buf; + if (!edev || !info || !buf) { + return -EINVAL; + } + + p += snprintf(p, left, "0x%x\n", info->legacy_sectors); + return (p - buf); +} + +static ssize_t edd_show_default_cylinders(struct edd_device *edev, char *buf) { struct edd_info *info = edd_dev_get_info(edev); @@ -384,6 +424,33 @@ edd_show_sectors(struct edd_device *edev */ static int +edd_has_legacy_cylinders(struct edd_device *edev) +{ + struct edd_info *info = edd_dev_get_info(edev); + if (!edev || !info) + return -EINVAL; + return info->legacy_cylinders > 0; +} + +static int +edd_has_legacy_heads(struct edd_device *edev) +{ + struct edd_info *info = edd_dev_get_info(edev); + if (!edev || !info) + return -EINVAL; + return info->legacy_heads > 0; +} + +static int +edd_has_legacy_sectors(struct edd_device *edev) +{ + struct edd_info *info = edd_dev_get_info(edev); + if (!edev || !info) + return -EINVAL; + return info->legacy_sectors > 0; +} + +static int edd_has_default_cylinders(struct edd_device *edev) { struct edd_info *info = edd_dev_get_info(edev); @@ -452,6 +519,12 @@ static EDD_DEVICE_ATTR(version, 0444, ed static EDD_DEVICE_ATTR(extensions, 0444, edd_show_extensions, NULL); static EDD_DEVICE_ATTR(info_flags, 0444, edd_show_info_flags, NULL); static EDD_DEVICE_ATTR(sectors, 0444, edd_show_sectors, NULL); +static EDD_DEVICE_ATTR(legacy_cylinders, 0444, edd_show_legacy_cylinders, + edd_has_legacy_cylinders); +static EDD_DEVICE_ATTR(legacy_heads, 0444, edd_show_legacy_heads, + edd_has_legacy_heads); +static EDD_DEVICE_ATTR(legacy_sectors, 0444, edd_show_legacy_sectors, + edd_has_legacy_sectors); static EDD_DEVICE_ATTR(default_cylinders, 0444, edd_show_default_cylinders, edd_has_default_cylinders); static EDD_DEVICE_ATTR(default_heads, 0444, edd_show_default_heads, @@ -478,6 +551,9 @@ static struct attribute * def_attrs[] = /* These attributes are conditional and only added for some devices. */ static struct edd_attribute * edd_attrs[] = { + &edd_attr_legacy_cylinders, + &edd_attr_legacy_heads, + &edd_attr_legacy_sectors, &edd_attr_default_cylinders, &edd_attr_default_heads, &edd_attr_default_sectors_per_track, diff -puN Documentation/i386/zero-page.txt~edd-legacy-parameters-fix Documentation/i386/zero-page.txt --- 25/Documentation/i386/zero-page.txt~edd-legacy-parameters-fix Tue Mar 9 15:02:19 2004 +++ 25-akpm/Documentation/i386/zero-page.txt Tue Mar 9 15:02:19 2004 @@ -75,7 +75,7 @@ Offset Type Description 0x2cc 4 bytes DISK80_SIG_BUFFER (setup.S) 0x2d0 - 0x600 E820MAP 0x600 - 0x7ff EDDBUF (setup.S) for disk signature read sector -0x600 - 0x7d3 EDDBUF (setup.S) for edd data +0x600 - 0x7eb EDDBUF (setup.S) for edd data 0x800 string, 2K max COMMAND_LINE, the kernel commandline as copied using CL_OFFSET. diff -puN include/asm-i386/edd.h~edd-legacy-parameters-fix include/asm-i386/edd.h --- 25/include/asm-i386/edd.h~edd-legacy-parameters-fix Tue Mar 9 15:02:19 2004 +++ 25-akpm/include/asm-i386/edd.h Tue Mar 9 15:02:19 2004 @@ -34,10 +34,11 @@ in empty_zero_block - treat this as 1 byte */ #define EDDBUF 0x600 /* addr of edd_info structs in empty_zero_block */ #define EDDMAXNR 6 /* number of edd_info structs starting at EDDBUF */ -#define EDDEXTSIZE 4 /* change these if you muck with the structures */ +#define EDDEXTSIZE 8 /* change these if you muck with the structures */ #define EDDPARMSIZE 74 #define CHECKEXTENSIONSPRESENT 0x41 #define GETDEVICEPARAMETERS 0x48 +#define LEGACYGETDEVICEPARAMETERS 0x08 #define EDDMAGIC1 0x55AA #define EDDMAGIC2 0xAA55 @@ -165,6 +166,9 @@ struct edd_info { u8 device; u8 version; u16 interface_support; + u16 legacy_cylinders; + u8 legacy_heads; + u8 legacy_sectors; struct edd_device_params params; } __attribute__ ((packed)); _