[iscsiboot] Initial revision of WinXP iSCSI boot driver and installer v0.9
authorMichael Brown <mbrown@fensystems.co.uk>
Thu, 18 Dec 2008 02:34:36 +0000 (02:34 +0000)
committerMichael Brown <mbrown@fensystems.co.uk>
Thu, 18 Dec 2008 02:42:26 +0000 (02:42 +0000)
15 files changed:
bin/.gitignore [new file with mode: 0644]
bin/iscsiboot.inf [new file with mode: 0755]
driver/.gitignore [new file with mode: 0644]
driver/acpi.h [new file with mode: 0644]
driver/ibft.h [new file with mode: 0644]
driver/iscsiboot.c [new file with mode: 0644]
driver/makefile [new file with mode: 0644]
driver/sources [new file with mode: 0644]
installer/.gitignore [new file with mode: 0644]
installer/registry.c [new file with mode: 0644]
installer/registry.h [new file with mode: 0644]
installer/setup.c [new file with mode: 0644]
installer/setupdi.c [new file with mode: 0644]
installer/setupdi.h [new file with mode: 0644]
installer/sources [new file with mode: 0644]

diff --git a/bin/.gitignore b/bin/.gitignore
new file mode 100644 (file)
index 0000000..ce50299
--- /dev/null
@@ -0,0 +1,2 @@
+i386
+amd64
diff --git a/bin/iscsiboot.inf b/bin/iscsiboot.inf
new file mode 100755 (executable)
index 0000000..e3b244a
--- /dev/null
@@ -0,0 +1,46 @@
+[Version]\r
+Signature = "$Windows NT$"\r
+Class = System\r
+ClassGUID = {4d36e97d-e325-11ce-bfc1-08002be10318}\r
+Provider = %fensys%\r
+CatalogFile = iscsiboot.cat\r
+DriverVer = 12/01/2008,0.1\r
+\r
+[Manufacturer]\r
+%fensys% = FenSystems\r
+\r
+[FenSystems]\r
+%iscsiboot% = iscsiboot,ROOT\iscsiboot\r
+\r
+[SourceDisksNames]\r
+0 = %srcdisk%\r
+\r
+[SourceDisksFiles.x86]\r
+iscsiboot.sys=0,\i386\r
+\r
+[SourceDisksFiles.amd64]\r
+iscsiboot.sys=0,\amd64\r
+\r
+[DestinationDirs]\r
+Files.Driver = 12\r
+\r
+[Files.Driver]\r
+iscsiboot.sys\r
+\r
+[iscsiboot]\r
+CopyFiles = Files.Driver\r
+\r
+[iscsiboot.Services]\r
+AddService = iscsiboot,0x00000002,Service\r
+\r
+[Service]\r
+ServiceType = 0x1\r
+StartType = 0x0\r
+LoadOrderGroup = Base\r
+ErrorControl = 0x1\r
+ServiceBinary = %12%\iscsiboot.sys\r
+\r
+[Strings]\r
+fensys = "Fen Systems Ltd."\r
+iscsiboot = "iSCSI Boot Driver"\r
+srcdisk = "Installation media"\r
diff --git a/driver/.gitignore b/driver/.gitignore
new file mode 100644 (file)
index 0000000..7ca21f7
--- /dev/null
@@ -0,0 +1,2 @@
+build*
+obj*
diff --git a/driver/acpi.h b/driver/acpi.h
new file mode 100644 (file)
index 0000000..d6ab831
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef _ACPI_H
+#define _ACPI_H
+
+/** @file
+ *
+ * ACPI data structures
+ *
+ */
+
+/**
+ * An ACPI description header
+ *
+ * This is the structure common to the start of all ACPI system
+ * description tables.
+ */
+#pragma pack(1)
+typedef struct _ACPI_DESCRIPTION_HEADER {
+       /** ACPI signature (4 ASCII characters) */
+       CHAR signature[4];
+       /** Length of table, in bytes, including header */
+       ULONG length;
+       /** ACPI Specification minor version number */
+       UCHAR revision;
+       /** To make sum of entire table == 0 */
+       UCHAR checksum;
+       /** OEM identification */
+       CHAR oem_id[6];
+       /** OEM table identification */
+       CHAR oem_table_id[8];
+       /** OEM revision number */
+       ULONG oem_revision;
+       /** ASL compiler vendor ID */
+       CHAR asl_compiler_id[4];
+       /** ASL compiler revision number */
+       ULONG asl_compiler_revision;
+} ACPI_DESCRIPTION_HEADER, *PACPI_DESCRIPTION_HEADER;
+#pragma pack()
+
+#endif /* _ACPI_H */
diff --git a/driver/ibft.h b/driver/ibft.h
new file mode 100644 (file)
index 0000000..ec493fd
--- /dev/null
@@ -0,0 +1,269 @@
+#ifndef _IBFT_H
+#define _IBFT_H
+
+/*
+ * Copyright Fen Systems Ltd. 2007.  Portions of this code are derived
+ * from IBM Corporation Sample Programs.  Copyright IBM Corporation
+ * 2004, 2007.  All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+/** @file
+ *
+ * iSCSI boot firmware table
+ *
+ * The information in this file is derived from the document "iSCSI
+ * Boot Firmware Table (iBFT)" as published by IBM at
+ *
+ * ftp://ftp.software.ibm.com/systems/support/system_x_pdf/ibm_iscsi_boot_firmware_table_v1.02.pdf
+ *
+ */
+
+#include "acpi.h"
+
+/** iSCSI Boot Firmware Table signature */
+#define IBFT_SIG "iBFT"
+
+/** A string within the iBFT */
+#pragma pack(1)
+typedef struct _IBFT_STRING {
+       /** Length of string */
+       USHORT length;
+       /** Offset to string */
+       USHORT offset;
+} IBFT_STRING, *PIBFT_STRING;
+#pragma pack()
+
+/** An IP address within the iBFT */
+#pragma pack(1)
+typedef struct _IBFT_IPADDR {
+       /** Reserved; must be zero */
+       USHORT zeroes[5];
+       /** Must be 0xffff if IPv4 address is present, otherwise zero */
+       USHORT ones;
+       /** The IPv4 address, or zero if not present */
+       ULONG in;
+} IBFT_IPADDR, *PIBFT_IPADDR;
+#pragma pack()
+
+/**
+ * iBFT structure header
+ *
+ * This structure is common to several sections within the iBFT.
+ */
+#pragma pack()
+typedef struct _IBFT_HEADER {
+       /** Structure ID
+        *
+        * This is an IBFT_STRUCTURE_ID_XXX constant
+        */
+       UCHAR structure_id;
+       /** Version (always 1) */
+       UCHAR version;
+       /** Length, including this header */
+       USHORT length;
+       /** Index 
+        *
+        * This is the number of the NIC or Target, when applicable.
+        */
+       UCHAR index;
+       /** Flags */
+       UCHAR flags;
+} IBFT_HEADER, *PIBFT_HEADER;
+#pragma pack()
+
+/**
+ * iBFT Control structure
+ *
+ */
+#pragma pack(1)
+typedef struct _IBFT_CONTROL {
+       /** Common header */
+       IBFT_HEADER header;
+       /** Extensions */
+       USHORT extensions;
+       /** Offset to Initiator structure */
+       USHORT initiator;
+       /** Offset to NIC structure for NIC 0 */
+       USHORT nic_0;
+       /** Offset to Target structure for target 0 */
+       USHORT target_0;
+       /** Offset to NIC structure for NIC 1 */
+       USHORT nic_1;
+       /** Offset to Target structure for target 1 */
+       USHORT target_1;
+} IBFT_CONTROL, *PIBFT_CONTROL;
+#pragma pack()
+
+/** Structure ID for Control section */
+#define IBFT_STRUCTURE_ID_CONTROL 0x01
+
+/** Attempt login only to specified target
+ *
+ * If this flag is not set, all targets will be logged in to.
+ */
+#define IBFT_FL_CONTROL_SINGLE_LOGIN_ONLY 0x01
+
+/**
+ * iBFT Initiator structure
+ *
+ */
+#pragma pack(1)
+typedef struct _IBFT_INITIATOR {
+       /** Common header */
+       IBFT_HEADER header;
+       /** iSNS server */
+       IBFT_IPADDR isns_server;
+       /** SLP server */
+       IBFT_IPADDR slp_server;
+       /** Primary and secondary Radius servers */
+       IBFT_IPADDR radius[2];
+       /** Initiator name */
+       IBFT_STRING initiator_name;
+} IBFT_INITIATOR, *PIBFT_INITIATOR;
+#pragma pack()
+
+/** Structure ID for Initiator section */
+#define IBFT_STRUCTURE_ID_INITIATOR 0x02
+
+/** Initiator block valid */
+#define IBFT_FL_INITIATOR_BLOCK_VALID 0x01
+
+/** Initiator firmware boot selected */
+#define IBFT_FL_INITIATOR_FIRMWARE_BOOT_SELECTED 0x02
+
+/**
+ * iBFT NIC structure
+ *
+ */
+#pragma pack(1)
+typedef struct _IBFT_NIC {
+       /** Common header */
+       IBFT_HEADER header;
+       /** IP address */
+       IBFT_IPADDR ip_address;
+       /** Subnet mask
+        *
+        * This is the length of the subnet mask in bits (e.g. /24).
+        */
+       UCHAR subnet_mask_prefix;
+       /** Origin */
+       UCHAR origin;
+       /** Default gateway */
+       IBFT_IPADDR gateway;
+       /** Primary and secondary DNS servers */
+       IBFT_IPADDR dns[2];
+       /** DHCP server */
+       IBFT_IPADDR dhcp;
+       /** VLAN tag */
+       USHORT vlan;
+       /** MAC address */
+       UCHAR mac_address[6];
+       /** PCI bus:dev:fn */
+       USHORT pci_bus_dev_func;
+       /** Hostname */
+       IBFT_STRING hostname;
+} IBFT_NIC, *PIBFT_NIC;
+#pragma pack()
+
+/** Structure ID for NIC section */
+#define IBFT_STRUCTURE_ID_NIC 0x03
+
+/** NIC block valid */
+#define IBFT_FL_NIC_BLOCK_VALID 0x01
+
+/** NIC firmware boot selected */
+#define IBFT_FL_NIC_FIRMWARE_BOOT_SELECTED 0x02
+
+/** NIC global / link local */
+#define IBFT_FL_NIC_GLOBAL 0x04
+
+/**
+ * iBFT Target structure
+ *
+ */
+#pragma pack(1)
+typedef struct _IBFT_TARGET {
+       /** Common header */
+       IBFT_HEADER header;
+       /** IP address */
+       IBFT_IPADDR ip_address;
+       /** TCP port */
+       USHORT socket;
+       /** Boot LUN */
+       ULONGLONG boot_lun;
+       /** CHAP type
+        *
+        * This is an IBFT_CHAP_XXX constant.
+        */
+       UCHAR chap_type;
+       /** NIC association */
+       UCHAR nic_association;
+       /** Target name */
+       IBFT_STRING target_name;
+       /** CHAP name */
+       IBFT_STRING chap_name;
+       /** CHAP secret */
+       IBFT_STRING chap_secret;
+       /** Reverse CHAP name */
+       IBFT_STRING reverse_chap_name;
+       /** Reverse CHAP secret */
+       IBFT_STRING reverse_chap_secret;
+} IBFT_TARGET, *PIBFT_TARGET;
+#pragma pack()
+
+/** Structure ID for Target section */
+#define IBFT_STRUCTURE_ID_TARGET 0x04
+
+/** Target block valid */
+#define IBFT_FL_TARGET_BLOCK_VALID 0x01
+
+/** Target firmware boot selected */
+#define IBFT_FL_TARGET_FIRMWARE_BOOT_SELECTED 0x02
+
+/** Target use Radius CHAP */
+#define IBFT_FL_TARGET_USE_CHAP 0x04
+
+/** Target use Radius rCHAP */
+#define IBFT_FL_TARGET_USE_RCHAP 0x08
+
+/* Values for chap_type */
+#define IBFT_CHAP_NONE         0       /**< No CHAP authentication */
+#define IBFT_CHAP_ONE_WAY      1       /**< One-way CHAP */
+#define IBFT_CHAP_MUTUAL       2       /**< Mutual CHAP */
+
+/**
+ * iSCSI Boot Firmware Table (iBFT)
+ */
+#pragma pack(1)
+typedef struct _IBFT_TABLE {
+       /** ACPI header */
+       ACPI_DESCRIPTION_HEADER acpi;
+       /** Reserved */
+       UCHAR reserved[12];
+       /** Control structure */
+       IBFT_CONTROL control;
+} IBFT_TABLE, *PIBFT_TABLE;
+#pragma pack()
+
+#endif /* _IBFT_H */
diff --git a/driver/iscsiboot.c b/driver/iscsiboot.c
new file mode 100644 (file)
index 0000000..dd93dab
--- /dev/null
@@ -0,0 +1,1188 @@
+/*
+ * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#pragma warning(disable:4201)  /* nameless struct/union warning */
+#pragma warning(disable:4214)  /* non-int bitfield warning */
+
+#include <ntddk.h>
+#include <ntstrsafe.h>
+#include <initguid.h>
+#include <ndis.h>
+#include <ndisguid.h>
+#include <ntddndis.h>
+#include <wdmsec.h>
+#include <iscsicfg.h>
+#include "ibft.h"
+
+/** Tag to use for memory allocation */
+#define ISCSIBOOT_POOL_TAG 'bcsi'
+
+/** Start of region to scan in base memory */
+#define BASEMEM_START 0x80000
+
+/** End of region to scan in base memory */
+#define BASEMEM_END 0xa0000
+
+/** Length of region to scan in base memory */
+#define BASEMEM_LEN ( BASEMEM_END - BASEMEM_START )
+
+/** IoControl code to retrieve iSCSI boot data */
+#define IOCTL_ISCSIBOOT CTL_CODE ( FILE_DEVICE_UNKNOWN, 1, METHOD_BUFFERED, \
+                                  FILE_READ_ACCESS )
+
+/** Device private data */
+typedef struct _ISCSIBOOT_PRIV {
+       /* Copy of iBFT */
+       PIBFT_TABLE ibft;
+} ISCSIBOOT_PRIV, *PISCSIBOOT_PRIV;
+
+/** Unique GUID for IoCreateDeviceSecure() */
+DEFINE_GUID ( GUID_ISCSIBOOT_CLASS, 0x8a2f8602, 0x8f0b, 0x4138,
+             0x8e, 0x16, 0x51, 0x9a, 0x59, 0xf3, 0x07, 0xca );
+
+/** iSCSI boot device name */
+static const WCHAR iscsiboot_device_name[] = L"\\Device\\iSCSIBoot";
+
+/** iSCSI boot device symlink name */
+static const WCHAR iscsiboot_device_symlink[] = L"\\DosDevices\\iSCSIBoot";
+
+/**
+ * Calculate byte checksum
+ *
+ * @v data             Region to checksum
+ * @v len              Length of region
+ * @ret checksum       Byte checksum
+ */
+static UCHAR byte_sum ( PUCHAR data, ULONG len ) {
+       UCHAR checksum = 0;
+       ULONG offset;
+
+       for ( offset = 0 ; offset < len ; offset++ )
+               checksum = ( ( UCHAR ) ( checksum + data[offset] ) );
+
+       return checksum;
+}
+
+/**
+ * Convert IPv4 address to string
+ *
+ * @v ipaddr           IP address
+ * @ret ipaddr         IP address string
+ *
+ * This function returns a static buffer.
+ */
+static LPSTR inet_ntoa ( ULONG ipaddr ) {
+       static CHAR buf[16];
+
+       RtlStringCbPrintfA ( buf, sizeof ( buf ), "%d.%d.%d.%d",
+                            ( ( ipaddr >> 0  ) & 0xff ),
+                            ( ( ipaddr >> 8  ) & 0xff ),
+                            ( ( ipaddr >> 16 ) & 0xff ),
+                            ( ( ipaddr >> 24 ) & 0xff ) );
+       return buf;
+}
+
+/**
+ * Open registry key
+ *
+ * @v reg_key_name     Registry key name
+ * @v reg_key          Registry key to fill in
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS reg_open ( LPCWSTR reg_key_name, PHANDLE reg_key ) {
+       UNICODE_STRING unicode_string;
+       OBJECT_ATTRIBUTES object_attrs;
+       NTSTATUS status;
+
+       RtlInitUnicodeString ( &unicode_string, reg_key_name );
+       InitializeObjectAttributes ( &object_attrs, &unicode_string,
+                                    OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
+                                    NULL, NULL );
+       status = ZwOpenKey ( reg_key, KEY_ALL_ACCESS, &object_attrs );
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "Could not open %S: %x\n", reg_key_name, status );
+               return status;
+       }
+
+       return STATUS_SUCCESS;
+}
+
+/**
+ * Close registry key
+ *
+ * @v reg_key          Registry key
+ */
+static VOID reg_close ( HANDLE reg_key ) {
+       ZwClose ( reg_key );
+}
+
+/**
+ * Fetch registry key value information
+ *
+ * @v reg_key          Registry key
+ * @v value_name       Registry value name
+ * @v kvi              Key value information block to allocate and fill in
+ * @ret ntstatus       NT status
+ *
+ * The caller must eventually free the allocated key value information
+ * block.
+ */
+static NTSTATUS fetch_reg_kvi ( HANDLE reg_key, LPCWSTR value_name,
+                               PKEY_VALUE_PARTIAL_INFORMATION *kvi ) {
+       UNICODE_STRING u_value_name;
+       ULONG kvi_len;
+       NTSTATUS status;
+
+       /* Get value length */
+       RtlInitUnicodeString ( &u_value_name, value_name );
+       status = ZwQueryValueKey ( reg_key, &u_value_name,
+                                  KeyValuePartialInformation, NULL, 0,
+                                  &kvi_len );
+       if ( ! ( ( status == STATUS_SUCCESS ) ||
+                ( status == STATUS_BUFFER_OVERFLOW ) ||
+                ( status == STATUS_BUFFER_TOO_SMALL ) ) ) {
+               DbgPrint ( "Could not get KVI length for \"%S\": %x\n",
+                          value_name, status );
+               goto err_zwqueryvaluekey_len;
+       }
+
+       /* Allocate value buffer */
+       *kvi = ExAllocatePoolWithTag ( NonPagedPool, kvi_len,
+                                      ISCSIBOOT_POOL_TAG );
+       if ( ! *kvi ) {
+               DbgPrint ( "Could not allocate KVI for \"%S\": %x\n",
+                          value_name, status );
+               goto err_exallocatepoolwithtag_kvi;
+       }
+
+       /* Fetch value */
+       status = ZwQueryValueKey ( reg_key, &u_value_name,
+                                  KeyValuePartialInformation, *kvi,
+                                  kvi_len, &kvi_len );
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "Could not get KVI for \"%S\": %x\n",
+                          value_name, status );
+               goto err_zwqueryvaluekey;
+       }
+
+       return STATUS_SUCCESS;
+
+ err_zwqueryvaluekey:
+       ExFreePool ( kvi );
+ err_exallocatepoolwithtag_kvi:
+ err_zwqueryvaluekey_len:
+       return status;
+}
+
+/**
+ * Fetch registry string value
+ *
+ * @v reg_key          Registry key
+ * @v value_name       Registry value name
+ * @v value            String value to allocate and fill in
+ * @ret ntstatus       NT status
+ *
+ * The caller must eventually free the allocated value.
+ */
+static NTSTATUS fetch_reg_sz ( HANDLE reg_key, LPCWSTR value_name,
+                              LPWSTR *value ) {
+       PKEY_VALUE_PARTIAL_INFORMATION kvi;
+       ULONG value_len;
+       NTSTATUS status;
+
+       /* Fetch key value information */
+       status = fetch_reg_kvi ( reg_key, value_name, &kvi );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_fetch_reg_kvi;
+
+       /* Allocate and populate string */
+       value_len = ( kvi->DataLength + sizeof ( value[0] ) );
+       *value = ExAllocatePoolWithTag ( NonPagedPool, value_len,
+                                        ISCSIBOOT_POOL_TAG );
+       if ( ! *value ) {
+               DbgPrint ( "Could not allocate value for \"%S\"\n",
+                          value_name );
+               status = STATUS_UNSUCCESSFUL;
+               goto err_exallocatepoolwithtag_value;
+       }
+       RtlZeroMemory ( *value, value_len );
+       RtlCopyMemory ( *value, kvi->Data, kvi->DataLength );
+
+ err_exallocatepoolwithtag_value:
+       ExFreePool ( kvi );
+ err_fetch_reg_kvi:
+       return status;
+}
+
+/**
+ * Fetch registry multiple-string value
+ *
+ * @v reg_key          Registry key
+ * @v value_name       Registry value name
+ * @v values           Array of string values to allocate and fill in
+ * @ret ntstatus       NT status
+ *
+ * The caller must eventually free the allocated values.
+ */
+static NTSTATUS fetch_reg_multi_sz ( HANDLE reg_key, LPCWSTR value_name,
+                                    LPWSTR **values ) {
+       PKEY_VALUE_PARTIAL_INFORMATION kvi;
+       LPWSTR string;
+       ULONG num_strings;
+       ULONG values_len;
+       ULONG i;
+       NTSTATUS status;
+
+       /* Fetch key value information */
+       status = fetch_reg_kvi ( reg_key, value_name, &kvi );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_fetch_reg_kvi;
+
+       /* Count number of strings in the array.  This is a
+        * potential(ly harmless) overestimate.
+        */
+       num_strings = 0;
+       for ( string = ( ( LPWSTR ) kvi->Data ) ;
+             string < ( ( LPWSTR ) ( kvi->Data + kvi->DataLength ) ) ;
+             string++ ) {
+               if ( ! *string )
+                       num_strings++;
+       }
+       
+       /* Allocate and populate string array */
+       values_len = ( ( ( num_strings + 1 ) * sizeof ( values[0] ) ) +
+                      kvi->DataLength + sizeof ( values[0][0] ) );
+       *values = ExAllocatePoolWithTag ( NonPagedPool, values_len,
+                                         ISCSIBOOT_POOL_TAG );
+       if ( ! *values ) {
+               DbgPrint ( "Could not allocate value array for \"%S\"\n",
+                          value_name );
+               status = STATUS_UNSUCCESSFUL;
+               goto err_exallocatepoolwithtag_value;
+       }
+       RtlZeroMemory ( *values, values_len );
+       string = ( ( LPWSTR ) ( *values + num_strings + 1 ) );
+       RtlCopyMemory ( string, kvi->Data, kvi->DataLength );
+       for ( i = 0 ; i < num_strings ; i++ ) {
+               (*values)[i] = string;
+               while ( *string )
+                       string++;
+               while ( ! *string )
+                       string++;
+       }
+
+ err_exallocatepoolwithtag_value:
+       ExFreePool ( kvi );
+ err_fetch_reg_kvi:
+       return status;
+}
+
+/**
+ * Store registry string value
+ *
+ * @v reg_key          Registry key
+ * @v value_name       Registry value name
+ * @v value            String value to store
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS reg_store_sz ( HANDLE reg_key, LPCWSTR value_name,
+                              LPWSTR value ) {
+       UNICODE_STRING u_value_name;
+       SIZE_T value_len;
+       NTSTATUS status;
+
+       RtlInitUnicodeString ( &u_value_name, value_name );
+       value_len = ( ( wcslen ( value ) + 1 ) * sizeof ( value[0] ) );
+       status = ZwSetValueKey ( reg_key, &u_value_name, 0, REG_SZ,
+                                value, ( ( ULONG ) value_len ) );
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "Could not store value \"%S\": %x\n",
+                          value_name, status );
+               return status;
+       }
+
+       return STATUS_SUCCESS;
+}
+
+/**
+ * Store registry string value
+ *
+ * @v reg_key          Registry key
+ * @v value_name       Registry value name
+ * @v ...              String values to store (NULL terminated)
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS reg_store_multi_sz ( HANDLE reg_key, LPCWSTR value_name,
+                                    ... ) {
+       UNICODE_STRING u_value_name;
+       va_list args;
+       LPCWSTR string;
+       SIZE_T values_len;
+       LPWSTR values;
+       LPWSTR value;
+       SIZE_T values_remaining;
+       SIZE_T value_len;
+       NTSTATUS status;
+
+       /* Calculate total buffer length */
+       values_len = sizeof ( string[0] );
+       va_start ( args, value_name );
+       while ( ( string = va_arg ( args, LPCWSTR ) ) != NULL ) {
+               values_len += ( ( wcslen ( string ) + 1 ) *
+                               sizeof ( string[0] ) );
+       }
+       va_end ( args );
+
+       /* Allocate buffer */
+       values = ExAllocatePoolWithTag ( NonPagedPool, values_len,
+                                        ISCSIBOOT_POOL_TAG );
+       if ( ! values ) {
+               DbgPrint ( "Could not allocate value buffer for \"%S\"\n" );
+               status = STATUS_UNSUCCESSFUL;
+               goto err_exallocatepoolwithtag;
+       }
+
+       /* Copy strings into buffer */
+       RtlZeroMemory ( values, values_len );
+       value = values;
+       values_remaining = values_len;
+       va_start ( args, value_name );
+       while ( ( string = va_arg ( args, LPCWSTR ) ) != NULL ) {
+               RtlStringCbCatW ( value, values_remaining, string );
+               value_len = ( ( wcslen ( value ) + 1 ) * sizeof ( value[0] ) );
+               value += ( value_len / sizeof ( value[0] ) );
+               values_remaining -= value_len;
+       }
+       va_end ( args );
+
+       /* Store value */
+       RtlInitUnicodeString ( &u_value_name, value_name );
+       status = ZwSetValueKey ( reg_key, &u_value_name, 0, REG_MULTI_SZ,
+                                values, ( ( ULONG ) values_len ) );
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "Could not store value \"%S\": %x\n",
+                          value_name, status );
+               goto err_zwsetvaluekey;
+       }
+
+ err_zwsetvaluekey:
+       ExFreePool ( values );
+ err_exallocatepoolwithtag:
+       return STATUS_SUCCESS;
+}
+
+/**
+ * Store registry dword value
+ *
+ * @v reg_key          Registry key
+ * @v value_name       Registry value name
+ * @v value            String value to store, or NULL
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS reg_store_dword ( HANDLE reg_key, LPCWSTR value_name,
+                                 ULONG value ) {
+       UNICODE_STRING u_value_name;
+       NTSTATUS status;
+
+       RtlInitUnicodeString ( &u_value_name, value_name );
+       status = ZwSetValueKey ( reg_key, &u_value_name, 0, REG_DWORD,
+                                &value, sizeof ( value ) );
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "Could not store value \"%S\": %x\n",
+                          value_name, status );
+               return status;
+       }
+
+       return STATUS_SUCCESS;
+}
+
+/**
+ * Search for iBFT
+ *
+ * @v start            Region in which to start searching
+ * @v len              Length of region
+ * @ret ibft_copy      Copy of iBFT, or NULL
+ *
+ * The returned iBFT is allocated using ExAllocatePool().
+ */
+static NTSTATUS find_ibft ( PIBFT_TABLE *ibft_copy ) {
+       PHYSICAL_ADDRESS basemem_phy;
+       PUCHAR basemem;
+       ULONG offset;
+       PIBFT_TABLE ibft;
+       NTSTATUS status;
+
+       /* Map base memory */
+       basemem_phy.QuadPart = BASEMEM_START;
+       basemem = MmMapIoSpace ( basemem_phy, BASEMEM_LEN, MmNonCached );
+       if ( ! basemem ) {
+               DbgPrint ( "Could not map base memory\n" );
+               status = STATUS_UNSUCCESSFUL;
+               goto err_mmmapiospace;
+       }
+
+       /* Scan for iBFT */
+       status = STATUS_NO_SUCH_FILE;
+       for ( offset = 0 ; offset < BASEMEM_LEN ; offset += 16 ) {
+               ibft = ( ( PIBFT_TABLE ) ( basemem + offset ) );
+               if ( memcmp ( ibft->acpi.signature, IBFT_SIG,
+                             sizeof ( ibft->acpi.signature ) ) != 0 )
+                       continue;
+               if ( ( offset + ibft->acpi.length ) > BASEMEM_LEN )
+                       continue;
+               if ( byte_sum ( ( ( PUCHAR ) ibft ), ibft->acpi.length ) != 0 )
+                       continue;
+               DbgPrint ( "Found iBFT at %05x OEM ID \"%.6s\" OEM table ID "
+                          "\"%.8s\"\n", ( BASEMEM_START + offset ),
+                          ibft->acpi.oem_id, ibft->acpi.oem_table_id );
+               /* Create copy of iBFT */
+               *ibft_copy = ExAllocatePoolWithTag ( NonPagedPool,
+                                                    ibft->acpi.length,
+                                                    ISCSIBOOT_POOL_TAG );
+               if ( ! *ibft_copy ) {
+                       DbgPrint ( "Could not allocate iBFT copy\n" );
+                       status = STATUS_NO_MEMORY;
+                       goto err_exallocatepoolwithtag;
+               }
+               RtlCopyMemory ( *ibft_copy, ibft, ibft->acpi.length );
+               status = STATUS_SUCCESS;
+               break;
+       }
+
+ err_exallocatepoolwithtag:
+       MmUnmapIoSpace ( basemem, BASEMEM_LEN );
+ err_mmmapiospace:
+       return status;
+}
+
+/**
+ * Check to see if iBFT string exists
+ *
+ * @v string           iBFT string
+ * @ret exists         String exists
+ */
+static BOOLEAN ibft_string_exists ( PIBFT_STRING string ) {
+       return ( ( BOOLEAN ) ( string->offset != 0 ) );
+}
+
+/**
+ * Read iBFT string
+ *
+ * @v ibft             iBFT
+ * @v string           iBFT string
+ * @ret string         Standard C string
+ */
+static LPSTR ibft_string ( PIBFT_TABLE ibft, PIBFT_STRING string ) {
+       if ( string->offset ) {
+               return ( ( ( PCHAR ) ibft ) + string->offset );
+       } else {
+               return "";
+       }
+}
+
+/**
+ * Check to see if iBFT IP address exists
+ *
+ * @v ipaddr           IP address
+ * @ret exists         IP address exists
+ */
+static BOOLEAN ibft_ipaddr_exists ( PIBFT_IPADDR ipaddr ) {
+       return ( ( BOOLEAN ) ( ipaddr->in != 0 ) );
+}
+
+/**
+ * Convert iBFT IP address to string
+ *
+ * @v ipaddr           IP address
+ * @ret ipaddr         IP address string
+ *
+ * This function returns a static buffer, as per inet_ntoa().
+ */
+static LPSTR ibft_ipaddr ( PIBFT_IPADDR ipaddr ) {
+       return inet_ntoa ( ipaddr->in );
+}
+
+/**
+ * Parse iBFT initiator structure
+ *
+ * @v ibft             iBFT
+ * @v initiator                Initiator structure
+ */
+static VOID parse_ibft_initiator ( PIBFT_TABLE ibft,
+                                  PIBFT_INITIATOR initiator ) {
+       PIBFT_HEADER header = &initiator->header;
+
+       /* Dump structure information */
+       DbgPrint ( "Found iBFT Initiator %d:\n", header->index );
+       DbgPrint ( "  Flags = %#02x%s%s\n", header->flags,
+                  ( header->flags & IBFT_FL_INITIATOR_BLOCK_VALID
+                    ? ", valid" : "" ),
+                  ( header->flags & IBFT_FL_INITIATOR_FIRMWARE_BOOT_SELECTED
+                    ? ", boot selected" : "" ) );
+       if ( ! ( header->flags & IBFT_FL_INITIATOR_BLOCK_VALID ) )
+               return;
+       DbgPrint ( "  iSNS = %s\n", ibft_ipaddr ( &initiator->isns_server ) );
+       DbgPrint ( "  SLP = %s\n", ibft_ipaddr ( &initiator->slp_server ) );
+       DbgPrint ( "  Radius = %s", ibft_ipaddr ( &initiator->radius[0] ) );
+       DbgPrint ( ", %s\n", ibft_ipaddr ( &initiator->radius[1] ) );
+       DbgPrint ( "  Name = %s\n",
+                  ibft_string ( ibft, &initiator->initiator_name ) );
+}
+
+/**
+ * Fetch NIC MAC address
+ *
+ * @v name             NDIS device name
+ * @v device           NDIS device object
+ * @v file             NDIS file object
+ * @v mac              MAC address buffer
+ * @v mac_len          MAC address buffer length
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS fetch_mac ( PUNICODE_STRING name, PDEVICE_OBJECT device,
+                           PFILE_OBJECT file, PUCHAR mac, ULONG mac_len ) {
+       KEVENT event;
+       ULONG in_buf;
+       IO_STATUS_BLOCK io_status;
+       PIRP irp;
+       PIO_STACK_LOCATION io_stack;
+       ULONG i;
+       NTSTATUS status;
+
+       /* Construct IRP to query MAC address */
+       KeInitializeEvent ( &event, NotificationEvent, FALSE );
+       in_buf = OID_802_3_CURRENT_ADDRESS;
+       irp = IoBuildDeviceIoControlRequest ( IOCTL_NDIS_QUERY_GLOBAL_STATS,
+                                             device, &in_buf,
+                                             sizeof ( in_buf ), mac, mac_len,
+                                             FALSE, &event, &io_status );
+       if ( ! irp ) {
+               DbgPrint ( "Could not build IRP to retrieve MAC for \"%wZ\"\n",
+                          name );
+               return STATUS_UNSUCCESSFUL;
+       }
+       io_stack = IoGetNextIrpStackLocation( irp );
+       io_stack->FileObject = file;
+
+       /* Issue IRP */
+       status = IoCallDriver ( device, irp );
+       if ( status == STATUS_PENDING ) {
+               status = KeWaitForSingleObject ( &event, Executive, KernelMode,
+                                                FALSE, NULL );
+       }
+       if ( NT_SUCCESS ( status ) )
+               status = io_status.Status;
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "IRP failed to retrieve MAC for \"%wZ\": %x\n",
+                          name, status );
+               return status;
+       }
+
+       /* Dump MAC address */
+       DbgPrint ( "Found NIC with MAC address" );
+       for ( i = 0 ; i < mac_len ; i++ )
+               DbgPrint ( "%c%02x", ( i ? ':' : ' ' ), mac[i] );
+       DbgPrint ( " at \"%wZ\"\n", name );
+
+       return STATUS_SUCCESS;
+}
+
+/**
+ * Fetch NIC PDO
+ *
+ * @v name             NDIS device name
+ * @v device           NDIS device object
+ * @v pdo              Associated physical device object
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS fetch_pdo ( PUNICODE_STRING name, PDEVICE_OBJECT device,
+                           PDEVICE_OBJECT *pdo ) {
+       KEVENT event;
+       IO_STATUS_BLOCK io_status;
+       PIRP irp;
+       PIO_STACK_LOCATION io_stack;
+       PDEVICE_RELATIONS relations;
+       NTSTATUS status;
+
+       /* Construct IRP to query MAC address */
+       KeInitializeEvent ( &event, NotificationEvent, FALSE );
+       irp = IoBuildSynchronousFsdRequest ( IRP_MJ_PNP, device, NULL, 0, NULL,
+                                            &event, &io_status );
+       if ( ! irp ) {
+               DbgPrint ( "Could not build IRP to retrieve PDO for \"%wZ\"\n",
+                          name );
+               return STATUS_UNSUCCESSFUL;
+       }
+       io_stack = IoGetNextIrpStackLocation( irp );
+       io_stack->MinorFunction = IRP_MN_QUERY_DEVICE_RELATIONS;
+       io_stack->Parameters.QueryDeviceRelations.Type = TargetDeviceRelation;
+
+       /* Issue IRP */
+       status = IoCallDriver ( device, irp );
+       if ( status == STATUS_PENDING ) {
+               status = KeWaitForSingleObject ( &event, Executive, KernelMode,
+                                                FALSE, NULL );
+       }
+       if ( NT_SUCCESS ( status ) )
+               status = io_status.Status;
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "IRP failed to retrieve PDO for \"%wZ\": %x\n",
+                          name, status );
+               return status;
+       }
+
+       /* Extract PDO */
+       relations = ( ( PDEVICE_RELATIONS ) io_status.Information );
+       *pdo = relations->Objects[0];
+
+       /* Free the relations list allocated by the IRP */
+       ExFreePool ( relations );
+
+       return STATUS_SUCCESS;
+}
+
+/**
+ * Fetch NetCfgInstanceId registry value
+ *
+ * @v pdo              Physical device object
+ * @v netcfginstanceid Value to allocate and fill in
+ * @ret ntstatus       NT status
+ *
+ * The caller must eventually free the allocated value.
+ */
+static NTSTATUS fetch_netcfginstanceid ( PDEVICE_OBJECT pdo,
+                                        LPWSTR *netcfginstanceid ) {
+       HANDLE reg_key;
+       NTSTATUS status;
+
+       /* Open driver registry key */
+       status = IoOpenDeviceRegistryKey ( pdo, PLUGPLAY_REGKEY_DRIVER,
+                                          KEY_READ, &reg_key );
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "Could not open driver registry key for PDO %p: "
+                          "%x\n", pdo, status );
+               goto err_ioopendeviceregistrykey;
+       }
+
+       /* Read NetCfgInstanceId value */
+       status = fetch_reg_sz ( reg_key, L"NetCfgInstanceId",
+                               netcfginstanceid );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_fetch_reg_wstr;
+
+ err_fetch_reg_wstr:
+       ZwClose ( reg_key );
+ err_ioopendeviceregistrykey:
+       return status;
+}
+
+/**
+ * Store IPv4 parameter into a string registry value
+ *
+ * @v reg_key          Registry key
+ * @v value_name       Registry value name
+ * @v ipaddr           IPv4 address
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS store_ipv4_parameter_sz ( HANDLE reg_key, LPCWSTR value_name,
+                                         ULONG ipaddr ) {
+       WCHAR buf[16];
+       LPWSTR value;
+
+       if ( ipaddr ) {
+               RtlStringCbPrintfW ( buf, sizeof ( buf ),
+                                    L"%S", inet_ntoa ( ipaddr ) );
+               value = buf;
+       } else {
+               value = L"";
+       }
+
+       return reg_store_sz ( reg_key, value_name, value );
+}
+
+/**
+ * Store IPv4 parameter into a multi-string registry value
+ *
+ * @v reg_key          Registry key
+ * @v value_name       Registry value name
+ * @v ipaddr           IPv4 address
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS store_ipv4_parameter_multi_sz ( HANDLE reg_key,
+                                               LPCWSTR value_name,
+                                               ULONG ipaddr ) {
+       WCHAR buf[16];
+       LPWSTR value;
+
+       if ( ipaddr ) {
+               RtlStringCbPrintfW ( buf, sizeof ( buf ),
+                                    L"%S", inet_ntoa ( ipaddr ) );
+               value = buf;
+       } else {
+               value = NULL;
+       }
+
+       return reg_store_multi_sz ( reg_key, value_name, value, NULL );
+}
+
+/**
+ * Store TCP/IP parameters in registry
+ *
+ * @v nic              iBFT NIC structure
+ * @v netcfginstanceid Interface name within registry
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS store_tcpip_parameters ( PIBFT_NIC nic,
+                                        LPCWSTR netcfginstanceid ) {
+       LPCWSTR key_name_prefix = ( L"\\Registry\\Machine\\SYSTEM\\"
+                                   L"CurrentControlSet\\Services\\"
+                                   L"Tcpip\\Parameters\\Interfaces\\" );
+       LPWSTR key_name;
+       SIZE_T key_name_len;
+       HANDLE reg_key;
+       ULONG subnet_mask;
+       NTSTATUS status;
+
+       /* Allocate key name */
+       key_name_len = ( ( wcslen ( key_name_prefix ) +
+                          wcslen ( netcfginstanceid ) + 1 ) *
+                        sizeof ( key_name[0] ) );
+       key_name = ExAllocatePoolWithTag ( NonPagedPool, key_name_len,
+                                          ISCSIBOOT_POOL_TAG );
+       if ( ! key_name ) {
+               DbgPrint ( "Could not allocate TCP/IP key name\n" );
+               status = STATUS_UNSUCCESSFUL;
+               goto err_exallocatepoolwithtag;
+       }
+
+       /* Construct key name */
+       RtlStringCbCopyW ( key_name, key_name_len, key_name_prefix );
+       RtlStringCbCatW ( key_name, key_name_len, netcfginstanceid );
+
+       /* Open key */
+       status = reg_open ( key_name, &reg_key );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_reg_open;
+
+       /* Store IP address */
+       status = store_ipv4_parameter_multi_sz ( reg_key, L"IPAddress",
+                                                nic->ip_address.in );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_reg_store;
+
+       /* Store subnet mask */
+       subnet_mask = ( 0xffffffffUL >> ( 32 - nic->subnet_mask_prefix ) );
+       status = store_ipv4_parameter_multi_sz ( reg_key, L"SubnetMask",
+                                                subnet_mask );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_reg_store;
+
+       /* Store default gateway */
+       status = store_ipv4_parameter_multi_sz ( reg_key, L"DefaultGateway",
+                                                nic->gateway.in );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_reg_store;
+
+       /* Store DNS servers */
+       status = store_ipv4_parameter_sz ( reg_key, L"NameServer",
+                                          nic->dns[0].in );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_reg_store;
+
+       /* Disable DHCP */
+       status = reg_store_dword ( reg_key, L"EnableDHCP", 0 );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_reg_store;
+
+ err_reg_store:
+       reg_close ( reg_key );
+ err_reg_open:
+       ExFreePool ( key_name );
+ err_exallocatepoolwithtag:
+       return status;
+}
+
+/**
+ * Try to configure NIC from iBFT NIC structure
+ *
+ * @v nic              iBFT NIC structure
+ * @v name             NDIS device name
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS try_configure_nic ( PIBFT_NIC nic, PUNICODE_STRING name ) {
+       BOOLEAN must_disable;
+       PFILE_OBJECT file;
+       PDEVICE_OBJECT device;
+       UCHAR mac[6];
+       PDEVICE_OBJECT pdo;
+       LPWSTR netcfginstanceid;
+       NTSTATUS status;
+
+       /* Enable interface if not already done */
+       status = IoSetDeviceInterfaceState ( name, TRUE );
+       must_disable = ( NT_SUCCESS ( status ) ? TRUE : FALSE );
+
+       /* Get device and file object pointers */
+       status = IoGetDeviceObjectPointer ( name, FILE_ALL_ACCESS, &file,
+                                           &device );
+       if ( ! NT_SUCCESS ( status ) ) {
+               /* Not an error, apparently; IoGetDeviceInterfaces()
+                * seems to return a whole load of interfaces that
+                * aren't attached to any objects.
+                */
+               goto err_iogetdeviceobjectpointer;
+       }
+
+       /* See if NIC matches */
+       status = fetch_mac ( name, device, file, mac, sizeof ( mac ) );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_fetch_mac;
+       if ( memcmp ( nic->mac_address, mac, sizeof ( mac ) ) != 0 )
+               goto err_compare_mac;
+       DbgPrint ( "iBFT NIC %d is interface \"%wZ\"\n",
+                  nic->header.index, name );
+
+       /* Get matching PDO */
+       status = fetch_pdo ( name, device, &pdo );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_fetch_pdo;
+       DbgPrint ( "iBFT NIC %d is PDO %p\n", nic->header.index, pdo );
+
+       /* Get NetCfgInstanceId */
+       status = fetch_netcfginstanceid ( pdo, &netcfginstanceid );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_fetch_netcfginstanceid;
+       DbgPrint ( "iBFT NIC %d is NetCfgInstanceId \"%S\"\n",
+                  nic->header.index, netcfginstanceid );
+
+       /* Store registry values */
+       status = store_tcpip_parameters ( nic, netcfginstanceid );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_store_tcpip_parameters;
+
+ err_store_tcpip_parameters:
+       ExFreePool ( netcfginstanceid );
+ err_fetch_netcfginstanceid:
+ err_fetch_pdo:
+ err_compare_mac:
+ err_fetch_mac:
+       /* Drop object reference */
+       ObDereferenceObject ( file );
+ err_iogetdeviceobjectpointer:
+       /* Disable interface if we had to enable it */
+       if ( must_disable )
+               IoSetDeviceInterfaceState ( name, FALSE );
+       return status;
+}
+
+/**
+ * Parse iBFT NIC structure
+ *
+ * @v ibft             iBFT
+ * @v nic              NIC structure
+ */
+static VOID parse_ibft_nic ( PIBFT_TABLE ibft, PIBFT_NIC nic ) {
+       PIBFT_HEADER header = &nic->header;
+       PWSTR symlinks;
+       PWSTR symlink;
+       UNICODE_STRING u_symlink;
+       NTSTATUS status;
+
+       /* Dump structure information */
+       DbgPrint ( "Found iBFT NIC %d:\n", header->index );
+       DbgPrint ( "  Flags = %#02x%s%s\n", header->flags,
+                  ( header->flags & IBFT_FL_NIC_BLOCK_VALID
+                    ? ", valid" : "" ),
+                  ( header->flags & IBFT_FL_NIC_FIRMWARE_BOOT_SELECTED
+                    ? ", boot selected" : "" ),
+                  ( header->flags & IBFT_FL_NIC_GLOBAL
+                    ? ", global address" : ", link local address" ) );
+       if ( ! ( header->flags & IBFT_FL_NIC_BLOCK_VALID ) )
+               return;
+       DbgPrint ( "  IP = %s/%d\n", ibft_ipaddr ( &nic->ip_address ),
+                  nic->subnet_mask_prefix );
+       DbgPrint ( "  Origin = %d\n", nic->origin );
+       DbgPrint ( "  Gateway = %s\n", ibft_ipaddr ( &nic->gateway ) );
+       DbgPrint ( "  DNS = %s", ibft_ipaddr ( &nic->dns[0] ) );
+       DbgPrint ( ", %s\n", ibft_ipaddr ( &nic->dns[1] ) );
+       DbgPrint ( "  DHCP = %s\n", ibft_ipaddr ( &nic->dhcp ) );
+       DbgPrint ( "  VLAN = %04x\n", nic->vlan );
+       DbgPrint ( "  MAC = %02x:%02x:%02x:%02x:%02x:%02x\n",
+                  nic->mac_address[0], nic->mac_address[1],
+                  nic->mac_address[2], nic->mac_address[3],
+                  nic->mac_address[4], nic->mac_address[5] );
+       DbgPrint ( "  PCI = %02x:%02x.%x\n",
+                  ( ( nic->pci_bus_dev_func >> 8 ) & 0xff ),
+                  ( ( nic->pci_bus_dev_func >> 3 ) & 0x1f ),
+                  ( ( nic->pci_bus_dev_func >> 0 ) & 0x07 ) );
+       DbgPrint ( "  Hostname = %s\n", ibft_string ( ibft, &nic->hostname ) );
+
+       /* Get list of all objects providing GUID_NDIS_LAN_CLASS interface */
+       status = IoGetDeviceInterfaces ( &GUID_NDIS_LAN_CLASS, NULL,
+                                        DEVICE_INTERFACE_INCLUDE_NONACTIVE,
+                                        &symlinks );
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "Could not fetch NIC list: %x\n", status );
+               return;
+       }
+
+       /* Configure any matching NICs */
+       for ( symlink = symlinks ;
+             RtlInitUnicodeString ( &u_symlink, symlink ) , *symlink ;
+             symlink += ( ( u_symlink.Length / sizeof ( *symlink ) ) + 1 ) ) {
+               try_configure_nic ( nic, &u_symlink );
+       }
+
+       /* Free object list */
+       ExFreePool ( symlinks );
+}
+
+/**
+ * Parse iBFT target structure
+ *
+ * @v ibft             iBFT
+ * @v target           Target structure
+ */
+static VOID parse_ibft_target ( PIBFT_TABLE ibft, PIBFT_TARGET target ) {
+       PIBFT_HEADER header = &target->header;
+
+       /* Dump structure information */
+       DbgPrint ( "Found iBFT target %d:\n", header->index );
+       DbgPrint ( "  Flags = %#02x%s%s\n", header->flags,
+                  ( header->flags & IBFT_FL_TARGET_BLOCK_VALID
+                    ? ", valid" : "" ),
+                  ( header->flags & IBFT_FL_TARGET_FIRMWARE_BOOT_SELECTED
+                    ? ", boot selected" : "" ),
+                  ( header->flags & IBFT_FL_TARGET_USE_CHAP
+                    ? ", Radius CHAP" : "" ),
+                  ( header->flags & IBFT_FL_TARGET_USE_RCHAP
+                    ? ", Radius rCHAP" : "" ) );
+       if ( ! ( header->flags & IBFT_FL_TARGET_BLOCK_VALID ) )
+               return;
+       DbgPrint ( "  IP = %s\n",
+                  ibft_ipaddr ( &target->ip_address ) );
+       DbgPrint ( "  Port = %d\n", target->socket );
+       DbgPrint ( "  LUN = %04x-%04x-%04x-%04x\n",
+                  ( ( target->boot_lun >> 48 ) & 0xffff ),
+                  ( ( target->boot_lun >> 32 ) & 0xffff ),
+                  ( ( target->boot_lun >> 16 ) & 0xffff ),
+                  ( ( target->boot_lun >> 0  ) & 0xffff ) );
+       DbgPrint ( "  CHAP type = %d (%s)\n", target->chap_type,
+                  ( ( target->chap_type == IBFT_CHAP_NONE ) ? "None" :
+                    ( ( target->chap_type == IBFT_CHAP_ONE_WAY ) ? "One-way" :
+                      ( ( target->chap_type == IBFT_CHAP_MUTUAL ) ? "Mutual" :
+                        "Unknown" ) ) ) );
+       DbgPrint ( "  NIC = %d\n", target->nic_association );
+       DbgPrint ( "  Name = %s\n",
+                  ibft_string ( ibft, &target->target_name ) );
+       DbgPrint ( "  CHAP name = %s\n",
+                  ibft_string ( ibft, &target->chap_name ) );
+       DbgPrint ( "  CHAP secret = %s\n",
+                  ( ibft_string_exists ( &target->chap_secret ) ?
+                    "<omitted>" : "" ) );
+       DbgPrint ( "  Reverse CHAP name = %s\n",
+                  ibft_string ( ibft, &target->reverse_chap_name ) );
+       DbgPrint ( "  Reverse CHAP secret = %s\n",
+                  ( ibft_string_exists ( &target->reverse_chap_secret ) ?
+                    "<omitted>" : "" ) );
+}
+
+/**
+ * Parse iBFT
+ *
+ * @v ibft             iBFT
+ */
+static VOID parse_ibft ( PIBFT_TABLE ibft ) {
+       PIBFT_CONTROL control = &ibft->control;
+       PUSHORT offset;
+       PIBFT_HEADER header;
+
+       /* Scan through all entries in the Control structure */
+       for ( offset = &control->extensions ;
+             ( ( PUCHAR ) offset ) <
+                     ( ( ( PUCHAR ) control ) + control->header.length ) ;
+             offset++ ) {
+               if ( ! *offset )
+                       continue;
+               header = ( ( PIBFT_HEADER ) ( ( ( PUCHAR ) ibft ) + *offset ));
+               switch ( header->structure_id ) {
+               case IBFT_STRUCTURE_ID_INITIATOR :
+                       parse_ibft_initiator ( ibft,
+                                              ( ( PIBFT_INITIATOR ) header ));
+                       break;
+               case IBFT_STRUCTURE_ID_NIC :
+                       parse_ibft_nic ( ibft, ( ( PIBFT_NIC ) header ) );
+                       break;
+               case IBFT_STRUCTURE_ID_TARGET :
+                       parse_ibft_target ( ibft,
+                                           ( ( PIBFT_TARGET ) header ) );
+                       break;
+               default :
+                       DbgPrint ( "Ignoring unknown iBFT structure ID %d "
+                                  "index %d\n", header->structure_id,
+                                  header->index );
+                       break;
+               }
+       }
+}
+
+/**
+ * Dummy IRP handler
+ *
+ * @v device           Device object
+ * @v irp              IRP
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS iscsiboot_dummy_irp ( PDEVICE_OBJECT device, PIRP irp ) {
+
+       irp->IoStatus.Status = STATUS_SUCCESS;
+       irp->IoStatus.Information = 0;
+       IoCompleteRequest ( irp, IO_NO_INCREMENT );
+
+       ( VOID ) device;
+       return STATUS_SUCCESS;
+}
+
+/**
+ * Retrieve iSCSI boot parameters
+ *
+ * @v device           
+
+/**
+ * IoControl IRP handler
+ *
+ * @v device           Device object
+ * @v irp              IRP
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS iscsiboot_iocontrol_irp ( PDEVICE_OBJECT device, PIRP irp ) {
+       PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation ( irp );
+       PISCSIBOOT_PRIV priv = device->DeviceExtension;
+       ULONG len;
+       NTSTATUS status;
+
+       switch ( irpsp->Parameters.DeviceIoControl.IoControlCode ) {
+       case IOCTL_ISCSIBOOT:
+               DbgPrint ( "iSCSI boot parameters requested\n" );
+               len = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
+               if ( len > priv->ibft->acpi.length )
+                       len = priv->ibft->acpi.length;
+               RtlCopyMemory ( irp->AssociatedIrp.SystemBuffer,
+                               priv->ibft, len );
+               status = STATUS_SUCCESS;
+               break;
+       default:
+               DbgPrint ( "Unrecognised IoControl %x\n",
+                          irpsp->Parameters.DeviceIoControl.IoControlCode );
+               status = STATUS_INVALID_DEVICE_REQUEST;
+               break;
+       }
+
+       irp->IoStatus.Status = status;
+       irp->IoStatus.Information = 0;
+       IoCompleteRequest ( irp, IO_NO_INCREMENT );
+       return status;
+}
+
+/**
+ * Create device object and symlink
+ *
+ * @v driver           Driver object
+ * @ret ntstatus       NT status
+ */
+static NTSTATUS create_iscsiboot_device ( PDRIVER_OBJECT driver,
+                                         PIBFT_TABLE ibft ) {
+       UNICODE_STRING u_device_name;
+       UNICODE_STRING u_device_symlink;
+       PISCSIBOOT_PRIV priv;
+       PDEVICE_OBJECT device;
+       NTSTATUS status;
+
+       /* Create device */
+       RtlInitUnicodeString ( &u_device_name, iscsiboot_device_name );
+       status = IoCreateDeviceSecure ( driver, sizeof ( *priv ),
+                                       &u_device_name, FILE_DEVICE_UNKNOWN,
+                                       FILE_DEVICE_SECURE_OPEN, FALSE,
+                                       &SDDL_DEVOBJ_SYS_ALL_ADM_ALL,
+                                       &GUID_ISCSIBOOT_CLASS, &device );
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "Could not create device \"%S\": %x\n",
+                          iscsiboot_device_name, status );
+               return status;
+       }
+       priv = device->DeviceExtension;
+       priv->ibft = ibft;
+       device->Flags &= ~DO_DEVICE_INITIALIZING;
+
+       /* Create device symlink */
+       RtlInitUnicodeString ( &u_device_symlink, iscsiboot_device_symlink );
+       status = IoCreateSymbolicLink ( &u_device_symlink, &u_device_name );
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "Could not create device symlink \"%S\": %x\n",
+                          iscsiboot_device_symlink, status );
+               return status;
+       }
+
+       return STATUS_SUCCESS;
+}
+
+/**
+ * Driver entry point
+ *
+ * @v DriverObject     Driver object
+ * @v RegistryPath     Driver-specific registry path
+ * @ret ntstatus       NT status
+ */
+NTSTATUS DriverEntry ( IN PDRIVER_OBJECT DriverObject,
+                      IN PUNICODE_STRING RegistryPath ) {
+       PIBFT_TABLE ibft;
+       NTSTATUS status;
+
+       DbgPrint ( "iSCSI Boot Parameter Driver initialising\n" );
+
+       /* Scan for iBFT */
+       status = find_ibft ( &ibft );
+       if ( ! NT_SUCCESS ( status ) ) {
+               DbgPrint ( "No iBFT found\n" );
+               /* Lack of an iBFT is not necessarily an error */
+               status = STATUS_SUCCESS;
+               goto err_no_ibft;
+       }
+
+       /* Parse iBFT */
+       parse_ibft ( ibft );
+
+       /* Hook in driver methods */
+       DriverObject->MajorFunction[IRP_MJ_CREATE] = iscsiboot_dummy_irp;
+       DriverObject->MajorFunction[IRP_MJ_CLOSE] = iscsiboot_dummy_irp;
+       DriverObject->MajorFunction[IRP_MJ_CLEANUP] = iscsiboot_dummy_irp;
+       DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
+               iscsiboot_iocontrol_irp;
+
+       /* Create device object */
+       status = create_iscsiboot_device ( DriverObject, ibft );
+       if ( ! NT_SUCCESS ( status ) )
+               goto err_create_iscsiboot_device;
+
+       DbgPrint ( "iSCSI Boot Parameter Driver initialisation complete\n" );
+
+ err_create_iscsiboot_device:
+ err_no_ibft:
+       ( VOID ) RegistryPath;
+       return status;
+}
diff --git a/driver/makefile b/driver/makefile
new file mode 100644 (file)
index 0000000..5acbbd2
--- /dev/null
@@ -0,0 +1 @@
+!INCLUDE $(NTMAKEENV)\makefile.def
diff --git a/driver/sources b/driver/sources
new file mode 100644 (file)
index 0000000..bec6af3
--- /dev/null
@@ -0,0 +1,11 @@
+TARGETNAME = iscsiboot
+
+TARGETTYPE = DRIVER
+
+TARGETPATH = ..\bin
+
+TARGETLIBS = $(DDK_LIB_PATH)\ndis.lib $(DDK_LIB_PATH)\ntstrsafe.lib $(DDK_LIB_PATH)\wdmsec.lib
+
+MSC_WARNING_LEVEL = /W4 /Wp64 /WX
+
+SOURCES = iscsiboot.c
diff --git a/installer/.gitignore b/installer/.gitignore
new file mode 100644 (file)
index 0000000..7ca21f7
--- /dev/null
@@ -0,0 +1,2 @@
+build*
+obj*
diff --git a/installer/registry.c b/installer/registry.c
new file mode 100644 (file)
index 0000000..17e591d
--- /dev/null
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <windows.h>
+#include "registry.h"
+
+#define eprintf(...) fprintf ( stderr, __VA_ARGS__ )
+#define array_size(x) ( sizeof ( (x) ) / sizeof ( (x)[0] ) )
+
+#pragma warning(disable: 4702) /* Unreachable code */
+
+/*****************************************************************************
+ *
+ * Generic routines for registry access
+ *
+ *****************************************************************************
+ */
+
+/**
+ * Open registry key
+ *
+ * @v key              Registry key
+ * @v subkey_name      Registry subkey name, or NULL
+ * @v subkey           Opened key
+ * @ret err            Error status
+ */
+LONG reg_open ( HKEY key, LPCWSTR subkey_name, PHKEY subkey ) {
+       LONG err;
+
+       err = RegOpenKeyExW ( key, subkey_name, 0, ( KEY_READ | KEY_WRITE ),
+                             subkey );
+       if ( err != ERROR_SUCCESS ) {
+               eprintf ( "Could not open \"%S\": %x\n", subkey_name, err );
+               return err;
+       }
+
+       return ERROR_SUCCESS;
+}
+
+/**
+ * Close registry key
+ *
+ * @v key              Registry key
+ */
+VOID reg_close ( HKEY key ) {
+       RegCloseKey ( key );
+}
+
+/**
+ * Check existence of registry key
+ *
+ * @v key              Registry key
+ * @v subkey_name      Registry subkey name, or NULL
+ * @ret err            Error status
+ */
+LONG reg_key_exists ( HKEY key, LPCWSTR subkey_name ) {
+       HKEY subkey;
+       LONG err;
+
+       err = reg_open ( key, subkey_name, &subkey );
+       if ( err != ERROR_SUCCESS )
+               return err;
+
+       reg_close ( subkey );
+       return ERROR_SUCCESS;
+}
+
+/**
+ * Read raw registry value
+ *
+ * @v key              Registry key
+ * @v subkey_name      Registry subkey name, or NULL
+ * @v value_name       Registry value name
+ * @v buffer           Buffer to allocate and fill in
+ * @v len              Length of buffer to fill in
+ * @ret err            Error status
+ *
+ * The caller must free() the returned value.
+ */
+LONG reg_query_value ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name,
+                      LPBYTE *buffer, LPDWORD len ) {
+       HKEY subkey;
+       LONG err;
+
+       /* Open subkey */
+       err = reg_open ( key, subkey_name, &subkey );
+       if ( err != ERROR_SUCCESS )
+               goto err_open_subkey;
+
+       /* Determine length */
+       err = RegQueryValueExW ( subkey, value_name, NULL, NULL, NULL, len );
+       if ( err != ERROR_SUCCESS ) {
+               if ( err != ERROR_FILE_NOT_FOUND ) {
+                       eprintf ( "Could not determine length of \"%S\": %x\n",
+                                 value_name, err );
+               }
+               goto err_get_length;
+       }
+
+       /* Allocate buffer for string + extra NUL (for safety) */
+       *buffer = malloc ( *len );
+       if ( ! *buffer ) {
+               eprintf ( "Could not allocate buffer for \"%S\": %x\n",
+                         value_name, err );
+               err = ERROR_NOT_ENOUGH_MEMORY;
+               goto err_malloc;
+       }
+       memset ( *buffer, 0, *len );
+
+       /* Read data */
+       err = RegQueryValueExW ( subkey, value_name, NULL, NULL,
+                                *buffer, len );
+       if ( err != ERROR_SUCCESS ) {
+               eprintf ( "Could not read data for \"%S\": %x\n",
+                         value_name, err );
+               goto err_read_data;
+       }
+
+       reg_close ( subkey );
+       return ERROR_SUCCESS;
+
+ err_read_data:
+       free ( *buffer );
+       *buffer = NULL;
+ err_malloc:
+ err_get_length:
+       reg_close ( subkey );
+ err_open_subkey:
+       return err;
+}
+
+/**
+ * Write raw registry value
+ *
+ * @v key              Registry key
+ * @v subkey_name      Registry subkey name, or NULL
+ * @v value_name       Registry value name
+ * @v data             Data to store
+ * @v len              Length of data
+ * @ret err            Error status
+ */
+LONG reg_set_value ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name,
+                    DWORD type, LPBYTE data, DWORD len ) {
+       HKEY subkey;
+       LONG err;
+
+       /* Open subkey */
+       err = reg_open ( key, subkey_name, &subkey );
+       if ( err != ERROR_SUCCESS )
+               goto err_open_subkey;
+
+       /* Store value */
+       err = RegSetValueExW ( subkey, value_name, 0, type, data, len );
+       if ( err != ERROR_SUCCESS ) {
+               eprintf ( "Could not write data for \"%S\": %x\n",
+                         value_name, err );
+               goto err_write_data;
+       }
+
+       /* Success */
+       err = 0;
+
+ err_write_data:
+       reg_close ( subkey );
+ err_open_subkey:
+       return err;
+}
+
+/**
+ * Check existence of registry value
+ *
+ * @v key              Registry key
+ * @v subkey_name      Registry subkey name, or NULL
+ * @v value_name       Registry value name
+ * @ret err            Error status
+ */
+LONG reg_value_exists ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name ) {
+       LPBYTE buffer;
+       DWORD len;
+       LONG err;
+
+       err = reg_query_value ( key, subkey_name, value_name, &buffer, &len );
+       if ( err != ERROR_SUCCESS )
+               return err;
+       free ( buffer );
+       return ERROR_SUCCESS;
+}
+
+/**
+ * Read REG_SZ value
+ *
+ * @v key              Registry key
+ * @v subkey_name      Registry subkey name, or NULL
+ * @v value_name       Registry value name
+ * @v sz               String to allocate and fill in
+ * @ret err            Error status
+ *
+ * The caller must free() the returned value "sz".
+ */
+LONG reg_query_sz ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name,
+                   LPWSTR *sz ) {
+       LPBYTE buffer;
+       DWORD len;
+       DWORD sz_len;
+       LONG err;
+
+       /* Read raw data */
+       err = reg_query_value ( key, subkey_name, value_name, &buffer, &len );
+       if ( err != ERROR_SUCCESS )
+               goto err_query_raw;
+
+       /* Allocate buffer for string + extra NUL (for safety) */
+       sz_len = ( len + sizeof ( sz[0] ) );
+       *sz = malloc ( sz_len );
+       if ( ! *sz ) {
+               eprintf ( "Could not allocate string for \"%S\": %x\n",
+                         value_name, err );
+               err = ERROR_NOT_ENOUGH_MEMORY;
+               goto err_malloc;
+       }
+       memset ( *sz, 0, sz_len );
+
+       /* Copy string data */
+       memcpy ( *sz, buffer, len );
+
+       /* Success */
+       free ( buffer );
+       return ERROR_SUCCESS;
+
+       free ( *sz );
+       *sz = NULL;
+ err_malloc:
+       free ( buffer );
+ err_query_raw:
+       return err;
+}
+
+/**
+ * Read REG_MULTI_SZ value
+ *
+ * @v key              Registry key
+ * @v subkey_name      Registry subkey name, or NULL
+ * @v value_name       Registry value name
+ * @v multi_sz         String array to allocate and fill in
+ * @ret err            Error status
+ *
+ * The caller must free() the returned value "multi_sz".
+ */
+LONG reg_query_multi_sz ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name,
+                         LPWSTR **multi_sz ) {
+       LPBYTE buffer;
+       DWORD len;
+       DWORD num_szs;
+       LPWSTR sz;
+       DWORD multi_sz_len;
+       DWORD i;
+       LONG err;
+
+       /* Read raw data */
+       err = reg_query_value ( key, subkey_name, value_name, &buffer, &len );
+       if ( err != ERROR_SUCCESS )
+               goto err_query_raw;
+
+       /* Count number of strings in the array.  This is a
+        * potential(ly harmless) overestimate.
+        */
+       num_szs = 0;
+       for ( sz = ( ( LPWSTR ) buffer ) ;
+             sz < ( ( LPWSTR ) ( buffer + len ) ) ; sz++ ) {
+               if ( ! *sz )
+                       num_szs++;
+       }
+       
+       /* Allocate and populate string array */
+       multi_sz_len = ( ( ( num_szs + 1 ) * sizeof ( multi_sz[0] ) ) +
+                        len + sizeof ( multi_sz[0][0] ) );
+       *multi_sz = malloc ( multi_sz_len );
+       if ( ! *multi_sz ) {
+               eprintf ( "Could not allocate string array for \"%S\"\n",
+                          value_name );
+               err = ERROR_NOT_ENOUGH_MEMORY;
+               goto err_malloc;
+       }
+       memset ( *multi_sz, 0, multi_sz_len );
+       sz = ( ( LPWSTR ) ( *multi_sz + num_szs + 1 ) );
+       memcpy ( sz, buffer, len );
+       for ( i = 0 ; i < num_szs ; i++ ) {
+               if ( ! *sz )
+                       break;
+               (*multi_sz)[i] = sz;
+               while ( *sz )
+                       sz++;
+               sz++;
+       }
+
+       /* Success */
+       free ( buffer );
+       return ERROR_SUCCESS;
+
+       free ( *multi_sz );
+       *multi_sz = NULL;
+ err_malloc:
+       free ( buffer );
+ err_query_raw:
+       return err;
+}
+
+/**
+ * Write REG_MULTI_SZ value
+ *
+ * @v key              Registry key
+ * @v subkey_name      Registry subkey name, or NULL
+ * @v value_name       Registry value name
+ * @v multi_sz         String array to allocate and fill in
+ * @ret err            Error status
+ *
+ * The caller must free() the returned value "multi_sz".
+ */
+LONG reg_set_multi_sz ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name,
+                       LPWSTR *multi_sz ) {
+       LPWSTR *sz;
+       SIZE_T sz_len;
+       SIZE_T len;
+       LPBYTE buffer;
+       SIZE_T used;
+       LONG err;
+
+       /* Calculate total length and allocate block */
+       len = sizeof ( sz[0][0] ); /* List-terminating NUL */
+       for ( sz = multi_sz ; *sz ; sz++ ) {
+               sz_len = ( ( wcslen ( *sz ) + 1 ) * sizeof ( sz[0][0] ) );
+               len += sz_len;
+       }
+       buffer = malloc ( len );
+       if ( ! buffer ) {
+               eprintf ( "Could not allocate string array for \"%S\"\n",
+                         value_name );
+               err = ERROR_NOT_ENOUGH_MEMORY;
+               goto err_malloc;
+       }
+
+       /* Populate block */
+       memset ( buffer, 0, len );
+       used = 0;
+       for ( sz = multi_sz ; *sz ; sz++ ) {
+               sz_len = ( ( wcslen ( *sz ) + 1 ) * sizeof ( sz[0][0] ) );
+               memcpy ( ( buffer + used ), *sz, sz_len );
+               used += sz_len;
+       }
+
+       /* Write block to registry */
+       err = reg_set_value ( key, subkey_name, value_name, REG_MULTI_SZ,
+                             buffer, ( ( DWORD ) len ) );
+       if ( err != ERROR_SUCCESS )
+               goto err_set_value;
+
+       /* Success */
+       err = ERROR_SUCCESS;
+
+ err_set_value:
+       free ( buffer );
+ err_malloc:
+       return err;
+}
+
+/**
+ * Read REG_DWORD value
+ *
+ * @v key              Registry key
+ * @v subkey_name      Registry subkey name, or NULL
+ * @v value_name       Registry value name
+ * @v dword            Dword to fill in
+ * @ret err            Error status
+ */
+LONG reg_query_dword ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name,
+                      PDWORD dword ) {
+       LPBYTE buffer;
+       DWORD len;
+       LONG err;
+
+       /* Read raw data */
+       err = reg_query_value ( key, subkey_name, value_name, &buffer, &len );
+       if ( err != ERROR_SUCCESS )
+               goto err_query_raw;
+
+       /* Sanity check */
+       if ( len != sizeof ( *dword ) ) {
+               eprintf ( "Bad size for dword \"%S\"\n", value_name );
+               goto err_bad_size;
+       }
+
+       /* Copy dword data */
+       memcpy ( &dword, buffer, sizeof ( dword ) );
+
+       /* Success */
+       err = ERROR_SUCCESS;
+
+ err_bad_size:
+       free ( buffer );
+ err_query_raw:
+       return err;
+}
+
+/**
+ * Write REG_DWORD value
+ *
+ * @v key              Registry key
+ * @v subkey_name      Registry subkey name, or NULL
+ * @v value_name       Registry value name
+ * @v dword            Dword
+ * @ret err            Error status
+ */
+LONG reg_set_dword ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name,
+                    DWORD dword ) {
+       return reg_set_value ( key, subkey_name, value_name, REG_DWORD,
+                              ( ( LPBYTE ) &dword ), sizeof ( dword ) );
+}
diff --git a/installer/registry.h b/installer/registry.h
new file mode 100644 (file)
index 0000000..130cbe0
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef REGISTRY_H
+#define REGSITRY_H
+
+#include <windows.h>
+
+extern LONG reg_open ( HKEY key, LPCWSTR subkey_name, PHKEY subkey );
+extern VOID reg_close ( HKEY key );
+extern LONG reg_key_exists ( HKEY key, LPCWSTR subkey_name );
+extern LONG reg_query_value ( HKEY key, LPCWSTR subkey_name,
+                             LPCWSTR value_name, LPBYTE *buffer,
+                             LPDWORD len );
+extern LONG reg_set_value ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name,
+                           DWORD type, LPBYTE data, DWORD len );
+extern LONG reg_value_exists ( HKEY key, LPCWSTR subkey_name,
+                              LPCWSTR value_name );
+extern LONG reg_query_sz ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name,
+                          LPWSTR *sz );
+extern LONG reg_query_multi_sz ( HKEY key, LPCWSTR subkey_name,
+                                LPCWSTR value_name, LPWSTR **multi_sz );
+extern LONG reg_set_multi_sz ( HKEY key, LPCWSTR subkey_name,
+                              LPCWSTR value_name, LPWSTR *multi_sz );
+extern LONG reg_query_dword ( HKEY key, LPCWSTR subkey_name,
+                             LPCWSTR value_name, PDWORD dword );
+extern LONG reg_set_dword ( HKEY key, LPCWSTR subkey_name, LPCWSTR value_name,
+                           DWORD dword );
+
+#endif /* REGISTRY_H */
diff --git a/installer/setup.c b/installer/setup.c
new file mode 100644 (file)
index 0000000..c61c6f8
--- /dev/null
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <windows.h>
+#include <initguid.h>
+#include <devguid.h>
+#include <setupapi.h>
+#include <cfgmgr32.h>
+#include "setupdi.h"
+#include "registry.h"
+
+#define eprintf(...) fprintf ( stderr, __VA_ARGS__ )
+#define array_size(x) ( sizeof ( (x) ) / sizeof ( (x)[0] ) )
+
+#pragma warning(disable: 4702) /* Unreachable code */
+
+/**
+ * Find service group
+ *
+ * @v sgo              Service group order array
+ * @v group_name       Service group name
+ * @ret entry          Entry within service group order array, or NULL
+ */
+static LPWSTR * find_service_group ( LPWSTR *sgo, LPCWSTR group_name ) {
+       LPWSTR *tmp;
+
+       for ( tmp = sgo ; *tmp ; tmp++ ) {
+               if ( _wcsicmp ( *tmp, group_name ) == 0 )
+                       return tmp;
+       }
+       eprintf ( "Cannot find service group \"%S\"\n", group_name );
+       return NULL;
+}
+
+/**
+ * Move service group
+ *
+ * @v sgo              Service group order array
+ * @v group_name       Service group name to move
+ * @v before_name      Service group name to move before
+ * @ret err            Error status
+ */
+static LONG move_service_group ( LPWSTR *sgo, LPCWSTR group_name,
+                                LPCWSTR before_name ) {
+       LPWSTR *group;
+       LPWSTR *before;
+       LPWSTR tmp;
+
+       group = find_service_group ( sgo, group_name );
+       if ( ! group )
+               return ERROR_FILE_NOT_FOUND;
+       before = find_service_group ( sgo, before_name );
+       if ( ! before )
+               return ERROR_FILE_NOT_FOUND;
+
+       while ( group > before ) {
+               tmp = *group;
+               *group = *( group - 1 );
+               group--;
+               *group = tmp;
+       }
+
+       return ERROR_SUCCESS;
+}
+
+/**
+ * Fix service group order
+ *
+ * @ret err            Error status
+ */
+static LONG fix_service_group_order ( void ) {
+       WCHAR sgo_key_name[] =
+               L"SYSTEM\\CurrentControlSet\\Control\\ServiceGroupOrder";
+       WCHAR sgo_value_name[] = L"List";
+       WCHAR sgo_backup_value_name[] = L"List.pre-iscsiboot";
+       LPWSTR *sgo;
+       LONG err;
+
+       /* Read existing service group order */
+       err = reg_query_multi_sz ( HKEY_LOCAL_MACHINE, sgo_key_name,
+                                  sgo_value_name, &sgo );
+       if ( err != ERROR_SUCCESS ) {
+               eprintf ( "Cannot read service group order: %x\n", err );
+               goto err_query_sgo;
+       }
+
+       /* Back up key (if no backup already exists) */
+       err = reg_value_exists ( HKEY_LOCAL_MACHINE, sgo_key_name,
+                                sgo_backup_value_name );
+       if ( err == ERROR_FILE_NOT_FOUND ) {
+               err = reg_set_multi_sz ( HKEY_LOCAL_MACHINE, sgo_key_name,
+                                        sgo_backup_value_name, sgo );
+               if ( err != ERROR_SUCCESS ) {
+                       eprintf ( "Could not back up service group order: "
+                                 "%x\n", err );
+                       goto err_set_sgo_backup;
+               }
+       }
+       if ( err != ERROR_SUCCESS ) {
+               eprintf ( "Cannot check service group order backup: %x\n",
+                         err );
+               goto err_exists_sgo_backup;
+       }
+
+       /* Move service groups to required places */
+       err = move_service_group ( sgo, L"PNP_TDI", L"SCSI miniport" );
+       if ( err != ERROR_SUCCESS )
+               return err;
+       err = move_service_group ( sgo, L"Base", L"PNP_TDI" );
+       if ( err != ERROR_SUCCESS )
+               return err;
+       err = move_service_group ( sgo, L"NDIS", L"Base" );
+       if ( err != ERROR_SUCCESS )
+               return err;
+       err = move_service_group ( sgo, L"NDIS Wrapper", L"NDIS" );
+       if ( err != ERROR_SUCCESS )
+               return err;
+
+       /* Write out modified service group order */
+       err = reg_set_multi_sz ( HKEY_LOCAL_MACHINE, sgo_key_name,
+                                sgo_value_name, sgo );
+       if ( err != ERROR_SUCCESS ) {
+               eprintf ( "Could not update service group order: %x\n", err );
+               goto err_set_sgo;
+       }
+
+       /* Success */
+       err = ERROR_SUCCESS;
+
+ err_set_sgo:
+ err_exists_sgo_backup:
+ err_set_sgo_backup:
+       free ( sgo );
+ err_query_sgo:
+       return err;
+}
+
+/**
+ * Check to see if a service exists
+ *
+ * @v service_name     Service name
+ * @ret err            Error status
+ */
+static LONG service_exists ( LPWSTR service_name ) {
+       WCHAR services_key_name[] =
+               L"SYSTEM\\CurrentControlSet\\Services";
+       HKEY services;
+       LONG err;
+
+       /* Open Services key */
+       err = reg_open ( HKEY_LOCAL_MACHINE, services_key_name, &services );
+       if ( err != ERROR_SUCCESS )
+               goto err_reg_open;
+
+       /* Check service key existence */
+       err = reg_key_exists ( services, service_name );
+
+       reg_close ( services );
+ err_reg_open:
+       return err;
+}
+
+/**
+ * Set service to start at boot time
+ *
+ * @v service_name     Service name
+ * @ret err            Error status
+ */
+static LONG set_boot_start ( LPWSTR service_name ) {
+       WCHAR services_key_name[] =
+               L"SYSTEM\\CurrentControlSet\\Services";
+       HKEY services;
+       LPWSTR *depend_on;
+       LPWSTR *tmp;
+       LONG err;
+
+       printf ( "Marking \"%S\" service as boot-start\n", service_name );
+
+       /* Open Services key */
+       err = reg_open ( HKEY_LOCAL_MACHINE, services_key_name, &services );
+       if ( err != ERROR_SUCCESS )
+               goto err_reg_open;
+
+       /* Look up service dependencies, if any */
+       err = reg_query_multi_sz ( services, service_name, L"DependOnService",
+                                  &depend_on );
+       if ( err == ERROR_SUCCESS ) {
+               /* Recurse into services that we depend on */
+               for ( tmp = depend_on ; *tmp ; tmp++ ) {
+                       printf ( "...\"%S\" depends on \"%S\"\n",
+                                service_name, *tmp );
+                       err = set_boot_start ( *tmp );
+                       if ( err != ERROR_SUCCESS ) {
+                               free ( depend_on );
+                               return err;
+                       }
+               }
+               free ( depend_on );
+       }
+
+       /* Set Start=0 for the service in question */
+       err = reg_set_dword ( services, service_name, L"Start", 0 );
+       if ( err != ERROR_SUCCESS ) {
+               eprintf ( "Could not mark \"%S\" service as boot-start: %x\n",
+                         service_name, err );
+               goto err_set_dword;
+       }
+
+       /* Success */
+       err = ERROR_SUCCESS;
+
+ err_set_dword:
+ err_reg_open:
+       reg_close ( services );
+       return err;
+}
+
+/**
+ * Set all NIC services to start at boot time
+ *
+ * @ret err            Error status
+ */
+static LONG set_boot_start_nics ( void ) {
+       HDEVINFO dev_info_list;
+       DWORD dev_index;
+       SP_DEVINFO_DATA dev_info;
+       WCHAR name[256];
+       WCHAR service[256];
+       ULONG status;
+       ULONG problem;
+       LONG err;
+
+       /* Get NIC list */
+       dev_info_list = SetupDiGetClassDevs ( &GUID_DEVCLASS_NET, NULL,
+                                             NULL, DIGCF_PRESENT );
+       if ( dev_info_list == INVALID_HANDLE_VALUE ) {
+               err = GetLastError();
+               eprintf ( "Could not get NIC list: %x\n", err );
+               goto err_setupdigetclassdevs;
+       }
+
+       /* Enumerate NICs */
+       for ( dev_index = 0 ; ; dev_index++ ) {
+               dev_info.cbSize = sizeof ( dev_info );
+               if ( ! SetupDiEnumDeviceInfo ( dev_info_list, dev_index,
+                                              &dev_info ) ) {
+                       err = GetLastError();
+                       if ( err == ERROR_NO_MORE_ITEMS )
+                               break;
+                       eprintf ( "Could not enumerate devices: %x\n", err );
+                       goto err_setupdienumdeviceinfo;
+               }
+
+               /* Fetch a name for the device, if available */
+               if ( ! ( SetupDiGetDeviceRegistryPropertyW( dev_info_list,
+                                                           &dev_info,
+                                                           SPDRP_FRIENDLYNAME,
+                                                           NULL,
+                                                           ( (PBYTE) name ),
+                                                           sizeof ( name ),
+                                                           NULL ) ||
+                        SetupDiGetDeviceRegistryPropertyW( dev_info_list,
+                                                           &dev_info,
+                                                           SPDRP_DEVICEDESC,
+                                                           NULL,
+                                                           ( (PBYTE) name ),
+                                                           sizeof ( name ),
+                                                           NULL ) ) ) {
+                       err = GetLastError();
+                       eprintf ( "Could not get device name: %x\n", err );
+                       goto err_setupdigetdeviceregistryproperty;
+               }
+
+               /* Fetch service name */
+               if ( ! SetupDiGetDeviceRegistryPropertyW ( dev_info_list,
+                                                          &dev_info,
+                                                          SPDRP_SERVICE,
+                                                          NULL,
+                                                          ( (PBYTE) service ),
+                                                          sizeof ( service ),
+                                                          NULL ) ) {
+                       err = GetLastError();
+                       eprintf ( "Could not get service name: %x\n", err );
+                       goto err_setupdigetdeviceregistryproperty;
+               }
+
+               /* See if this is a hidden device */
+               err = CM_Get_DevNode_Status ( &status, &problem,
+                                             dev_info.DevInst, 0 );
+               if ( err != CR_SUCCESS ) {
+                       eprintf ( "Could not get device status: %x\n", err );
+                       goto err_cm_get_devnode_status;
+               }
+
+               /* Skip if this is a hidden device.  This is something
+                * of a heuristic.  It's a fairly safe bet that
+                * anything showing up as non-hidden is a real NIC;
+                * the remainder tend to be "WAN Miniport" devices et
+                * al., which will crash if set as boot-start.
+                */
+               if ( status & DN_NO_SHOW_IN_DM )
+                       continue;
+
+               printf ( "Found NIC \"%S\"\n", name );
+
+               /* Mark NIC service as boot-start */
+               err = set_boot_start ( service );
+               if ( err != ERROR_SUCCESS )
+                       goto err_set_boot_start;
+       }
+
+       /* Success */
+       err = 0;
+
+ err_set_boot_start:
+ err_cm_get_devnode_status:
+ err_setupdigetdeviceregistryproperty:
+ err_setupdienumdeviceinfo:
+       SetupDiDestroyDeviceInfoList ( dev_info_list );
+ err_setupdigetclassdevs:
+       return err;
+}
+
+/**
+ * Fix up various registry settings
+ *
+ * @ret err            Error status
+ */
+static LONG fix_registry ( void ) {
+       WCHAR ddms_key_name[] =
+               L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";
+       WCHAR dpe_key_name[] =
+               L"SYSTEM\\CurrentControlSet\\Control\\Session Manager"
+               L"\\Memory Management";
+       LONG err;
+
+       /* Disable DHCP media sensing */
+       err = reg_set_dword ( HKEY_LOCAL_MACHINE, ddms_key_name,
+                             L"DisableDhcpMediaSense", 1 );
+       if ( err != ERROR_SUCCESS ) {
+               eprintf ( "Could not disable DHCP media sensing: %x\n", err );
+               return err;
+       }
+
+       /* Disable kernel paging */
+       err = reg_set_dword ( HKEY_LOCAL_MACHINE, dpe_key_name,
+                             L"DisablePagingExecutive", 1 );
+       if ( err != ERROR_SUCCESS ) {
+               eprintf ( "Could not disable kernel paging: %x\n", err );
+               return err;
+       }
+
+       /* Update ServiceGroupOrder */
+       if ( ( err = fix_service_group_order() ) != ERROR_SUCCESS )
+               return err;
+
+       /* Set relevant services to be boot-start */
+       if ( ( err = set_boot_start ( L"NDIS" ) ) != ERROR_SUCCESS )
+               return err;
+       if ( ( err = set_boot_start ( L"Tcpip" ) ) != ERROR_SUCCESS )
+               return err;
+       if ( ( err = set_boot_start ( L"iScsiPrt" ) ) != ERROR_SUCCESS )
+               return err;
+       /* PSched service is not present on Win2k3 */
+       if ( ( ( err = service_exists ( L"PSched" ) ) == ERROR_SUCCESS ) &&
+            ( ( err = set_boot_start ( L"PSched" ) ) != ERROR_SUCCESS ) )
+               return err;
+       if ( ( err = set_boot_start_nics() ) != ERROR_SUCCESS )
+               return err;
+
+       return ERROR_SUCCESS;
+}
+
+/**
+ * Main entry point
+ *
+ * @v argc             Number of arguments
+ * @v argv             Argument list
+ * @ret exit           Exit status
+ */
+int __cdecl main ( int argc, char **argv ) {
+       CHAR bin_path[MAX_PATH];
+       CHAR inf_rel_path[MAX_PATH];
+       CHAR inf_path[MAX_PATH];
+       CHAR *file_part;
+       WCHAR inf_path_w[MAX_PATH];
+       WCHAR hw_id[] = L"ROOT\\iscsiboot";
+       DWORD len;
+       CHAR key;
+
+       printf ( "iSCSI Boot Driver Installation\n" );
+       printf ( "==============================\n\n" );
+
+       /* Check for iSCSI initiator existence */
+       if ( service_exists ( L"iScsiPrt" ) != 0 ) {
+               eprintf ( "\nYou must install the Microsoft iSCSI initiator "
+                         "before installing this\nsoftware!\n" );
+               goto fail;
+       }
+
+       /* Fix up registry before installing driver */
+       if ( fix_registry() != 0 )
+               goto fail;
+
+       /* Locate .inf file */
+       len = GetFullPathName ( argv[0], array_size ( bin_path ), bin_path,
+                               &file_part );
+       if ( ( len == 0 ) || ( len >= array_size ( bin_path ) ) )
+               goto fail;
+       if ( file_part )
+               *file_part = 0;
+       _snprintf ( inf_rel_path, sizeof ( inf_rel_path ),
+                   "%s\\..\\iscsiboot.inf", bin_path );
+       len = GetFullPathName ( inf_rel_path, array_size ( inf_path ),
+                               inf_path, NULL );
+       if ( ( len == 0 ) || ( len >= array_size ( inf_path ) ) )
+               goto fail;
+       printf ( "Installing from \"%s\"\n", inf_path );
+
+       /* Install/update driver */
+       _snwprintf ( inf_path_w, array_size ( inf_path_w ), L"%S", inf_path );
+       if ( install_or_update_driver ( inf_path_w, hw_id ) != 0 )
+               goto fail;
+
+       /* Success */
+       printf ( "iSCSI Boot Driver installed successfully\n" );
+
+       ( VOID ) argc;
+       exit ( EXIT_SUCCESS );
+
+ fail:
+       eprintf ( "\n*** INSTALLATION FAILED ***\n" );
+       eprintf ( "Press Enter to continue\n" );
+       scanf ( "%c", &key );
+       exit ( EXIT_FAILURE );
+}
diff --git a/installer/setupdi.c b/installer/setupdi.c
new file mode 100644 (file)
index 0000000..6cfb498
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <windows.h>
+#include <setupapi.h>
+#include <cfgmgr32.h>
+#include <objbase.h>
+#include <newdev.h>
+#include "setupdi.h"
+
+#define eprintf(...) fprintf ( stderr, __VA_ARGS__ )
+#define array_size(x) ( sizeof ( (x) ) / sizeof ( (x)[0] ) )
+
+/*****************************************************************************
+ *
+ * Generic routines for installing non-PnP drivers
+ *
+ *****************************************************************************
+ */
+
+/**
+ * Count device nodes
+ *
+ * @v inf_path         Path to .INF file
+ * @v hw_id            Hardware ID
+ * @v count            Number of device nodes
+ * @ret rc             Return status code
+ */
+static int count_device_nodes ( LPWSTR inf_path, LPWSTR hw_id, DWORD *count ) {
+       GUID class_guid;
+       WCHAR class_name[MAX_CLASS_NAME_LEN];
+       HDEVINFO dev_info_list;
+       DWORD dev_index;
+       SP_DEVINFO_DATA dev_info;
+       WCHAR hw_ids[256];
+       DWORD hw_ids_len;
+       DWORD rc;
+
+       /* Initialise count */
+       *count = 0;
+
+       /* Get class GUID and name */
+       if ( ! SetupDiGetINFClassW ( inf_path, &class_guid, class_name,
+                                    array_size ( class_name ), NULL ) ) {
+               rc = GetLastError();
+               eprintf ( "Could not fetch class GUID from %S: %x\n",
+                         inf_path, rc );
+               goto err_setupdigetinfclass;
+       }
+
+       /* Get device list */
+       dev_info_list = SetupDiGetClassDevsW ( &class_guid, NULL, NULL, 0 );
+       if ( dev_info_list == INVALID_HANDLE_VALUE ) {
+               rc = GetLastError();
+               eprintf ( "Could not get device list: %x\n", rc );
+               goto err_setupdigetclassdevs;
+       }
+
+       /* Enumerate devices */
+       for ( dev_index = 0 ; ; dev_index++ ) {
+               dev_info.cbSize = sizeof ( dev_info );
+               if ( ! SetupDiEnumDeviceInfo ( dev_info_list, dev_index,
+                                              &dev_info ) ) {
+                       rc = GetLastError();
+                       if ( rc == ERROR_NO_MORE_ITEMS )
+                               break;
+                       eprintf ( "Could not enumerate devices: %x\n", rc );
+                       goto err_setupdienumdeviceinfo;
+               }
+
+               /* Fetch hardare IDs */
+               if ( ! SetupDiGetDeviceRegistryPropertyW ( dev_info_list,
+                                                          &dev_info,
+                                                          SPDRP_HARDWAREID,
+                                                          NULL,
+                                                          ( (PBYTE) hw_ids ),
+                                                          sizeof ( hw_ids ),
+                                                          &hw_ids_len ) ){
+                       rc = GetLastError();
+                       eprintf ( "Could not get hardware ID: %x\n", rc );
+                       goto err_setupdigetdeviceregistryproperty;
+               }
+
+               /* Compare hardware IDs */
+               if ( _wcsicmp ( hw_id, hw_ids ) == 0 ) {
+                       /* Increment count of matching devices */
+                       (*count)++;
+               }
+       }
+
+       /* Success */
+       rc = 0;
+
+ err_setupdigetdeviceregistryproperty:
+ err_setupdienumdeviceinfo:
+       SetupDiDestroyDeviceInfoList ( dev_info_list );
+ err_setupdigetclassdevs:
+ err_setupdigetinfclass:
+       return rc;
+}
+
+/**
+ * Install a device node
+ *
+ * @v inf_path         Path to .INF file
+ * @v hw_id            Hardware ID
+ * @ret rc             Return status code
+ */
+static int install_device_node ( LPWSTR inf_path, LPWSTR hw_id ) {
+       GUID class_guid;
+       WCHAR class_name[MAX_CLASS_NAME_LEN];
+       HDEVINFO dev_info_list;
+       SP_DEVINFO_DATA dev_info;
+       WCHAR dev_instance[256];
+       WCHAR hw_ids[256];
+       DWORD hw_ids_len;
+       DWORD rc;
+
+       printf ( "Installing device node for hardware ID \"%S\"\n", hw_id );
+
+       /* Get class GUID and name */
+       if ( ! SetupDiGetINFClassW ( inf_path, &class_guid, class_name,
+                                    array_size ( class_name ), NULL ) ) {
+               rc = GetLastError();
+               eprintf ( "Could not fetch class GUID from %S: %x\n",
+                         inf_path, rc );
+               goto err_setupdigetinfclass;
+       }
+
+       /* Create empty device information list */
+       dev_info_list = SetupDiCreateDeviceInfoList ( &class_guid, 0 );
+       if ( dev_info_list == INVALID_HANDLE_VALUE ) {
+               rc = GetLastError();
+               eprintf ( "Could not create device info list: %x\n", rc );
+               goto err_setupdicreatedeviceinfolist;
+       }
+
+       /* Create device information element */
+       dev_info.cbSize = sizeof ( dev_info );
+       if ( ! SetupDiCreateDeviceInfoW ( dev_info_list, class_name,
+                                         &class_guid, NULL, 0,
+                                         DICD_GENERATE_ID, &dev_info ) ) {
+               rc = GetLastError();
+               eprintf ( "Could not create device info: %x\n", rc );
+               goto err_setupdicreatedeviceinfo;
+       }
+
+       /* Fetch device instance ID */
+       if ( ! SetupDiGetDeviceInstanceIdW ( dev_info_list, &dev_info,
+                                            dev_instance,
+                                            array_size ( dev_instance ),
+                                            NULL ) ) {
+               rc = GetLastError();
+               eprintf ( "Could not get device instance ID: %x\n", rc );
+               goto err_setupdigetdeviceinstanceid;
+       }
+       printf ( "Device instance ID is \"%S\"\n", dev_instance );
+
+       /* Add the hardware ID */
+       hw_ids_len = _snwprintf ( hw_ids, array_size ( hw_ids ),
+                                 L"%s%c", hw_id, 0 );
+       if ( ! SetupDiSetDeviceRegistryPropertyW ( dev_info_list, &dev_info,
+                                                  SPDRP_HARDWAREID,
+                                                  ( ( LPBYTE ) hw_ids ),
+                                                  ( ( hw_ids_len + 1 ) *
+                                                    sizeof ( hw_ids[0] ) ) )){
+               rc = GetLastError();
+               eprintf ( "Could not set hardware ID: %x\n", rc );
+               goto err_setupdisetdeviceregistryproperty;
+       }
+
+       /* Install the device node */
+       if ( ! SetupDiCallClassInstaller ( DIF_REGISTERDEVICE, dev_info_list,
+                                          &dev_info ) ) {
+               rc = GetLastError();
+               eprintf ( "Could not install device node: %x\n", rc );
+               goto err_setupdicallclassinstaller;
+       }
+
+       /* Success */
+       rc = 0;
+
+ err_setupdicallclassinstaller:
+ err_setupdisetdeviceregistryproperty:
+ err_setupdigetdeviceinstanceid:
+ err_setupdicreatedeviceinfo:
+       SetupDiDestroyDeviceInfoList ( dev_info_list );
+ err_setupdicreatedeviceinfolist:
+ err_setupdigetinfclass:
+       return rc;
+}
+
+/**
+ * Update driver
+ *
+ * @v inf_path         Path to .INF file
+ * @v hw_id            Hardware ID
+ * @ret rc             Return status code
+ */
+static int update_driver ( LPWSTR inf_path, LPWSTR hw_id ) {
+       BOOL reboot_required;
+       int rc;
+
+       printf ( "Updating driver for hardware ID \"%S\"\n", hw_id );
+
+       /* Update driver */
+       if ( ! UpdateDriverForPlugAndPlayDevicesW ( NULL, hw_id, inf_path,
+                                                   0, &reboot_required ) ) {
+               rc = GetLastError();
+               eprintf ( "Could not update driver: %x\n", rc );
+               goto err_updatedriverforplugandplaydevices;
+       }
+
+       /* Success */
+       rc = 0;
+
+ err_updatedriverforplugandplaydevices:
+       return rc;
+}
+
+/**
+ * Install (or update) driver
+ *
+ * @v inf_path         Path to .INF file
+ * @v hw_id            Hardware ID
+ * @ret rc             Return status code
+ */
+int install_or_update_driver ( LPWSTR inf_path, LPWSTR hw_id ) {
+       DWORD existing_devices;
+       int rc;
+
+       /* See if device node exists */
+       if ( ( rc = count_device_nodes ( inf_path, hw_id,
+                                        &existing_devices ) ) != 0 )
+               return rc;
+
+       /* Install device node (if necessary) */
+       if ( ( existing_devices == 0 ) &&
+            ( ( rc = install_device_node ( inf_path, hw_id ) ) != 0 ) )
+               return rc;
+            
+       /* Update driver */
+       if ( ( rc = update_driver ( inf_path, hw_id ) ) != 0 )
+               return rc;
+
+       return 0;
+}
diff --git a/installer/setupdi.h b/installer/setupdi.h
new file mode 100644 (file)
index 0000000..04a8cd5
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef SETUPDI_H
+#define SETUPDI_H
+
+#include <windows.h>
+
+extern int install_or_update_driver ( LPWSTR inf_path, LPWSTR hw_id );
+
+#endif /* SETUPDI_H */
diff --git a/installer/sources b/installer/sources
new file mode 100644 (file)
index 0000000..ca5eee4
--- /dev/null
@@ -0,0 +1,19 @@
+TARGETNAME = setup
+
+TARGETTYPE = PROGRAM
+
+TARGETPATH = ..\bin
+
+TARGETLIBS = $(DDK_LIB_PATH)\setupapi.lib \
+            $(DDK_LIB_PATH)\newdev.lib
+
+USE_MSVCRT = 1
+
+MSC_WARNING_LEVEL = /W4 /Wp64 /WX
+
+UMTYPE = console
+
+UMENTRY = tmain
+
+SOURCES = setup.c setupdi.c registry.c
+