| /* | |
| * 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 ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #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/settings.h> | |
| #include <ipxe/dhcp.h> | |
| #include <ipxe/ndp.h> | |
| #include <ipxe/efi/efi.h> | |
| #include <ipxe/efi/efi_driver.h> | |
| #include <ipxe/efi/efi_strings.h> | |
| #include <ipxe/efi/efi_path.h> | |
| /** @file | |
| * | |
| * EFI device paths | |
| * | |
| */ | |
| /** 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 */ | |
| struct settings settings; | |
| /** Device path */ | |
| EFI_DEVICE_PATH_PROTOCOL *path; | |
| }; | |
| /** An EFI device path setting */ | |
| struct efi_path_setting { | |
| /** Setting */ | |
| const struct setting *setting; | |
| /** | |
| * Fetch setting | |
| * | |
| * @v pathset Path setting | |
| * @v path Device path | |
| * @v data Buffer to fill with setting data | |
| * @v len Length of buffer | |
| * @ret len Length of setting data, or negative error | |
| */ | |
| int ( * fetch ) ( struct efi_path_setting *pathset, | |
| EFI_DEVICE_PATH_PROTOCOL *path, | |
| void *data, size_t len ); | |
| /** Path type */ | |
| uint8_t type; | |
| /** Path subtype */ | |
| uint8_t subtype; | |
| /** Offset within device path */ | |
| uint8_t offset; | |
| /** Length (if fixed) */ | |
| uint8_t len; | |
| }; | |
| /** | |
| * 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 ) { | |
| size_t len; | |
| /* 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; | |
| /* Treat an invalid length as ending the device path */ | |
| len = ( ( path->Length[1] << 8 ) | path->Length[0] ); | |
| if ( len < sizeof ( *path ) ) | |
| return NULL; | |
| /* Move to next component of the device path */ | |
| path = ( ( ( void * ) path ) + len ); | |
| 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 ) ); | |
| } | |
| /** | |
| * Check that device path is well-formed | |
| * | |
| * @v path Device path, or NULL | |
| * @v max Maximum device path length | |
| * @ret rc Return status code | |
| */ | |
| int efi_path_check ( EFI_DEVICE_PATH_PROTOCOL *path, size_t max ) { | |
| EFI_DEVICE_PATH_PROTOCOL *next; | |
| size_t remaining = max; | |
| size_t len; | |
| /* Check that path terminates within maximum length */ | |
| for ( ; ; path = next ) { | |
| if ( remaining < sizeof ( *path ) ) | |
| return -EINVAL; | |
| next = efi_path_next ( path ); | |
| if ( ! next ) | |
| break; | |
| len = ( ( ( void * ) next ) - ( ( void * ) path ) ); | |
| if ( remaining < len ) | |
| return -EINVAL; | |
| remaining -= len; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Get MAC address from device path | |
| * | |
| * @v path Device path | |
| * @ret mac MAC address, or NULL if not found | |
| */ | |
| void * efi_path_mac ( EFI_DEVICE_PATH_PROTOCOL *path ) { | |
| EFI_DEVICE_PATH_PROTOCOL *next; | |
| MAC_ADDR_DEVICE_PATH *mac; | |
| /* Search for MAC address path */ | |
| for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { | |
| if ( ( path->Type == MESSAGING_DEVICE_PATH ) && | |
| ( path->SubType == MSG_MAC_ADDR_DP ) ) { | |
| mac = container_of ( path, MAC_ADDR_DEVICE_PATH, | |
| Header ); | |
| return &mac->MacAddress; | |
| } | |
| } | |
| /* No MAC address found */ | |
| return NULL; | |
| } | |
| /** | |
| * 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 IP address family from device path | |
| * | |
| * @v path Device path | |
| * @ret family Address family, or 0 if not recognised | |
| */ | |
| unsigned int efi_path_family ( EFI_DEVICE_PATH_PROTOCOL *path ) { | |
| EFI_DEVICE_PATH_PROTOCOL *next; | |
| /* Search for IP address device path */ | |
| for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { | |
| if ( path->Type == MESSAGING_DEVICE_PATH ) { | |
| if ( path->SubType == MSG_IPv4_DP ) { | |
| return AF_INET; | |
| } else if ( path->SubType == MSG_IPv6_DP ) { | |
| return AF_INET6; | |
| } | |
| } | |
| } | |
| /* No IP address 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; | |
| } | |
| /** | |
| * Parse URI from device path | |
| * | |
| * @v path Device path | |
| * @ret uri URI, or NULL if not a URI | |
| */ | |
| struct uri * efi_path_uri ( EFI_DEVICE_PATH_PROTOCOL *path ) { | |
| EFI_DEVICE_PATH_PROTOCOL *next; | |
| URI_DEVICE_PATH *uripath; | |
| char *uristring; | |
| struct uri *uri; | |
| size_t len; | |
| /* Search for URI device path */ | |
| for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { | |
| if ( ( path->Type == MESSAGING_DEVICE_PATH ) && | |
| ( path->SubType == MSG_URI_DP ) ) { | |
| /* Calculate path length */ | |
| uripath = container_of ( path, URI_DEVICE_PATH, | |
| Header ); | |
| len = ( ( ( path->Length[1] << 8 ) | path->Length[0] ) | |
| - offsetof ( typeof ( *uripath ), Uri ) ); | |
| /* Parse URI */ | |
| uristring = zalloc ( len + 1 /* NUL */ ); | |
| if ( ! uristring ) | |
| return NULL; | |
| memcpy ( uristring, uripath->Uri, len ); | |
| uri = parse_uri ( uristring ); | |
| free ( uristring ); | |
| return uri; | |
| } | |
| } | |
| /* No URI path found */ | |
| return NULL; | |
| } | |
| /** | |
| * 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 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 | |
| * | |
| * @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 ) { | |
| EFI_DEVICE_PATH_PROTOCOL *parent; | |
| 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; | |
| /* Get parent EFI device path */ | |
| parent = efi_parent_path ( netdev->dev ); | |
| /* Calculate device path length */ | |
| prefix_len = efi_path_len ( parent ); | |
| len = ( prefix_len + sizeof ( *macpath ) + sizeof ( *vlanpath ) + | |
| sizeof ( *end ) ); | |
| /* Allocate device path */ | |
| path = zalloc ( len ); | |
| if ( ! path ) | |
| return NULL; | |
| /* Construct device path */ | |
| memcpy ( path, parent, 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 ); | |
| 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; | |
| /* Get parent EFI device path */ | |
| parent = efi_parent_path ( ib_srp->ibdev->dev ); | |
| /* Calculate device path length */ | |
| prefix_len = efi_path_len ( parent ); | |
| len = ( prefix_len + sizeof ( *ibpath ) + sizeof ( *end ) ); | |
| /* Allocate device path */ | |
| path = zalloc ( len ); | |
| if ( ! path ) | |
| return NULL; | |
| /* Construct device path */ | |
| memcpy ( path, parent, 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; | |
| EFI_DEVICE_PATH_PROTOCOL *parent; | |
| 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 ); | |
| /* 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 ( parent ); | |
| len = ( prefix_len + ( count * sizeof ( *usbpath ) ) + | |
| sizeof ( *end ) ); | |
| /* Allocate device path */ | |
| path = zalloc ( len ); | |
| if ( ! path ) | |
| return NULL; | |
| /* Construct device path */ | |
| memcpy ( path, parent, 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; | |
| } | |
| /** | |
| * Get EFI device path from load option | |
| * | |
| * @v load EFI load option | |
| * @v len Length of EFI load option | |
| * @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_load_path ( EFI_LOAD_OPTION *load, | |
| size_t len ) { | |
| EFI_DEVICE_PATH_PROTOCOL *path; | |
| EFI_DEVICE_PATH_PROTOCOL *copy; | |
| CHAR16 *wdesc; | |
| size_t path_max; | |
| size_t wmax; | |
| size_t wlen; | |
| /* Check basic structure size */ | |
| if ( len < sizeof ( *load ) ) { | |
| DBGC ( load, "EFI load option too short for header:\n" ); | |
| DBGC_HDA ( load, 0, load, len ); | |
| return NULL; | |
| } | |
| /* Get length of description */ | |
| wdesc = ( ( ( void * ) load ) + sizeof ( *load ) ); | |
| wmax = ( ( len - sizeof ( *load ) ) / sizeof ( wdesc[0] ) ); | |
| wlen = wcsnlen ( wdesc, wmax ); | |
| if ( wlen == wmax ) { | |
| DBGC ( load, "EFI load option has unterminated " | |
| "description:\n" ); | |
| DBGC_HDA ( load, 0, load, len ); | |
| return NULL; | |
| } | |
| /* Get inline device path */ | |
| path = ( ( ( void * ) load ) + sizeof ( *load ) + | |
| ( wlen * sizeof ( wdesc[0] ) ) + 2 /* wNUL */ ); | |
| path_max = ( len - sizeof ( *load ) - ( wlen * sizeof ( wdesc[0] ) ) | |
| - 2 /* wNUL */ ); | |
| if ( load->FilePathListLength > path_max ) { | |
| DBGC ( load, "EFI load option too short for path(s):\n" ); | |
| DBGC_HDA ( load, 0, load, len ); | |
| return NULL; | |
| } | |
| /* Check path length */ | |
| if ( efi_path_check ( path, path_max ) != 0 ) { | |
| DBGC ( load, "EFI load option has unterminated device " | |
| "path:\n" ); | |
| DBGC_HDA ( load, 0, load, len ); | |
| return NULL; | |
| } | |
| /* Allocate copy of path */ | |
| copy = malloc ( path_max ); | |
| if ( ! copy ) | |
| return NULL; | |
| memcpy ( copy, path, path_max ); | |
| return copy; | |
| } | |
| /** | |
| * Get EFI device path for numbered boot option | |
| * | |
| * @v number Boot option number | |
| * @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_boot_path ( unsigned int number ) { | |
| EFI_RUNTIME_SERVICES *rs = efi_systab->RuntimeServices; | |
| EFI_GUID *guid = &efi_global_variable; | |
| CHAR16 wname[ 9 /* "BootXXXX" + wNUL */ ]; | |
| EFI_LOAD_OPTION *load; | |
| EFI_DEVICE_PATH *path; | |
| UINT32 attrs; | |
| UINTN size; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Construct variable name */ | |
| efi_snprintf ( wname, ( sizeof ( wname ) / sizeof ( wname[0] ) ), | |
| "Boot%04X", number ); | |
| /* Get variable length */ | |
| size = 0; | |
| if ( ( efirc = rs->GetVariable ( wname, guid, &attrs, &size, | |
| NULL ) != EFI_BUFFER_TOO_SMALL ) ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( rs, "EFI could not get size of %ls: %s\n", | |
| wname, strerror ( rc ) ); | |
| goto err_size; | |
| } | |
| /* Allocate temporary buffer for EFI_LOAD_OPTION */ | |
| load = malloc ( size ); | |
| if ( ! load ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| /* Read variable */ | |
| if ( ( efirc = rs->GetVariable ( wname, guid, &attrs, &size, | |
| load ) != 0 ) ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( rs, "EFI could not read %ls: %s\n", | |
| wname, strerror ( rc ) ); | |
| goto err_read; | |
| } | |
| DBGC2 ( rs, "EFI boot option %ls is:\n", wname ); | |
| DBGC2_HDA ( rs, 0, load, size ); | |
| /* Get device path from load option */ | |
| path = efi_load_path ( load, size ); | |
| if ( ! path ) { | |
| rc = -EINVAL; | |
| DBGC ( rs, "EFI could not parse %ls: %s\n", | |
| wname, strerror ( rc ) ); | |
| goto err_path; | |
| } | |
| /* Free temporary buffer */ | |
| free ( load ); | |
| return path; | |
| err_path: | |
| err_read: | |
| free ( load ); | |
| err_alloc: | |
| err_size: | |
| return NULL; | |
| } | |
| /** | |
| * Get EFI device path for current boot option | |
| * | |
| * @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_current_boot_path ( void ) { | |
| EFI_RUNTIME_SERVICES *rs = efi_systab->RuntimeServices; | |
| EFI_GUID *guid = &efi_global_variable; | |
| CHAR16 wname[] = L"BootCurrent"; | |
| UINT16 current; | |
| UINT32 attrs; | |
| UINTN size; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Read current boot option index */ | |
| size = sizeof ( current ); | |
| if ( ( efirc = rs->GetVariable ( wname, guid, &attrs, &size, | |
| ¤t ) != 0 ) ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( rs, "EFI could not read %ls: %s\n", | |
| wname, strerror ( rc ) ); | |
| return NULL; | |
| } | |
| /* Get device path from this boot option */ | |
| return efi_boot_path ( current ); | |
| } | |
| /** | |
| * 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; | |
| } | |
| /** | |
| * Fetch an EFI device path fixed-size setting | |
| * | |
| * @v pathset Path setting | |
| * @v path Device path | |
| * @v data Buffer to fill with setting data | |
| * @v len Length of buffer | |
| * @ret len Length of setting data, or negative error | |
| */ | |
| static int efi_path_fetch_fixed ( struct efi_path_setting *pathset, | |
| EFI_DEVICE_PATH_PROTOCOL *path, | |
| void *data, size_t len ) { | |
| /* Copy data */ | |
| if ( len > pathset->len ) | |
| len = pathset->len; | |
| memcpy ( data, ( ( ( void * ) path ) + pathset->offset ), len ); | |
| return pathset->len; | |
| } | |
| /** | |
| * Fetch an EFI device path DNS setting | |
| * | |
| * @v pathset Path setting | |
| * @v path Device path | |
| * @v data Buffer to fill with setting data | |
| * @v len Length of buffer | |
| * @ret len Length of setting data, or negative error | |
| */ | |
| static int efi_path_fetch_dns ( struct efi_path_setting *pathset, | |
| EFI_DEVICE_PATH_PROTOCOL *path, | |
| void *data, size_t len ) { | |
| DNS_DEVICE_PATH *dns = container_of ( path, DNS_DEVICE_PATH, Header ); | |
| unsigned int count; | |
| unsigned int i; | |
| size_t frag_len; | |
| /* Check applicability */ | |
| if ( ( !! dns->IsIPv6 ) != | |
| ( pathset->setting->type == &setting_type_ipv6 ) ) | |
| return -ENOENT; | |
| /* Calculate number of addresses */ | |
| count = ( ( ( ( path->Length[1] << 8 ) | path->Length[0] ) - | |
| pathset->offset ) / sizeof ( dns->DnsServerIp[0] ) ); | |
| /* Copy data */ | |
| for ( i = 0 ; i < count ; i++ ) { | |
| frag_len = len; | |
| if ( frag_len > pathset->len ) | |
| frag_len = pathset->len; | |
| memcpy ( data, &dns->DnsServerIp[i], frag_len ); | |
| data += frag_len; | |
| len -= frag_len; | |
| } | |
| return ( count * pathset->len ); | |
| } | |
| /* Avoid dragging in IPv4, IPv6, and DNS support if not otherwise used */ | |
| extern const struct setting ip_setting __attribute__ (( weak )); | |
| extern const struct setting netmask_setting __attribute__ (( weak )); | |
| extern const struct setting gateway_setting __attribute__ (( weak )); | |
| extern const struct setting ip6_setting __attribute__ (( weak )); | |
| extern const struct setting len6_setting __attribute__ (( weak )); | |
| extern const struct setting gateway6_setting __attribute__ (( weak )); | |
| extern const struct setting dns_setting __attribute__ (( weak )); | |
| extern const struct setting dns6_setting __attribute__ (( weak )); | |
| /** EFI device path settings */ | |
| static struct efi_path_setting efi_path_settings[] = { | |
| { &ip_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, | |
| MSG_IPv4_DP, offsetof ( IPv4_DEVICE_PATH, LocalIpAddress ), | |
| sizeof ( struct in_addr ) }, | |
| { &netmask_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, | |
| MSG_IPv4_DP, offsetof ( IPv4_DEVICE_PATH, SubnetMask ), | |
| sizeof ( struct in_addr ) }, | |
| { &gateway_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, | |
| MSG_IPv4_DP, offsetof ( IPv4_DEVICE_PATH, GatewayIpAddress ), | |
| sizeof ( struct in_addr ) }, | |
| { &ip6_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, | |
| MSG_IPv6_DP, offsetof ( IPv6_DEVICE_PATH, LocalIpAddress ), | |
| sizeof ( struct in6_addr ) }, | |
| { &len6_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, | |
| MSG_IPv6_DP, offsetof ( IPv6_DEVICE_PATH, PrefixLength ), | |
| sizeof ( uint8_t ) }, | |
| { &gateway6_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, | |
| MSG_IPv6_DP, offsetof ( IPv6_DEVICE_PATH, GatewayIpAddress ), | |
| sizeof ( struct in6_addr ) }, | |
| { &dns_setting, efi_path_fetch_dns, MESSAGING_DEVICE_PATH, | |
| MSG_DNS_DP, offsetof ( DNS_DEVICE_PATH, DnsServerIp ), | |
| sizeof ( struct in_addr ) }, | |
| { &dns6_setting, efi_path_fetch_dns, MESSAGING_DEVICE_PATH, | |
| MSG_DNS_DP, offsetof ( DNS_DEVICE_PATH, DnsServerIp ), | |
| sizeof ( struct in6_addr ) }, | |
| }; | |
| /** | |
| * Fetch value of EFI device path 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 efi_path_fetch ( struct settings *settings, struct setting *setting, | |
| void *data, size_t len ) { | |
| struct efi_path_settings *pathsets = | |
| container_of ( settings, struct efi_path_settings, settings ); | |
| EFI_DEVICE_PATH_PROTOCOL *path = pathsets->path; | |
| EFI_DEVICE_PATH_PROTOCOL *next; | |
| struct efi_path_setting *pathset; | |
| unsigned int i; | |
| int ret; | |
| /* Find matching path setting, if any */ | |
| for ( i = 0 ; i < ( sizeof ( efi_path_settings ) / | |
| sizeof ( efi_path_settings[0] ) ) ; i++ ) { | |
| /* Check for a matching setting */ | |
| pathset = &efi_path_settings[i]; | |
| if ( ! pathset->setting ) | |
| continue; | |
| if ( setting_cmp ( setting, pathset->setting ) != 0 ) | |
| continue; | |
| /* Find matching device path element, if any */ | |
| for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { | |
| /* Check for a matching path type */ | |
| if ( ( path->Type != pathset->type ) || | |
| ( path->SubType != pathset->subtype ) ) | |
| continue; | |
| /* Fetch value */ | |
| if ( ( ret = pathset->fetch ( pathset, path, | |
| data, len ) ) < 0 ) | |
| return ret; | |
| /* Apply default type, if not already set */ | |
| if ( ! setting->type ) | |
| setting->type = pathset->setting->type; | |
| return ret; | |
| } | |
| break; | |
| } | |
| return -ENOENT; | |
| } | |
| /** EFI device path settings operations */ | |
| static struct settings_operations efi_path_settings_operations = { | |
| .fetch = efi_path_fetch, | |
| }; | |
| /** | |
| * Create per-netdevice EFI path settings | |
| * | |
| * @v netdev Network device | |
| * @v priv Private data | |
| * @ret rc Return status code | |
| */ | |
| static int efi_path_net_probe ( struct net_device *netdev, void *priv ) { | |
| struct efi_path_settings *pathsets = priv; | |
| struct settings *settings = &pathsets->settings; | |
| EFI_DEVICE_PATH_PROTOCOL *path = efi_loaded_image_path; | |
| const char *name; | |
| unsigned int vlan; | |
| unsigned int family; | |
| void *mac; | |
| int rc; | |
| /* Check applicability */ | |
| pathsets->path = path; | |
| mac = efi_path_mac ( path ); | |
| vlan = efi_path_vlan ( path ); | |
| family = efi_path_family ( path ); | |
| if ( ( mac == NULL ) || | |
| ( memcmp ( mac, netdev->ll_addr, | |
| netdev->ll_protocol->ll_addr_len ) != 0 ) || | |
| ( vlan != vlan_tag ( netdev ) ) ) { | |
| DBGC ( settings, "EFI path %s does not apply to %s\n", | |
| efi_devpath_text ( path ), netdev->name ); | |
| return 0; | |
| } | |
| /* Mark network device to be opened automatically */ | |
| netdev->state |= NETDEV_AUTO_OPEN; | |
| /* Determine settings block name */ | |
| switch ( family ) { | |
| case AF_INET: | |
| name = DHCP_SETTINGS_NAME; | |
| break; | |
| case AF_INET6: | |
| name = NDP_SETTINGS_NAME; | |
| break; | |
| default: | |
| DBGC ( settings, "EFI path %s has no IP address\n", | |
| efi_devpath_text ( path ) ); | |
| return 0; | |
| } | |
| /* Never override a real settings block */ | |
| if ( find_child_settings ( netdev_settings ( netdev ), name ) ) { | |
| DBGC ( settings, "EFI path %s not overriding %s.%s settings\n", | |
| efi_devpath_text ( path ), netdev->name, name ); | |
| return 0; | |
| } | |
| /* Initialise and register settings */ | |
| settings_init ( settings, &efi_path_settings_operations, | |
| &netdev->refcnt, NULL ); | |
| if ( ( rc = register_settings ( settings, netdev_settings ( netdev ), | |
| name ) ) != 0 ) { | |
| DBGC ( settings, "EFI path %s could not register %s.%s: %s\n", | |
| efi_devpath_text ( path ), netdev->name, name, | |
| strerror ( rc ) ); | |
| return rc; | |
| } | |
| DBGC ( settings, "EFI path %s registered %s.%s\n", | |
| efi_devpath_text ( path ), netdev->name, name ); | |
| return 0; | |
| } | |
| /** EFI path settings per-netdevice driver */ | |
| struct net_driver efi_path_net_driver __net_driver = { | |
| .name = "EFI path", | |
| .priv_len = sizeof ( struct efi_path_settings ), | |
| .probe = efi_path_net_probe, | |
| }; |