[usb] Add "usbscan" command for iterating over USB devices
Implement a "usbscan" command as a direct analogy of the existing
"pciscan" command, allowing scripts to iterate over all detected USB
devices.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/config/config.c b/src/config/config.c
index f532c1d..0f950eb 100644
--- a/src/config/config.c
+++ b/src/config/config.c
@@ -302,6 +302,9 @@
#ifdef IMAGE_CRYPT_CMD
REQUIRE_OBJECT ( image_crypt_cmd );
#endif
+#ifdef USB_CMD
+REQUIRE_OBJECT ( usb_cmd );
+#endif
/*
* Drag in miscellaneous objects
diff --git a/src/config/general.h b/src/config/general.h
index b82e560..763a34a 100644
--- a/src/config/general.h
+++ b/src/config/general.h
@@ -171,6 +171,7 @@
//#define IMAGE_MEM_CMD /* Read memory command */
#define IMAGE_ARCHIVE_CMD /* Archive image management commands */
#define SHIM_CMD /* EFI shim command (or dummy command) */
+//#define USB_CMD /* USB commands */
/*
* ROM-specific options
diff --git a/src/drivers/bus/usb.c b/src/drivers/bus/usb.c
index 428ae26..e1e7d51 100644
--- a/src/drivers/bus/usb.c
+++ b/src/drivers/bus/usb.c
@@ -1323,7 +1323,8 @@
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 +1726,25 @@
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 +2135,11 @@
/* 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 +2213,23 @@
}
/**
+ * 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 +2251,7 @@
/******************************************************************************
*
- * USB address assignment
+ * USB device addressing
*
******************************************************************************
*/
@@ -2250,6 +2292,35 @@
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/hci/commands/usb_cmd.c b/src/hci/commands/usb_cmd.c
new file mode 100644
index 0000000..d1086fd
--- /dev/null
+++ b/src/hci/commands/usb_cmd.c
@@ -0,0 +1,122 @@
+/*
+ * 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 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.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <getopt.h>
+#include <ipxe/usb.h>
+#include <ipxe/command.h>
+#include <ipxe/parseopt.h>
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+/** @file
+ *
+ * USB commands
+ *
+ */
+
+/** "usbscan" options */
+struct usbscan_options {};
+
+/** "usbscan" option list */
+static struct option_descriptor usbscan_opts[] = {};
+
+/** "usbscan" command descriptor */
+static struct command_descriptor usbscan_cmd =
+ COMMAND_DESC ( struct usbscan_options, usbscan_opts, 1, 1,
+ "<setting>" );
+
+/**
+ * "usbscan" command
+ *
+ * @v argc Argument count
+ * @v argv Argument list
+ * @ret rc Return status code
+ */
+static int usbscan_exec ( int argc, char **argv ) {
+ struct usbscan_options opts;
+ struct named_setting setting;
+ struct usb_device *usb;
+ unsigned long prev;
+ uint16_t busdev;
+ int len;
+ int rc;
+
+ /* Parse options */
+ if ( ( rc = parse_options ( argc, argv, &usbscan_cmd, &opts ) ) != 0 )
+ goto err_parse_options;
+
+ /* Parse setting name */
+ if ( ( rc = parse_autovivified_setting ( argv[optind],
+ &setting ) ) != 0 )
+ goto err_parse_setting;
+
+ /* Determine starting bus:dev.fn address */
+ if ( ( len = fetchn_setting ( setting.settings, &setting.setting,
+ NULL, &setting.setting, &prev ) ) < 0 ) {
+ /* Setting not yet defined: start searching from 00:00 */
+ busdev = 0;
+ } else {
+ /* Setting is defined: start searching from next location */
+ busdev = ( prev + 1 );
+ if ( ! busdev ) {
+ rc = -ENOENT;
+ goto err_end;
+ }
+ }
+
+ /* Find next existent USB device */
+ if ( ( rc = usb_find_next ( &usb, &busdev ) ) != 0 )
+ goto err_find_next;
+
+ /* Apply default type if necessary. Use ":uint16" rather than
+ * ":hex" to allow for easy inclusion within a
+ * "${usb/${location}....}" constructed setting.
+ */
+ if ( ! setting.setting.type )
+ setting.setting.type = &setting_type_uint16;
+
+ /* Store setting */
+ if ( ( rc = storen_setting ( setting.settings, &setting.setting,
+ busdev ) ) != 0 ) {
+ printf ( "Could not store \"%s\": %s\n",
+ setting.setting.name, strerror ( rc ) );
+ goto err_store;
+ }
+
+ err_store:
+ err_end:
+ err_find_next:
+ err_parse_setting:
+ err_parse_options:
+ return rc;
+}
+
+/** USB commands */
+struct command usb_commands[] __command = {
+ {
+ .name = "usbscan",
+ .exec = usbscan_exec,
+ },
+};
diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h
index 42bc177..01a2be6 100644
--- a/src/include/ipxe/errfile.h
+++ b/src/include/ipxe/errfile.h
@@ -423,6 +423,7 @@
#define ERRFILE_editstring ( ERRFILE_OTHER | 0x00610000 )
#define ERRFILE_widget_ui ( ERRFILE_OTHER | 0x00620000 )
#define ERRFILE_form_ui ( ERRFILE_OTHER | 0x00630000 )
+#define ERRFILE_usb_cmd ( ERRFILE_OTHER | 0x00640000 )
/** @} */
diff --git a/src/include/ipxe/usb.h b/src/include/ipxe/usb.h
index 911247e..d9891b7 100644
--- a/src/include/ipxe/usb.h
+++ b/src/include/ipxe/usb.h
@@ -54,6 +54,20 @@
USB_SPEED_SUPER = USB_SPEED ( 5, 3 ),
};
+/** Define a USB bus:device address
+ *
+ * @v bus Bus address
+ * @v dev Device address
+ * @ret busdev Bus:device address
+ */
+#define USB_BUSDEV( bus, dev ) ( ( (bus) << 8 ) | (dev) )
+
+/** Extract USB bus address */
+#define USB_BUS( busdev ) ( (busdev) >> 8 )
+
+/** Extract USB device address */
+#define USB_DEV( busdev ) ( (busdev) & 0xff )
+
/** USB packet IDs */
enum usb_pid {
/** IN PID */
@@ -956,6 +970,12 @@
/** Host controller operations set */
struct usb_host_operations *op;
+ /** Bus address
+ *
+ * This is an internal index used only to allow a USB device
+ * to be identified via a nominal bus:device address.
+ */
+ unsigned int address;
/** Largest transfer allowed on the bus */
size_t mtu;
/** Address in-use mask
@@ -1269,6 +1289,9 @@
usb_endpoint_companion_descriptor ( struct usb_configuration_descriptor *config,
struct usb_endpoint_descriptor *desc );
+extern struct usb_device * find_usb ( struct usb_bus *bus,
+ unsigned int address );
+
extern struct usb_hub * alloc_usb_hub ( struct usb_bus *bus,
struct usb_device *usb,
unsigned int ports,
@@ -1285,11 +1308,13 @@
extern int register_usb_bus ( struct usb_bus *bus );
extern void unregister_usb_bus ( struct usb_bus *bus );
extern void free_usb_bus ( struct usb_bus *bus );
+extern struct usb_bus * find_usb_bus ( unsigned int address );
extern struct usb_bus * find_usb_bus_by_location ( unsigned int bus_type,
unsigned int location );
extern int usb_alloc_address ( struct usb_bus *bus );
extern void usb_free_address ( struct usb_bus *bus, unsigned int address );
+extern int usb_find_next ( struct usb_device **usb, uint16_t *busdev );
extern unsigned int usb_route_string ( struct usb_device *usb );
extern struct usb_port * usb_root_hub_port ( struct usb_device *usb );
extern struct usb_port * usb_transaction_translator ( struct usb_device *usb );