]> git.proxmox.com Git - efi-boot-shim.git/blame - netboot.c
Define the PXE 2nd stage loader in the beginning of the file
[efi-boot-shim.git] / netboot.c
CommitLineData
1c595706
MG
1/*
2 * netboot - trivial UEFI first-stage bootloader netboot support
3 *
4 * Copyright 2012 Red Hat, Inc <mjg@redhat.com>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
13 * Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the
16 * distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 * Significant portions of this code are derived from Tianocore
32 * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
33 * Corporation.
34 */
35
36#include <efi.h>
37#include <efilib.h>
38#include <string.h>
39#include "shim.h"
40#include "netboot.h"
41
bd145c60 42#define DEFAULT_LOADER "/grub.efi"
1c595706
MG
43
44static inline unsigned short int __swap16(unsigned short int x)
45{
46 __asm__("xchgb %b0,%h0"
47 : "=q" (x)
48 : "0" (x));
49 return x;
50}
51
52#define ntohs(x) __swap16(x)
53#define htons(x) ntohs(x)
54
55static EFI_PXE_BASE_CODE *pxe;
56static EFI_IP_ADDRESS tftp_addr;
e2979f2c 57static UINT8 *full_path;
1c595706
MG
58
59
1c595706
MG
60typedef struct {
61 UINT16 OpCode;
62 UINT16 Length;
63 UINT8 Data[1];
64} EFI_DHCP6_PACKET_OPTION;
65
66/*
67 * usingNetboot
68 * Returns TRUE if we identify a protocol that is enabled and Providing us with
69 * the needed information to fetch a grubx64.efi image
70 */
71BOOLEAN findNetboot(EFI_HANDLE image_handle)
72{
73 UINTN bs = sizeof(EFI_HANDLE);
74 EFI_GUID pxe_base_code_protocol = EFI_PXE_BASE_CODE_PROTOCOL;
75 EFI_HANDLE *hbuf;
76 BOOLEAN rc = FALSE;
77 void *buffer = AllocatePool(bs);
78 UINTN errcnt = 0;
79 UINTN i;
80 EFI_STATUS status;
81
82 if (!buffer)
83 return FALSE;
84
85try_again:
86 status = uefi_call_wrapper(BS->LocateHandle,5, ByProtocol,
87 &pxe_base_code_protocol, NULL, &bs,
88 buffer);
89
90 if (status == EFI_BUFFER_TOO_SMALL) {
91 errcnt++;
92 FreePool(buffer);
93 if (errcnt > 1)
94 return FALSE;
95 buffer = AllocatePool(bs);
96 if (!buffer)
97 return FALSE;
98 goto try_again;
99 }
100
e4d55afe
MG
101 if (status == EFI_NOT_FOUND) {
102 FreePool(buffer);
103 return FALSE;
104 }
105
1c595706
MG
106 /*
107 * We have a list of pxe supporting protocols, lets see if any are
108 * active
109 */
110 hbuf = buffer;
111 pxe = NULL;
112 for (i=0; i < (bs / sizeof(EFI_HANDLE)); i++) {
113 status = uefi_call_wrapper(BS->OpenProtocol, 6, hbuf[i],
114 &pxe_base_code_protocol,
e2979f2c 115 (void **)&pxe, image_handle, NULL,
1c595706
MG
116 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
117
118 if (status != EFI_SUCCESS) {
119 pxe = NULL;
120 continue;
121 }
122
123 if (!pxe || !pxe->Mode) {
124 pxe = NULL;
125 continue;
126 }
127
128 if (pxe->Mode->Started && pxe->Mode->DhcpAckReceived) {
129 /*
130 * We've located a pxe protocol handle thats been
131 * started and has received an ACK, meaning its
132 * something we'll be able to get tftp server info
133 * out of
134 */
135 rc = TRUE;
136 break;
137 }
138
139 }
140
141 FreePool(buffer);
142 return rc;
143}
144
af049ff4 145static CHAR8 *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt)
1c595706
MG
146{
147 void *optr;
148 EFI_DHCP6_PACKET_OPTION *option;
af049ff4 149 CHAR8 *url;
1c595706
MG
150 UINT32 urllen;
151
152 optr = pkt->DhcpOptions;
153
154 for(;;) {
155 option = (EFI_DHCP6_PACKET_OPTION *)optr;
156
157 if (ntohs(option->OpCode) == 0)
158 return NULL;
159
160 if (ntohs(option->OpCode) == 59) {
161 /* This is the bootfile url option */
162 urllen = ntohs(option->Length);
6eaa1a9c 163 url = AllocateZeroPool(urllen+1);
1c595706
MG
164 if (!url)
165 return NULL;
1c595706
MG
166 memcpy(url, option->Data, urllen);
167 return url;
168 }
169 optr += 4 + ntohs(option->Length);
170 }
171
172 return NULL;
173}
174
175static UINT16 str2ns(UINT8 *str)
176{
177 UINT16 ret = 0;
178 UINT8 v;
179 for(;*str;str++) {
180 if ('0' <= *str && *str <= '9')
181 v = *str - '0';
182 else if ('A' <= *str && *str <= 'F')
183 v = *str - 'A' + 10;
184 else if ('a' <= *str && *str <= 'f')
185 v = *str - 'a' + 10;
186 else
187 v = 0;
188 ret = (ret << 4) + v;
189 }
190 return htons(ret);
191}
192
193static UINT8 *str2ip6(char *str)
194{
195 UINT8 i, j, p;
196 size_t len;
197 UINT8 *a, *b, t;
198 static UINT16 ip[8];
199
200 for(i=0; i < 8; i++) {
201 ip[i] = 0;
202 }
203 len = strlen((UINT8 *)str);
204 a = b = (UINT8 *)str;
205 for(i=p=0; i < len; i++, b++) {
206 if (*b != ':')
207 continue;
208 *b = '\0';
209 ip[p++] = str2ns(a);
210 *b = ':';
211 a = b + 1;
212 if ( *(b+1) == ':' )
213 break;
214 }
215 a = b = (UINT8 *)(str + len);
216 for(j=len, p=7; j > i; j--, a--) {
217 if (*a != ':')
218 continue;
219 t = *b;
220 *b = '\0';
221 ip[p--] = str2ns(a+1);
222 *b = t;
223 b = a;
224 }
225 return (UINT8 *)ip;
226}
227
af049ff4 228static BOOLEAN extract_tftp_info(CHAR8 *url)
1c595706 229{
e2979f2c 230 CHAR8 *start, *end;
69a54db4 231 char ip6str[40];
bd145c60 232 CHAR8 *template = DEFAULT_LOADER;
1c595706
MG
233
234 if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) {
235 Print(L"URLS MUST START WITH tftp://\n");
236 return FALSE;
237 }
af049ff4 238 start = url + 7;
1c595706
MG
239 if (*start != '[') {
240 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
241 return FALSE;
242 }
243
244 start++;
245 end = start;
246 while ((*end != '\0') && (*end != ']')) {
247 end++;
69a54db4
SL
248 if (end - start > 39) {
249 Print(L"TFTP URL includes malformed IPv6 address\n");
250 return FALSE;
251 }
1c595706
MG
252 }
253 if (end == '\0') {
254 Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n");
255 return FALSE;
256 }
69a54db4 257 memset(ip6str, 0, 40);
3816832b 258 memcpy(ip6str, start, end - start);
1c595706
MG
259 end++;
260 memcpy(&tftp_addr.v6, str2ip6(ip6str), 16);
e2979f2c 261 full_path = AllocateZeroPool(strlen(end)+strlen(template)+1);
1c595706
MG
262 if (!full_path)
263 return FALSE;
e2979f2c
SL
264 memcpy(full_path, end, strlen(end));
265 end = (CHAR8 *)strrchr((char *)full_path, '/');
1c595706 266 if (!end)
e2979f2c
SL
267 end = (CHAR8 *)full_path;
268 memcpy(end, template, strlen(template));
269 end[strlen(template)] = '\0';
1c595706
MG
270
271 return TRUE;
272}
273
274static EFI_STATUS parseDhcp6()
275{
276 EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
af049ff4 277 CHAR8 *bootfile_url;
1c595706
MG
278
279 bootfile_url = get_v6_bootfile_url(packet);
1c595706
MG
280 if (!bootfile_url)
281 return EFI_NOT_FOUND;
6eaa1a9c
SL
282 if (extract_tftp_info(bootfile_url) == FALSE) {
283 FreePool(bootfile_url);
284 return EFI_NOT_FOUND;
285 }
286 FreePool(bootfile_url);
1c595706
MG
287 return EFI_SUCCESS;
288}
289
290static EFI_STATUS parseDhcp4()
291{
bd145c60 292 CHAR8 *template = DEFAULT_LOADER;
e2979f2c 293 full_path = AllocateZeroPool(strlen(template)+1);
1c595706 294
e2979f2c 295 if (!full_path)
1c595706
MG
296 return EFI_OUT_OF_RESOURCES;
297
1c595706
MG
298 memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
299
e2979f2c 300 memcpy(full_path, template, strlen(template));
1c595706
MG
301
302 /* Note we don't capture the filename option here because we know its shim.efi
303 * We instead assume the filename at the end of the path is going to be grubx64.efi
304 */
305 return EFI_SUCCESS;
306}
307
308EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle)
309{
310
311 EFI_STATUS rc;
312
313 if (!pxe)
314 return EFI_NOT_READY;
315
316 memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr));
317
318 /*
319 * If we've discovered an active pxe protocol figure out
320 * if its ipv4 or ipv6
321 */
322 if (pxe->Mode->UsingIpv6){
323 rc = parseDhcp6();
324 } else
325 rc = parseDhcp4();
326 return rc;
327}
328
fbc486b5 329EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz)
1c595706
MG
330{
331 EFI_STATUS rc;
332 EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE;
333 BOOLEAN overwrite = FALSE;
334 BOOLEAN nobuffer = FALSE;
335 UINTN blksz = 512;
336
337 Print(L"Fetching Netboot Image\n");
338 if (*buffer == NULL) {
339 *buffer = AllocatePool(4096 * 1024);
340 if (!*buffer)
341 return EFI_OUT_OF_RESOURCES;
342 *bufsiz = 4096 * 1024;
343 }
344
345try_again:
346 rc = uefi_call_wrapper(pxe->Mtftp, 10, pxe, read, *buffer, overwrite,
fbc486b5 347 bufsiz, &blksz, &tftp_addr, full_path, NULL, nobuffer);
1c595706
MG
348
349 if (rc == EFI_BUFFER_TOO_SMALL) {
350 /* try again, doubling buf size */
351 *bufsiz *= 2;
352 FreePool(*buffer);
353 *buffer = AllocatePool(*bufsiz);
354 if (!*buffer)
355 return EFI_OUT_OF_RESOURCES;
356 goto try_again;
357 }
358
5ccacd3a
SL
359 if (rc != EFI_SUCCESS && *buffer) {
360 FreePool(*buffer);
361 }
1c595706 362 return rc;
1c595706 363}