| /* |
| * Copyright (C) 2020 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. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER ); |
| |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <byteswap.h> |
| #include <ipxe/netdevice.h> |
| #include <ipxe/vlan.h> |
| #include <ipxe/uuid.h> |
| #include <ipxe/tcpip.h> |
| #include <ipxe/uri.h> |
| #include <ipxe/iscsi.h> |
| #include <ipxe/aoe.h> |
| #include <ipxe/fcp.h> |
| #include <ipxe/ib_srp.h> |
| #include <ipxe/usb.h> |
| #include <ipxe/efi/efi.h> |
| #include <ipxe/efi/efi_driver.h> |
| #include <ipxe/efi/efi_path.h> |
| |
| /** @file |
| * |
| * EFI device paths |
| * |
| */ |
| |
| /** |
| * Find next element in device path |
| * |
| * @v path Device path, or NULL |
| * @v next Next element in device path, or NULL if at end |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_path_next ( EFI_DEVICE_PATH_PROTOCOL *path ) { |
| |
| /* Check for non-existent device path */ |
| if ( ! path ) |
| return NULL; |
| |
| /* Check for end of device path */ |
| if ( path->Type == END_DEVICE_PATH_TYPE ) |
| return NULL; |
| |
| /* Move to next component of the device path */ |
| path = ( ( ( void * ) path ) + |
| /* There's this amazing new-fangled thing known as |
| * a UINT16, but who wants to use one of those? */ |
| ( ( path->Length[1] << 8 ) | path->Length[0] ) ); |
| |
| return path; |
| } |
| |
| /** |
| * Find previous element of device path |
| * |
| * @v path Device path, or NULL for no path |
| * @v curr Current element in device path, or NULL for end of path |
| * @ret prev Previous element in device path, or NULL |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_path_prev ( EFI_DEVICE_PATH_PROTOCOL *path, |
| EFI_DEVICE_PATH_PROTOCOL *curr ) { |
| EFI_DEVICE_PATH_PROTOCOL *tmp; |
| |
| /* Find immediately preceding element */ |
| while ( ( tmp = efi_path_next ( path ) ) != curr ) { |
| path = tmp; |
| } |
| |
| return path; |
| } |
| |
| /** |
| * Find end of device path |
| * |
| * @v path Device path, or NULL |
| * @ret path_end End of device path, or NULL |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_path_end ( EFI_DEVICE_PATH_PROTOCOL *path ) { |
| |
| return efi_path_prev ( path, NULL ); |
| } |
| |
| /** |
| * Find length of device path (excluding terminator) |
| * |
| * @v path Device path, or NULL |
| * @ret path_len Length of device path |
| */ |
| size_t efi_path_len ( EFI_DEVICE_PATH_PROTOCOL *path ) { |
| EFI_DEVICE_PATH_PROTOCOL *end = efi_path_end ( path ); |
| |
| return ( ( ( void * ) end ) - ( ( void * ) path ) ); |
| } |
| |
| /** |
| * Get VLAN tag from device path |
| * |
| * @v path Device path |
| * @ret tag VLAN tag, or 0 if not a VLAN |
| */ |
| unsigned int efi_path_vlan ( EFI_DEVICE_PATH_PROTOCOL *path ) { |
| EFI_DEVICE_PATH_PROTOCOL *next; |
| VLAN_DEVICE_PATH *vlan; |
| |
| /* Search for VLAN device path */ |
| for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { |
| if ( ( path->Type == MESSAGING_DEVICE_PATH ) && |
| ( path->SubType == MSG_VLAN_DP ) ) { |
| vlan = container_of ( path, VLAN_DEVICE_PATH, Header ); |
| return vlan->VlanId; |
| } |
| } |
| |
| /* No VLAN device path found */ |
| return 0; |
| } |
| |
| /** |
| * Get partition GUID from device path |
| * |
| * @v path Device path |
| * @v guid Partition GUID to fill in |
| * @ret rc Return status code |
| */ |
| int efi_path_guid ( EFI_DEVICE_PATH_PROTOCOL *path, union uuid *guid ) { |
| EFI_DEVICE_PATH_PROTOCOL *next; |
| HARDDRIVE_DEVICE_PATH *hd; |
| int rc; |
| |
| /* Search for most specific partition device path */ |
| rc = -ENOENT; |
| for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { |
| |
| /* Skip non-harddrive device paths */ |
| if ( path->Type != MEDIA_DEVICE_PATH ) |
| continue; |
| if ( path->SubType != MEDIA_HARDDRIVE_DP ) |
| continue; |
| |
| /* Skip non-GUID signatures */ |
| hd = container_of ( path, HARDDRIVE_DEVICE_PATH, Header ); |
| if ( hd->SignatureType != SIGNATURE_TYPE_GUID ) |
| continue; |
| |
| /* Extract GUID */ |
| memcpy ( guid, hd->Signature, sizeof ( *guid ) ); |
| uuid_mangle ( guid ); |
| |
| /* Record success, but continue searching in case |
| * there exists a more specific GUID (e.g. a partition |
| * GUID rather than a disk GUID). |
| */ |
| rc = 0; |
| } |
| |
| return rc; |
| } |
| |
| /** |
| * Concatenate EFI device paths |
| * |
| * @v ... List of device paths (NULL terminated) |
| * @ret path Concatenated device path, or NULL on error |
| * |
| * The caller is responsible for eventually calling free() on the |
| * allocated device path. |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_paths ( EFI_DEVICE_PATH_PROTOCOL *first, ... ) { |
| EFI_DEVICE_PATH_PROTOCOL *path; |
| EFI_DEVICE_PATH_PROTOCOL *src; |
| EFI_DEVICE_PATH_PROTOCOL *dst; |
| EFI_DEVICE_PATH_PROTOCOL *end; |
| va_list args; |
| size_t len; |
| |
| /* Calculate device path length */ |
| va_start ( args, first ); |
| len = 0; |
| src = first; |
| while ( src ) { |
| len += efi_path_len ( src ); |
| src = va_arg ( args, EFI_DEVICE_PATH_PROTOCOL * ); |
| } |
| va_end ( args ); |
| |
| /* Allocate device path */ |
| path = zalloc ( len + sizeof ( *end ) ); |
| if ( ! path ) |
| return NULL; |
| |
| /* Populate device path */ |
| va_start ( args, first ); |
| dst = path; |
| src = first; |
| while ( src ) { |
| len = efi_path_len ( src ); |
| memcpy ( dst, src, len ); |
| dst = ( ( ( void * ) dst ) + len ); |
| src = va_arg ( args, EFI_DEVICE_PATH_PROTOCOL * ); |
| } |
| va_end ( args ); |
| end = dst; |
| efi_path_terminate ( end ); |
| |
| return path; |
| } |
| |
| /** |
| * Construct EFI device path for network device |
| * |
| * @v netdev Network device |
| * @ret path EFI device path, or NULL on error |
| * |
| * The caller is responsible for eventually calling free() on the |
| * allocated device path. |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_netdev_path ( struct net_device *netdev ) { |
| struct efi_device *efidev; |
| EFI_DEVICE_PATH_PROTOCOL *path; |
| MAC_ADDR_DEVICE_PATH *macpath; |
| VLAN_DEVICE_PATH *vlanpath; |
| EFI_DEVICE_PATH_PROTOCOL *end; |
| unsigned int tag; |
| size_t prefix_len; |
| size_t len; |
| |
| /* Find parent EFI device */ |
| efidev = efidev_parent ( netdev->dev ); |
| if ( ! efidev ) |
| return NULL; |
| |
| /* Calculate device path length */ |
| prefix_len = efi_path_len ( efidev->path ); |
| len = ( prefix_len + sizeof ( *macpath ) + sizeof ( *vlanpath ) + |
| sizeof ( *end ) ); |
| |
| /* Allocate device path */ |
| path = zalloc ( len ); |
| if ( ! path ) |
| return NULL; |
| |
| /* Construct device path */ |
| memcpy ( path, efidev->path, prefix_len ); |
| macpath = ( ( ( void * ) path ) + prefix_len ); |
| macpath->Header.Type = MESSAGING_DEVICE_PATH; |
| macpath->Header.SubType = MSG_MAC_ADDR_DP; |
| macpath->Header.Length[0] = sizeof ( *macpath ); |
| assert ( netdev->ll_protocol->ll_addr_len < |
| sizeof ( macpath->MacAddress ) ); |
| memcpy ( &macpath->MacAddress, netdev->ll_addr, |
| netdev->ll_protocol->ll_addr_len ); |
| macpath->IfType = ntohs ( netdev->ll_protocol->ll_proto ); |
| if ( ( tag = vlan_tag ( netdev ) ) ) { |
| vlanpath = ( ( ( void * ) macpath ) + sizeof ( *macpath ) ); |
| vlanpath->Header.Type = MESSAGING_DEVICE_PATH; |
| vlanpath->Header.SubType = MSG_VLAN_DP; |
| vlanpath->Header.Length[0] = sizeof ( *vlanpath ); |
| vlanpath->VlanId = tag; |
| end = ( ( ( void * ) vlanpath ) + sizeof ( *vlanpath ) ); |
| } else { |
| end = ( ( ( void * ) macpath ) + sizeof ( *macpath ) ); |
| } |
| efi_path_terminate ( end ); |
| |
| return path; |
| } |
| |
| /** |
| * Construct EFI device path for URI |
| * |
| * @v uri URI |
| * @ret path EFI device path, or NULL on error |
| * |
| * The caller is responsible for eventually calling free() on the |
| * allocated device path. |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_uri_path ( struct uri *uri ) { |
| EFI_DEVICE_PATH_PROTOCOL *path; |
| EFI_DEVICE_PATH_PROTOCOL *end; |
| URI_DEVICE_PATH *uripath; |
| size_t uri_len; |
| size_t uripath_len; |
| size_t len; |
| |
| /* Calculate device path length */ |
| uri_len = ( format_uri ( uri, NULL, 0 ) + 1 /* NUL */ ); |
| uripath_len = ( sizeof ( *uripath ) + uri_len ); |
| len = ( uripath_len + sizeof ( *end ) ); |
| |
| /* Allocate device path */ |
| path = zalloc ( len ); |
| if ( ! path ) |
| return NULL; |
| |
| /* Construct device path */ |
| uripath = ( ( void * ) path ); |
| uripath->Header.Type = MESSAGING_DEVICE_PATH; |
| uripath->Header.SubType = MSG_URI_DP; |
| uripath->Header.Length[0] = ( uripath_len & 0xff ); |
| uripath->Header.Length[1] = ( uripath_len >> 8 ); |
| format_uri ( uri, uripath->Uri, uri_len ); |
| end = ( ( ( void * ) path ) + uripath_len ); |
| efi_path_terminate ( end ); |
| |
| return path; |
| } |
| |
| /** |
| * Construct EFI device path for iSCSI device |
| * |
| * @v iscsi iSCSI session |
| * @ret path EFI device path, or NULL on error |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_iscsi_path ( struct iscsi_session *iscsi ) { |
| struct sockaddr_tcpip *st_target; |
| struct net_device *netdev; |
| EFI_DEVICE_PATH_PROTOCOL *netpath; |
| EFI_DEVICE_PATH_PROTOCOL *path; |
| EFI_DEVICE_PATH_PROTOCOL *end; |
| ISCSI_DEVICE_PATH *iscsipath; |
| char *name; |
| size_t prefix_len; |
| size_t name_len; |
| size_t iscsi_len; |
| size_t len; |
| |
| /* Get network device associated with target address */ |
| st_target = ( ( struct sockaddr_tcpip * ) &iscsi->target_sockaddr ); |
| netdev = tcpip_netdev ( st_target ); |
| if ( ! netdev ) |
| goto err_netdev; |
| |
| /* Get network device path */ |
| netpath = efi_netdev_path ( netdev ); |
| if ( ! netpath ) |
| goto err_netpath; |
| |
| /* Calculate device path length */ |
| prefix_len = efi_path_len ( netpath ); |
| name_len = ( strlen ( iscsi->target_iqn ) + 1 /* NUL */ ); |
| iscsi_len = ( sizeof ( *iscsipath ) + name_len ); |
| len = ( prefix_len + iscsi_len + sizeof ( *end ) ); |
| |
| /* Allocate device path */ |
| path = zalloc ( len ); |
| if ( ! path ) |
| goto err_alloc; |
| |
| /* Construct device path */ |
| memcpy ( path, netpath, prefix_len ); |
| iscsipath = ( ( ( void * ) path ) + prefix_len ); |
| iscsipath->Header.Type = MESSAGING_DEVICE_PATH; |
| iscsipath->Header.SubType = MSG_ISCSI_DP; |
| iscsipath->Header.Length[0] = iscsi_len; |
| iscsipath->LoginOption = ISCSI_LOGIN_OPTION_AUTHMETHOD_NON; |
| memcpy ( &iscsipath->Lun, &iscsi->lun, sizeof ( iscsipath->Lun ) ); |
| name = ( ( ( void * ) iscsipath ) + sizeof ( *iscsipath ) ); |
| memcpy ( name, iscsi->target_iqn, name_len ); |
| end = ( ( ( void * ) name ) + name_len ); |
| efi_path_terminate ( end ); |
| |
| /* Free temporary paths */ |
| free ( netpath ); |
| |
| return path; |
| |
| err_alloc: |
| free ( netpath ); |
| err_netpath: |
| err_netdev: |
| return NULL; |
| } |
| |
| /** |
| * Construct EFI device path for AoE device |
| * |
| * @v aoedev AoE device |
| * @ret path EFI device path, or NULL on error |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_aoe_path ( struct aoe_device *aoedev ) { |
| struct { |
| SATA_DEVICE_PATH sata; |
| EFI_DEVICE_PATH_PROTOCOL end; |
| } satapath; |
| EFI_DEVICE_PATH_PROTOCOL *netpath; |
| EFI_DEVICE_PATH_PROTOCOL *path; |
| |
| /* Get network device path */ |
| netpath = efi_netdev_path ( aoedev->netdev ); |
| if ( ! netpath ) |
| goto err_netdev; |
| |
| /* Construct SATA path */ |
| memset ( &satapath, 0, sizeof ( satapath ) ); |
| satapath.sata.Header.Type = MESSAGING_DEVICE_PATH; |
| satapath.sata.Header.SubType = MSG_SATA_DP; |
| satapath.sata.Header.Length[0] = sizeof ( satapath.sata ); |
| satapath.sata.HBAPortNumber = aoedev->major; |
| satapath.sata.PortMultiplierPortNumber = aoedev->minor; |
| efi_path_terminate ( &satapath.end ); |
| |
| /* Construct overall device path */ |
| path = efi_paths ( netpath, &satapath, NULL ); |
| if ( ! path ) |
| goto err_paths; |
| |
| /* Free temporary paths */ |
| free ( netpath ); |
| |
| return path; |
| |
| err_paths: |
| free ( netpath ); |
| err_netdev: |
| return NULL; |
| } |
| |
| /** |
| * Construct EFI device path for Fibre Channel device |
| * |
| * @v desc FCP device description |
| * @ret path EFI device path, or NULL on error |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_fcp_path ( struct fcp_description *desc ) { |
| struct { |
| FIBRECHANNELEX_DEVICE_PATH fc; |
| EFI_DEVICE_PATH_PROTOCOL end; |
| } __attribute__ (( packed )) *path; |
| |
| /* Allocate device path */ |
| path = zalloc ( sizeof ( *path ) ); |
| if ( ! path ) |
| return NULL; |
| |
| /* Construct device path */ |
| path->fc.Header.Type = MESSAGING_DEVICE_PATH; |
| path->fc.Header.SubType = MSG_FIBRECHANNELEX_DP; |
| path->fc.Header.Length[0] = sizeof ( path->fc ); |
| memcpy ( path->fc.WWN, &desc->wwn, sizeof ( path->fc.WWN ) ); |
| memcpy ( path->fc.Lun, &desc->lun, sizeof ( path->fc.Lun ) ); |
| efi_path_terminate ( &path->end ); |
| |
| return &path->fc.Header; |
| } |
| |
| /** |
| * Construct EFI device path for Infiniband SRP device |
| * |
| * @v ib_srp Infiniband SRP device |
| * @ret path EFI device path, or NULL on error |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_ib_srp_path ( struct ib_srp_device *ib_srp ) { |
| const struct ipxe_ib_sbft *sbft = &ib_srp->sbft; |
| 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 *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; |
| |
| /* Calculate device path length */ |
| prefix_len = efi_path_len ( efidev->path ); |
| len = ( prefix_len + sizeof ( *ibpath ) + sizeof ( *end ) ); |
| |
| /* Allocate device path */ |
| path = zalloc ( len ); |
| if ( ! path ) |
| return NULL; |
| |
| /* Construct device path */ |
| memcpy ( path, efidev->path, prefix_len ); |
| ibpath = ( ( ( void * ) path ) + prefix_len ); |
| ibpath->Header.Type = MESSAGING_DEVICE_PATH; |
| ibpath->Header.SubType = MSG_INFINIBAND_DP; |
| ibpath->Header.Length[0] = sizeof ( *ibpath ); |
| ibpath->ResourceFlags = INFINIBAND_RESOURCE_FLAG_STORAGE_PROTOCOL; |
| memcpy ( ibpath->PortGid, &sbft->ib.dgid, sizeof ( ibpath->PortGid ) ); |
| memcpy ( &ibpath->ServiceId, &sbft->ib.service_id, |
| sizeof ( ibpath->ServiceId ) ); |
| memcpy ( &ibpath->TargetPortId, &id->ib.ioc_guid, |
| sizeof ( ibpath->TargetPortId ) ); |
| memcpy ( &ibpath->DeviceId, &id->ib.id_ext, |
| sizeof ( ibpath->DeviceId ) ); |
| end = ( ( ( void * ) ibpath ) + sizeof ( *ibpath ) ); |
| efi_path_terminate ( end ); |
| |
| return path; |
| } |
| |
| /** |
| * Construct EFI device path for USB function |
| * |
| * @v func USB function |
| * @ret path EFI device path, or NULL on error |
| * |
| * The caller is responsible for eventually calling free() on the |
| * allocated device path. |
| */ |
| 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 *path; |
| EFI_DEVICE_PATH_PROTOCOL *end; |
| USB_DEVICE_PATH *usbpath; |
| unsigned int count; |
| size_t prefix_len; |
| size_t len; |
| |
| /* Sanity check */ |
| assert ( func->desc.count >= 1 ); |
| |
| /* Find parent EFI device */ |
| efidev = efidev_parent ( &func->dev ); |
| if ( ! efidev ) |
| return NULL; |
| |
| /* Calculate device path length */ |
| count = ( usb_depth ( usb ) + 1 ); |
| prefix_len = efi_path_len ( efidev->path ); |
| len = ( prefix_len + ( count * sizeof ( *usbpath ) ) + |
| sizeof ( *end ) ); |
| |
| /* Allocate device path */ |
| path = zalloc ( len ); |
| if ( ! path ) |
| return NULL; |
| |
| /* Construct device path */ |
| memcpy ( path, efidev->path, prefix_len ); |
| end = ( ( ( void * ) path ) + len - sizeof ( *end ) ); |
| efi_path_terminate ( end ); |
| usbpath = ( ( ( void * ) end ) - sizeof ( *usbpath ) ); |
| usbpath->InterfaceNumber = func->interface[0]; |
| for ( ; usb ; usbpath--, usb = usb->port->hub->usb ) { |
| usbpath->Header.Type = MESSAGING_DEVICE_PATH; |
| usbpath->Header.SubType = MSG_USB_DP; |
| usbpath->Header.Length[0] = sizeof ( *usbpath ); |
| usbpath->ParentPortNumber = ( usb->port->address - 1 ); |
| } |
| |
| return path; |
| } |
| |
| /** |
| * Describe object as an EFI device path |
| * |
| * @v intf Interface |
| * @ret path EFI device path, or NULL |
| * |
| * The caller is responsible for eventually calling free() on the |
| * allocated device path. |
| */ |
| EFI_DEVICE_PATH_PROTOCOL * efi_describe ( struct interface *intf ) { |
| struct interface *dest; |
| efi_describe_TYPE ( void * ) *op = |
| intf_get_dest_op ( intf, efi_describe, &dest ); |
| void *object = intf_object ( dest ); |
| EFI_DEVICE_PATH_PROTOCOL *path; |
| |
| if ( op ) { |
| path = op ( object ); |
| } else { |
| path = NULL; |
| } |
| |
| intf_put ( dest ); |
| return path; |
| } |