You can import this changeset into BK by piping this whole message to '| bk receive [path to repository]' or apply the patch as usual. =================================================================== ChangeSet@1.749.2.1, 2002-10-24 15:48:17-05:00, Matt_Domsch@dell.com EDD: x86 BIOS Enhanced Disk Drive Services backported from 2.5.44 to 2.4.20-pre11 Documentation/Configure.help | 9 Documentation/i386/zero-page.txt | 2 arch/i386/boot/setup.S | 69 +++ arch/i386/config.in | 4 arch/i386/defconfig | 1 arch/i386/kernel/Makefile | 1 arch/i386/kernel/edd.c | 679 +++++++++++++++++++++++++++++++++++++++ arch/i386/kernel/i386_ksyms.c | 6 arch/i386/kernel/setup.c | 21 + include/asm-i386/edd.h | 172 +++++++++ 10 files changed, 964 insertions diff -Nru a/Documentation/Configure.help b/Documentation/Configure.help --- a/Documentation/Configure.help Thu Oct 24 16:00:31 2002 +++ b/Documentation/Configure.help Thu Oct 24 16:00:31 2002 @@ -18893,6 +18893,15 @@ . 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 -Nru a/Documentation/i386/zero-page.txt b/Documentation/i386/zero-page.txt --- a/Documentation/i386/zero-page.txt Thu Oct 24 16:00:30 2002 +++ b/Documentation/i386/zero-page.txt Thu Oct 24 16:00:30 2002 @@ -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 -Nru a/arch/i386/boot/setup.S b/arch/i386/boot/setup.S --- a/arch/i386/boot/setup.S Thu Oct 24 16:00:30 2002 +++ b/arch/i386/boot/setup.S Thu Oct 24 16:00:30 2002 @@ -45,6 +45,10 @@ * New A20 code ported from SYSLINUX by H. Peter Anvin. AMD Elan bugfixes * by Robert Schwebel, December 2001 * + * BIOS Enhanced Disk Drive support + * by Matt Domsch October 2002 + * conformant to T13 Committee www.t13.org + * projects 1572D, 1484D, 1386D, 1226DT */ #include @@ -53,6 +57,7 @@ #include #include #include +#include #include /* 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 -Nru a/arch/i386/config.in b/arch/i386/config.in --- a/arch/i386/config.in Thu Oct 24 16:00:30 2002 +++ b/arch/i386/config.in Thu Oct 24 16:00:30 2002 @@ -186,6 +186,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 -Nru a/arch/i386/defconfig b/arch/i386/defconfig --- a/arch/i386/defconfig Thu Oct 24 16:00:30 2002 +++ b/arch/i386/defconfig Thu Oct 24 16:00:30 2002 @@ -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 -Nru a/arch/i386/kernel/Makefile b/arch/i386/kernel/Makefile --- a/arch/i386/kernel/Makefile Thu Oct 24 16:00:30 2002 +++ b/arch/i386/kernel/Makefile Thu Oct 24 16:00:30 2002 @@ -40,5 +40,6 @@ obj-$(CONFIG_X86_LOCAL_APIC) += mpparse.o apic.o nmi.o obj-$(CONFIG_X86_IO_APIC) += io_apic.o acpitable.o obj-$(CONFIG_X86_VISWS_APIC) += visws_apic.o +obj-$(CONFIG_EDD) += edd.o include $(TOPDIR)/Rules.make diff -Nru a/arch/i386/kernel/edd.c b/arch/i386/kernel/edd.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/arch/i386/kernel/edd.c Thu Oct 24 16:00:31 2002 @@ -0,0 +1,679 @@ +/* + * linux/arch/i386/kernel/edd.c + * Copyright (C) 2002 Dell Computer Corporation + * by Matt Domsch + * + * BIOS Enhanced Disk Drive Services (EDD) + * conformant to T13 Committee 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 driverfs. + * + * 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: + * - Convert to using /proc instead of driverfs + * - move edd.[ch] to better locations if/when one is decided + * - keep current with 2.5 EDD code changes + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Matt Domsch "); +MODULE_DESCRIPTION("proc interface to BIOS EDD information"); +MODULE_LICENSE("GPL"); + +#define EDD_VERSION "0.06 2002-Oct-24" +#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, email = 0, nonzero_path = 0, + len = sizeof (*edd) - 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 *) edd) + 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"); + email++; + } + + if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE)) { + goto out; + } + + for (i = 30; i <= 73; i++) { + c = *(((uint8_t *) edd) + i + 4); + if (c) + nonzero_path++; + checksum += c; + } + + if (checksum) { + p += snprintf(p, left, + "Warning: Spec violation. Device Path checksum invalid.\n"); + email++; + } + + if (!nonzero_path) { + p += snprintf(p, left, "Error: Spec violation. Empty device path.\n"); + email++; + 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"); + email++; + } + +out: + if (email) { + p += snprintf(p, left, "\nPlease check %s\n", REPORT_URL); + p += snprintf(p, left, "to see if this 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 1; + return !info->params.num_default_cylinders; +} + +static int +edd_has_default_heads(struct edd_device *edev) +{ + struct edd_info *info = edd_dev_get_info(edev); + if (!edev || !info) + return 1; + return !info->params.num_default_heads; +} + +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 1; + return !info->params.sectors_per_track; +} + +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 1; + + if (!(info->params.key == 0xBEDD || info->params.key == 0xDDBE)) { + return 1; + } + + for (i = 30; i <= 73; i++) { + c = *(((uint8_t *) edd) + i + 4); + if (c) { + nonzero_path++; + break; + } + } + if (!nonzero_path) { + return 1; + } + + return 0; +} + +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 driverfs 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 -Nru a/arch/i386/kernel/i386_ksyms.c b/arch/i386/kernel/i386_ksyms.c --- a/arch/i386/kernel/i386_ksyms.c Thu Oct 24 16:00:30 2002 +++ b/arch/i386/kernel/i386_ksyms.c Thu Oct 24 16:00:30 2002 @@ -28,6 +28,7 @@ #include #include #include +#include extern void dump_thread(struct pt_regs *, struct user *); extern spinlock_t rtc_lock; @@ -178,4 +179,9 @@ #ifdef CONFIG_MULTIQUAD EXPORT_SYMBOL(xquad_portio); +#endif + +#ifdef CONFIG_EDD_MODULE +EXPORT_SYMBOL(edd); +EXPORT_SYMBOL(eddnr); #endif diff -Nru a/arch/i386/kernel/setup.c b/arch/i386/kernel/setup.c --- a/arch/i386/kernel/setup.c Thu Oct 24 16:00:30 2002 +++ b/arch/i386/kernel/setup.c Thu Oct 24 16:00:30 2002 @@ -115,6 +115,7 @@ #include #include #include +#include /* * Machine setup.. */ @@ -193,6 +194,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 @@ -693,6 +696,23 @@ return 0; } +#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE) +unsigned char eddnr; +struct edd_info edd[EDDNR]; +/** + * 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. @@ -1105,6 +1125,7 @@ rd_doload = ((RAMDISK_FLAGS & RAMDISK_LOAD_FLAG) != 0); #endif setup_memory_region(); + copy_edd(); if (!MOUNT_ROOT_RDONLY) root_mountflags &= ~MS_RDONLY; diff -Nru a/include/asm-i386/edd.h b/include/asm-i386/edd.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/include/asm-i386/edd.h Thu Oct 24 16:00:31 2002 @@ -0,0 +1,172 @@ +/* + * linux/include/asm-i386/edd.h + * Copyright (C) 2002 Dell Computer Corporation + * by Matt Domsch + * + * 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[EDDNR]; +extern unsigned char eddnr; +#endif /*!__ASSEMBLY__ */ + +#endif /* _ASM_I386_EDD_H */ =================================================================== This BitKeeper patch contains the following changesets: 1.749.2.1 ## Wrapped with gzip_uu ## begin 664 bkpatch17416 M'XL(`&]?N#T``^P\:7?:2+:?X5=4NT\RX&"0A%ALQSF-#4YX<;`/X'3Z=7)T MA%0R>A823XN7:>>_OWNKM`$2BSN>F3<]66RIJNY^Z]:M33^3:X^Z1X5/JN\K M76?F:=/BS^2#X_E'!9U:5E5S9E`P=!PHJ,U,3ZM-)[69SEK6)K44&LUN,2_W%.CPK#WOOKB\ZP6#PY(6=3U;ZA(^J3 MDY.B[[AWJJ5[OZC^U'+LJN^JMC>COHH)'(DF'+:%XE=B@>+#CGV)14(7BNPU"JRZX MB5EO-VNWU+6I5?.H'\RK6DH',MCB26[51>%)%0500$L6#5'3I%8C4]_K<8)9 M90GT*1T^U>5&0]S(8=?1@AFU?=4W';MVYMB&>1.XM#JEUGR!RSI8"IZ%IXE$ MFX8V$1LB%6B]J6=SN1%OBE.IW9#:.W+*%/!WZCH'<_6&5OT'?Y';UE-#KH-S MMR?"(6U/#NN2KHG@6-MPFX,[Q;$L"H>-':RO4T-C*E@VO"@+[<83I8VV>BCJ MJM$^/`1&-QE^$5V*KT9;D'?Q2HZE:MJ+?`E/XF&C(3_5!5$VZ.'AX40U)/#W M37PMHDOQ)0HM:1=]32#@AGX]6F%-;`)K34%OBQ-5DEIMVC(D8Q-K*QC3_B<* M(O:4[;H;!/K%SB:#'1M2J]EZDAKB1#0FS;8L3R2-RL_'*$&G$)OUW0/,)_66 M&J9%5R*,7&\VGH`_JAN&T*AKNBIH.7UW/=)TB*D?RKF*,VW-"G1:4[W9`4.& M8DX7Q(28)[::;0FB?[,N4-H2-;%1G^3X_S8(Q:?Z84N6=]<;/BNWWN/,6XW. MC5:[V7YJMPP(>VVUKC:`1ZF^I>Y6$*?TUX0WB24#V3Z!F<&`WA/4_5%.F^)G M(A>+/]QU63X@+&8"\I$DKLT$7B01T,BIZ7^D=$Y=I@A2FSHSFIN$'=P[[FTM M1U7 M*LR]JH0,Z=Q2-4IT3-A?T`].7/FCZYY,_5)Z:Q,4!>D"[J$\MD\\,$)SQP7 M9&:I`@.8/#+%$ZYX\C;+"N^@)3;>F,B2$B2]96R*@ZGCSE3;QP1V+-:1@YGI M^Y22^_O[JB_6JXY[PUA`=?X/U7P//*(E=2L$4AT9?X&$^$N2FMUQR,)X:GJ` M7*?$A[#N$9.307$0S9VI`V<@$V>UVR6::ED>0AJV+)(#2)JI=DMZ#SZU/0#R MR)5+/4B>B&KK8;,V-'L/,YLN1:%@"N2JX&T23A`5X"_ MN0EL@.">:E#B^6Z@^9`_>DDKK8*00!.X9O2ASL?JR-&J:8%!LAM@@L"CX8(& M/)2W00ZY@2,C)@`<\UQR,1\4!98.O@`/X4=$?= MF4<<@[V\'UR#N#9U58M<.8(^\*HOOA#T=Q40,4V<.K$Q5'YF[-\%+)Y0$'C4"BVD'&I-?^^,/E]=C MTAG\1G[M#(>=P?BW8V8(!VKI'>6HS-G<0I4#+Q`6_$>0#3%\Z@W//@!(Y[1_ MT1__1D`CY_WQH#<:D?/+(>F0J\YPW#_#62VYNAY>78YZT.='%-FBB&"-;@RF M7A!=AQAD6I'!:L6O85\=7W8OC_`!G,ZQP:BL*P2>:=^0&D80T(/G4U5'0T1F MY^UG#G0J[-2_:]-O"#6A/G9>R]&8CL%9C-K]%(1W;,JT2S7T?`Y]"\,-T0+7 M1;=F/@OAC'<&[#@:"Y5>R.O/86)"WO+8`AX36##3>;=2@_:S;[)J<'G`RZHP M;=//1J5FEFN(*1/`4B=9Y98),263]%PS,XM![XJQ!`$Y&4_'WH%"/EUVKR]Z M2N<:'&]8VLL-CMTH..Z5CR.@;F]T-NQ?C?N7@])>:&*PFX$C"%@Q#DJIJ)6" MONB?]0:C7FGO_=4%%H-Q8*YD@H4!1OG<&XX`+]D3JD*31?:#2\T_D.2]A6;= MWF=`HPPZGWK*J/_?/2(VX_IA[^IR.%:NAQ=D;^K[\Z-:C>EA1DLZ(@?Q6%X\H1+'I3?OZ9`9$3\@1_Q?C%4&3EAOJL*$L53!-% M,(T+([EK,\T@@^6=1FU$V#E."9O#U!H#>L;9FU`VY$<"".&%Z0&GF#K:`400Y. MPI<0Q[L0&.M..-6D\JT050C'L8:A8$58IKI@-E=<]5Y!+PZ%G532PB1.'HMS MXT^91+RY`\FR,@%RD^.P:$H?)H'Q>UOX5H'41\-G"9_WI_`?T&C0,(#D[\:& M?"-4,,`CC70-3%%N@`LKF$7")/25N8OC@HX5%:(C91C3?8YMIC[$8&(3ZNZG M.&\L+8&^#7&1UZ^YL.0=`=V!8(4I0'(I`+J@PAN7`]\P:RDML,4Z4<3IVQ1Y M0)Q#\SAL_N8-)P@4WYP0CS4S2J"GO5>"]$#V*J2TH"EPF#D&FP*S]D\FARA! M81D+"QJP]+?JWU@+:GDT+MR?LS(U349%,AK0T#C*^9LW[/Q.#;VAU;/WT1'YB@0(>@##7;RC7:ACC3"?I1 M1=5U2.>\(_+J`7T-&2(\*TA1B/-49:[ZTZKIJ=4T,%,QEVA[+J_.^E^`3;D, M5N9Q9FNXC=+%4H"4&&R.\$?UE4[8],:F%H@;;"TN3A:`@>T;>Y;C;]_:"&P- M<_WM(4(AGJ7V_NF@^QRU?[D:CIX#]V%\-69PV]IK?-H%ZUC6#NXXL?4JKI.X M=U1G,Z-T_\KW_\"^M9U[>T=J(52:8"KL[AJ>DDG2FB@=<_!7#-/M;<)T8J-G MQ^E5%,\)U&O"]"(!@.N,.U<8RQH;(S6?!6#0(L0*[/71BS?FS@K6G)MAR=;- M@<)6H25+H*T&GD2G6^D7`I.I6HH=S";4W13>TNP%WJ2Z`/P\/L7ZH;R5OFE@[L*>B8BK`/0\ MML[[I\/>5OWM_IYY`C`6N\*6+!KFQ*55A-^Z\;/=H2]=;N4.IDYMW_0?%5^] MV4G=DE--PSZ/RV&GW]VR\RVPN263KFKJ55S7?_Q3+COBL6LSE[O&+B\S>.V0 MF.S2?9=2$_$9,%(\$/^HC*:VSS8\XFPF7I+!=>'`!IX\ZBTO?%<()\\WR@+@ M[<";@[W"_:,AJX19"[M3*Z,*K3!=:;<8,`99XEOOLZVFEK]"@X&OG6T*L5'0+;,6`4W M#CGZD(<"\T>V9/9#[,R8OG&@>SN!OY+]UWGZ?T):]=0$@"WWE4JER,,32YEH M+<8F\J6Q)<-TK^%+?7&/`%5I:4&BBC^KROA4`/33F)AIWZD6#%/K%)GF=?T8 MU'-=Q\T@W9O-_4<2;ABPV+Y*+U_?.8MB/^VT*I:.8?':ZO?-<[IL*KF3NAPR MH2K3M7_6G%=A/$[W#DG(,22H]8BSP,HW9!+VE455CW(O(>%*;[('R_#G`>/9 M$,Z#'Q$'SOL&#!I^)>(T#].<,^%16^=')5(G9ESZOP'U M<&G?QR`62?U#DX.L(1EO&`#]?^*(_,(C9)XQA`=_]+I*MS_ZJ'3.SGJCT?I.?&X^8(Z! M1]U43:.>%W>1+>F%^^,7EV^N(WI#'=3#(V%IV&Y4AKU/EY\[IQ>]=01<.G/NU(E%=\/] MZ[`_[N'1K?[Y;^O0XZ26XL4[TWC,K@<]\_[9QT\@+:. MW(SJIAH>!,0DPC1,?K!P-]H8U#8I#N/7[GH;1()=#7NC#0YG.X3+$YZDW8W2 M]:@'#V.QKIP/&L(Z.@&N-X43T(804GG):*130PTL7]$>+1./[OX;!Z4U^5(Z M6PHM:0>S5>7\HZR!!_[^8XD,2S#%_*.LX$$FY+B>,J>N`N.+=OL?BW"+K"CF MI2T2$OQ+ZC]_"@J&6S]E' MVQJ5U2VW"AE<7^#28@YHN-Z4@HQ+U@,F4_$4;+IP/7B2?*?`TX7KP4-'3L'& M)>L!5V)0"L5J73%<+R8D-[AN)L7B10897IY/(LJ?-J!?Z=,5LHAS?9ZTAH&L MU"'7G.%*SX(UH[*%H)*/)-IC2.%8+$KCR+I4D]PGV0<9V*OW^S>\2U(LO(ZO MF,1=(UT8>7VZ+.7-Z>*4EZ:+(P=,EV5YU&IMY`BK-5G62C,2*3A=&JNL6,"N M4,FZZ)*^A(+G`@+;I3>F!^BV"?G)/:?]N0.FV;=!4\=Q=>H*U[Y*68\,8SRU>J078U,[',N(83X0XNLF6 MG97-G7E@J3[%%MN.J2E?QL=\]48'-=EOOL#&1M6EK;X2PITDW0)/.RYMR6'% MP3MV:0J&T%+J]?5KDJKE(WJT-$+$<\B:Y.,D^RN1*$_Z(^5B_YHK'SH=;I+IBK$%@9X!JT!M!_: M"A[UR!,8\]Q6@BS+;"NPD(I\H3TKA+=DU\F6VV3E->5C:)'(%G.SBXC,#U5= M3X3+Z!/+>YX,92HW8N_'N2E#Y(HS]7:S&\:>=,(SKE@].,E"ST*E(=9X9WCT](::3TS[O](7DB\#3\]93WC0H>ODD=TV%^4EQQ MY0P3KUP,Y.@PNH?GD^.)9UK[6;=9H^,QB`\W&=EQ,L(1I8ZGI94:.T>DT%5M MIHYVX65N=J*+]SDO^2:&CY\"`-DQ&<>1-OT)`-7S@ADT9O?\`8WM\JTL72?W ME)W:PB;\CECR=02B6MB;'ZM+I[J(PM@HQOS@R,:,%E\T2X='-SH1E6-U?M@( M]7M;PL#%EH3)7GQ/&P9;TS+]1W+W"@:C5WIX]L(C!FYP\"-[8;A(7=&N<#G+ M:??!=[[CM4)MZ5PXE6`,P)?HDC(;$UPM-8:P65O&4,"<:64P2+H1CY',-.+R3.EK,:S) MZKIA/XW1N='L*SW5*B20..@Q5&CH)))&8%N)G9*7`9="J1>IE./`GYEC1=VK M$"4;2<'WD.O,?":R7-3M(ZL"0-Q7P23IL,?6TA1(9<-.@@]))XGZQKKNL*M: M-FMEDU*6=/(]-PW+T0>VYI^MX#$A"@Y0%18S+43J@&+\:%7V![D6/UJ5W6;- M1ZO^Q&?#LK]9)0K_3[Y9E:.JCX0)6[S*T^60+.OA1W^RZL_:9.6+51ML(K:D ME_IB%7[=B7`_7?U:5?@U*OX)N:6O4>4H_UE?HP+Y%KY&E8/[Q;]&E?JL$F84 M[)L4)O\Z#L8P?M;-)V)]"EG^P\D?LEB1V]^G6WW(*DY?X!_>@]7QH#EBQ"]9 MW;A.,"=Z^-5-TL6O5I$A!#A<.R`"*76"FP!F4:+,1&;?PXH'=)APD/#;*ZGO M8-4`FX>-:SIBO,Q&9R?K9*V-DU62E;(ZK=`` MPS(Q,(@9`FB]__WJT3W3\P`,64MWTOG#;H#NZNZJZNKJJNJJ.!H#4FNB/-.D M4$HW(%A$@P6G`L9%4$(I1HB/,:D]LFKVT=?/BMX@G,5>,,..J.JB-0&5EW2_D68%ZF?!'C?@C*(&- MG$J0O9B7I`/'-PGT-H&?K8QD4C&>-+`,D.)%X*T4@-O"_CO'ZY@7\W8!D21J=\9K274O&?!01DOB5(+3?Y^O5/UZ]_3H,/95018-^U MDC9OKS_SG1H`P*7Z,USFL$$[N]JW-Z]!(5@W&E=7A1]L^.'JJM&@M&J2+X`! M/EW?OGKW6Z^7RYQ4&HTJTK\S2_SP@S"KA4X[0DIE)ZO8:4<,J.QD%SOE@S9% M87I.-;>J_<&#Y0LK"?@3N;_2M65#^/)=M'YV23\]/&];/Z>DW]:@.ZV?6])/ M!5C]>JEF5/Z[$O%2]P2ZNI\O[PA]0A@E\X MMMCB["_\)CWI]'V):[JR;+JB$#TA!Z7'?]2:OY=@AG.T_L:++DD-<_U&_I'$ M#I<+.,U`PXQ82!"@.W_#C1/_,?W2%MJ;2?;YR$5S6]=5[9)WE')FZK/3I9^S M+V?ZZLODS2=_`XO79U+Y"V1-XD3H M]>!2/@>L^D.XA0<1!LYD1VG#B2MAMP6F,U'_5LE*U&>9BJ0PZ)XQYP,T-%#6 MI4&P1J4@PLQNJ%\,`DZ\E)D/D/Z!D#$92&$Y#^^^GA-/'=M]',_C[^@N'^.B M$4?C">2Y[0RA6%-1),<(&FO8!2HYW?P$W3TSY"BKW3.8+&=;)M/^N^#_[9L" MAE$L9H5,ENB)HCL/''VR23:-(AF:N\:I/X-K+%YP_OG\'[KZ+/,5:S_G;U?8 M`DV[N\JW[*]7]/U%98PYJ#OU*5R2X\LXF/K1)GIH61G+A?^W;/N^B85"V/[H MVED+9.O"[NRT0#Y:QOS7']Z_N7G;HQ3H:)&GY/=8_B9G;MRUT&.,CE:[W6F( MCK&SFI)D8".=I`'7>V\CR!1P2\_TU55R)1/3^S.RR6V#B0!4:GNX5$\$.;&I M->651Q!#'PTLJ':OQL%@3*8AZ$>-8(M`3[(8H;V6+$8%(Q;>9N&:O9:UG[X% M'J<'KQN8VI8ZA'/5%IK!B42XG=3(:-+W)X'_C2U5?9\RSM>@'YJ\IJ"*\MM% MM,*6*M88.G1; M'5L!Z6\=I'WON([5H%UVZ!Y[-!O_1Q+\7FK(3KSJ/PNNW+1SLQ77?,R&.FV:;>34#>.5RR6C^ZE8IJ(\VDU5SA MF(S=>2\O?4^M)>-NZ<-$%Y?TA+^^'-3]X7(G1-=VX#\."&73PJID))0/Y9?F MHPGEM,H%2&,J!K6U%(F^MF/8PFT)=Z?_1,E@-.@^Q)4C/L!='AD*E_LX=4-N M&@W@QK)D_V@#@9]=1P`F6:]@RQW[DOSA67J48%+,DJ][G+J_:IS".LDLN14U M3*)35<$$=E44LUE^%?*/%_"K$,II);SQ2]<:BY-M%4M.BLW;T+RD)?5>Z1T;! M`@ZA$?;C_$TP-A^4W@+!*"=+%*-Y'2'*B`26-#6M@H)Z5#[UYG2\24T6@,AR M!>P4Q4(O:G`X\L(5FIU;KAP>_1>9,:$[CIH'T4;?@#):TY=R7FUS7*,Q:GQP M8UN`@7"1B@#9XTP;P2(!2@,-4U'Y8E:&<41XG3A$.CKF(2U>D,HP6-#I7,`6 MG`R)OQ/C:VD6=<5H"W\.!WD$JG8<3(0?8)X/X:FU#$,_FCW#=_8!YLL'*-Q. M\05`41%/8P]XF%*0\&+D1*D`";YDNMO@]2!F?0:88!A=1('P)BMO$_%"(IT+ MT%,G,9_ST^%75*M'(0\Q*Q'HT3,R$&"XD8@HG+=$J,5BQ$@D42&==PIQ&8P" MP@""SUXZA,UC3WP/^P>QH.BS!*^4<4KA]0IME^$,7<+^.?E6>%?5V*53V[8S M:)Q(+CH]1TE@*"SSSI#D6,YQ`8H6./1-6CX(BPQM,#K^K"HA@YX9CPNC\MP` MBT`(1!JL"%VZM(LQWSC.9B-6E/SF#Q`=@,"%3+$F0L3N*H@`P:G@&E+!F5VN MS8SWB&(!*+X?-].%$#+*]E0\'2:,(6'@\<3675730;5E7E#B"Q=!VXZ:`,U7 ME2=R8Z6[JB:>1@'VQ:)*R(RU#%LET[CSY['XBO[ZOC_B7>7%=#0@Y'[EB5FC MLE;O/U:I/:(6TPPLE:R#;:+F@:W7;1-#!2?46",N1K68>#T&7-!M'M\;7*A! MRAU4`,@;(_!3\4::94$'--(ELS\*FO77--[4^R,84)P7SL1R*O+;.Q86R,)& MY>N`0[*`?^EG?$"/"]"R&I7!=*Z-9*@]O+'LXN1[J$&"%)')T\C MF<0<;FJX1716*7$X$L$K.K7;^Z@IV?C'$`4YIYJB_8&9!3E&,&D#((X;PG,@/` MOJ;$_%[EY0Q)#8H(Q@+ZXMGNU5QS7"3A/0`IVG`J\\T@Y%>?3G"(.3OJ*@-HN!"9C?M&TW9- MXJ+&H;=6Z[&XB,45*#.Z3;&/R0W)58RU3JD:]%:V2E9[#%LUT$!QJ@\-QQD: MPF!"Y15M51'A0RA]5%'CA-[SR1*4ZET$+X6/PL-VV@#?L9J=%I/]OX7J)?9C MKL*\KWBJ6N-11@L;B!WVOYX_R=SG];\*)XVMA^7$UZL@'\$`AU=G-H#DLV%] MNKF;^'U032]'FZB^#,+Z+'P(^+;5L#J68SI8B+?-/&`?>H`T'_,`86LW6JRX MBO0^^NMK/,J>:6ZQ`>$18XI&QOR#<5L%$X\!)P,F[OSTV^VK#^_P,4&U6_R. M7OV4\I!ZV'0X^\B>!?'1I,!(=V7YUF/]0&7TN*PN%69&">*B478?!\G%'Z6$ M6*WMW--I"#L3>/7^(P=7/3_+58FKBC-2]%^P-IL-UZ*`2.QVEO>;9OI!LRH: MV&%4JW6DJ;+4Z[K+6RO?`&H&AW.F3VKOS#YA8^NL]D>FFKQE@H*U=5HF0;7% MQ^OIX.HM$\'EMTDO)=Z[_.74GP[F^&9[6%.H5:\S:=O2BZ%3JB^G**`M;1B* M/_\2LN8>QB@JP0!L8"(?5-+&L-5/,1'T(,T$_=)WW4%_Z+G&?P`D<]H0GH<` !```` ` end