diff options
Diffstat (limited to 'src/interface/efi/efi_path.c')
-rw-r--r-- | src/interface/efi/efi_path.c | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index 23f1bb84d..ac3c04987 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -34,6 +34,8 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <ipxe/fcp.h> #include <ipxe/ib_srp.h> #include <ipxe/usb.h> +#include <ipxe/settings.h> +#include <ipxe/dhcp.h> #include <ipxe/efi/efi.h> #include <ipxe/efi/efi_driver.h> #include <ipxe/efi/efi_path.h> @@ -44,6 +46,40 @@ FILE_LICENCE ( GPL2_OR_LATER ); * */ +/** 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 * @@ -657,3 +693,208 @@ EFI_DEVICE_PATH_PROTOCOL * efi_describe ( struct interface *intf ) { 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 ); +} + +/** 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 ( 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; + unsigned int vlan; + void *mac; + int rc; + + /* Check applicability */ + pathsets->path = path; + mac = efi_path_mac ( path ); + vlan = efi_path_vlan ( 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; + } + + /* Never override a real DHCP settings block */ + if ( find_child_settings ( netdev_settings ( netdev ), + DHCP_SETTINGS_NAME ) ) { + DBGC ( settings, "EFI path %s not overriding %s DHCP " + "settings\n", efi_devpath_text ( path ), netdev->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 ), + DHCP_SETTINGS_NAME ) ) != 0 ) { + DBGC ( settings, "EFI path %s could not register for %s: %s\n", + efi_devpath_text ( path ), netdev->name, + strerror ( rc ) ); + return rc; + } + DBGC ( settings, "EFI path %s registered for %s\n", + efi_devpath_text ( path ), netdev->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, +}; |