Commit 7df2bda6 authored by Matt Domsch's avatar Matt Domsch

EDD: x86 BIOS Enhanced Disk Drive support

The major changes implemented in this patch:
arch/i386/boot/setup.S - int13 real mode calls store results in empty_zero_page
arch/i386/kernel/setup.c - copy results from empty_zero_page to local storage
arch/i386/kernel/edd.c - module exports results via driverfs

x86 systems suffer from a disconnect between what BIOS believes is the
boot disk, and what Linux thinks BIOS thinks is the boot disk.  This
manifests itself in multi-disk systems - it's quite possible to
install a distribution, only to fail on reboot - the disk installed to
is not the disk BIOS is booting from.  Dell restricts our possible
standard factory installed Linux offerings to "disks on no more than
one controller" to avoid this problem, but mechanisms now exist to
solve it and allow such configurations.

BIOS Enhanced Disk Device Services (EDD) 3.0 provides the ability for
disk adapter BIOSs to tell the OS what it believes is the boot disk.
While this isn't widely implemented in BIOSs yet, it's time that Linux
received support to be ready as BIOSs with this feature do become
available.  At a minimum, LSI MegaRAID cards support this today.

EDD works by providing the bus (PCI, PCI-X, ISA, InfiniBand, PCI
Express, or HyperTransport) location (e.g. PCI 02:01.0) and interface
(ATAPI, ATA, SCSI, USB, 1394, FibreChannel, I2O, RAID, SATA) location
(e.g. SCSI ID 5 LUN 0) information for each BIOS int13 device.

The patch below creates CONFIG_EDD, that when defined, makes the
BIOS int13 calls to retrieve and store this information.  The data is
copied to a safe place in setup.c, and exported via driverfs.

Here's a sample driverfs tree with two BIOS int13 devices - dev 80 has
incorrect PCI bus information, thus no symlinks are made, but as much
info as possible is presented.  Dev 81 has correct PCI and SCSI
information, thus symlinks are made to the actual disc device.

/driverfs
|-- bios
|   |-- int13_dev80
|   |   |-- extensions
|   |   |-- host_bus
|   |   |-- info_flags
|   |   |-- interface
|   |   |-- raw_data
|   |   |-- sectors
|   |   `-- version
|   `-- int13_dev81
|       |-- extensions
|       |-- host_bus
|       |-- info_flags
|       |-- interface
|       |-- pci_dev -> ../../root/pci2/02:0c.0/03:00.0/04:00.0
|       |-- raw_data
|       |-- disc -> ../../root/pci2/02:0c.0/03:00.0/04:00.0/scsi4/4:0:0:0
|       |-- sectors
|       `-- version
|-- bus
|   |-- scsi
|   |   |-- devices
|   |   |   |-- 4:0:0:0 -> ../../../root/pci2/02:0c.0/03:00.0/04:00.0/scsi4/4:0:0:0
|   |   `-- drivers
|   |       `-- sd
`-- root
    |-- pci2
    |   |-- 02:0c.0
    |   |   |-- 03:00.0
    |   |   |   |-- 04:00.0
    |   |   |   |   |-- irq
    |   |   |   |   |-- name
    |   |   |   |   |-- power
    |   |   |   |   |-- resource
    |   |   |   |   `-- scsi4
    |   |   |   |       |-- 4:0:0:0
    |   |   |   |       |   |-- 4:0:0:0::p1
    |   |   |   |       |   |   |-- kdev
    |   |   |   |       |   |   |-- name
    |   |   |   |       |   |   |-- power
    |   |   |   |       |   |   `-- type
    |   |   |   |       |   |-- 4:0:0:0:disc
    |   |   |   |       |   |   |-- kdev
    |   |   |   |       |   |   |-- name
    |   |   |   |       |   |   |-- power
    |   |   |   |       |   |   `-- type
    |   |   |   |       |   |-- name
    |   |   |   |       |   |-- power
    |   |   |   |       |   `-- type


(Yes, the 'bios' top-level directory isn't the right place,
 and Patrick has promised to make something there in the future,
 at which point this can be moved.)

The 'raw_data' file contains the full set of information returned by BIOS
with extra error reporting.  This exists for vendor BIOS debugging purposes.

The 'host-bus' file contains the PCI (or ISA, HyperTransport, ...)
identifying information, as BIOS knows it.

The 'interface' file contains the SCSI (or IDE, USB, ...) identifying
information, as BIOS knows it.

The 'extensions' file lists the BIOS EDD extensions per spec.
The 'info_flags' file lists the BIOS EDD device information flags per spec.
The 'sectors' file reports the number of sectors BIOS believes this
device has.
The 'version' file lists the EDD version.  To have device path
information, this must be 0x30 or above.  Earlier EDD versions exist
without the device path - as much information as is available is presented.

At most 6 BIOS devices are reported, as that fills the space that's
left in the empty_zero_page.  In general you only care about device
80h, though for software RAID1 knowing what 81h is might be useful also.



Known issues:
- module unload leaves a directory around.  Seems related to
  creating symlinks in that directory.  Seen on kernel 2.5.41.
- refcounting of struct device objects could be improved.

TODO:
- Add IDE and USB disk device support
- when driverfs model of discs and partitions changes,
  update symlink accordingly.
- Get symlink creator helper functions exported from
  drivers/base instead of duplicating them here.
- move edd.[ch] to better locations if/when one is decided

I'd also like to acknowledge the help and comments received from Greg
KH and Patrick Mochel.  This isn't something driverfs was originally
conceived to handle, their assistance has been invaluable.

Please pull from:

BK:
    http://mdomsch.bkbits.net/linux-2.5-edd-tolinus

Patch (against 2.5.41+BK-current):
    http://domsch.com/linux/edd30/edd-driverfs-6.patch
    http://domsch.com/linux/edd30/edd-driverfs-6.patch.sign

Thanks,
Matt

-- 
Matt Domsch
Sr. Software Engineer, Lead Engineer, Architect
Dell Linux Solutions www.dell.com/linux
Linux on Dell mailing lists @ http://lists.us.dell.com
parent 716041cc
......@@ -31,6 +31,7 @@ Offset Type Description
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 @@ Offset Type Description
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.
......
......@@ -1075,3 +1075,11 @@ CONFIG_SCx200
This support is also available as a module. If compiled as a
module, it will be called scx200.o.
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 driverfs.
This option is experimental, but believed to be safe,
and most disk controller BIOS vendors do not yet implement this feature.
......@@ -44,6 +44,9 @@
*
* New A20 code ported from SYSLINUX by H. Peter Anvin. AMD Elan bugfixes
* by Robert Schwebel, December 2001 <robert@schwebel.de>
*
* BIOS Enhanced Disk Drive support
* by Matt Domsch <Matt_Domsch@dell.com> September 2002
*
*/
......@@ -53,6 +56,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 */
......@@ -543,6 +547,49 @@ no_32_apm_bios:
done_apm_bios:
#endif
#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
# Do the BIOS Enhanced Disk Drive calls
# 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 $0x41, %ah # Function 41
movw $0x55aa, %bx # magic
int $0x13 # make the call
jc edd_done # no more BIOS devices
cmpw $0xAA55, %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 $0x48, %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 ...
cmpw $0, %cs:realmode_swtch
jz rmodeswtch_normal
......
......@@ -215,6 +215,10 @@ tristate '/dev/cpu/microcode - Intel IA32 CPU microcode support' CONFIG_MICROCOD
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 \
......
......@@ -70,6 +70,7 @@ CONFIG_X86_MCE_P4THERMAL=y
# 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
......
......@@ -26,6 +26,7 @@ obj-$(CONFIG_X86_LOCAL_APIC) += apic.o nmi.o
obj-$(CONFIG_X86_IO_APIC) += io_apic.o
obj-$(CONFIG_SOFTWARE_SUSPEND) += suspend.o
obj-$(CONFIG_X86_NUMAQ) += numaq.o
obj-$(CONFIG_EDD) += edd.o
EXTRA_AFLAGS := -traditional
......
This diff is collapsed.
......@@ -29,6 +29,7 @@
#include <asm/pgtable.h>
#include <asm/pgalloc.h>
#include <asm/tlbflush.h>
#include <asm/edd.h>
extern void dump_thread(struct pt_regs *, struct user *);
extern spinlock_t rtc_lock;
......@@ -182,3 +183,8 @@ extern int is_sony_vaio_laptop;
EXPORT_SYMBOL(is_sony_vaio_laptop);
EXPORT_SYMBOL(__PAGE_KERNEL);
#ifdef CONFIG_EDD_MODULE
EXPORT_SYMBOL(edd);
EXPORT_SYMBOL(eddnr);
#endif
......@@ -36,6 +36,7 @@
#include <linux/highmem.h>
#include <asm/e820.h>
#include <asm/mpspec.h>
#include <asm/edd.h>
#include <asm/setup.h>
#include <asm/arch_hooks.h>
#include "setup_arch_pre.h"
......@@ -466,6 +467,22 @@ static int __init copy_e820_map(struct e820entry * biosmap, int nr_map)
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 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.
......@@ -843,6 +860,7 @@ void __init setup_arch(char **cmdline_p)
#endif
ARCH_SETUP
setup_memory_region();
copy_edd();
if (!MOUNT_ROOT_RDONLY)
root_mountflags &= ~MS_RDONLY;
......
/*
* linux/include/asm-i386/edd.h
* Copyright (C) 2002 Dell Computer Corporation
* by Matt Domsch <Matt_Domsch@dell.com>
*
* 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
#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 */
......@@ -37,6 +37,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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment