efi-boot-shim/netboot.c
Ard Biesheuvel f7a182154e Factor out x86-isms and add cross compile support
This patch cleans up and refactors the Makefiles to better allow new
architectures to be added:
- remove unused Makefile definitions
- import Makefile definitions from top level rather than redefining
- move x86 specific CFLAGS to inside ifeq() blocks
- remove x86 inline asm
- allow $(FORMAT) to be overridden: this is necessary as there exists no
  EFI or PE/COFF aware objcopy for ARM

Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
2014-08-12 10:54:05 -04:00

343 lines
8.2 KiB
C

/*
* netboot - trivial UEFI first-stage bootloader netboot support
*
* Copyright 2012 Red Hat, Inc <mjg@redhat.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Significant portions of this code are derived from Tianocore
* (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
* Corporation.
*/
#include <efi.h>
#include <efilib.h>
#include <string.h>
#include "shim.h"
#include "netboot.h"
#include "str.h"
#define ntohs(x) __builtin_bswap16(x) /* supported both by GCC and clang */
#define htons(x) ntohs(x)
static EFI_PXE_BASE_CODE *pxe;
static EFI_IP_ADDRESS tftp_addr;
static CHAR8 *full_path;
typedef struct {
UINT16 OpCode;
UINT16 Length;
UINT8 Data[1];
} EFI_DHCP6_PACKET_OPTION;
static CHAR8 *
translate_slashes(char *str)
{
int i;
int j;
if (str == NULL)
return (CHAR8 *)str;
for (i = 0, j = 0; str[i] != '\0'; i++, j++) {
if (str[i] == '\\') {
str[j] = '/';
if (str[i+1] == '\\')
i++;
}
}
return (CHAR8 *)str;
}
/*
* usingNetboot
* Returns TRUE if we identify a protocol that is enabled and Providing us with
* the needed information to fetch a grubx64.efi image
*/
BOOLEAN findNetboot(EFI_HANDLE device)
{
EFI_STATUS status;
status = uefi_call_wrapper(BS->HandleProtocol, 3, device,
&PxeBaseCodeProtocol, (VOID **)&pxe);
if (status != EFI_SUCCESS) {
pxe = NULL;
return FALSE;
}
if (!pxe || !pxe->Mode) {
pxe = NULL;
return FALSE;
}
if (!pxe->Mode->Started || !pxe->Mode->DhcpAckReceived) {
pxe = NULL;
return FALSE;
}
/*
* We've located a pxe protocol handle thats been started and has
* received an ACK, meaning its something we'll be able to get
* tftp server info out of
*/
return TRUE;
}
static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
{
void *optr;
EFI_DHCP6_PACKET_OPTION *option;
CHAR8 *url;
UINT32 urllen;
optr = pkt->DhcpOptions;
for(;;) {
option = (EFI_DHCP6_PACKET_OPTION *)optr;
if (ntohs(option->OpCode) == 0)
return NULL;
if (ntohs(option->OpCode) == 59) {
/* This is the bootfile url option */
urllen = ntohs(option->Length);
url = AllocateZeroPool(urllen+1);
if (!url)
return NULL;
memcpy(url, option->Data, urllen);
return url;
}
optr += 4 + ntohs(option->Length);
}
return NULL;
}
static CHAR16 str2ns(CHAR8 *str)
{
CHAR16 ret = 0;
CHAR8 v;
for(;*str;str++) {
if ('0' <= *str && *str <= '9')
v = *str - '0';
else if ('A' <= *str && *str <= 'F')
v = *str - 'A' + 10;
else if ('a' <= *str && *str <= 'f')
v = *str - 'a' + 10;
else
v = 0;
ret = (ret << 4) + v;
}
return htons(ret);
}
static CHAR8 *str2ip6(CHAR8 *str)
{
UINT8 i, j, p;
size_t len;
CHAR8 *a, *b, t;
static UINT16 ip[8];
for(i=0; i < 8; i++) {
ip[i] = 0;
}
len = strlen(str);
a = b = str;
for(i=p=0; i < len; i++, b++) {
if (*b != ':')
continue;
*b = '\0';
ip[p++] = str2ns(a);
*b = ':';
a = b + 1;
if ( *(b+1) == ':' )
break;
}
a = b = (str + len);
for(j=len, p=7; j > i; j--, a--) {
if (*a != ':')
continue;
t = *b;
*b = '\0';
ip[p--] = str2ns(a+1);
*b = t;
b = a;
}
return (CHAR8 *)ip;
}
static BOOLEAN extract_tftp_info(CHAR8 *url)
{
CHAR8 *start, *end;
CHAR8 ip6str[40];
CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
Print(L"URLS MUST START WITH tftp://\n");
return FALSE;
}
start = url + 7;
if (*start != '[') {
Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
return FALSE;
}
start++;
end = start;
while ((*end != '\0') && (*end != ']')) {
end++;
if (end - start > 39) {
Print(L"TFTP URL includes malformed IPv6 address\n");
return FALSE;
}
}
if (end == '\0') {
Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
return FALSE;
}
memset(ip6str, 0, 40);
memcpy(ip6str, start, end - start);
end++;
memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
if (!full_path)
return FALSE;
memcpy(full_path, end, strlen(end));
end = (CHAR8 *)strrchr((char *)full_path, '/');
if (!end)
end = (CHAR8 *)full_path;
memcpy(end, template, strlen(template));
end[strlen(template)] = '\0';
return TRUE;
}
static EFI_STATUS parseDhcp6()
{
EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
CHAR8 *bootfile_url;
bootfile_url = get_v6_bootfile_url(packet);
if (!bootfile_url)
return EFI_NOT_FOUND;
if (extract_tftp_info(bootfile_url) == FALSE) {
FreePool(bootfile_url);
return EFI_NOT_FOUND;
}
FreePool(bootfile_url);
return EFI_SUCCESS;
}
static EFI_STATUS parseDhcp4()
{
CHAR8 *template = (CHAR8 *)translate_slashes(DEFAULT_LOADER_CHAR);
INTN template_len = strlen(template) + 1;
INTN dir_len = strnlena(pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile, 127);
INTN i;
UINT8 *dir = pxe->Mode->DhcpAck.Dhcpv4.BootpBootFile;
for (i = dir_len; i >= 0; i--) {
if (dir[i] == '/')
break;
}
dir_len = (i >= 0) ? i + 1 : 0;
full_path = AllocateZeroPool(dir_len + template_len);
if (!full_path)
return EFI_OUT_OF_RESOURCES;
if (dir_len > 0) {
strncpya(full_path, dir, dir_len);
if (full_path[dir_len-1] == '/' && template[0] == '/')
full_path[dir_len-1] = '\0';
}
if (dir_len == 0 && dir[0] != '/' && template[0] == '/')
template++;
strcata(full_path, template);
memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
return EFI_SUCCESS;
}
EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
{
EFI_STATUS rc;
if (!pxe)
return EFI_NOT_READY;
memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr));
/*
* If we've discovered an active pxe protocol figure out
* if its ipv4 or ipv6
*/
if (pxe->Mode->UsingIpv6){
rc = parseDhcp6();
} else
rc = parseDhcp4();
return rc;
}
EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz)
{
EFI_STATUS rc;
EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
BOOLEAN overwrite = FALSE;
BOOLEAN nobuffer = FALSE;
UINTN blksz = 512;
Print(L"Fetching Netboot Image\n");
if (*buffer == NULL) {
*buffer = AllocatePool(4096 * 1024);
if (!*buffer)
return EFI_OUT_OF_RESOURCES;
*bufsiz = 4096 * 1024;
}
try_again:
rc = uefi_call_wrapper(pxe->Mtftp, 10, pxe, read, *buffer, overwrite,
bufsiz, &blksz, &tftp_addr, full_path, NULL, nobuffer);
if (rc == EFI_BUFFER_TOO_SMALL) {
/* try again, doubling buf size */
*bufsiz *= 2;
FreePool(*buffer);
*buffer = AllocatePool(*bufsiz);
if (!*buffer)
return EFI_OUT_OF_RESOURCES;
goto try_again;
}
if (rc != EFI_SUCCESS && *buffer) {
FreePool(*buffer);
}
return rc;
}