2003-09-13 21:11:06

by Matt Domsch

[permalink] [raw]
Subject: [PATCH 2.4] BIOS Enhanced Disk Drive Services 3.0 support

Dave and hpa, this adds BIOS Enhanced Disk Drive Services 3.0 support
(makes int13 calls in setup.S to detect BIOS support, exports data via
/proc with the edd.o module).

This patch has been in 2.4.x-ac since January, and I've
heard no ill reports. Equivalent support was included in 2.5.44.

Two in-production BIOSes actually support EDD3.0 today properly, LSI RAID
controllers and Adaptec 39160 add-in cards. Several others are working
toward compliance.

Also posted at:
http://domsch.com/linux/edd30/linux/linux-2.4.23-pre4-edd-20030913.patch.gz
http://domsch.com/linux/edd30/linux/linux-2.4.23-pre4-edd-20030913.patch.gz.asc

Please ack and forward to Marcelo.

Thanks,
Matt

--
Matt Domsch
Sr. Software Engineer
Dell Linux Solutions http://www.dell.com/linux
Linux on Dell mailing lists @ http://lists.us.dell.com

diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet ../linux-2.4/Documentation/Configure.help linux-2.4-edd-work/Documentation/Configure.help
--- ../linux-2.4/Documentation/Configure.help Sat Sep 13 13:42:37 2003
+++ linux-2.4-edd-work/Documentation/Configure.help Sat Sep 13 14:13:03 2003
@@ -20170,6 +20170,15 @@
<file:Documentation/modules.txt>. The module will be called
cpuid.o

+x86 BIOS Enhanced Disk Drive support
+CONFIG_EDD
+ Say Y or M here if you want to enable BIOS Enhanced Disk Drive
+ Services real mode BIOS calls to determine which disk
+ BIOS tries boot from. This information is then exported via /proc.
+
+ This option is experimental, but believed to be safe,
+ and most disk controller BIOS vendors do not yet implement this feature.
+
SBC-60XX Watchdog Timer
CONFIG_60XX_WDT
This driver can be used with the watchdog timer found on some
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet ../linux-2.4/Documentation/i386/zero-page.txt linux-2.4-edd-work/Documentation/i386/zero-page.txt
--- ../linux-2.4/Documentation/i386/zero-page.txt Thu Jan 30 17:07:28 2003
+++ linux-2.4-edd-work/Documentation/i386/zero-page.txt Tue Jan 21 23:28:14 2003
@@ -31,6 +31,7 @@

0x1e0 unsigned long ALT_MEM_K, alternative mem check, in Kb
0x1e8 char number of entries in E820MAP (below)
+0x1e9 unsigned char number of entries in EDDBUF (below)
0x1f1 char size of setup.S, number of sectors
0x1f2 unsigned short MOUNT_ROOT_RDONLY (if !=0)
0x1f4 unsigned short size of compressed kernel-part in the
@@ -66,6 +67,7 @@
0x220 4 bytes (setup.S)
0x224 unsigned short setup.S heap end pointer
0x2d0 - 0x600 E820MAP
+0x600 - 0x7D4 EDDBUF (setup.S)

0x800 string, 2K max COMMAND_LINE, the kernel commandline as
copied using CL_OFFSET.
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet ../linux-2.4/arch/i386/boot/setup.S linux-2.4-edd-work/arch/i386/boot/setup.S
--- ../linux-2.4/arch/i386/boot/setup.S Mon Nov 4 16:23:31 2002
+++ linux-2.4-edd-work/arch/i386/boot/setup.S Tue Jan 21 23:28:15 2003
@@ -45,6 +45,10 @@
* New A20 code ported from SYSLINUX by H. Peter Anvin. AMD Elan bugfixes
* by Robert Schwebel, December 2001 <[email protected]>
*
+ * BIOS Enhanced Disk Drive support
+ * by Matt Domsch <[email protected]> October 2002
+ * conformant to T13 Committee http://www.t13.org
+ * projects 1572D, 1484D, 1386D, 1226DT
*/

#include <linux/config.h>
@@ -53,6 +57,7 @@
#include <linux/compile.h>
#include <asm/boot.h>
#include <asm/e820.h>
+#include <asm/edd.h>
#include <asm/page.h>

/* Signature words to ensure LILO loaded us right */
@@ -541,6 +546,70 @@
no_32_apm_bios:
andw $0xfffd, (76) # remove 32 bit support bit
done_apm_bios:
+#endif
+
+#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
+# 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"
+#
+# 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
+# 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
+# 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.
+# 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 $0, (EDDNR) # zero value at EDDNR
+ 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:-4(%si) # store device number
+ movb %ah, %ds:-3(%si) # store version
+ movw %cx, %ds:-2(%si) # store extensions
+ incb (EDDNR) # note that we stored something
+
+edd_get_device_params:
+ movw $EDDPARMSIZE, %ds:(%si) # put size
+ movb $GETDEVICEPARAMETERS, %ah # Function 48
+ int $0x13 # make the call
+ # Don't check for fail return
+ # it doesn't matter.
+ 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

# Now we want to move to protected mode ...
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet ../linux-2.4/arch/i386/config.in linux-2.4-edd-work/arch/i386/config.in
--- ../linux-2.4/arch/i386/config.in Sat Sep 6 20:36:44 2003
+++ linux-2.4-edd-work/arch/i386/config.in Sat Sep 13 14:13:04 2003
@@ -201,6 +201,10 @@
tristate '/dev/cpu/*/msr - Model-specific register support' CONFIG_X86_MSR
tristate '/dev/cpu/*/cpuid - CPU information support' CONFIG_X86_CPUID

+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ tristate 'BIOS Enhanced Disk Drive calls determine boot disk (EXPERIMENTAL)' CONFIG_EDD
+fi
+
choice 'High Memory Support' \
"off CONFIG_NOHIGHMEM \
4GB CONFIG_HIGHMEM4G \
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet ../linux-2.4/arch/i386/defconfig linux-2.4-edd-work/arch/i386/defconfig
--- ../linux-2.4/arch/i386/defconfig Sat Sep 6 20:36:44 2003
+++ linux-2.4-edd-work/arch/i386/defconfig Sat Sep 13 14:13:04 2003
@@ -56,6 +56,7 @@
# CONFIG_MICROCODE is not set
# CONFIG_X86_MSR is not set
# CONFIG_X86_CPUID is not set
+# CONFIG_EDD is not set
CONFIG_NOHIGHMEM=y
# CONFIG_HIGHMEM4G is not set
# CONFIG_HIGHMEM64G is not set
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet ../linux-2.4/arch/i386/kernel/Makefile linux-2.4-edd-work/arch/i386/kernel/Makefile
--- ../linux-2.4/arch/i386/kernel/Makefile Sat Sep 13 13:52:24 2003
+++ linux-2.4-edd-work/arch/i386/kernel/Makefile Sat Sep 13 14:13:04 2003
@@ -43,5 +43,6 @@
obj-$(CONFIG_X86_LOCAL_APIC) += mpparse.o apic.o nmi.o
obj-$(CONFIG_X86_IO_APIC) += io_apic.o
obj-$(CONFIG_X86_VISWS_APIC) += visws_apic.o
+obj-$(CONFIG_EDD) += edd.o

include $(TOPDIR)/Rules.make
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet ../linux-2.4/arch/i386/kernel/edd.c linux-2.4-edd-work/arch/i386/kernel/edd.c
--- ../linux-2.4/arch/i386/kernel/edd.c Wed Dec 31 18:00:00 1969
+++ linux-2.4-edd-work/arch/i386/kernel/edd.c Tue Jan 21 23:26:08 2003
@@ -0,0 +1,672 @@
+/*
+ * linux/arch/i386/kernel/edd.c
+ * Copyright (C) 2002 Dell Computer Corporation
+ * by Matt Domsch <[email protected]>
+ *
+ * BIOS Enhanced Disk Drive Services (EDD)
+ * conformant to T13 Committee http://www.t13.org
+ * projects 1572D, 1484D, 1386D, 1226DT
+ *
+ * This code takes information provided by BIOS EDD calls
+ * fn41 - Check Extensions Present and
+ * fn48 - Get Device Parametes with EDD extensions
+ * made in setup.S, copied to safe structures in setup.c,
+ * and presents it in /proc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * TODO:
+ * - move edd.[ch] to better locations if/when one is decided
+ * - keep current with 2.5 EDD code changes
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/stat.h>
+#include <linux/ctype.h>
+#include <linux/slab.h>
+#include <linux/limits.h>
+#include <linux/pci.h>
+#include <linux/proc_fs.h>
+#include <asm/edd.h>
+
+MODULE_AUTHOR("Matt Domsch <[email protected]>");
+MODULE_DESCRIPTION("proc interface to BIOS EDD information");
+MODULE_LICENSE("GPL");
+
+#define EDD_VERSION "0.09 2003-Jan-21"
+#define EDD_DEVICE_NAME_SIZE 16
+#define REPORT_URL "http://domsch.com/linux/edd30/results.html"
+
+#define left (count - (p - page) - 1)
+
+static struct proc_dir_entry *bios_dir;
+
+struct attr_entry {
+ struct proc_dir_entry *entry;
+ struct list_head node;
+};
+
+struct edd_device {
+ char name[EDD_DEVICE_NAME_SIZE];
+ struct edd_info *info;
+ struct proc_dir_entry *dir;
+ struct list_head attr_list;
+};
+
+static struct edd_device *edd_devices[EDDMAXNR];
+
+struct edd_attribute {
+ char *name;
+ int (*show)(char *page, char **start, off_t off,
+ int count, int *eof, void *data);
+ int (*test) (struct edd_device * edev);
+};
+
+#define EDD_DEVICE_ATTR(_name,_show,_test) \
+struct edd_attribute edd_attr_##_name = { \
+ .name = __stringify(_name), \
+ .show = _show, \
+ .test = _test, \
+};
+
+static inline struct edd_info *
+edd_dev_get_info(struct edd_device *edev)
+{
+ return edev->info;
+}
+
+static inline void
+edd_dev_set_info(struct edd_device *edev, struct edd_info *info)
+{
+ edev->info = info;
+}
+
+static int
+proc_calc_metrics(char *page, char **start, off_t off,
+ int count, int *eof, int len)
+{
+ if (len <= off+count) *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len>count) len = count;
+ if (len<0) len = 0;
+ return len;
+}
+
+static int
+edd_dump_raw_data(char *b, int count, void *data, int length)
+{
+ char *orig_b = b;
+ char hexbuf[80], ascbuf[20], *h, *a, c;
+ unsigned char *p = data;
+ unsigned long column = 0;
+ int length_printed = 0, d;
+ const char maxcolumn = 16;
+ while (length_printed < length && count > 0) {
+ h = hexbuf;
+ a = ascbuf;
+ for (column = 0;
+ column < maxcolumn && length_printed < length; column++) {
+ h += sprintf(h, "%02x ", (unsigned char) *p);
+ if (!isprint(*p))
+ c = '.';
+ else
+ c = *p;
+ a += sprintf(a, "%c", c);
+ p++;
+ length_printed++;
+ }
+ /* pad out the line */
+ for (; column < maxcolumn; column++) {
+ h += sprintf(h, " ");
+ a += sprintf(a, " ");
+ }
+ d = snprintf(b, count, "%s\t%s\n", hexbuf, ascbuf);
+ b += d;
+ count -= d;
+ }
+ return (b - orig_b);
+}
+
+static int
+edd_show_host_bus(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ struct edd_info *info = data;
+ char *p = page;
+ int i;
+
+ if (!info || !page || off) {
+ return proc_calc_metrics(page, start, off, count, eof, 0);
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (isprint(info->params.host_bus_type[i])) {
+ p += snprintf(p, left, "%c", info->params.host_bus_type[i]);
+ } else {
+ p += snprintf(p, left, " ");
+ }
+ }
+
+ if (!strncmp(info->params.host_bus_type, "ISA", 3)) {
+ p += snprintf(p, left, "\tbase_address: %x\n",
+ info->params.interface_path.isa.base_address);
+ } else if (!strncmp(info->params.host_bus_type, "PCIX", 4) ||
+ !strncmp(info->params.host_bus_type, "PCI", 3)) {
+ p += snprintf(p, left,
+ "\t%02x:%02x.%d channel: %u\n",
+ info->params.interface_path.pci.bus,
+ info->params.interface_path.pci.slot,
+ info->params.interface_path.pci.function,
+ info->params.interface_path.pci.channel);
+ } else if (!strncmp(info->params.host_bus_type, "IBND", 4) ||
+ !strncmp(info->params.host_bus_type, "XPRS", 4) ||
+ !strncmp(info->params.host_bus_type, "HTPT", 4)) {
+ p += snprintf(p, left,
+ "\tTBD: %llx\n",
+ info->params.interface_path.ibnd.reserved);
+
+ } else {
+ p += snprintf(p, left, "\tunknown: %llx\n",
+ info->params.interface_path.unknown.reserved);
+ }
+ return proc_calc_metrics(page, start, off, count, eof, (p - page));
+}
+
+static int
+edd_show_interface(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ struct edd_info *info = data;
+ char *p = page;
+ int i;
+
+ if (!info || !page || off) {
+ return proc_calc_metrics(page, start, off, count, eof, 0);
+ }
+
+ for (i = 0; i < 8; i++) {
+ if (isprint(info->params.interface_type[i])) {
+ p += snprintf(p, left, "%c", info->params.interface_type[i]);
+ } else {
+ p += snprintf(p, left, " ");
+ }
+ }
+ if (!strncmp(info->params.interface_type, "ATAPI", 5)) {
+ p += snprintf(p, left, "\tdevice: %u lun: %u\n",
+ info->params.device_path.atapi.device,
+ info->params.device_path.atapi.lun);
+ } else if (!strncmp(info->params.interface_type, "ATA", 3)) {
+ p += snprintf(p, left, "\tdevice: %u\n",
+ info->params.device_path.ata.device);
+ } else if (!strncmp(info->params.interface_type, "SCSI", 4)) {
+ p += snprintf(p, left, "\tid: %u lun: %llu\n",
+ info->params.device_path.scsi.id,
+ info->params.device_path.scsi.lun);
+ } else if (!strncmp(info->params.interface_type, "USB", 3)) {
+ p += snprintf(p, left, "\tserial_number: %llx\n",
+ info->params.device_path.usb.serial_number);
+ } else if (!strncmp(info->params.interface_type, "1394", 4)) {
+ p += snprintf(p, left, "\teui: %llx\n",
+ info->params.device_path.i1394.eui);
+ } else if (!strncmp(info->params.interface_type, "FIBRE", 5)) {
+ p += snprintf(p, left, "\twwid: %llx lun: %llx\n",
+ info->params.device_path.fibre.wwid,
+ info->params.device_path.fibre.lun);
+ } else if (!strncmp(info->params.interface_type, "I2O", 3)) {
+ p += snprintf(p, left, "\tidentity_tag: %llx\n",
+ info->params.device_path.i2o.identity_tag);
+ } else if (!strncmp(info->params.interface_type, "RAID", 4)) {
+ p += snprintf(p, left, "\tidentity_tag: %x\n",
+ info->params.device_path.raid.array_number);
+ } else if (!strncmp(info->params.interface_type, "SATA", 4)) {
+ p += snprintf(p, left, "\tdevice: %u\n",
+ info->params.device_path.sata.device);
+ } else {
+ p += snprintf(p, left, "\tunknown: %llx %llx\n",
+ info->params.device_path.unknown.reserved1,
+ info->params.device_path.unknown.reserved2);
+ }
+
+ return proc_calc_metrics(page, start, off, count, eof, (p - page));
+}
+
+/**
+ * edd_show_raw_data() - unparses EDD information, returned to user-space
+ *
+ * Returns: number of bytes written, or 0 on failure
+ */
+static int
+edd_show_raw_data(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ struct edd_info *info = data;
+ char *p = page;
+ int i, warn_padding = 0, nonzero_path = 0,
+ len = sizeof (*info) - 4;
+ uint8_t checksum = 0, c = 0;
+ if (!info || !page || off) {
+ return proc_calc_metrics(page, start, off, count, eof, 0);
+ }
+
+ if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE))
+ len = info->params.length;
+
+ p += snprintf(p, left, "int13 fn48 returned data:\n\n");
+ p += edd_dump_raw_data(p, left, ((char *) info) + 4, len);
+
+ /* Spec violation. Adaptec AIC7899 returns 0xDDBE
+ here, when it should be 0xBEDD.
+ */
+ p += snprintf(p, left, "\n");
+ if (info->params.key == 0xDDBE) {
+ p += snprintf(p, left,
+ "Warning: Spec violation. Key should be 0xBEDD, is 0xDDBE\n");
+ }
+
+ if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE)) {
+ goto out;
+ }
+
+ for (i = 30; i <= 73; i++) {
+ c = *(((uint8_t *) info) + i + 4);
+ if (c)
+ nonzero_path++;
+ checksum += c;
+ }
+
+ if (checksum) {
+ p += snprintf(p, left,
+ "Warning: Spec violation. Device Path checksum invalid.\n");
+ }
+
+ if (!nonzero_path) {
+ p += snprintf(p, left, "Error: Spec violation. Empty device path.\n");
+ goto out;
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (!isprint(info->params.host_bus_type[i])) {
+ warn_padding++;
+ }
+ }
+ for (i = 0; i < 8; i++) {
+ if (!isprint(info->params.interface_type[i])) {
+ warn_padding++;
+ }
+ }
+
+ if (warn_padding) {
+ p += snprintf(p, left,
+ "Warning: Spec violation. Padding should be 0x20.\n");
+ }
+
+out:
+ p += snprintf(p, left, "\nPlease check %s\n", REPORT_URL);
+ p += snprintf(p, left, "to see if this device has been reported. If not,\n");
+ p += snprintf(p, left, "please send the information requested there.\n");
+
+ return proc_calc_metrics(page, start, off, count, eof, (p - page));
+}
+
+static int
+edd_show_version(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ struct edd_info *info = data;
+ char *p = page;
+ if (!info || !page || off) {
+ return proc_calc_metrics(page, start, off, count, eof, 0);
+ }
+
+ p += snprintf(p, left, "0x%02x\n", info->version);
+ return proc_calc_metrics(page, start, off, count, eof, (p - page));
+}
+
+static int
+edd_show_extensions(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ struct edd_info *info = data;
+ char *p = page;
+ if (!info || !page || off) {
+ return proc_calc_metrics(page, start, off, count, eof, 0);
+ }
+
+ if (info->interface_support & EDD_EXT_FIXED_DISK_ACCESS) {
+ p += snprintf(p, left, "Fixed disk access\n");
+ }
+ if (info->interface_support & EDD_EXT_DEVICE_LOCKING_AND_EJECTING) {
+ p += snprintf(p, left, "Device locking and ejecting\n");
+ }
+ if (info->interface_support & EDD_EXT_ENHANCED_DISK_DRIVE_SUPPORT) {
+ p += snprintf(p, left, "Enhanced Disk Drive support\n");
+ }
+ if (info->interface_support & EDD_EXT_64BIT_EXTENSIONS) {
+ p += snprintf(p, left, "64-bit extensions\n");
+ }
+ return proc_calc_metrics(page, start, off, count, eof, (p - page));
+}
+
+static int
+edd_show_info_flags(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ struct edd_info *info = data;
+ char *p = page;
+ if (!info || !page || off) {
+ return proc_calc_metrics(page, start, off, count, eof, 0);
+ }
+
+ if (info->params.info_flags & EDD_INFO_DMA_BOUNDRY_ERROR_TRANSPARENT)
+ p += snprintf(p, left, "DMA boundry error transparent\n");
+ if (info->params.info_flags & EDD_INFO_GEOMETRY_VALID)
+ p += snprintf(p, left, "geometry valid\n");
+ if (info->params.info_flags & EDD_INFO_REMOVABLE)
+ p += snprintf(p, left, "removable\n");
+ if (info->params.info_flags & EDD_INFO_WRITE_VERIFY)
+ p += snprintf(p, left, "write verify\n");
+ if (info->params.info_flags & EDD_INFO_MEDIA_CHANGE_NOTIFICATION)
+ p += snprintf(p, left, "media change notification\n");
+ if (info->params.info_flags & EDD_INFO_LOCKABLE)
+ p += snprintf(p, left, "lockable\n");
+ if (info->params.info_flags & EDD_INFO_NO_MEDIA_PRESENT)
+ p += snprintf(p, left, "no media present\n");
+ if (info->params.info_flags & EDD_INFO_USE_INT13_FN50)
+ p += snprintf(p, left, "use int13 fn50\n");
+ return proc_calc_metrics(page, start, off, count, eof, (p - page));
+}
+
+static int
+edd_show_default_cylinders(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ struct edd_info *info = data;
+ char *p = page;
+ if (!info || !page || off) {
+ return proc_calc_metrics(page, start, off, count, eof, 0);
+ }
+
+ p += snprintf(p, left, "0x%x\n", info->params.num_default_cylinders);
+ return proc_calc_metrics(page, start, off, count, eof, (p - page));
+}
+
+static int
+edd_show_default_heads(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ struct edd_info *info = data;
+ char *p = page;
+ if (!info || !page || off) {
+ return proc_calc_metrics(page, start, off, count, eof, 0);
+ }
+
+ p += snprintf(p, left, "0x%x\n", info->params.num_default_heads);
+ return proc_calc_metrics(page, start, off, count, eof, (p - page));
+}
+
+static int
+edd_show_default_sectors_per_track(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ struct edd_info *info = data;
+ char *p = page;
+ if (!info || !page || off) {
+ return proc_calc_metrics(page, start, off, count, eof, 0);
+ }
+
+ p += snprintf(p, left, "0x%x\n", info->params.sectors_per_track);
+ return proc_calc_metrics(page, start, off, count, eof, (p - page));
+}
+
+static int
+edd_show_sectors(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ struct edd_info *info = data;
+ char *p = page;
+ if (!info || !page || off) {
+ return proc_calc_metrics(page, start, off, count, eof, 0);
+ }
+
+ p += snprintf(p, left, "0x%llx\n", info->params.number_of_sectors);
+ return proc_calc_metrics(page, start, off, count, eof, (p - page));
+}
+
+static int
+edd_has_default_cylinders(struct edd_device *edev)
+{
+ struct edd_info *info = edd_dev_get_info(edev);
+ if (!edev || !info)
+ return 0;
+ return info->params.num_default_cylinders > 0;
+}
+
+static int
+edd_has_default_heads(struct edd_device *edev)
+{
+ struct edd_info *info = edd_dev_get_info(edev);
+ if (!edev || !info)
+ return 0;
+ return info->params.num_default_heads > 0;
+}
+
+static int
+edd_has_default_sectors_per_track(struct edd_device *edev)
+{
+ struct edd_info *info = edd_dev_get_info(edev);
+ if (!edev || !info)
+ return 0;
+ return info->params.sectors_per_track > 0;
+}
+
+static int
+edd_has_edd30(struct edd_device *edev)
+{
+ struct edd_info *info = edd_dev_get_info(edev);
+ int i, nonzero_path = 0;
+ char c;
+
+ if (!edev || !info)
+ return 0;
+
+ if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE)) {
+ return 0;
+ }
+
+ for (i = 30; i <= 73; i++) {
+ c = *(((uint8_t *) info) + i + 4);
+ if (c) {
+ nonzero_path++;
+ break;
+ }
+ }
+ if (!nonzero_path) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static EDD_DEVICE_ATTR(raw_data, edd_show_raw_data, NULL);
+static EDD_DEVICE_ATTR(version, edd_show_version, NULL);
+static EDD_DEVICE_ATTR(extensions, edd_show_extensions, NULL);
+static EDD_DEVICE_ATTR(info_flags, edd_show_info_flags, NULL);
+static EDD_DEVICE_ATTR(sectors, edd_show_sectors, NULL);
+static EDD_DEVICE_ATTR(default_cylinders, edd_show_default_cylinders,
+ edd_has_default_cylinders);
+static EDD_DEVICE_ATTR(default_heads, edd_show_default_heads,
+ edd_has_default_heads);
+static EDD_DEVICE_ATTR(default_sectors_per_track,
+ edd_show_default_sectors_per_track,
+ edd_has_default_sectors_per_track);
+static EDD_DEVICE_ATTR(interface, edd_show_interface,edd_has_edd30);
+static EDD_DEVICE_ATTR(host_bus, edd_show_host_bus, edd_has_edd30);
+
+static struct edd_attribute *def_attrs[] = {
+ &edd_attr_raw_data,
+ &edd_attr_version,
+ &edd_attr_extensions,
+ &edd_attr_info_flags,
+ &edd_attr_sectors,
+ &edd_attr_default_cylinders,
+ &edd_attr_default_heads,
+ &edd_attr_default_sectors_per_track,
+ &edd_attr_interface,
+ &edd_attr_host_bus,
+ NULL,
+};
+
+static inline void
+edd_device_unregister(struct edd_device *edev)
+{
+ struct list_head *pos, *next;
+ struct attr_entry *ae;
+
+ list_for_each_safe(pos, next, &edev->attr_list) {
+ ae = list_entry(pos, struct attr_entry, node);
+ remove_proc_entry(ae->entry->name, edev->dir);
+ list_del(&ae->node);
+ kfree(ae);
+ }
+
+ remove_proc_entry(edev->dir->name, bios_dir);
+}
+
+static int
+edd_populate_dir(struct edd_device *edev)
+{
+ struct edd_attribute *attr;
+ struct attr_entry *ae;
+ int i;
+ int error = 0;
+
+ for (i = 0; (attr=def_attrs[i]); i++) {
+ if (!attr->test || (attr->test && attr->test(edev))) {
+ ae = kmalloc(sizeof (*ae), GFP_KERNEL);
+ if (ae == NULL) {
+ error = 1;
+ break;
+ }
+ INIT_LIST_HEAD(&ae->node);
+ ae->entry =
+ create_proc_read_entry(attr->name, 0444,
+ edev->dir, attr->show,
+ edd_dev_get_info(edev));
+ if (ae->entry == NULL) {
+ error = 1;
+ break;
+ }
+ list_add(&ae->node, &edev->attr_list);
+ }
+ }
+
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int
+edd_make_dir(struct edd_device *edev)
+{
+ int error=1;
+
+ edev->dir = proc_mkdir(edev->name, bios_dir);
+ if (edev->dir != NULL) {
+ edev->dir->mode = (S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO);
+ error = edd_populate_dir(edev);
+ }
+ return error;
+}
+
+static int
+edd_device_register(struct edd_device *edev, int i)
+{
+ int error;
+
+ if (!edev)
+ return 1;
+ memset(edev, 0, sizeof (*edev));
+ INIT_LIST_HEAD(&edev->attr_list);
+ edd_dev_set_info(edev, &edd[i]);
+ snprintf(edev->name, EDD_DEVICE_NAME_SIZE, "int13_dev%02x",
+ edd[i].device);
+ error = edd_make_dir(edev);
+ return error;
+}
+
+/**
+ * edd_init() - creates /proc/bios tree of EDD data
+ *
+ * This assumes that eddnr and edd were
+ * assigned in setup.c already.
+ */
+static int __init
+edd_init(void)
+{
+ unsigned int i;
+ int rc = 0;
+ struct edd_device *edev;
+
+ printk(KERN_INFO "BIOS EDD facility v%s, %d devices found\n",
+ EDD_VERSION, eddnr);
+
+ if (!eddnr) {
+ printk(KERN_INFO "EDD information not available.\n");
+ return 1;
+ }
+
+ bios_dir = proc_mkdir("bios", NULL);
+ if (bios_dir == NULL)
+ return 1;
+
+ for (i = 0; i < eddnr && i < EDDMAXNR && !rc; i++) {
+ edev = kmalloc(sizeof (*edev), GFP_KERNEL);
+ if (!edev) {
+ rc = 1;
+ break;
+ }
+
+ rc = edd_device_register(edev, i);
+ if (rc) {
+ break;
+ }
+ edd_devices[i] = edev;
+ }
+
+ if (rc) {
+ for (i = 0; i < eddnr && i < EDDMAXNR; i++) {
+ if ((edev = edd_devices[i])) {
+ edd_device_unregister(edev);
+ kfree(edev);
+ }
+ }
+
+ remove_proc_entry(bios_dir->name, NULL);
+ }
+
+ return rc;
+}
+
+static void __exit
+edd_exit(void)
+{
+ int i;
+ struct edd_device *edev;
+
+ for (i = 0; i < eddnr && i < EDDMAXNR; i++) {
+ if ((edev = edd_devices[i])) {
+ edd_device_unregister(edev);
+ kfree(edev);
+ }
+ }
+
+ remove_proc_entry(bios_dir->name, NULL);
+}
+
+module_init(edd_init);
+module_exit(edd_exit);
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet ../linux-2.4/arch/i386/kernel/i386_ksyms.c linux-2.4-edd-work/arch/i386/kernel/i386_ksyms.c
--- ../linux-2.4/arch/i386/kernel/i386_ksyms.c Sat Sep 13 13:52:24 2003
+++ linux-2.4-edd-work/arch/i386/kernel/i386_ksyms.c Sat Sep 13 14:13:05 2003
@@ -28,6 +28,7 @@
#include <asm/desc.h>
#include <asm/pgtable.h>
#include <asm/pgalloc.h>
+#include <asm/edd.h>

extern void dump_thread(struct pt_regs *, struct user *);
extern spinlock_t rtc_lock;
@@ -179,4 +180,9 @@

#ifdef CONFIG_MULTIQUAD
EXPORT_SYMBOL(xquad_portio);
+#endif
+
+#ifdef CONFIG_EDD_MODULE
+EXPORT_SYMBOL(edd);
+EXPORT_SYMBOL(eddnr);
#endif
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet ../linux-2.4/arch/i386/kernel/setup.c linux-2.4-edd-work/arch/i386/kernel/setup.c
--- ../linux-2.4/arch/i386/kernel/setup.c Sat Sep 13 13:52:25 2003
+++ linux-2.4-edd-work/arch/i386/kernel/setup.c Sat Sep 13 14:13:05 2003
@@ -120,6 +120,7 @@
#include <asm/dma.h>
#include <asm/mpspec.h>
#include <asm/mmu_context.h>
+#include <asm/edd.h>
/*
* Machine setup..
*/
@@ -211,6 +212,8 @@
#define KERNEL_START (*(unsigned long *) (PARAM+0x214))
#define INITRD_START (*(unsigned long *) (PARAM+0x218))
#define INITRD_SIZE (*(unsigned long *) (PARAM+0x21c))
+#define EDD_NR (*(unsigned char *) (PARAM+EDDNR))
+#define EDD_BUF ((struct edd_info *) (PARAM+EDDBUF))
#define COMMAND_LINE ((char *) (PARAM+2048))
#define COMMAND_LINE_SIZE 256

@@ -715,6 +718,23 @@
return 0;
}

+#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
+unsigned char eddnr;
+struct edd_info edd[EDDMAXNR];
+/**
+ * copy_edd() - Copy the BIOS EDD information
+ * from empty_zero_page into a safe place.
+ *
+ */
+static inline void copy_edd(void)
+{
+ eddnr = EDD_NR;
+ memcpy(edd, EDD_BUF, sizeof(edd));
+}
+#else
+#define copy_edd() do {} while (0)
+#endif
+
/*
* Do NOT EVER look at the BIOS memory size location.
* It does not work on many machines.
@@ -1151,6 +1171,7 @@
rd_doload = ((RAMDISK_FLAGS & RAMDISK_LOAD_FLAG) != 0);
#endif
setup_memory_region();
+ copy_edd();

if (!MOUNT_ROOT_RDONLY)
root_mountflags &= ~MS_RDONLY;
diff -Nur --exclude=RCS --exclude=CVS --exclude=SCCS --exclude=BitKeeper --exclude=ChangeSet ../linux-2.4/include/asm-i386/edd.h linux-2.4-edd-work/include/asm-i386/edd.h
--- ../linux-2.4/include/asm-i386/edd.h Wed Dec 31 18:00:00 1969
+++ linux-2.4-edd-work/include/asm-i386/edd.h Tue Jan 21 23:23:37 2003
@@ -0,0 +1,172 @@
+/*
+ * linux/include/asm-i386/edd.h
+ * Copyright (C) 2002 Dell Computer Corporation
+ * by Matt Domsch <[email protected]>
+ *
+ * structures and definitions for the int 13h, ax={41,48}h
+ * BIOS Enhanced Disk Drive Services
+ * This is based on the T13 group document D1572 Revision 0 (August 14 2002)
+ * available at http://www.t13.org/docs2002/d1572r0.pdf. It is
+ * very similar to D1484 Revision 3 http://www.t13.org/docs2002/d1484r3.pdf
+ *
+ * In a nutshell, arch/i386/boot/setup.S populates a scratch table
+ * in the empty_zero_block that contains a list of BIOS-enumerated
+ * boot devices.
+ * In arch/i386/kernel/setup.c, this information is
+ * transferred into the edd structure, and in arch/i386/kernel/edd.c, that
+ * information is used to identify BIOS boot disk. The code in setup.S
+ * is very sensitive to the size of these structures.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#ifndef _ASM_I386_EDD_H
+#define _ASM_I386_EDD_H
+
+#define EDDNR 0x1e9 /* addr of number of edd_info structs at EDDBUF
+ 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 EDDPARMSIZE 74
+#define CHECKEXTENSIONSPRESENT 0x41
+#define GETDEVICEPARAMETERS 0x48
+#define EDDMAGIC1 0x55AA
+#define EDDMAGIC2 0xAA55
+
+#ifndef __ASSEMBLY__
+
+#define EDD_EXT_FIXED_DISK_ACCESS (1 << 0)
+#define EDD_EXT_DEVICE_LOCKING_AND_EJECTING (1 << 1)
+#define EDD_EXT_ENHANCED_DISK_DRIVE_SUPPORT (1 << 2)
+#define EDD_EXT_64BIT_EXTENSIONS (1 << 3)
+
+#define EDD_INFO_DMA_BOUNDRY_ERROR_TRANSPARENT (1 << 0)
+#define EDD_INFO_GEOMETRY_VALID (1 << 1)
+#define EDD_INFO_REMOVABLE (1 << 2)
+#define EDD_INFO_WRITE_VERIFY (1 << 3)
+#define EDD_INFO_MEDIA_CHANGE_NOTIFICATION (1 << 4)
+#define EDD_INFO_LOCKABLE (1 << 5)
+#define EDD_INFO_NO_MEDIA_PRESENT (1 << 6)
+#define EDD_INFO_USE_INT13_FN50 (1 << 7)
+
+struct edd_device_params {
+ u16 length;
+ u16 info_flags;
+ u32 num_default_cylinders;
+ u32 num_default_heads;
+ u32 sectors_per_track;
+ u64 number_of_sectors;
+ u16 bytes_per_sector;
+ u32 dpte_ptr; /* 0xFFFFFFFF for our purposes */
+ u16 key; /* = 0xBEDD */
+ u8 device_path_info_length; /* = 44 */
+ u8 reserved2;
+ u16 reserved3;
+ u8 host_bus_type[4];
+ u8 interface_type[8];
+ union {
+ struct {
+ u16 base_address;
+ u16 reserved1;
+ u32 reserved2;
+ } __attribute__ ((packed)) isa;
+ struct {
+ u8 bus;
+ u8 slot;
+ u8 function;
+ u8 channel;
+ u32 reserved;
+ } __attribute__ ((packed)) pci;
+ /* pcix is same as pci */
+ struct {
+ u64 reserved;
+ } __attribute__ ((packed)) ibnd;
+ struct {
+ u64 reserved;
+ } __attribute__ ((packed)) xprs;
+ struct {
+ u64 reserved;
+ } __attribute__ ((packed)) htpt;
+ struct {
+ u64 reserved;
+ } __attribute__ ((packed)) unknown;
+ } interface_path;
+ union {
+ struct {
+ u8 device;
+ u8 reserved1;
+ u16 reserved2;
+ u32 reserved3;
+ u64 reserved4;
+ } __attribute__ ((packed)) ata;
+ struct {
+ u8 device;
+ u8 lun;
+ u8 reserved1;
+ u8 reserved2;
+ u32 reserved3;
+ u64 reserved4;
+ } __attribute__ ((packed)) atapi;
+ struct {
+ u16 id;
+ u64 lun;
+ u16 reserved1;
+ u32 reserved2;
+ } __attribute__ ((packed)) scsi;
+ struct {
+ u64 serial_number;
+ u64 reserved;
+ } __attribute__ ((packed)) usb;
+ struct {
+ u64 eui;
+ u64 reserved;
+ } __attribute__ ((packed)) i1394;
+ struct {
+ u64 wwid;
+ u64 lun;
+ } __attribute__ ((packed)) fibre;
+ struct {
+ u64 identity_tag;
+ u64 reserved;
+ } __attribute__ ((packed)) i2o;
+ struct {
+ u32 array_number;
+ u32 reserved1;
+ u64 reserved2;
+ } __attribute((packed)) raid;
+ struct {
+ u8 device;
+ u8 reserved1;
+ u16 reserved2;
+ u32 reserved3;
+ u64 reserved4;
+ } __attribute__ ((packed)) sata;
+ struct {
+ u64 reserved1;
+ u64 reserved2;
+ } __attribute__ ((packed)) unknown;
+ } device_path;
+ u8 reserved4;
+ u8 checksum;
+} __attribute__ ((packed));
+
+struct edd_info {
+ u8 device;
+ u8 version;
+ u16 interface_support;
+ struct edd_device_params params;
+} __attribute__ ((packed));
+
+extern struct edd_info edd[EDDMAXNR];
+extern unsigned char eddnr;
+#endif /*!__ASSEMBLY__ */
+
+#endif /* _ASM_I386_EDD_H */