[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; struct list_head list;
/** The underlying iPXE network device */ /** The underlying iPXE network device */
struct net_device *netdev; struct net_device *netdev;
/** The underlying EFI device */ /** EFI parent device handle (if any) */
struct efi_device *efidev; EFI_HANDLE parent;
/** EFI device handle */ /** EFI device handle */
EFI_HANDLE handle; EFI_HANDLE handle;
/** The SNP structure itself */ /** 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 */ /** An EFI device path settings block */
struct efi_path_settings { struct efi_path_settings {
/** Settings interface */ /** Settings interface */
@@ -352,6 +380,24 @@ EFI_DEVICE_PATH_PROTOCOL * efi_paths ( EFI_DEVICE_PATH_PROTOCOL *first, ... ) {
return path; 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 * 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. * allocated device path.
*/ */
EFI_DEVICE_PATH_PROTOCOL * efi_netdev_path ( struct net_device *netdev ) { 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; EFI_DEVICE_PATH_PROTOCOL *path;
MAC_ADDR_DEVICE_PATH *macpath; MAC_ADDR_DEVICE_PATH *macpath;
VLAN_DEVICE_PATH *vlanpath; 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 prefix_len;
size_t len; size_t len;
/* Find parent EFI device */ /* Get parent EFI device path */
efidev = efidev_parent ( netdev->dev ); parent = efi_parent_path ( netdev->dev );
if ( ! efidev )
return NULL;
/* Calculate device path length */ /* Calculate device path length */
prefix_len = efi_path_len ( efidev->path ); prefix_len = efi_path_len ( parent );
len = ( prefix_len + sizeof ( *macpath ) + sizeof ( *vlanpath ) + len = ( prefix_len + sizeof ( *macpath ) + sizeof ( *vlanpath ) +
sizeof ( *end ) ); sizeof ( *end ) );
@@ -387,7 +431,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_netdev_path ( struct net_device *netdev ) {
return NULL; return NULL;
/* Construct device path */ /* Construct device path */
memcpy ( path, efidev->path, prefix_len ); memcpy ( path, parent, prefix_len );
macpath = ( ( ( void * ) path ) + prefix_len ); macpath = ( ( ( void * ) path ) + prefix_len );
macpath->Header.Type = MESSAGING_DEVICE_PATH; macpath->Header.Type = MESSAGING_DEVICE_PATH;
macpath->Header.SubType = MSG_MAC_ADDR_DP; 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 = union ib_srp_target_port_id *id =
container_of ( &sbft->srp.target, union ib_srp_target_port_id, container_of ( &sbft->srp.target, union ib_srp_target_port_id,
srp ); srp );
struct efi_device *efidev; EFI_DEVICE_PATH_PROTOCOL *parent;
EFI_DEVICE_PATH_PROTOCOL *path; EFI_DEVICE_PATH_PROTOCOL *path;
INFINIBAND_DEVICE_PATH *ibpath; INFINIBAND_DEVICE_PATH *ibpath;
EFI_DEVICE_PATH_PROTOCOL *end; EFI_DEVICE_PATH_PROTOCOL *end;
size_t prefix_len; size_t prefix_len;
size_t len; size_t len;
/* Find parent EFI device */ /* Get parent EFI device path */
efidev = efidev_parent ( ib_srp->ibdev->dev ); parent = efi_parent_path ( ib_srp->ibdev->dev );
if ( ! efidev )
return NULL;
/* Calculate device path length */ /* Calculate device path length */
prefix_len = efi_path_len ( efidev->path ); prefix_len = efi_path_len ( parent );
len = ( prefix_len + sizeof ( *ibpath ) + sizeof ( *end ) ); len = ( prefix_len + sizeof ( *ibpath ) + sizeof ( *end ) );
/* Allocate device path */ /* Allocate device path */
@@ -623,7 +665,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_ib_srp_path ( struct ib_srp_device *ib_srp ) {
return NULL; return NULL;
/* Construct device path */ /* Construct device path */
memcpy ( path, efidev->path, prefix_len ); memcpy ( path, parent, prefix_len );
ibpath = ( ( ( void * ) path ) + prefix_len ); ibpath = ( ( ( void * ) path ) + prefix_len );
ibpath->Header.Type = MESSAGING_DEVICE_PATH; ibpath->Header.Type = MESSAGING_DEVICE_PATH;
ibpath->Header.SubType = MSG_INFINIBAND_DP; 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 ) { EFI_DEVICE_PATH_PROTOCOL * efi_usb_path ( struct usb_function *func ) {
struct usb_device *usb = func->usb; struct usb_device *usb = func->usb;
struct efi_device *efidev; EFI_DEVICE_PATH_PROTOCOL *parent;
EFI_DEVICE_PATH_PROTOCOL *path; EFI_DEVICE_PATH_PROTOCOL *path;
EFI_DEVICE_PATH_PROTOCOL *end; EFI_DEVICE_PATH_PROTOCOL *end;
USB_DEVICE_PATH *usbpath; USB_DEVICE_PATH *usbpath;
@@ -664,14 +706,12 @@ EFI_DEVICE_PATH_PROTOCOL * efi_usb_path ( struct usb_function *func ) {
/* Sanity check */ /* Sanity check */
assert ( func->desc.count >= 1 ); assert ( func->desc.count >= 1 );
/* Find parent EFI device */ /* Get parent EFI device path */
efidev = efidev_parent ( &func->dev ); parent = efi_parent_path ( &func->dev );
if ( ! efidev )
return NULL;
/* Calculate device path length */ /* Calculate device path length */
count = ( usb_depth ( usb ) + 1 ); count = ( usb_depth ( usb ) + 1 );
prefix_len = efi_path_len ( efidev->path ); prefix_len = efi_path_len ( parent );
len = ( prefix_len + ( count * sizeof ( *usbpath ) ) + len = ( prefix_len + ( count * sizeof ( *usbpath ) ) +
sizeof ( *end ) ); sizeof ( *end ) );
@@ -681,7 +721,7 @@ EFI_DEVICE_PATH_PROTOCOL * efi_usb_path ( struct usb_function *func ) {
return NULL; return NULL;
/* Construct device path */ /* Construct device path */
memcpy ( path, efidev->path, prefix_len ); memcpy ( path, parent, prefix_len );
end = ( ( ( void * ) path ) + len - sizeof ( *end ) ); end = ( ( ( void * ) path ) + len - sizeof ( *end ) );
efi_path_terminate ( end ); efi_path_terminate ( end );
usbpath = ( ( ( void * ) end ) - sizeof ( *usbpath ) ); 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; EFI_STATUS efirc;
int rc; 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 */ /* Allocate the SNP device */
snpdev = zalloc ( sizeof ( *snpdev ) ); snpdev = zalloc ( sizeof ( *snpdev ) );
if ( ! snpdev ) { if ( ! snpdev ) {
@@ -1807,9 +1799,13 @@ static int efi_snp_probe ( struct net_device *netdev, void *priv __unused ) {
goto err_alloc_snp; goto err_alloc_snp;
} }
snpdev->netdev = netdev_get ( netdev ); snpdev->netdev = netdev_get ( netdev );
snpdev->efidev = efidev;
INIT_LIST_HEAD ( &snpdev->rx ); INIT_LIST_HEAD ( &snpdev->rx );
/* Find parent EFI device, if any */
efidev = efidev_parent ( netdev->dev );
if ( efidev )
snpdev->parent = efidev->device;
/* Sanity check */ /* Sanity check */
if ( netdev->ll_protocol->ll_addr_len > sizeof ( EFI_MAC_ADDRESS ) ) { if ( netdev->ll_protocol->ll_addr_len > sizeof ( EFI_MAC_ADDRESS ) ) {
DBGC ( snpdev, "SNPDEV %p cannot support link-layer 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; goto err_open_nii31;
} }
/* Add as child of EFI parent device */ /* Add as child of EFI parent device, if applicable */
if ( ( rc = efi_child_add ( efidev->device, snpdev->handle ) ) != 0 ) { if ( snpdev->parent &&
( ( rc = efi_child_add ( snpdev->parent,
snpdev->handle ) ) != 0 ) ) {
DBGC ( snpdev, "SNPDEV %p could not become child of %s: %s\n", DBGC ( snpdev, "SNPDEV %p could not become child of %s: %s\n",
snpdev, efi_handle_name ( efidev->device ), snpdev, efi_handle_name ( efidev->device ),
strerror ( rc ) ); strerror ( rc ) );
@@ -1959,7 +1957,8 @@ static int efi_snp_probe ( struct net_device *netdev, void *priv __unused ) {
list_del ( &snpdev->list ); list_del ( &snpdev->list );
if ( snpdev->package_list ) if ( snpdev->package_list )
leak |= efi_snp_hii_uninstall ( snpdev ); 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: err_efi_child_add:
efi_close_by_driver ( snpdev->handle, &efi_nii31_protocol_guid ); efi_close_by_driver ( snpdev->handle, &efi_nii31_protocol_guid );
err_open_nii31: err_open_nii31:
@@ -1996,7 +1995,6 @@ static int efi_snp_probe ( struct net_device *netdev, void *priv __unused ) {
free ( snpdev ); free ( snpdev );
} }
err_alloc_snp: err_alloc_snp:
err_no_efidev:
if ( leak ) if ( leak )
DBGC ( snpdev, "SNPDEV %p nullified and leaked\n", snpdev ); DBGC ( snpdev, "SNPDEV %p nullified and leaked\n", snpdev );
return rc; return rc;
@@ -2051,7 +2049,8 @@ static void efi_snp_remove ( struct net_device *netdev, void *priv __unused ) {
list_del ( &snpdev->list ); list_del ( &snpdev->list );
if ( snpdev->package_list ) if ( snpdev->package_list )
leak |= efi_snp_hii_uninstall ( snpdev ); 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_nii_protocol_guid );
efi_close_by_driver ( snpdev->handle, &efi_nii31_protocol_guid ); efi_close_by_driver ( snpdev->handle, &efi_nii31_protocol_guid );
if ( ( ! efi_shutdown_in_progress ) && if ( ( ! efi_shutdown_in_progress ) &&