diff options
Diffstat (limited to 'lib/efi_loader/efi_net.c')
| -rw-r--r-- | lib/efi_loader/efi_net.c | 371 | 
1 files changed, 365 insertions, 6 deletions
| diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c index 7cd536705f4..67593ef50c0 100644 --- a/lib/efi_loader/efi_net.c +++ b/lib/efi_loader/efi_net.c @@ -16,7 +16,10 @@   */  #include <efi_loader.h> +#include <dm.h> +#include <linux/sizes.h>  #include <malloc.h> +#include <vsprintf.h>  #include <net.h>  static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_PROTOCOL_GUID; @@ -32,6 +35,19 @@ static int rx_packet_num;  static struct efi_net_obj *netobj;  /* + * The current network device path. This device path is updated when a new + * bootfile is downloaded from the network. If then the bootfile is loaded + * as an efi image, net_dp is passed as the device path of the loaded image. + */ +static struct efi_device_path *net_dp; + +static struct wget_http_info efi_wget_info = { +	.set_bootdev = false, +	.check_buffer_size = true, + +}; + +/*   * The notification function of this event is called in every timer cycle   * to check if a new network packet has been received.   */ @@ -44,11 +60,13 @@ static struct efi_event *wait_for_packet;  /**   * struct efi_net_obj - EFI object representing a network interface   * - * @header:	EFI object header - * @net:	simple network protocol interface - * @net_mode:	status of the network interface - * @pxe:	PXE base code protocol interface - * @pxe_mode:	status of the PXE base code protocol + * @header:			EFI object header + * @net:			simple network protocol interface + * @net_mode:			status of the network interface + * @pxe:			PXE base code protocol interface + * @pxe_mode:			status of the PXE base code protocol + * @ip4_config2:		IP4 Config2 protocol interface + * @http_service_binding:	Http service binding protocol interface   */  struct efi_net_obj {  	struct efi_object header; @@ -56,6 +74,12 @@ struct efi_net_obj {  	struct efi_simple_network_mode net_mode;  	struct efi_pxe_base_code_protocol pxe;  	struct efi_pxe_mode pxe_mode; +#if IS_ENABLED(CONFIG_EFI_IP4_CONFIG2_PROTOCOL) +	struct efi_ip4_config2_protocol ip4_config2; +#endif +#if IS_ENABLED(CONFIG_EFI_HTTP_PROTOCOL) +	struct efi_service_binding_protocol http_service_binding; +#endif  };  /* @@ -901,8 +925,10 @@ efi_status_t efi_net_register(void)  			     &netobj->net);  	if (r != EFI_SUCCESS)  		goto failure_to_add_protocol; +	if (!net_dp) +		efi_net_set_dp("Net", NULL);  	r = efi_add_protocol(&netobj->header, &efi_guid_device_path, -			     efi_dp_from_eth()); +			     net_dp);  	if (r != EFI_SUCCESS)  		goto failure_to_add_protocol;  	r = efi_add_protocol(&netobj->header, &efi_pxe_base_code_protocol_guid, @@ -981,6 +1007,25 @@ efi_status_t efi_net_register(void)  		return r;  	} +#if IS_ENABLED(CONFIG_EFI_IP4_CONFIG2_PROTOCOL) +	r = efi_ipconfig_register(&netobj->header, &netobj->ip4_config2); +	if (r != EFI_SUCCESS) +		goto failure_to_add_protocol; +#endif + +#ifdef CONFIG_EFI_HTTP_PROTOCOL +	r = efi_http_register(&netobj->header, &netobj->http_service_binding); +	if (r != EFI_SUCCESS) +		goto failure_to_add_protocol; +	/* +	 * No harm on doing the following. If the PXE handle is present, the client could +	 * find it and try to get its IP address from it. In here the PXE handle is present +	 * but the PXE protocol is not yet implmenented, so we add this in the meantime. +	 */ +	efi_net_get_addr((struct efi_ipv4_address *)&netobj->pxe_mode.station_ip, +			 (struct efi_ipv4_address *)&netobj->pxe_mode.subnet_mask, NULL); +#endif +  	return EFI_SUCCESS;  failure_to_add_protocol:  	printf("ERROR: Failure to add protocol\n"); @@ -997,3 +1042,317 @@ out_of_resources:  	printf("ERROR: Out of memory\n");  	return EFI_OUT_OF_RESOURCES;  } + +/** + * efi_net_set_dp() - set device path of efi net device + * + * This gets called to update the device path when a new boot + * file is downloaded + * + * @dev:	dev to set the device path from + * @server:	remote server address + * Return:	status code + */ +efi_status_t efi_net_set_dp(const char *dev, const char *server) +{ +	efi_free_pool(net_dp); + +	net_dp = NULL; +	if (!strcmp(dev, "Net")) +		net_dp = efi_dp_from_eth(); +	else if (!strcmp(dev, "Http")) +		net_dp = efi_dp_from_http(server); + +	if (!net_dp) +		return EFI_OUT_OF_RESOURCES; + +	return EFI_SUCCESS; +} + +/** + * efi_net_get_dp() - get device path of efi net device + * + * Produce a copy of the current device path + * + * @dp:		copy of the current device path, or NULL on error + */ +void efi_net_get_dp(struct efi_device_path **dp) +{ +	if (!dp) +		return; +	if (!net_dp) +		efi_net_set_dp("Net", NULL); +	if (net_dp) +		*dp = efi_dp_dup(net_dp); +} + +/** + * efi_net_get_addr() - get IP address information + * + * Copy the current IP address, mask, and gateway into the + * efi_ipv4_address structs pointed to by ip, mask and gw, + * respectively. + * + * @ip:		pointer to an efi_ipv4_address struct to + *		be filled with the current IP address + * @mask:	pointer to an efi_ipv4_address struct to + *		be filled with the current network mask + * @gw:		pointer to an efi_ipv4_address struct to be + *		filled with the current network gateway + */ +void efi_net_get_addr(struct efi_ipv4_address *ip, +		      struct efi_ipv4_address *mask, +		      struct efi_ipv4_address *gw) +{ +#ifdef CONFIG_NET_LWIP +	char ipstr[] = "ipaddr\0\0"; +	char maskstr[] = "netmask\0\0"; +	char gwstr[] = "gatewayip\0\0"; +	int idx; +	struct in_addr tmp; +	char *env; + +	idx = dev_seq(eth_get_dev()); + +	if (idx < 0 || idx > 99) { +		log_err("unexpected idx %d\n", idx); +		return; +	} + +	if (idx) { +		sprintf(ipstr, "ipaddr%d", idx); +		sprintf(maskstr, "netmask%d", idx); +		sprintf(gwstr, "gatewayip%d", idx); +	} + +	env = env_get(ipstr); +	if (env && ip) { +		tmp = string_to_ip(env); +		memcpy(ip, &tmp, sizeof(tmp)); +	} + +	env = env_get(maskstr); +	if (env && mask) { +		tmp = string_to_ip(env); +		memcpy(mask, &tmp, sizeof(tmp)); +	} +	env = env_get(gwstr); +	if (env && gw) { +		tmp = string_to_ip(env); +		memcpy(gw, &tmp, sizeof(tmp)); +	} +#else +	if (ip) +		memcpy(ip, &net_ip, sizeof(net_ip)); +	if (mask) +		memcpy(mask, &net_netmask, sizeof(net_netmask)); +#endif +} + +/** + * efi_net_set_addr() - set IP address information + * + * Set the current IP address, mask, and gateway to the + * efi_ipv4_address structs pointed to by ip, mask and gw, + * respectively. + * + * @ip:		pointer to new IP address + * @mask:	pointer to new network mask to set + * @gw:		pointer to new network gateway + */ +void efi_net_set_addr(struct efi_ipv4_address *ip, +		      struct efi_ipv4_address *mask, +		      struct efi_ipv4_address *gw) +{ +#ifdef CONFIG_NET_LWIP +	char ipstr[] = "ipaddr\0\0"; +	char maskstr[] = "netmask\0\0"; +	char gwstr[] = "gatewayip\0\0"; +	int idx; +	struct in_addr *addr; +	char tmp[46]; + +	idx = dev_seq(eth_get_dev()); + +	if (idx < 0 || idx > 99) { +		log_err("unexpected idx %d\n", idx); +		return; +	} + +	if (idx) { +		sprintf(ipstr, "ipaddr%d", idx); +		sprintf(maskstr, "netmask%d", idx); +		sprintf(gwstr, "gatewayip%d", idx); +	} + +	if (ip) { +		addr = (struct in_addr *)ip; +		ip_to_string(*addr, tmp); +		env_set(ipstr, tmp); +	} + +	if (mask) { +		addr = (struct in_addr *)mask; +		ip_to_string(*addr, tmp); +		env_set(maskstr, tmp); +	} + +	if (gw) { +		addr = (struct in_addr *)gw; +		ip_to_string(*addr, tmp); +		env_set(gwstr, tmp); +	} +#else +	if (ip) +		memcpy(&net_ip, ip, sizeof(*ip)); +	if (mask) +		memcpy(&net_netmask, mask, sizeof(*mask)); +#endif +} + +/** + * efi_net_set_buffer() - allocate a buffer of min 64K + * + * @buffer:	allocated buffer + * @size:	desired buffer size + * Return:	status code + */ +static efi_status_t efi_net_set_buffer(void **buffer, size_t size) +{ +	efi_status_t ret = EFI_SUCCESS; + +	if (size < SZ_64K) +		size = SZ_64K; + +	*buffer = efi_alloc(size); +	if (!*buffer) +		ret = EFI_OUT_OF_RESOURCES; + +	efi_wget_info.buffer_size = (ulong)size; + +	return ret; +} + +/** + * efi_net_parse_headers() - parse HTTP headers + * + * Parses the raw buffer efi_wget_info.headers into an array headers + * of efi structs http_headers. The array should be at least + * MAX_HTTP_HEADERS long. + * + * @num_headers:	number of headers + * @headers:		caller provided array of struct http_headers + */ +void efi_net_parse_headers(ulong *num_headers, struct http_header *headers) +{ +	if (!num_headers || !headers) +		return; + +	// Populate info with http headers. +	*num_headers = 0; +	const uchar *line_start = efi_wget_info.headers; +	const uchar *line_end; +	ulong count; +	struct http_header *current_header; +	const uchar *separator; +	size_t name_length, value_length; + +	// Skip the first line (request or status line) +	line_end = strstr(line_start, "\r\n"); + +	if (line_end) +		line_start = line_end + 2; + +	while ((line_end = strstr(line_start, "\r\n")) != NULL) { +		count = *num_headers; +		if (line_start == line_end || count >= MAX_HTTP_HEADERS) +			break; +		current_header = headers + count; +		separator = strchr(line_start, ':'); +		if (separator) { +			name_length = separator - line_start; +			++separator; +			while (*separator == ' ') +				++separator; +			value_length = line_end - separator; +			if (name_length < MAX_HTTP_HEADER_NAME && +			    value_length < MAX_HTTP_HEADER_VALUE) { +				strncpy(current_header->name, line_start, name_length); +				current_header->name[name_length] = '\0'; +				strncpy(current_header->value, separator, value_length); +				current_header->value[value_length] = '\0'; +				(*num_headers)++; +			} +		} +		line_start = line_end + 2; +	} +} + +/** + * efi_net_do_request() - issue an HTTP request using wget + * + * @url:		url + * @method:		HTTP method + * @buffer:		data buffer + * @status_code:	HTTP status code + * @file_size:		file size in bytes + * @headers_buffer:	headers buffer + * Return:		status code + */ +efi_status_t efi_net_do_request(u8 *url, enum efi_http_method method, void **buffer, +				u32 *status_code, ulong *file_size, char *headers_buffer) +{ +	efi_status_t ret = EFI_SUCCESS; +	int wget_ret; +	static bool last_head; + +	if (!buffer || !file_size) +		return EFI_ABORTED; + +	efi_wget_info.method = (enum wget_http_method)method; +	efi_wget_info.headers = headers_buffer; + +	switch (method) { +	case HTTP_METHOD_GET: +		ret = efi_net_set_buffer(buffer, last_head ? (size_t)efi_wget_info.hdr_cont_len : 0); +		if (ret != EFI_SUCCESS) +			goto out; +		wget_ret = wget_request((ulong)*buffer, url, &efi_wget_info); +		if ((ulong)efi_wget_info.hdr_cont_len > efi_wget_info.buffer_size) { +			// Try again with updated buffer size +			efi_free_pool(*buffer); +			ret = efi_net_set_buffer(buffer, (size_t)efi_wget_info.hdr_cont_len); +			if (ret != EFI_SUCCESS) +				goto out; +			if (wget_request((ulong)*buffer, url, &efi_wget_info)) { +				efi_free_pool(*buffer); +				ret = EFI_DEVICE_ERROR; +				goto out; +			} +		} else if (wget_ret) { +			efi_free_pool(*buffer); +			ret = EFI_DEVICE_ERROR; +			goto out; +		} +		// Pass the actual number of received bytes to the application +		*file_size = efi_wget_info.file_size; +		*status_code = efi_wget_info.status_code; +		last_head = false; +		break; +	case HTTP_METHOD_HEAD: +		ret = efi_net_set_buffer(buffer, 0); +		if (ret != EFI_SUCCESS) +			goto out; +		wget_request((ulong)*buffer, url, &efi_wget_info); +		*file_size = 0; +		*status_code = efi_wget_info.status_code; +		last_head = true; +		break; +	default: +		ret = EFI_UNSUPPORTED; +		break; +	} + +out: +	return ret; +} | 
