[efi] Allow for creating devices with no EFI parent device

On some systems (observed on an AWS m8g.medium instance in eu-west-2),
the UEFI firmware fails to enumerate some of the underlying hardware
devices.  On these systems, we cannot comply with the UEFI device
model by adding our SNP device as a child of the hardware device and
appending to the parent hardware device path, since no parent hardware
device has been created.

Work around these systems by allowing for the creation of SNP devices
with no parent device.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2025-11-25 11:59:03 +00:00
parent dfea3bbfad
commit 19dffdc836
3 changed files with 76 additions and 37 deletions

View File

@@ -30,8 +30,8 @@ struct efi_snp_device {
struct list_head list;
/** The underlying iPXE network device */
struct net_device *netdev;
/** The underlying EFI device */
struct efi_device *efidev;
/** EFI parent device handle (if any) */
EFI_HANDLE parent;
/** EFI device handle */
EFI_HANDLE handle;
/** The SNP structure itself */

View File

@@ -47,6 +47,34 @@ FILE_LICENCE ( GPL2_OR_LATER );
*
*/
/** Dummy parent device path
*
* This is used as the parent device path when we need to construct a
* path for a device that has no EFI parent device.
*/
static struct {
BBS_BBS_DEVICE_PATH bbs;
CHAR8 tring[4];
EFI_DEVICE_PATH_PROTOCOL end;
} __attribute__ (( packed )) efi_dummy_parent_path = {
.bbs = {
.Header = {
.Type = BBS_DEVICE_PATH,
.SubType = BBS_BBS_DP,
.Length[0] = ( sizeof ( efi_dummy_parent_path.bbs ) +
sizeof ( efi_dummy_parent_path.tring )),
},
.DeviceType = BBS_TYPE_UNKNOWN,
.String[0] = 'i',
},
.tring = "PXE",
.end = {
.Type = END_DEVICE_PATH_TYPE,
.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE,
.Length[0] = sizeof ( efi_dummy_parent_path.end ),
},
};
/** An EFI device path settings block */
struct efi_path_settings {
/** Settings interface */
@@ -352,6 +380,24 @@ EFI_DEVICE_PATH_PROTOCOL * efi_paths ( EFI_DEVICE_PATH_PROTOCOL *first, ... ) {
return path;
}
/**
* Construct EFI parent device path
*
* @v dev Generic device
* @ret path Parent (or dummy) device path
*/
static EFI_DEVICE_PATH_PROTOCOL * efi_parent_path ( struct device *dev ) {
struct efi_device *efidev;
/* Use EFI parent device's path, if possible */
efidev = efidev_parent ( dev );
if ( efidev )
return efidev->path;
/* Otherwise, use a dummy parent device path */
return &efi_dummy_parent_path.bbs.Header;
}
/**
* Construct EFI device path for network device
*
@@ -362,7 +408,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_paths ( EFI_DEVICE_PATH_PROTOCOL *first, ... ) {
* allocated device path.
*/
EFI_DEVICE_PATH_PROTOCOL * efi_netdev_path ( struct net_device *netdev ) {
struct efi_device *efidev;
EFI_DEVICE_PATH_PROTOCOL *parent;
EFI_DEVICE_PATH_PROTOCOL *path;
MAC_ADDR_DEVICE_PATH *macpath;
VLAN_DEVICE_PATH *vlanpath;
@@ -371,13 +417,11 @@ EFI_DEVICE_PATH_PROTOCOL * efi_netdev_path ( struct net_device *netdev ) {
size_t prefix_len;
size_t len;
/* Find parent EFI device */
efidev = efidev_parent ( netdev->dev );
if ( ! efidev )
return NULL;
/* Get parent EFI device path */
parent = efi_parent_path ( netdev->dev );
/* Calculate device path length */
prefix_len = efi_path_len ( efidev->path );
prefix_len = efi_path_len ( parent );
len = ( prefix_len + sizeof ( *macpath ) + sizeof ( *vlanpath ) +
sizeof ( *end ) );
@@ -387,7 +431,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_netdev_path ( struct net_device *netdev ) {
return NULL;
/* Construct device path */
memcpy ( path, efidev->path, prefix_len );
memcpy ( path, parent, prefix_len );
macpath = ( ( ( void * ) path ) + prefix_len );
macpath->Header.Type = MESSAGING_DEVICE_PATH;
macpath->Header.SubType = MSG_MAC_ADDR_DP;
@@ -601,20 +645,18 @@ EFI_DEVICE_PATH_PROTOCOL * efi_ib_srp_path ( struct ib_srp_device *ib_srp ) {
union ib_srp_target_port_id *id =
container_of ( &sbft->srp.target, union ib_srp_target_port_id,
srp );
struct efi_device *efidev;
EFI_DEVICE_PATH_PROTOCOL *parent;
EFI_DEVICE_PATH_PROTOCOL *path;
INFINIBAND_DEVICE_PATH *ibpath;
EFI_DEVICE_PATH_PROTOCOL *end;
size_t prefix_len;
size_t len;
/* Find parent EFI device */
efidev = efidev_parent ( ib_srp->ibdev->dev );
if ( ! efidev )
return NULL;
/* Get parent EFI device path */
parent = efi_parent_path ( ib_srp->ibdev->dev );
/* Calculate device path length */
prefix_len = efi_path_len ( efidev->path );
prefix_len = efi_path_len ( parent );
len = ( prefix_len + sizeof ( *ibpath ) + sizeof ( *end ) );
/* Allocate device path */
@@ -623,7 +665,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_ib_srp_path ( struct ib_srp_device *ib_srp ) {
return NULL;
/* Construct device path */
memcpy ( path, efidev->path, prefix_len );
memcpy ( path, parent, prefix_len );
ibpath = ( ( ( void * ) path ) + prefix_len );
ibpath->Header.Type = MESSAGING_DEVICE_PATH;
ibpath->Header.SubType = MSG_INFINIBAND_DP;
@@ -653,7 +695,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_ib_srp_path ( struct ib_srp_device *ib_srp ) {
*/
EFI_DEVICE_PATH_PROTOCOL * efi_usb_path ( struct usb_function *func ) {
struct usb_device *usb = func->usb;
struct efi_device *efidev;
EFI_DEVICE_PATH_PROTOCOL *parent;
EFI_DEVICE_PATH_PROTOCOL *path;
EFI_DEVICE_PATH_PROTOCOL *end;
USB_DEVICE_PATH *usbpath;
@@ -664,14 +706,12 @@ EFI_DEVICE_PATH_PROTOCOL * efi_usb_path ( struct usb_function *func ) {
/* Sanity check */
assert ( func->desc.count >= 1 );
/* Find parent EFI device */
efidev = efidev_parent ( &func->dev );
if ( ! efidev )
return NULL;
/* Get parent EFI device path */
parent = efi_parent_path ( &func->dev );
/* Calculate device path length */
count = ( usb_depth ( usb ) + 1 );
prefix_len = efi_path_len ( efidev->path );
prefix_len = efi_path_len ( parent );
len = ( prefix_len + ( count * sizeof ( *usbpath ) ) +
sizeof ( *end ) );
@@ -681,7 +721,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_usb_path ( struct usb_function *func ) {
return NULL;
/* Construct device path */
memcpy ( path, efidev->path, prefix_len );
memcpy ( path, parent, prefix_len );
end = ( ( ( void * ) path ) + len - sizeof ( *end ) );
efi_path_terminate ( end );
usbpath = ( ( ( void * ) end ) - sizeof ( *usbpath ) );

View File

@@ -1792,14 +1792,6 @@ static int efi_snp_probe ( struct net_device *netdev, void *priv __unused ) {
EFI_STATUS efirc;
int rc;
/* Find parent EFI device */
efidev = efidev_parent ( netdev->dev );
if ( ! efidev ) {
DBG ( "SNP skipping non-EFI device %s\n", netdev->name );
rc = 0;
goto err_no_efidev;
}
/* Allocate the SNP device */
snpdev = zalloc ( sizeof ( *snpdev ) );
if ( ! snpdev ) {
@@ -1807,9 +1799,13 @@ static int efi_snp_probe ( struct net_device *netdev, void *priv __unused ) {
goto err_alloc_snp;
}
snpdev->netdev = netdev_get ( netdev );
snpdev->efidev = efidev;
INIT_LIST_HEAD ( &snpdev->rx );
/* Find parent EFI device, if any */
efidev = efidev_parent ( netdev->dev );
if ( efidev )
snpdev->parent = efidev->device;
/* Sanity check */
if ( netdev->ll_protocol->ll_addr_len > sizeof ( EFI_MAC_ADDRESS ) ) {
DBGC ( snpdev, "SNPDEV %p cannot support link-layer address "
@@ -1931,8 +1927,10 @@ static int efi_snp_probe ( struct net_device *netdev, void *priv __unused ) {
goto err_open_nii31;
}
/* Add as child of EFI parent device */
if ( ( rc = efi_child_add ( efidev->device, snpdev->handle ) ) != 0 ) {
/* Add as child of EFI parent device, if applicable */
if ( snpdev->parent &&
( ( rc = efi_child_add ( snpdev->parent,
snpdev->handle ) ) != 0 ) ) {
DBGC ( snpdev, "SNPDEV %p could not become child of %s: %s\n",
snpdev, efi_handle_name ( efidev->device ),
strerror ( rc ) );
@@ -1959,7 +1957,8 @@ static int efi_snp_probe ( struct net_device *netdev, void *priv __unused ) {
list_del ( &snpdev->list );
if ( snpdev->package_list )
leak |= efi_snp_hii_uninstall ( snpdev );
efi_child_del ( efidev->device, snpdev->handle );
if ( snpdev->parent )
efi_child_del ( snpdev->parent, snpdev->handle );
err_efi_child_add:
efi_close_by_driver ( snpdev->handle, &efi_nii31_protocol_guid );
err_open_nii31:
@@ -1996,7 +1995,6 @@ static int efi_snp_probe ( struct net_device *netdev, void *priv __unused ) {
free ( snpdev );
}
err_alloc_snp:
err_no_efidev:
if ( leak )
DBGC ( snpdev, "SNPDEV %p nullified and leaked\n", snpdev );
return rc;
@@ -2051,7 +2049,8 @@ static void efi_snp_remove ( struct net_device *netdev, void *priv __unused ) {
list_del ( &snpdev->list );
if ( snpdev->package_list )
leak |= efi_snp_hii_uninstall ( snpdev );
efi_child_del ( snpdev->efidev->device, snpdev->handle );
if ( snpdev->parent )
efi_child_del ( snpdev->parent, snpdev->handle );
efi_close_by_driver ( snpdev->handle, &efi_nii_protocol_guid );
efi_close_by_driver ( snpdev->handle, &efi_nii31_protocol_guid );
if ( ( ! efi_shutdown_in_progress ) &&