summaryrefslogtreecommitdiffstats
path: root/src/drivers/bus
diff options
context:
space:
mode:
authorSimon Rettberg2026-01-28 12:53:53 +0100
committerSimon Rettberg2026-01-28 12:53:53 +0100
commit8e82785c584dc13e20f9229decb95bd17bbe9cd1 (patch)
treea8b359e59196be5b2e3862bed189107f4bc9975f /src/drivers/bus
parentMerge branch 'master' into openslx (diff)
parent[prefix] Make unlzma.S compatible with 386 class CPUs (diff)
downloadipxe-openslx.tar.gz
ipxe-openslx.tar.xz
ipxe-openslx.zip
Merge branch 'master' into openslxopenslx
Diffstat (limited to 'src/drivers/bus')
-rw-r--r--src/drivers/bus/cdc.c1
-rw-r--r--src/drivers/bus/devtree.c357
-rw-r--r--src/drivers/bus/ecam.c40
-rw-r--r--src/drivers/bus/pci.c97
-rw-r--r--src/drivers/bus/pci_settings.c3
-rw-r--r--src/drivers/bus/pcibackup.c1
-rw-r--r--src/drivers/bus/pcibridge.c28
-rw-r--r--src/drivers/bus/pcicloud.c269
-rw-r--r--src/drivers/bus/pciextra.c37
-rw-r--r--src/drivers/bus/pcimsix.c53
-rw-r--r--src/drivers/bus/usb.c84
-rw-r--r--src/drivers/bus/usb_settings.c179
12 files changed, 1078 insertions, 71 deletions
diff --git a/src/drivers/bus/cdc.c b/src/drivers/bus/cdc.c
index 373a03072..c3a2a450b 100644
--- a/src/drivers/bus/cdc.c
+++ b/src/drivers/bus/cdc.c
@@ -22,6 +22,7 @@
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+FILE_SECBOOT ( PERMITTED );
#include <stddef.h>
#include <ipxe/usb.h>
diff --git a/src/drivers/bus/devtree.c b/src/drivers/bus/devtree.c
new file mode 100644
index 000000000..f71ad1ea4
--- /dev/null
+++ b/src/drivers/bus/devtree.c
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2025 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+/** @file
+ *
+ * Devicetree bus
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ipxe/device.h>
+#include <ipxe/fdt.h>
+#include <ipxe/iomap.h>
+#include <ipxe/devtree.h>
+
+static struct dt_driver dt_node_driver __dt_driver;
+
+/**
+ * Map devicetree range
+ *
+ * @v dt Devicetree device
+ * @v offset Starting node offset
+ * @v index Region index
+ * @v len Length to map, or 0 to map whole region
+ * @ret io_addr I/O address, or NULL on error
+ */
+void * dt_ioremap ( struct dt_device *dt, unsigned int offset,
+ unsigned int index, size_t len ) {
+ struct fdt_reg_cells regs;
+ uint64_t address;
+ uint64_t size;
+ void *io_addr;
+ int rc;
+
+ /* Get parent region cell size specification */
+ if ( ( rc = fdt_parent_reg_cells ( &sysfdt, offset, &regs ) ) != 0 ) {
+ DBGC ( dt, "DT %s could not get region cell sizes: %s\n",
+ dt->name, strerror ( rc ) );
+ return NULL;
+ }
+
+ /* Read address */
+ if ( ( rc = fdt_reg_address ( &sysfdt, offset, &regs, index,
+ &address ) ) != 0 ) {
+ DBGC ( dt, "DT %s could not read region %d address: %s\n",
+ dt->name, index, strerror ( rc ) );
+ return NULL;
+ }
+
+ /* Read size (or assume sufficient, if tree specifies no sizes) */
+ size = len;
+ if ( regs.size_cells &&
+ ( ( rc = fdt_reg_size ( &sysfdt, offset, &regs, index,
+ &size ) ) != 0 ) ) {
+ DBGC ( dt, "DT %s could not read region %d size: %s\n",
+ dt->name, index, strerror ( rc ) );
+ return NULL;
+ }
+
+ /* Use region size as length if not specified */
+ if ( ! len )
+ len = size;
+ DBGC ( dt, "DT %s region %d at %#08llx+%#04llx\n",
+ dt->name, index, ( ( unsigned long long ) address ),
+ ( ( unsigned long long ) size ) );
+
+ /* Verify size */
+ if ( len > size ) {
+ DBGC ( dt, "DT %s region %d is too small (%#llx/%#zx bytes)\n",
+ dt->name, index, ( ( unsigned long long ) size ), len );
+ return NULL;
+ }
+
+ /* Map region */
+ io_addr = ioremap ( address, len );
+ if ( ! io_addr ) {
+ DBGC ( dt, "DT %s could not map region %d\n",
+ dt->name, index );
+ return NULL;
+ }
+
+ return io_addr;
+}
+
+/**
+ * Find devicetree driver
+ *
+ * @v dt Devicetree device
+ * @v offset Starting node offset
+ * @ret driver Driver
+ */
+static struct dt_driver * dt_find_driver ( struct dt_device *dt,
+ unsigned int offset ) {
+ struct dt_driver *driver;
+ const char *ids;
+ const char *id;
+ unsigned int count;
+ unsigned int i;
+
+ /* Read compatible programming model identifiers */
+ ids = fdt_strings ( &sysfdt, offset, "compatible", &count );
+
+ /* Look for a compatible driver */
+ for ( id = ids ; count-- ; id += ( strlen ( id ) + 1 ) ) {
+ DBGC2 ( &sysfdt, "DT %s is compatible with %s\n",
+ dt->name, id );
+ for_each_table_entry ( driver, DT_DRIVERS ) {
+ for ( i = 0 ; i < driver->id_count ; i++ ) {
+ if ( strcmp ( id, driver->ids[i] ) == 0 ) {
+ DBGC ( dt, "DT %s has %s driver %s\n",
+ dt->name, id, driver->name );
+ return driver;
+ }
+ }
+ }
+ }
+
+ /* Use generic node driver if no other driver matches */
+ return &dt_node_driver;
+}
+
+/**
+ * Probe devicetree device
+ *
+ * @v dt Devicetree device
+ * @v offset Starting node offset
+ * @ret rc Return status code
+ */
+static int dt_probe ( struct dt_device *dt, unsigned int offset ) {
+ struct dt_driver *driver;
+ int rc;
+
+ /* Identify driver */
+ driver = dt_find_driver ( dt, offset );
+ dt->driver = driver;
+ dt->dev.driver_name = driver->name;
+
+ /* Probe device */
+ if ( ( rc = driver->probe ( dt, offset ) ) != 0 ) {
+ if ( driver != &dt_node_driver ) {
+ DBGC ( dt, "DT %s could not probe: %s\n",
+ dt->name, strerror ( rc ) );
+ }
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Remove devicetree device
+ *
+ * @v dt Devicetree device
+ */
+static void dt_remove ( struct dt_device *dt ) {
+ struct dt_driver *driver = dt->driver;
+
+ /* Remove device */
+ driver->remove ( dt );
+ if ( driver != &dt_node_driver )
+ DBGC ( dt, "DT %s removed\n", dt->name );
+}
+
+/**
+ * Probe devicetree node
+ *
+ * @v parent Parent generic device
+ * @v offset Starting node offset
+ * @ret rc Return status code
+ */
+int dt_probe_node ( struct device *parent, unsigned int offset ) {
+ struct fdt_descriptor desc;
+ struct dt_device *dt;
+ const char *name;
+ int rc;
+
+ /* Describe token */
+ if ( ( rc = fdt_describe ( &sysfdt, offset, &desc ) ) != 0 )
+ goto err_describe;
+
+ /* Allocate and initialise device */
+ dt = zalloc ( sizeof ( *dt ) );
+ if ( ! dt ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ name = ( offset ? desc.name : "root node" );
+ dt->name = dt->dev.name;
+ snprintf ( dt->dev.name, sizeof ( dt->dev.name ), "%s", name );
+ dt->dev.desc.bus_type = BUS_TYPE_DT;
+ dt->dev.desc.location = fdt_phandle ( &sysfdt, offset );
+ dt->dev.parent = parent;
+ INIT_LIST_HEAD ( &dt->dev.children );
+ list_add_tail ( &dt->dev.siblings, &parent->children );
+
+ /* Probe device */
+ if ( ( rc = dt_probe ( dt, offset ) ) != 0 )
+ goto err_probe;
+
+ return 0;
+
+ dt_remove ( dt );
+ err_probe:
+ list_del ( &dt->dev.siblings );
+ free ( dt );
+ err_alloc:
+ err_describe:
+ return rc;
+}
+
+/**
+ * Remove devicetree node
+ *
+ * @v parent Parent generic device
+ */
+void dt_remove_node ( struct device *parent ) {
+ struct dt_device *dt;
+
+ /* Identify most recently added child */
+ dt = list_last_entry ( &parent->children, struct dt_device,
+ dev.siblings );
+ assert ( dt != NULL );
+
+ /* Remove driver */
+ dt_remove ( dt );
+
+ /* Delete and free device */
+ list_del ( &dt->dev.siblings );
+ free ( dt );
+}
+
+/**
+ * Probe devicetree children
+ *
+ * @v parent Parent device
+ * @v offset Starting node offset
+ * @ret rc Return status code
+ */
+int dt_probe_children ( struct dt_device *parent, unsigned int offset ) {
+ struct fdt_descriptor desc;
+ int depth;
+ int rc;
+
+ /* Probe any immediate child nodes */
+ for ( depth = -1 ; ; depth += desc.depth, offset = desc.next ) {
+
+ /* Describe token */
+ if ( ( rc = fdt_describe ( &sysfdt, offset, &desc ) ) != 0 ) {
+ DBGC ( &sysfdt, "DT %s has malformed node: %s\n",
+ parent->name, strerror ( rc ) );
+ goto err_describe;
+ }
+
+ /* Terminate when we exit this node */
+ if ( ( depth == 0 ) && ( desc.depth < 0 ) )
+ break;
+
+ /* Probe child node, if applicable */
+ if ( ( depth == 0 ) && desc.name && ( ! desc.data ) ) {
+ DBGC2 ( &sysfdt, "DT %s is child of %s\n",
+ desc.name, parent->name );
+ dt_probe_node ( &parent->dev, desc.offset );
+ }
+ }
+
+ /* Fail if we have no children (so that this device will be freed) */
+ if ( list_empty ( &parent->dev.children ) ) {
+ rc = -ENODEV;
+ goto err_no_children;
+ }
+
+ return 0;
+
+ err_no_children:
+ err_describe:
+ dt_remove_children ( parent );
+ return rc;
+}
+
+/**
+ * Remove devicetree children
+ *
+ * @v parent Parent device
+ */
+void dt_remove_children ( struct dt_device *parent ) {
+
+ /* Remove all child nodes */
+ while ( ! list_empty ( &parent->dev.children ) )
+ dt_remove_node ( &parent->dev );
+}
+
+/** Generic node driver */
+static struct dt_driver dt_node_driver __dt_driver = {
+ .name = "node",
+ .probe = dt_probe_children,
+ .remove = dt_remove_children,
+};
+
+/**
+ * Probe devicetree bus
+ *
+ * @v rootdev Devicetree root device
+ * @ret rc Return status code
+ */
+static int dt_probe_all ( struct root_device *rootdev ) {
+
+ /* Probe root node */
+ return dt_probe_node ( &rootdev->dev, 0 );
+}
+
+/**
+ * Remove devicetree bus
+ *
+ * @v rootdev Devicetree root device
+ */
+static void dt_remove_all ( struct root_device *rootdev ) {
+
+ /* Remove root node */
+ dt_remove_node ( &rootdev->dev );
+}
+
+/** Devicetree bus root device driver */
+static struct root_driver dt_root_driver = {
+ .probe = dt_probe_all,
+ .remove = dt_remove_all,
+};
+
+/** Devicetree bus root device */
+struct root_device dt_root_device __root_device = {
+ .dev = { .name = "DT" },
+ .driver = &dt_root_driver,
+};
diff --git a/src/drivers/bus/ecam.c b/src/drivers/bus/ecam.c
index 5e3debddd..602f7bd8a 100644
--- a/src/drivers/bus/ecam.c
+++ b/src/drivers/bus/ecam.c
@@ -23,8 +23,8 @@
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+#include <string.h>
#include <errno.h>
-#include <ipxe/uaccess.h>
#include <ipxe/ecam.h>
/** @file
@@ -46,49 +46,43 @@ static struct ecam_mapping ecam;
*/
static int ecam_find ( uint32_t busdevfn, struct pci_range *range,
struct ecam_allocation *alloc ) {
- struct ecam_allocation tmp;
+ struct ecam_table *mcfg;
+ struct ecam_allocation *tmp;
unsigned int best = 0;
- unsigned int offset;
unsigned int count;
unsigned int index;
- userptr_t mcfg;
- uint32_t length;
+ unsigned int i;
uint32_t start;
/* Return empty range on error */
range->count = 0;
/* Locate MCFG table */
- mcfg = acpi_table ( ECAM_SIGNATURE, 0 );
+ mcfg = container_of ( acpi_table ( ECAM_SIGNATURE, 0 ),
+ struct ecam_table, acpi );
if ( ! mcfg ) {
DBGC ( &ecam, "ECAM found no MCFG table\n" );
return -ENOTSUP;
}
- /* Get length of table */
- copy_from_user ( &length, mcfg,
- offsetof ( struct ecam_table, acpi.length ),
- sizeof ( length ) );
-
/* Iterate over allocations */
- for ( offset = offsetof ( struct ecam_table, alloc ) ;
- ( offset + sizeof ( tmp ) ) <= le32_to_cpu ( length ) ;
- offset += sizeof ( tmp ) ) {
+ for ( i = 0 ; ( offsetof ( typeof ( *mcfg ), alloc[ i + 1 ] ) <=
+ le32_to_cpu ( mcfg->acpi.length ) ) ; i++ ) {
/* Read allocation */
- copy_from_user ( &tmp, mcfg, offset, sizeof ( tmp ) );
+ tmp = &mcfg->alloc[i];
DBGC2 ( &ecam, "ECAM %04x:[%02x-%02x] has base %08llx\n",
- le16_to_cpu ( tmp.segment ), tmp.start, tmp.end,
- ( ( unsigned long long ) le64_to_cpu ( tmp.base ) ) );
- start = PCI_BUSDEVFN ( le16_to_cpu ( tmp.segment ),
- tmp.start, 0, 0 );
- count = PCI_BUSDEVFN ( 0, ( tmp.end - tmp.start + 1 ), 0, 0 );
+ le16_to_cpu ( tmp->segment ), tmp->start, tmp->end,
+ ( ( unsigned long long ) le64_to_cpu ( tmp->base ) ) );
+ start = PCI_BUSDEVFN ( le16_to_cpu ( tmp->segment ),
+ tmp->start, 0, 0 );
+ count = PCI_BUSDEVFN ( 0, ( tmp->end - tmp->start + 1 ), 0, 0 );
/* Check for a matching or new closest allocation */
index = ( busdevfn - start );
if ( ( index < count ) || ( index > best ) ) {
if ( alloc )
- memcpy ( alloc, &tmp, sizeof ( *alloc ) );
+ memcpy ( alloc, tmp, sizeof ( *alloc ) );
range->start = start;
range->count = count;
best = index;
@@ -276,6 +270,7 @@ int ecam_write ( struct pci_device *pci, unsigned int location,
return 0;
}
+PROVIDE_PCIAPI_INLINE ( ecam, pci_can_probe );
PROVIDE_PCIAPI ( ecam, pci_discover, ecam_discover );
PROVIDE_PCIAPI_INLINE ( ecam, pci_read_config_byte );
PROVIDE_PCIAPI_INLINE ( ecam, pci_read_config_word );
@@ -284,5 +279,4 @@ PROVIDE_PCIAPI_INLINE ( ecam, pci_write_config_byte );
PROVIDE_PCIAPI_INLINE ( ecam, pci_write_config_word );
PROVIDE_PCIAPI_INLINE ( ecam, pci_write_config_dword );
PROVIDE_PCIAPI_INLINE ( ecam, pci_ioremap );
-
-struct pci_api ecam_api = PCIAPI_RUNTIME ( ecam );
+PROVIDE_PCIAPI_RUNTIME ( ecam, PCIAPI_PRIORITY_ECAM );
diff --git a/src/drivers/bus/pci.c b/src/drivers/bus/pci.c
index 92b389641..30163300a 100644
--- a/src/drivers/bus/pci.c
+++ b/src/drivers/bus/pci.c
@@ -25,6 +25,7 @@
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+FILE_SECBOOT ( PERMITTED );
#include <stdint.h>
#include <stdlib.h>
@@ -105,6 +106,92 @@ unsigned long pci_bar_start ( struct pci_device *pci, unsigned int reg ) {
}
/**
+ * Set the start of a PCI BAR
+ *
+ * @v pci PCI device
+ * @v reg PCI register number
+ * @v start BAR start address
+ */
+void pci_bar_set ( struct pci_device *pci, unsigned int reg,
+ unsigned long start ) {
+ unsigned int type;
+ uint32_t low;
+ uint32_t high;
+ uint16_t cmd;
+
+ /* Save the original command register and disable decoding */
+ pci_read_config_word ( pci, PCI_COMMAND, &cmd );
+ pci_write_config_word ( pci, PCI_COMMAND,
+ ( cmd & ~( PCI_COMMAND_MEM |
+ PCI_COMMAND_IO ) ) );
+
+ /* Check for a 64-bit BAR */
+ pci_read_config_dword ( pci, reg, &low );
+ type = ( low & ( PCI_BASE_ADDRESS_SPACE_IO |
+ PCI_BASE_ADDRESS_MEM_TYPE_MASK ) );
+
+ /* Write low 32 bits */
+ low = start;
+ pci_write_config_dword ( pci, reg, low );
+
+ /* Write high 32 bits, if applicable */
+ if ( type == PCI_BASE_ADDRESS_MEM_TYPE_64 ) {
+ if ( sizeof ( unsigned long ) > sizeof ( uint32_t ) ) {
+ high = ( ( ( uint64_t ) start ) >> 32 );
+ } else {
+ high = 0;
+ }
+ pci_write_config_dword ( pci, reg + 4, high );
+ }
+
+ /* Restore the original command register */
+ pci_write_config_word ( pci, PCI_COMMAND, cmd );
+}
+
+/**
+ * Get the size of a PCI BAR
+ *
+ * @v pci PCI device
+ * @v reg PCI register number
+ * @ret size BAR size
+ *
+ * Most drivers should not need to call this function. It is not
+ * necessary to map the whole PCI BAR, only the portion that will be
+ * used for register access. Since register offsets are almost always
+ * fixed by hardware design, the length of the mapped portion will
+ * almost always be a compile-time constant.
+ */
+unsigned long pci_bar_size ( struct pci_device *pci, unsigned int reg ) {
+ unsigned long start;
+ unsigned long size;
+ uint16_t cmd;
+
+ /* Save the original command register and disable decoding */
+ pci_read_config_word ( pci, PCI_COMMAND, &cmd );
+ pci_write_config_word ( pci, PCI_COMMAND,
+ ( cmd & ~( PCI_COMMAND_MEM |
+ PCI_COMMAND_IO ) ) );
+
+ /* Save the original start address */
+ start = pci_bar_start ( pci, reg );
+
+ /* Set all possible bits */
+ pci_bar_set ( pci, reg, -1UL );
+
+ /* Determine size by finding lowest set bit */
+ size = pci_bar_start ( pci, reg );
+ size &= ( -size );
+
+ /* Restore the original start address */
+ pci_bar_set ( pci, reg, start );
+
+ /* Restore the original command register */
+ pci_write_config_word ( pci, PCI_COMMAND, cmd );
+
+ return size;
+}
+
+/**
* Read membase and ioaddr for a PCI device
*
* @v pci PCI device
@@ -374,6 +461,10 @@ static int pcibus_probe ( struct root_device *rootdev ) {
if ( ( rc = pci_find_next ( pci, &busdevfn ) ) != 0 )
break;
+ /* Skip automatic probing if prohibited */
+ if ( ! pci_can_probe ( pci ) )
+ continue;
+
/* Look for a driver */
if ( ( rc = pci_find_driver ( pci ) ) != 0 ) {
DBGC ( pci, PCI_FMT " (%04x:%04x class %06x) has no "
@@ -434,3 +525,9 @@ struct root_device pci_root_device __root_device = {
.dev = { .name = "PCI" },
.driver = &pci_root_driver,
};
+
+/* Drag in objects via pcibus_probe() */
+REQUIRING_SYMBOL ( pcibus_probe );
+
+/* Drag in PCI configuration */
+REQUIRE_OBJECT ( config_pci );
diff --git a/src/drivers/bus/pci_settings.c b/src/drivers/bus/pci_settings.c
index 98005559d..3e320da43 100644
--- a/src/drivers/bus/pci_settings.c
+++ b/src/drivers/bus/pci_settings.c
@@ -22,8 +22,10 @@
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+FILE_SECBOOT ( PERMITTED );
#include <stdio.h>
+#include <string.h>
#include <errno.h>
#include <ipxe/pci.h>
#include <ipxe/settings.h>
@@ -124,5 +126,6 @@ static void pci_settings_init ( void ) {
/** PCI device settings initialiser */
struct init_fn pci_settings_init_fn __init_fn ( INIT_NORMAL ) = {
+ .name = "pci",
.initialise = pci_settings_init,
};
diff --git a/src/drivers/bus/pcibackup.c b/src/drivers/bus/pcibackup.c
index 4cf126f83..81fcb7e05 100644
--- a/src/drivers/bus/pcibackup.c
+++ b/src/drivers/bus/pcibackup.c
@@ -22,6 +22,7 @@
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+FILE_SECBOOT ( PERMITTED );
#include <stdint.h>
#include <ipxe/pci.h>
diff --git a/src/drivers/bus/pcibridge.c b/src/drivers/bus/pcibridge.c
index d2763faf9..67c97589f 100644
--- a/src/drivers/bus/pcibridge.c
+++ b/src/drivers/bus/pcibridge.c
@@ -68,6 +68,8 @@ static int pcibridge_probe ( struct pci_device *pci ) {
struct pci_bridge *bridge;
uint16_t base;
uint16_t limit;
+ uint32_t base_hi;
+ uint32_t limit_hi;
int rc;
/* Allocate and initialise structure */
@@ -78,17 +80,33 @@ static int pcibridge_probe ( struct pci_device *pci ) {
}
bridge->pci = pci;
- /* Read configuration */
+ /* Read bus configuration */
pci_read_config_dword ( pci, PCI_PRIMARY, &bridge->buses );
cpu_to_le32s ( &buses );
+
+ /* Read memory base and limit */
pci_read_config_word ( pci, PCI_MEM_BASE, &base );
bridge->membase = ( ( base & ~PCI_MEM_MASK ) << 16 );
pci_read_config_word ( pci, PCI_MEM_LIMIT, &limit );
bridge->memlimit = ( ( ( ( limit | PCI_MEM_MASK ) + 1 ) << 16 ) - 1 );
- DBGC ( bridge, "BRIDGE " PCI_FMT " bus %02x to [%02x,%02x) mem "
- "[%08x,%08x)\n", PCI_ARGS ( pci ), bridge->primary,
- bridge->secondary, bridge->subordinate, bridge->membase,
- bridge->memlimit );
+
+ /* Read prefetchable memory base and limit */
+ pci_read_config_word ( pci, PCI_PREFMEM_BASE, &base );
+ pci_read_config_dword ( pci, PCI_PREFMEM_BASE_HI, &base_hi );
+ bridge->prefmembase = ( ( ( base & ~PCI_MEM_MASK ) << 16 ) |
+ ( ( ( uint64_t ) base_hi ) << 32 ) );
+ pci_read_config_word ( pci, PCI_PREFMEM_LIMIT, &limit );
+ pci_read_config_dword ( pci, PCI_PREFMEM_LIMIT_HI, &limit_hi );
+ bridge->prefmemlimit = ( ( ( ( ( limit | PCI_MEM_MASK ) + 1 ) << 16 ) |
+ ( ( ( uint64_t ) limit_hi ) << 32 ) ) - 1 );
+
+ DBGC ( bridge, "BRIDGE " PCI_FMT " bus %02x to [%02x,%02x)\n",
+ PCI_ARGS ( pci ), bridge->primary, bridge->secondary,
+ bridge->subordinate );
+ DBGC ( bridge, "BRIDGE " PCI_FMT " mem [%08x,%08x) prefmem "
+ "[%08llx,%08llx)\n", PCI_ARGS ( pci ), bridge->membase,
+ bridge->memlimit, ( ( unsigned long long ) bridge->prefmembase ),
+ ( ( unsigned long long ) bridge->prefmemlimit ) );
/* Add to list of PCI bridges */
list_add ( &bridge->list, &pcibridges );
diff --git a/src/drivers/bus/pcicloud.c b/src/drivers/bus/pcicloud.c
new file mode 100644
index 000000000..0173c3158
--- /dev/null
+++ b/src/drivers/bus/pcicloud.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2022 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+#include <string.h>
+#include <ipxe/pci.h>
+#include <ipxe/pcicloud.h>
+
+/** @file
+ *
+ * Cloud VM PCI configuration space access
+ *
+ */
+
+/** Cached PCI configuration space access API */
+static struct {
+ /** PCI bus:dev.fn address range */
+ struct pci_range range;
+ /** API for this bus:dev.fn address */
+ struct pci_api *api;
+} pcicloud;
+
+/**
+ * Find PCI configuration space access API for address
+ *
+ * @v busdevfn Starting PCI bus:dev.fn address
+ * @v range PCI bus:dev.fn address range to fill in
+ * @ret api Configuration space access API, or NULL
+ */
+static struct pci_api * pcicloud_find ( uint32_t busdevfn,
+ struct pci_range *range ) {
+ struct pci_range candidate;
+ struct pci_api *api;
+ uint32_t best = 0;
+ uint32_t index;
+ uint32_t first;
+ uint32_t last;
+
+ /* Return empty range on error */
+ range->count = 0;
+
+ /* Try discovery via all known APIs */
+ for_each_table_entry ( api, PCI_APIS ) {
+
+ /* Discover via this API */
+ api->pci_discover ( busdevfn, &candidate );
+
+ /* Check for a matching or new closest allocation */
+ index = ( busdevfn - candidate.start );
+ if ( ( index < candidate.count ) || ( index > best ) ) {
+ memcpy ( range, &candidate, sizeof ( *range ) );
+ best = index;
+ }
+
+ /* Stop if this range contains the target bus:dev.fn address */
+ if ( index < candidate.count ) {
+ first = range->start;
+ last = ( range->start + range->count - 1 );
+ DBGC ( &pcicloud, "PCICLOUD [" PCI_FMT "," PCI_FMT ") "
+ "using %s API\n", PCI_SEG ( first ),
+ PCI_BUS ( first ), PCI_SLOT ( first ),
+ PCI_FUNC ( first ), PCI_SEG ( last ),
+ PCI_BUS ( last ), PCI_SLOT ( last ),
+ PCI_FUNC ( last ), api->name );
+ return api;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Find next PCI bus:dev.fn address range in system
+ *
+ * @v busdevfn Starting PCI bus:dev.fn address
+ * @v range PCI bus:dev.fn address range to fill in
+ */
+static void pcicloud_discover ( uint32_t busdevfn, struct pci_range *range ) {
+
+ /* Find new range, if any */
+ pcicloud_find ( busdevfn, range );
+}
+
+/**
+ * Find configuration space access API for PCI device
+ *
+ * @v pci PCI device
+ * @ret api Configuration space access API
+ */
+static struct pci_api * pcicloud_api ( struct pci_device *pci ) {
+ struct pci_range *range = &pcicloud.range;
+ struct pci_api *api;
+ uint32_t first;
+ uint32_t last;
+
+ /* Reuse cached API if applicable */
+ if ( ( pci->busdevfn - range->start ) < range->count )
+ return pcicloud.api;
+
+ /* Find highest priority API claiming this range */
+ api = pcicloud_find ( pci->busdevfn, range );
+
+ /* Fall back to lowest priority API for any unclaimed gaps in ranges */
+ if ( ! api ) {
+ api = ( table_end ( PCI_APIS ) - 1 );
+ range->count = ( range->start - pci->busdevfn );
+ range->start = pci->busdevfn;
+ first = range->start;
+ last = ( range->start + range->count - 1 );
+ DBGC ( &pcicloud, "PCICLOUD [" PCI_FMT "," PCI_FMT ") falling "
+ "back to %s API\n", PCI_SEG ( first ),
+ PCI_BUS ( first ), PCI_SLOT ( first ),
+ PCI_FUNC ( first ), PCI_SEG ( last ), PCI_BUS ( last ),
+ PCI_SLOT ( last ), PCI_FUNC ( last ), api->name );
+ }
+
+ /* Cache API for this range */
+ pcicloud.api = api;
+
+ return api;
+}
+
+/**
+ * Check if PCI bus probing is allowed
+ *
+ * @v pci PCI device
+ * @ret ok Bus probing is allowed
+ */
+static int pcicloud_can_probe ( struct pci_device *pci ) {
+ struct pci_api *api = pcicloud_api ( pci );
+
+ return api->pci_can_probe ( pci );
+}
+
+/**
+ * Read byte from PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value read
+ * @ret rc Return status code
+ */
+static int pcicloud_read_config_byte ( struct pci_device *pci,
+ unsigned int where, uint8_t *value ) {
+ struct pci_api *api = pcicloud_api ( pci );
+
+ return api->pci_read_config_byte ( pci, where, value );
+}
+
+/**
+ * Read 16-bit word from PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value read
+ * @ret rc Return status code
+ */
+static int pcicloud_read_config_word ( struct pci_device *pci,
+ unsigned int where, uint16_t *value ) {
+ struct pci_api *api = pcicloud_api ( pci );
+
+ return api->pci_read_config_word ( pci, where, value );
+}
+
+/**
+ * Read 32-bit dword from PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value read
+ * @ret rc Return status code
+ */
+static int pcicloud_read_config_dword ( struct pci_device *pci,
+ unsigned int where, uint32_t *value ) {
+ struct pci_api *api = pcicloud_api ( pci );
+
+ return api->pci_read_config_dword ( pci, where, value );
+}
+
+/**
+ * Write byte to PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value to be written
+ * @ret rc Return status code
+ */
+static int pcicloud_write_config_byte ( struct pci_device *pci,
+ unsigned int where, uint8_t value ) {
+ struct pci_api *api = pcicloud_api ( pci );
+
+ return api->pci_write_config_byte ( pci, where, value );
+}
+
+/**
+ * Write 16-bit word to PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value to be written
+ * @ret rc Return status code
+ */
+static int pcicloud_write_config_word ( struct pci_device *pci,
+ unsigned int where, uint16_t value ) {
+ struct pci_api *api = pcicloud_api ( pci );
+
+ return api->pci_write_config_word ( pci, where, value );
+}
+
+/**
+ * Write 32-bit dword to PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value to be written
+ * @ret rc Return status code
+ */
+static int pcicloud_write_config_dword ( struct pci_device *pci,
+ unsigned int where, uint32_t value ) {
+ struct pci_api *api = pcicloud_api ( pci );
+
+ return api->pci_write_config_dword ( pci, where, value );
+}
+
+/**
+ * Map PCI bus address as an I/O address
+ *
+ * @v bus_addr PCI bus address
+ * @v len Length of region
+ * @ret io_addr I/O address, or NULL on error
+ */
+static void * pcicloud_ioremap ( struct pci_device *pci,
+ unsigned long bus_addr, size_t len ) {
+ struct pci_api *api = pcicloud_api ( pci );
+
+ return api->pci_ioremap ( pci, bus_addr, len );
+}
+
+PROVIDE_PCIAPI ( cloud, pci_can_probe, pcicloud_can_probe );
+PROVIDE_PCIAPI ( cloud, pci_discover, pcicloud_discover );
+PROVIDE_PCIAPI ( cloud, pci_read_config_byte, pcicloud_read_config_byte );
+PROVIDE_PCIAPI ( cloud, pci_read_config_word, pcicloud_read_config_word );
+PROVIDE_PCIAPI ( cloud, pci_read_config_dword, pcicloud_read_config_dword );
+PROVIDE_PCIAPI ( cloud, pci_write_config_byte, pcicloud_write_config_byte );
+PROVIDE_PCIAPI ( cloud, pci_write_config_word, pcicloud_write_config_word );
+PROVIDE_PCIAPI ( cloud, pci_write_config_dword, pcicloud_write_config_dword );
+PROVIDE_PCIAPI ( cloud, pci_ioremap, pcicloud_ioremap );
diff --git a/src/drivers/bus/pciextra.c b/src/drivers/bus/pciextra.c
index 1eeb9b2a0..f769a3172 100644
--- a/src/drivers/bus/pciextra.c
+++ b/src/drivers/bus/pciextra.c
@@ -1,4 +1,5 @@
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+FILE_SECBOOT ( PERMITTED );
#include <stdint.h>
#include <ipxe/timer.h>
@@ -80,42 +81,6 @@ int pci_find_next_capability ( struct pci_device *pci, int pos, int cap ) {
}
/**
- * Find the size of a PCI BAR
- *
- * @v pci PCI device
- * @v reg PCI register number
- * @ret size BAR size
- *
- * It should not be necessary for any Etherboot code to call this
- * function.
- */
-unsigned long pci_bar_size ( struct pci_device *pci, unsigned int reg ) {
- uint16_t cmd;
- uint32_t start, size;
-
- /* Save the original command register */
- pci_read_config_word ( pci, PCI_COMMAND, &cmd );
- /* Save the original bar */
- pci_read_config_dword ( pci, reg, &start );
- /* Compute which bits can be set */
- pci_write_config_dword ( pci, reg, ~0 );
- pci_read_config_dword ( pci, reg, &size );
- /* Restore the original size */
- pci_write_config_dword ( pci, reg, start );
- /* Find the significant bits */
- /* Restore the original command register. This reenables decoding. */
- pci_write_config_word ( pci, PCI_COMMAND, cmd );
- if ( start & PCI_BASE_ADDRESS_SPACE_IO ) {
- size &= ~PCI_BASE_ADDRESS_IO_MASK;
- } else {
- size &= ~PCI_BASE_ADDRESS_MEM_MASK;
- }
- /* Find the lowest bit set */
- size = size & ~( size - 1 );
- return size;
-}
-
-/**
* Perform PCI Express function-level reset (FLR)
*
* @v pci PCI device
diff --git a/src/drivers/bus/pcimsix.c b/src/drivers/bus/pcimsix.c
index eb0450d91..008c1c22f 100644
--- a/src/drivers/bus/pcimsix.c
+++ b/src/drivers/bus/pcimsix.c
@@ -22,6 +22,7 @@
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+FILE_SECBOOT ( PERMITTED );
#include <stdint.h>
#include <errno.h>
@@ -33,6 +34,38 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*
* PCI MSI-X interrupts
*
+ * Interrupts as such are not used in iPXE, which operates in polling
+ * mode. However, some network cards (such as the Intel 40GbE and
+ * 100GbE NICs) will defer writing out completions until the point of
+ * asserting an MSI-X interrupt.
+ *
+ * From the point of view of the PCI device, asserting an MSI-X
+ * interrupt is just a 32-bit DMA write of an opaque value to an
+ * opaque target address. The PCI device has no know to know whether
+ * or not the target address corresponds to a real APIC.
+ *
+ * We can therefore trick the PCI device into believing that it is
+ * asserting an MSI-X interrupt, by configuring it to write an opaque
+ * 32-bit value to a dummy target address in host memory. This is
+ * sufficient to trigger the associated write of the completions to
+ * host memory.
+ *
+ * When running in a virtual machine, the hypervisor will intercept
+ * our attempt to configure MSI-X on the PCI device. The physical
+ * hardware will be configured to raise an interrupt under the
+ * hypervisor's control, which will then be reflected back into the
+ * virtual machine. The opaque value that we write will be assumed to
+ * indicate an interrupt vector number (as would normally be the case
+ * when configuring MSI-X), and the opaque address will generally be
+ * ignored. The reflected interrupt will be ignored (since it is not
+ * enabled within the virtual machine), but the device still asserts
+ * an MSI-X interrupt and so still triggers the associated write of
+ * the completions to host memory.
+ *
+ * Note that since the opaque target address will generally be ignored
+ * by the hypervisor, we cannot examine the value present at the dummy
+ * target address to find out whether or not an interrupt has been
+ * raised.
*/
/**
@@ -103,6 +136,8 @@ static void * pci_msix_ioremap ( struct pci_device *pci, struct pci_msix *msix,
*/
int pci_msix_enable ( struct pci_device *pci, struct pci_msix *msix ) {
uint16_t ctrl;
+ physaddr_t msg;
+ unsigned int i;
int rc;
/* Locate capability */
@@ -134,6 +169,19 @@ int pci_msix_enable ( struct pci_device *pci, struct pci_msix *msix ) {
goto err_pba;
}
+ /* Allocate dummy target */
+ msix->msg = dma_alloc ( &pci->dma, &msix->map, sizeof ( *msix->msg ),
+ sizeof ( *msix->msg ) );
+ if ( ! msix->msg ) {
+ rc = -ENOMEM;
+ goto err_msg;
+ }
+
+ /* Map all interrupts to dummy target by default */
+ msg = dma ( &msix->map, msix->msg );
+ for ( i = 0 ; i < msix->count ; i++ )
+ pci_msix_map ( msix, i, msg, 0 );
+
/* Enable MSI-X */
ctrl &= ~PCI_MSIX_CTRL_MASK;
ctrl |= PCI_MSIX_CTRL_ENABLE;
@@ -141,6 +189,8 @@ int pci_msix_enable ( struct pci_device *pci, struct pci_msix *msix ) {
return 0;
+ dma_free ( &msix->map, msix->msg, sizeof ( *msix->msg ) );
+ err_msg:
iounmap ( msix->pba );
err_pba:
iounmap ( msix->table );
@@ -163,6 +213,9 @@ void pci_msix_disable ( struct pci_device *pci, struct pci_msix *msix ) {
ctrl &= ~PCI_MSIX_CTRL_ENABLE;
pci_write_config_word ( pci, ( msix->cap + PCI_MSIX_CTRL ), ctrl );
+ /* Free dummy target */
+ dma_free ( &msix->map, msix->msg, sizeof ( *msix->msg ) );
+
/* Unmap pending bit array */
iounmap ( msix->pba );
diff --git a/src/drivers/bus/usb.c b/src/drivers/bus/usb.c
index 428ae26c1..30c288df9 100644
--- a/src/drivers/bus/usb.c
+++ b/src/drivers/bus/usb.c
@@ -22,6 +22,7 @@
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+FILE_SECBOOT ( PERMITTED );
#include <stdlib.h>
#include <stdio.h>
@@ -914,10 +915,9 @@ static unsigned int usb_get_default_language ( struct usb_device *usb ) {
*/
int usb_get_string_descriptor ( struct usb_device *usb, unsigned int index,
unsigned int language, char *buf, size_t len ) {
- size_t max = ( len ? ( len - 1 /* NUL */ ) : 0 );
struct {
struct usb_descriptor_header header;
- uint16_t character[max];
+ uint16_t character[len];
} __attribute__ (( packed )) *desc;
unsigned int actual;
unsigned int i;
@@ -952,10 +952,9 @@ int usb_get_string_descriptor ( struct usb_device *usb, unsigned int index,
sizeof ( desc->character[0] ) );
/* Copy to buffer */
- for ( i = 0 ; ( ( i < actual ) && ( i < max ) ) ; i++ )
+ memset ( buf, 0, len );
+ for ( i = 0 ; ( ( i < actual ) && ( i < len ) ) ; i++ )
buf[i] = le16_to_cpu ( desc->character[i] );
- if ( len )
- buf[i] = '\0';
/* Free buffer */
free ( desc );
@@ -1323,7 +1322,8 @@ usb_probe_all ( struct usb_device *usb,
func->name = func->dev.name;
func->usb = usb;
func->dev.desc.bus_type = BUS_TYPE_USB;
- func->dev.desc.location = usb->address;
+ func->dev.desc.location =
+ USB_BUSDEV ( bus->address, usb->address );
func->dev.desc.vendor = le16_to_cpu ( usb->device.vendor );
func->dev.desc.device = le16_to_cpu ( usb->device.product );
snprintf ( func->dev.name, sizeof ( func->dev.name ),
@@ -1725,6 +1725,25 @@ static void free_usb ( struct usb_device *usb ) {
free ( usb );
}
+/**
+ * Find USB device by address
+ *
+ * @v bus USB bus
+ * @v address Device address
+ * @ret usb USB device, or NULL if not found
+ */
+struct usb_device * find_usb ( struct usb_bus *bus, unsigned int address ) {
+ struct usb_device *usb;
+
+ /* Search for a matching non-zero address */
+ list_for_each_entry ( usb, &bus->devices, list ) {
+ if ( address && ( usb->address == address ) )
+ return usb;
+ }
+
+ return NULL;
+}
+
/******************************************************************************
*
* USB device hotplug event handling
@@ -2115,6 +2134,11 @@ int register_usb_bus ( struct usb_bus *bus ) {
/* Sanity checks */
assert ( bus->hub != NULL );
+ /* Assign the first available bus address */
+ bus->address = 0;
+ while ( find_usb_bus ( bus->address ) != NULL )
+ bus->address++;
+
/* Open bus */
if ( ( rc = bus->host->open ( bus ) ) != 0 )
goto err_open;
@@ -2188,6 +2212,23 @@ void free_usb_bus ( struct usb_bus *bus ) {
}
/**
+ * Find USB bus by address
+ *
+ * @v address Bus address
+ * @ret bus USB bus, or NULL
+ */
+struct usb_bus * find_usb_bus ( unsigned int address ) {
+ struct usb_bus *bus;
+
+ for_each_usb_bus ( bus ) {
+ if ( bus->address == address )
+ return bus;
+ }
+
+ return NULL;
+}
+
+/**
* Find USB bus by device location
*
* @v bus_type Bus type
@@ -2209,7 +2250,7 @@ struct usb_bus * find_usb_bus_by_location ( unsigned int bus_type,
/******************************************************************************
*
- * USB address assignment
+ * USB device addressing
*
******************************************************************************
*/
@@ -2250,6 +2291,35 @@ void usb_free_address ( struct usb_bus *bus, unsigned int address ) {
bus->addresses &= ~( 1ULL << ( address - 1 ) );
}
+/**
+ * Find next USB device
+ *
+ * @v usb USB device to fill in
+ * @v busdev Starting bus:dev address
+ * @ret busdev Bus:dev address of next USB device
+ * @ret rc Return status code
+ */
+int usb_find_next ( struct usb_device **usb, uint16_t *busdev ) {
+ struct usb_bus *bus;
+
+ do {
+ /* Find USB bus, if any */
+ bus = find_usb_bus ( USB_BUS ( *busdev ) );
+ if ( ! bus ) {
+ *busdev |= ( USB_BUS ( 1 ) - 1 );
+ continue;
+ }
+
+ /* Find USB device, if any */
+ *usb = find_usb ( bus, USB_DEV ( *busdev ) );
+ if ( *usb )
+ return 0;
+
+ } while ( ++(*busdev) );
+
+ return -ENODEV;
+}
+
/******************************************************************************
*
* USB bus topology
diff --git a/src/drivers/bus/usb_settings.c b/src/drivers/bus/usb_settings.c
new file mode 100644
index 000000000..e34c79126
--- /dev/null
+++ b/src/drivers/bus/usb_settings.c
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2024 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+FILE_SECBOOT ( PERMITTED );
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ipxe/usb.h>
+#include <ipxe/settings.h>
+#include <ipxe/init.h>
+
+/** @file
+ *
+ * USB device settings
+ *
+ */
+
+/** USB device settings scope */
+static const struct settings_scope usb_settings_scope;
+
+/**
+ * Check applicability of USB device setting
+ *
+ * @v settings Settings block
+ * @v setting Setting
+ * @ret applies Setting applies within this settings block
+ */
+static int usb_settings_applies ( struct settings *settings __unused,
+ const struct setting *setting ) {
+
+ return ( setting->scope == &usb_settings_scope );
+}
+
+/**
+ * Fetch value of USB device setting
+ *
+ * @v settings Settings block
+ * @v setting Setting to fetch
+ * @v data Buffer to fill with setting data
+ * @v len Length of buffer
+ * @ret len Length of setting data, or negative error
+ */
+static int usb_settings_fetch ( struct settings *settings __unused,
+ struct setting *setting,
+ void *data, size_t len ) {
+ uint8_t *dst = data;
+ const uint8_t *src;
+ const uint8_t *desc;
+ struct usb_bus *bus;
+ struct usb_device *usb;
+ int tag_direction;
+ unsigned int tag_busdev;
+ unsigned int tag_offset;
+ unsigned int tag_len;
+ unsigned int index;
+ int rc;
+
+ /* Extract parameters from tag */
+ tag_direction = ( ( setting->tag & ( 1 << 31 ) ) ? +1 : -1 );
+ tag_busdev = ( ( setting->tag >> 16 ) & 0x7fff );
+ tag_offset = ( ( setting->tag >> 8 ) & 0xff );
+ tag_len = ( ( setting->tag >> 0 ) & 0xff );
+
+ /* Locate USB device */
+ bus = find_usb_bus ( USB_BUS ( tag_busdev ) );
+ if ( ! bus )
+ return -ENODEV;
+ usb = find_usb ( bus, USB_DEV ( tag_busdev ) );
+ if ( ! usb )
+ return -ENODEV;
+ desc = ( ( const uint8_t * ) &usb->device );
+
+ /* Following the usage of SMBIOS settings tags, a <length> of
+ * zero indicates that the byte at <offset> contains a string
+ * index. An <offset> of zero indicates that the <length>
+ * contains a literal string index.
+ *
+ * Since the byte at offset zero can never contain a string
+ * index, and a literal string index can never be zero, the
+ * combination of both <length> and <offset> being zero
+ * indicates that the entire structure is to be read.
+ *
+ * By default we reverse the byte direction since USB values
+ * are little-endian and iPXE settings are big-endian. We
+ * invert this default when reading the entire structure.
+ */
+ if ( ( tag_len == 0 ) && ( tag_offset == 0 ) ) {
+ tag_len = sizeof ( usb->device );
+ tag_direction = -tag_direction;
+ } else if ( ( tag_len == 0 ) || ( tag_offset == 0 ) ) {
+ index = tag_len;
+ if ( ( ! index ) && ( tag_offset < sizeof ( usb->device ) ) )
+ index = desc[tag_offset];
+ if ( ( rc = usb_get_string_descriptor ( usb, index, 0, data,
+ len ) ) < 0 ) {
+ return rc;
+ }
+ if ( ! setting->type )
+ setting->type = &setting_type_string;
+ return rc;
+ }
+
+ /* Limit length */
+ if ( tag_offset > sizeof ( usb->device ) ) {
+ tag_len = 0;
+ } else if ( ( tag_offset + tag_len ) > sizeof ( usb->device ) ) {
+ tag_len = ( sizeof ( usb->device ) - tag_offset );
+ }
+
+ /* Copy data, reversing endianness if applicable */
+ dst = data;
+ src = ( desc + tag_offset );
+ if ( tag_direction < 0 )
+ src += ( tag_len - 1 );
+ if ( len > tag_len )
+ len = tag_len;
+ for ( ; len-- ; src += tag_direction, dst++ )
+ *dst = *src;
+
+ /* Set type to ":hexraw" if not already specified */
+ if ( ! setting->type )
+ setting->type = &setting_type_hexraw;
+
+ return tag_len;
+}
+
+/** USB device settings operations */
+static struct settings_operations usb_settings_operations = {
+ .applies = usb_settings_applies,
+ .fetch = usb_settings_fetch,
+};
+
+/** USB device settings */
+static struct settings usb_settings = {
+ .refcnt = NULL,
+ .siblings = LIST_HEAD_INIT ( usb_settings.siblings ),
+ .children = LIST_HEAD_INIT ( usb_settings.children ),
+ .op = &usb_settings_operations,
+ .default_scope = &usb_settings_scope,
+};
+
+/** Initialise USB device settings */
+static void usb_settings_init ( void ) {
+ int rc;
+
+ if ( ( rc = register_settings ( &usb_settings, NULL, "usb" ) ) != 0 ) {
+ DBG ( "USB could not register settings: %s\n",
+ strerror ( rc ) );
+ return;
+ }
+}
+
+/** USB device settings initialiser */
+struct init_fn usb_settings_init_fn __init_fn ( INIT_NORMAL ) = {
+ .name = "usb",
+ .initialise = usb_settings_init,
+};