This patch adds a /sysfs/firmware/ibft/table binary blob which exports the iSCSI Boot Firmware Table (iBFT) structure.
What is iSCSI Boot Firmware Table? It is a mechanism for the iSCSI tools to extract from the machine NICs the iSCSI connection information so that they can automagically mount the iSCSI share/target. Currently the iSCSI information is hard-coded in the initrd. The full details of the structure are located at: ftp://ftp.software.ibm.com/systems/support/system_x_pdf/ibm_iscsi_boot_firmware_table_v1.02.pdf Signed-off-by: Konrad Rzeszutek <[EMAIL PROTECTED]> Signed-off-by: Peter Jones <[EMAIL PROTECTED]> diff --git a/arch/i386/kernel/setup.c b/arch/i386/kernel/setup.c index d474cd6..11d700f 100644 --- a/arch/i386/kernel/setup.c +++ b/arch/i386/kernel/setup.c @@ -46,7 +46,7 @@ #include <linux/kexec.h> #include <linux/crash_dump.h> #include <linux/dmi.h> #include <linux/pfn.h> - +#include <linux/iscsi_ibft.h> #include <video/edid.h> #include <asm/apic.h> @@ -150,6 +150,9 @@ static inline void copy_edd(void) } #endif +void *ibft_phys; +EXPORT_SYMBOL(ibft_phys); + int __initdata user_defined_memmap = 0; /* @@ -456,6 +459,15 @@ #ifdef CONFIG_KEXEC reserve_bootmem(crashk_res.start, crashk_res.end - crashk_res.start + 1); #endif + + /* Scan for an iBFT (iSCSI Boot Firmware Table) */ + { + unsigned int ibft_len = find_ibft(); + if (ibft_len) + /* The specs says to scan for the table between 512k to 1MB. + We reserve it n case it is in the e820 RAM section. */ + reserve_bootmem(ibft_phys, PAGE_ALIGN(ibft_len)); + } } /* diff --git a/arch/x86_64/kernel/setup.c b/arch/x86_64/kernel/setup.c index af838f6..0d12775 100644 --- a/arch/x86_64/kernel/setup.c +++ b/arch/x86_64/kernel/setup.c @@ -44,6 +44,7 @@ #include <linux/cpufreq.h> #include <linux/dmi.h> #include <linux/dma-mapping.h> #include <linux/ctype.h> +#include <linux/iscsi_ibft.h> #include <asm/mtrr.h> #include <asm/uaccess.h> @@ -196,6 +197,9 @@ static inline void copy_edd(void) } #endif +void *ibft_phys; +EXPORT_SYMBOL(ibft_phys); + #define EBDA_ADDR_POINTER 0x40E unsigned __initdata ebda_addr; @@ -365,6 +369,15 @@ #ifdef CONFIG_KEXEC crashk_res.end - crashk_res.start + 1); } #endif + /* Scan for an iBFT (iSCSI Boot Firmware Table) */ + { + unsigned int ibft_len = find_ibft(); + if (ibft_len) + /* The specs says to scan for the table between 512k to 1MB. + We reserve it in case it is in the e820 RAM section. */ + reserve_bootmem_generic((unsigned long)ibft_phys, + PAGE_ALIGN(ibft_len)); + } paging_init(); diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 05f02a3..2d9f01a 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -93,4 +93,14 @@ config DMIID information from userspace through /sys/class/dmi/id/ or if you want DMI-based module auto-loading. +config ISCSI_IBFT + tristate "iSCSI Boot Firmware Table Attributes" + depends on X86 + default n + help + This option enables support for detection of an iSCSI + Boot Firmware Table (iBFT). If you wish to detect iSCSI boot + parameters dynamically during system boot, say Y. + Otherwise, say N. + endmenu diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 8d4ebc8..b6319f7 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_EFI_PCDP) += pcdp.o obj-$(CONFIG_DELL_RBU) += dell_rbu.o obj-$(CONFIG_DCDBAS) += dcdbas.o obj-$(CONFIG_DMIID) += dmi-id.o +obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o diff --git a/drivers/firmware/iscsi_ibft.c b/drivers/firmware/iscsi_ibft.c new file mode 100644 index 0000000..b3767fe --- /dev/null +++ b/drivers/firmware/iscsi_ibft.c @@ -0,0 +1,201 @@ +/* + * drivers/firmware/iscsi_ibft.c + * Copyright 2007 Red Hat, Inc. + * by Peter Jones <[EMAIL PROTECTED]> + * Copyright 2007 IBM + * by Konrad Rzeszutek <[EMAIL PROTECTED]> + * + * This code exposes the the iSCSI Boot Format Table to userland via sysfs. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/stat.h> +#include <linux/err.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/limits.h> +#include <linux/device.h> +#include <linux/pci.h> +#include <linux/blkdev.h> + +#include <linux/iscsi_ibft.h> + +#define ISCSI_IBFT_VERSION "0.2" +#define ISCSI_IBFT_DATE "2007-Aug-29" + +MODULE_AUTHOR +("Peter Jones <[EMAIL PROTECTED]> and Konrad Rzeszutek <[EMAIL PROTECTED]>"); +MODULE_DESCRIPTION("sysfs interface to BIOS iBFT information"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ISCSI_IBFT_VERSION); + + +static void ibft_release(struct kobject *kobj) +{ + struct ibft_device *ibft = container_of(kobj, struct ibft_device, kobj); + kfree(ibft->hdr); + kfree(ibft); +} + +static ssize_t +ibft_read_binary(struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + + struct ibft_device *ibft = container_of(kobj, struct ibft_device, kobj); + ssize_t len = ibft->hdr->length; + + if (off > len) + return 0; + + if (off + count > len) + count = len - off; + + memcpy(buf, ibft->hdr + off, count); + + return count; +} +static int +ibft_mmap_binary(struct kobject *kobj, struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + struct ibft_device *ibft = container_of(kobj, struct ibft_device, kobj); + ssize_t len = ibft->hdr->length; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long off = vma->vm_pgoff << PAGE_SHIFT; + unsigned long pos; + unsigned long pfn; + int i; + + pos = (unsigned long)ibft->hdr; + + if (off > len) + return -EINVAL; + + if (vma->vm_flags & VM_WRITE) + return -EPERM; + + for (i = 0; i < len; i += PAGE_SIZE) { + pfn = virt_to_phys((void *)(pos + off)) >> PAGE_SHIFT; + if (remap_pfn_range + (vma, start, pfn, PAGE_SIZE, vma->vm_page_prot)) + return -EAGAIN; + start += PAGE_SIZE; + if (size <= PAGE_SIZE) + break; + size -= PAGE_SIZE; + } + return 0; +} +static struct bin_attribute ibft_attribute_binary = { + .attr = { + .name = "binary", + .mode = S_IRUSR, + .owner = THIS_MODULE, + }, + .read = ibft_read_binary, + .write = NULL, + .mmap = ibft_mmap_binary +}; +static struct kobj_type ktype_ibft = { + .release = ibft_release, +}; + +static decl_subsys(ibft, &ktype_ibft, NULL); + +static int ibft_device_register(struct ibft_device *idev) +{ + int error = 0; + int len = 0; + struct ibft_header *hdr; + + if (!idev) + return 1; + + /* Copy over the data */ + hdr = (struct ibft_header *)phys_to_virt((unsigned long)ibft_phys); + len = hdr->length; + + /* Need PAGE_ALING for mmap functionality. */ + idev->hdr = kzalloc(PAGE_ALIGN(len), GFP_KERNEL); + if (!idev->hdr) + return -ENOMEM; + + memcpy(idev->hdr, hdr, len); + + /* This is firmware/ibft */ + kobject_set_name(&idev->kobj, "table"); + kobj_set_kset_s(idev, ibft_subsys); + error = kobject_register(&idev->kobj); + + if (!error) { + ibft_attribute_binary.size = idev->hdr->length; + error = + sysfs_create_bin_file(&idev->kobj, &ibft_attribute_binary); + } + + /* The de-allocation part is done by module_exit() */ + return error; +} + +static struct ibft_device *ibft_idev; +/* + * ibft_init() - creates sysfs tree entry for ibft data + */ +static int __init ibft_init(void) +{ + int rc = 0; + + printk(KERN_INFO "BIOS iBFT facility v%s %s\n", ISCSI_IBFT_VERSION, + ISCSI_IBFT_DATE); + + if (!ibft_phys) + find_ibft(); + + /* What if the ibft_subsys is underneath another struct? */ + rc = firmware_register(&ibft_subsys); + if (rc) + return rc; + + if (ibft_phys) { + printk(KERN_INFO "iBFT detected at 0x%lx.\n", + (unsigned long)ibft_phys); + ibft_idev = kzalloc(sizeof(*ibft_idev), GFP_KERNEL); + if (!ibft_idev) + return -ENOMEM; + + rc = ibft_device_register(ibft_idev); + if (rc) { + kfree(ibft_idev); + return rc; + } + } else { + printk(KERN_INFO "No iBFT detected.\n"); + } + return rc; +} + +static void __exit ibft_exit(void) +{ + if (ibft_idev) + kobject_unregister(&ibft_idev->kobj); + + firmware_unregister(&ibft_subsys); + printk(KERN_INFO "BIOS iBFT unloaded.\n"); +} + +module_init(ibft_init); +module_exit(ibft_exit); diff --git a/include/linux/iscsi_ibft.h b/include/linux/iscsi_ibft.h new file mode 100644 index 0000000..5e7b267 --- /dev/null +++ b/include/linux/iscsi_ibft.h @@ -0,0 +1,58 @@ +#ifndef ISCSI_IBFT_H +#define ISCSI_IBFT_H + +extern void *ibft_phys; + +struct ibft_header { + char signature[4]; + u32 length; + u8 revision; + u8 checksum; + char oem_id[6]; + char oem_table_id[8]; + char reserved[24]; +}; + +struct ibft_device { + struct ibft_header *hdr; + struct kobject kobj; +}; + +#if defined(CONFIG_ISCSI_IBFT) || defined(CONFIG_ISCSI_IBFT_MODULES) + +#define IBFT_SIGN "iBFT" +#define IBFT_SIGN_LEN 4 +#define IBFT_START 0x80000 /* 512kB */ +#define IBFT_END 0x100000 /* 1MB */ +#define VGA_MEM 0xA0000 /* VGA buffer */ +#define VGA_SIZE 0x20000 /* 132kB */ +static inline ssize_t find_ibft(void) +{ + unsigned long pos; + for (pos = IBFT_START; pos < IBFT_END; pos += 16) { + void *virt; + /* The table can't be inside the VGA BIOS reserved space, + * so skip that area */ + if (pos == VGA_MEM-PAGE_SIZE) + pos += VGA_SIZE+PAGE_SIZE; + virt = phys_to_virt(pos); + if (memcmp(virt, IBFT_SIGN, IBFT_SIGN_LEN) == 0) { + unsigned long *addr = + (unsigned long *)phys_to_virt(pos + 4); + unsigned int len = *addr; + /* if the length of the table extends past 1M, + * the table cannot be valid. */ + if (pos + len <= (IBFT_END-1)) { + ibft_phys = (void *)pos; + return len; + } + } + } + return 0; +} + +#else + +static inline ssize_t find_ibft(void) { return 0; }; +#endif +#endif /* ISCSI_IBFT_H */ - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/