diff options
Diffstat (limited to 'src/drivers/bus')
| -rw-r--r-- | src/drivers/bus/cdc.c | 1 | ||||
| -rw-r--r-- | src/drivers/bus/devtree.c | 357 | ||||
| -rw-r--r-- | src/drivers/bus/ecam.c | 40 | ||||
| -rw-r--r-- | src/drivers/bus/pci.c | 97 | ||||
| -rw-r--r-- | src/drivers/bus/pci_settings.c | 3 | ||||
| -rw-r--r-- | src/drivers/bus/pcibackup.c | 1 | ||||
| -rw-r--r-- | src/drivers/bus/pcibridge.c | 28 | ||||
| -rw-r--r-- | src/drivers/bus/pcicloud.c | 269 | ||||
| -rw-r--r-- | src/drivers/bus/pciextra.c | 37 | ||||
| -rw-r--r-- | src/drivers/bus/pcimsix.c | 53 | ||||
| -rw-r--r-- | src/drivers/bus/usb.c | 84 | ||||
| -rw-r--r-- | src/drivers/bus/usb_settings.c | 179 |
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, ®s ) ) != 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, ®s, 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, ®s, 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, +}; |
