[PATCH] Add iSCSI iBFT patch to the kernel.

Konrad Rzeszutek konradr at redhat.com
Mon Sep 10 19:18:39 UTC 2007


The patch below has been tested on a bunch of HP, Dell, NEC, and IBM
 machines by me and works without trouble. 

I was humbly wondering if Fedora kernel community would be OK picking 
this patch it up so that it can be tested on some other platforms before 
I go forth on LKML with it? And obviously, any comments on the patch
are much appreciated. The testing is to make sure that this patch
does not panic the box when it scans the memory (find_ibft function).

For those that don't know what iSCSI is, here is a nice description:
http://en.wikipedia.org/wiki/ISCSI
and here
http://linux-iscsi.sourceforge.net/

The issue at hand is to support iSCSI boot automatically. Right now
when Fedora Core is installed on a iSCSI target and booted up from
a NIC that supports software iSCSI initiator (the NIC firmware 
"exports" a disk) all the connection information (IQN, IP, CHAP password,
etc) are all hard-coded in the initrd. This is OK, except when the
password changes (and programmed in the NIC) the initrd can't boot 
anymore (unless the system admin also logged in the machine and changed
the settings and re-created the initrd before rebooting the box). 
The goal is to have the initrd able to extract the iSCSI information 
from the NIC and use them.

The patch below is one step in making this goal. The other necessary
parts for this are to have iscsi-initirator-utils support this
(upstream has the patches for this), and anaconda (not yet there). 

Thank you for your time.

-----
PATCH:
-----


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. To enable this
please set CONFIG_ISCSI_IBFT to m.

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 <konradr at linux.vnet.ibm.com>
Signed-off-by: Peter Jones <pjones at redhat.com>

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 <pjones at redhat.com>
+ *  Copyright 2007 IBM
+ *  by Konrad Rzeszutek <konradr at us.ibm.com>
+ *
+ * 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 <pjones at redhat.com> and Konrad Rzeszutek <konradr at us.ibm.com>");
+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 */

-- 
Konrad Rzeszutek 1-(978)-392-3903 or 1-(617)-693-1718
IBM on-site partner.  




More information about the Fedora-kernel-list mailing list