xref: /openbsd/sys/arch/armv7/stand/efiboot/efipxe.c (revision 097a140d)
1 /*	$OpenBSD: efipxe.c,v 1.7 2021/03/11 11:16:56 jsg Exp $	*/
2 /*
3  * Copyright (c) 2017 Patrick Wildt <patrick@blueri.se>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/disklabel.h>
20 #include <sys/socket.h>
21 
22 #include <net/if.h>
23 
24 #include <netinet/in.h>
25 #include <netinet/if_ether.h>
26 #include <netinet/ip.h>
27 #include <netinet/ip_var.h>
28 #include <netinet/udp.h>
29 #include <netinet/udp_var.h>
30 
31 #include <libsa.h>
32 #include <lib/libsa/tftp.h>
33 #include <lib/libsa/net.h>
34 #include <lib/libsa/netif.h>
35 
36 #include <efi.h>
37 #include <efiapi.h>
38 #include "eficall.h"
39 #include "efiboot.h"
40 #include "disk.h"
41 
42 extern EFI_BOOT_SERVICES	*BS;
43 extern EFI_DEVICE_PATH		*efi_bootdp;
44 
45 extern char			*bootmac;
46 static UINT8			 boothw[16];
47 struct in_addr			 bootip, servip;
48 extern struct in_addr		 gateip;
49 static EFI_GUID			 devp_guid = DEVICE_PATH_PROTOCOL;
50 static EFI_GUID			 net_guid = EFI_SIMPLE_NETWORK_PROTOCOL;
51 static EFI_GUID			 pxe_guid = EFI_PXE_BASE_CODE_PROTOCOL;
52 static EFI_SIMPLE_NETWORK	*NET = NULL;
53 static EFI_PXE_BASE_CODE	*PXE = NULL;
54 static EFI_PHYSICAL_ADDRESS	 txbuf;
55 static int			 use_mtftp = 0;
56 
57 extern int	 efi_device_path_depth(EFI_DEVICE_PATH *dp, int);
58 extern int	 efi_device_path_ncmp(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *, int);
59 
60 int		 efinet_probe(struct netif *, void *);
61 int		 efinet_match(struct netif *, void *);
62 void		 efinet_init(struct iodesc *, void *);
63 int		 efinet_get(struct iodesc *, void *, size_t, time_t);
64 int		 efinet_put(struct iodesc *, void *, size_t);
65 void		 efinet_end(struct netif *);
66 
67 /*
68  * TFTP initial probe.  This function discovers PXE handles and tries
69  * to figure out if there has already been a successful PXE handshake.
70  * If so, set the PXE variable.
71  */
72 void
73 efi_pxeprobe(void)
74 {
75 	EFI_SIMPLE_NETWORK *net;
76 	EFI_PXE_BASE_CODE *pxe;
77 	EFI_DEVICE_PATH *dp0;
78 	EFI_HANDLE *handles;
79 	EFI_STATUS status;
80 	UINTN nhandles;
81 	int i, depth;
82 
83 	if (efi_bootdp == NULL)
84 		return;
85 
86 	status = EFI_CALL(BS->LocateHandleBuffer, ByProtocol, &pxe_guid, NULL,
87 	    &nhandles, &handles);
88 	if (status != EFI_SUCCESS)
89 		return;
90 
91 	for (i = 0; i < nhandles; i++) {
92 		EFI_PXE_BASE_CODE_DHCPV4_PACKET *dhcp;
93 
94 		status = EFI_CALL(BS->HandleProtocol, handles[i],
95 		    &devp_guid, (void **)&dp0);
96 		if (status != EFI_SUCCESS)
97 			continue;
98 
99 		depth = efi_device_path_depth(efi_bootdp, MESSAGING_DEVICE_PATH);
100 		if (depth == -1 || efi_device_path_ncmp(efi_bootdp, dp0, depth))
101 			continue;
102 
103 		status = EFI_CALL(BS->HandleProtocol, handles[i], &net_guid,
104 		    (void **)&net);
105 		if (status != EFI_SUCCESS)
106 			continue;
107 
108 		status = EFI_CALL(BS->HandleProtocol, handles[i], &pxe_guid,
109 		    (void **)&pxe);
110 		if (status != EFI_SUCCESS)
111 			continue;
112 
113 		if (pxe->Mode == NULL)
114 			continue;
115 
116 		if (pxe->Mtftp != NULL) {
117 			status = EFI_CALL(pxe->Mtftp, NULL, 0, NULL,
118 			    FALSE, NULL, NULL, NULL, NULL, NULL, FALSE);
119 			if (status != EFI_UNSUPPORTED)
120 				use_mtftp = 1;
121 		}
122 
123 		dhcp = (EFI_PXE_BASE_CODE_DHCPV4_PACKET *)&pxe->Mode->DhcpAck;
124 		memcpy(&bootip, dhcp->BootpYiAddr, sizeof(bootip));
125 		memcpy(&servip, dhcp->BootpSiAddr, sizeof(servip));
126 		memcpy(&gateip, dhcp->BootpSiAddr, sizeof(gateip));
127 		memcpy(boothw, dhcp->BootpHwAddr, sizeof(boothw));
128 		bootmac = boothw;
129 		NET = net;
130 		PXE = pxe;
131 		break;
132 	}
133 }
134 
135 /*
136  * TFTP filesystem layer implementation.
137  */
138 struct mtftp_handle {
139 	unsigned char	*inbuf;	/* input buffer */
140 	size_t		 inbufsize;
141 	off_t		 inbufoff;
142 };
143 
144 int
145 mtftp_open(char *path, struct open_file *f)
146 {
147 	struct mtftp_handle *tftpfile;
148 	EFI_PHYSICAL_ADDRESS addr;
149 	EFI_IP_ADDRESS dstip;
150 	EFI_STATUS status;
151 	UINT64 size;
152 
153 	if (strcmp("tftp", f->f_dev->dv_name) != 0)
154 		return ENXIO;
155 
156 	if (PXE == NULL)
157 		return ENXIO;
158 
159 	if (!use_mtftp)
160 		return ENXIO;
161 
162 	tftpfile = alloc(sizeof(*tftpfile));
163 	if (tftpfile == NULL)
164 		return ENOMEM;
165 	memset(tftpfile, 0, sizeof(*tftpfile));
166 
167 	memcpy(&dstip, &servip, sizeof(servip));
168 	status = EFI_CALL(PXE->Mtftp, PXE, EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE,
169 	    NULL, FALSE, &size, NULL, &dstip, path, NULL, FALSE);
170 	if (status != EFI_SUCCESS) {
171 		free(tftpfile, sizeof(*tftpfile));
172 		return ENOENT;
173 	}
174 	tftpfile->inbufsize = size;
175 
176 	if (tftpfile->inbufsize == 0)
177 		goto out;
178 
179 	status = EFI_CALL(BS->AllocatePages, AllocateAnyPages, EfiLoaderData,
180 	    EFI_SIZE_TO_PAGES(tftpfile->inbufsize), &addr);
181 	if (status != EFI_SUCCESS) {
182 		free(tftpfile, sizeof(*tftpfile));
183 		return ENOMEM;
184 	}
185 	tftpfile->inbuf = (unsigned char *)((paddr_t)addr);
186 
187 	status = EFI_CALL(PXE->Mtftp, PXE, EFI_PXE_BASE_CODE_TFTP_READ_FILE,
188 	    tftpfile->inbuf, FALSE, &size, NULL, &dstip, path, NULL, FALSE);
189 	if (status != EFI_SUCCESS) {
190 		free(tftpfile, sizeof(*tftpfile));
191 		return ENXIO;
192 	}
193 out:
194 	f->f_fsdata = tftpfile;
195 	return 0;
196 }
197 
198 int
199 mtftp_close(struct open_file *f)
200 {
201 	struct mtftp_handle *tftpfile = f->f_fsdata;
202 
203 	if (tftpfile->inbuf != NULL)
204 		EFI_CALL(BS->FreePages, (paddr_t)tftpfile->inbuf,
205 		    EFI_SIZE_TO_PAGES(tftpfile->inbufsize));
206 	free(tftpfile, sizeof(*tftpfile));
207 	return 0;
208 }
209 
210 int
211 mtftp_read(struct open_file *f, void *addr, size_t size, size_t *resid)
212 {
213 	struct mtftp_handle *tftpfile = f->f_fsdata;
214 	size_t toread;
215 
216 	if (size > tftpfile->inbufsize - tftpfile->inbufoff)
217 		toread = tftpfile->inbufsize - tftpfile->inbufoff;
218 	else
219 		toread = size;
220 
221 	if (toread != 0) {
222 		memcpy(addr, tftpfile->inbuf + tftpfile->inbufoff, toread);
223 		tftpfile->inbufoff += toread;
224 	}
225 
226 	if (resid != NULL)
227 		*resid = size - toread;
228 	return 0;
229 }
230 
231 int
232 mtftp_write(struct open_file *f, void *start, size_t size, size_t *resid)
233 {
234 	return EROFS;
235 }
236 
237 off_t
238 mtftp_seek(struct open_file *f, off_t offset, int where)
239 {
240 	struct mtftp_handle *tftpfile = f->f_fsdata;
241 
242 	switch(where) {
243 	case SEEK_CUR:
244 		if (tftpfile->inbufoff + offset < 0 ||
245 		    tftpfile->inbufoff + offset > tftpfile->inbufsize) {
246 			errno = EOFFSET;
247 			break;
248 		}
249 		tftpfile->inbufoff += offset;
250 		return (tftpfile->inbufoff);
251 	case SEEK_SET:
252 		if (offset < 0 || offset > tftpfile->inbufsize) {
253 			errno = EOFFSET;
254 			break;
255 		}
256 		tftpfile->inbufoff = offset;
257 		return (tftpfile->inbufoff);
258 	case SEEK_END:
259 		tftpfile->inbufoff = tftpfile->inbufsize;
260 		return (tftpfile->inbufoff);
261 	default:
262 		errno = EINVAL;
263 	}
264 	return((off_t)-1);
265 }
266 
267 int
268 mtftp_stat(struct open_file *f, struct stat *sb)
269 {
270 	struct mtftp_handle *tftpfile = f->f_fsdata;
271 
272 	sb->st_mode = 0444;
273 	sb->st_nlink = 1;
274 	sb->st_uid = 0;
275 	sb->st_gid = 0;
276 	sb->st_size = tftpfile->inbufsize;
277 
278 	return 0;
279 }
280 
281 int
282 mtftp_readdir(struct open_file *f, char *name)
283 {
284 	return EOPNOTSUPP;
285 }
286 
287 /*
288  * Overload generic TFTP implementation to check that
289  * we actually have a driver.
290  */
291 int
292 efitftp_open(char *path, struct open_file *f)
293 {
294 	if (strcmp("tftp", f->f_dev->dv_name) != 0)
295 		return ENXIO;
296 
297 	if (NET == NULL || PXE == NULL)
298 		return ENXIO;
299 
300 	if (use_mtftp)
301 		return ENXIO;
302 
303 	return tftp_open(path, f);
304 }
305 
306 /*
307  * Dummy network device.
308  */
309 int tftpdev_sock = -1;
310 
311 int
312 tftpopen(struct open_file *f, ...)
313 {
314 	EFI_STATUS status;
315 	u_int unit, part;
316 	va_list ap;
317 
318 	va_start(ap, f);
319 	unit = va_arg(ap, u_int);
320 	part = va_arg(ap, u_int);
321 	va_end(ap);
322 
323 	/* No PXE set -> no PXE available */
324 	if (PXE == NULL)
325 		return 1;
326 
327 	if (unit != 0)
328 		return 1;
329 
330 	if (!use_mtftp) {
331 		status = EFI_CALL(BS->AllocatePages, AllocateAnyPages,
332 		    EfiLoaderData, EFI_SIZE_TO_PAGES(RECV_SIZE), &txbuf);
333 		if (status != EFI_SUCCESS)
334 			return ENOMEM;
335 
336 		if ((tftpdev_sock = netif_open("efinet")) < 0) {
337 			EFI_CALL(BS->FreePages, txbuf,
338 			    EFI_SIZE_TO_PAGES(RECV_SIZE));
339 			return ENXIO;
340 		}
341 
342 		f->f_devdata = &tftpdev_sock;
343 	}
344 
345 	return 0;
346 }
347 
348 int
349 tftpclose(struct open_file *f)
350 {
351 	int ret = 0;
352 
353 	if (!use_mtftp) {
354 		ret = netif_close(*(int *)f->f_devdata);
355 		EFI_CALL(BS->FreePages, txbuf, EFI_SIZE_TO_PAGES(RECV_SIZE));
356 		txbuf = 0;
357 	}
358 
359 	return ret;
360 }
361 
362 int
363 tftpioctl(struct open_file *f, u_long cmd, void *data)
364 {
365 	return EOPNOTSUPP;
366 }
367 
368 int
369 tftpstrategy(void *devdata, int rw, daddr_t blk, size_t size, void *buf,
370 	size_t *rsize)
371 {
372 	return EOPNOTSUPP;
373 }
374 
375 /*
376  * Simple Network Protocol driver.
377  */
378 struct netif_stats efinet_stats;
379 struct netif_dif efinet_ifs[] = {
380 	{ 0, 1, &efinet_stats, 0, 0, },
381 };
382 
383 struct netif_driver efinet_driver = {
384 	"efinet",
385 	efinet_match,
386 	efinet_probe,
387 	efinet_init,
388 	efinet_get,
389 	efinet_put,
390 	efinet_end,
391 	efinet_ifs,
392 	nitems(efinet_ifs)
393 };
394 
395 int
396 efinet_match(struct netif *nif, void *v)
397 {
398 	return 1;
399 }
400 
401 int
402 efinet_probe(struct netif *nif, void *v)
403 {
404 	if (strncmp(v, efinet_driver.netif_bname, 3))
405 		return -1;
406 
407 	return 0;
408 }
409 
410 void
411 efinet_init(struct iodesc *desc, void *v)
412 {
413 	EFI_SIMPLE_NETWORK *net = NET;
414 	EFI_STATUS status;
415 
416 	if (net == NULL)
417 		return;
418 
419 	if (net->Mode->State == EfiSimpleNetworkStopped) {
420 		status = EFI_CALL(net->Start, net);
421 		if (status != EFI_SUCCESS)
422 			return;
423 	}
424 
425 	if (net->Mode->State != EfiSimpleNetworkInitialized) {
426 		status = EFI_CALL(net->Initialize, net, 0, 0);
427 		if (status != EFI_SUCCESS)
428 			return;
429 	}
430 
431 	EFI_CALL(net->ReceiveFilters, net,
432 	    EFI_SIMPLE_NETWORK_RECEIVE_UNICAST |
433 	    EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST,
434 	    0, FALSE, 0, NULL);
435 
436 	memcpy(desc->myea, net->Mode->CurrentAddress.Addr, 6);
437 	memcpy(&desc->myip, &bootip, sizeof(bootip));
438 	desc->xid = 1;
439 }
440 
441 int
442 efinet_get(struct iodesc *desc, void *pkt, size_t len, time_t tmo)
443 {
444 	EFI_SIMPLE_NETWORK *net = NET;
445 	EFI_STATUS status;
446 	UINTN bufsz, pktsz;
447 	time_t t;
448 	char *buf, *ptr;
449 	ssize_t ret = -1;
450 
451 	if (net == NULL)
452 		return ret;
453 
454 	bufsz = net->Mode->MaxPacketSize + ETHER_HDR_LEN + ETHER_CRC_LEN;
455 	buf = alloc(bufsz + ETHER_ALIGN);
456 	if (buf == NULL)
457 		return ret;
458 	ptr = buf + ETHER_ALIGN;
459 
460 	t = getsecs();
461 	status = EFI_NOT_READY;
462 	while ((getsecs() - t) < tmo) {
463 		pktsz = bufsz;
464 		status = EFI_CALL(net->Receive, net, NULL, &pktsz, ptr,
465 		    NULL, NULL, NULL);
466 		if (status == EFI_SUCCESS)
467 			break;
468 		if (status != EFI_NOT_READY)
469 			break;
470 	}
471 
472 	if (status == EFI_SUCCESS) {
473 		memcpy(pkt, ptr, min((ssize_t)pktsz, len));
474 		ret = min((ssize_t)pktsz, len);
475 	}
476 
477 	free(buf, bufsz + ETHER_ALIGN);
478 	return ret;
479 }
480 
481 int
482 efinet_put(struct iodesc *desc, void *pkt, size_t len)
483 {
484 	EFI_SIMPLE_NETWORK *net = NET;
485 	EFI_STATUS status;
486 	void *buf = NULL;
487 	int ret = -1;
488 
489 	if (net == NULL)
490 		goto out;
491 
492 	if (len > RECV_SIZE)
493 		goto out;
494 
495 	memcpy((void *)txbuf, pkt, len);
496 	status = EFI_CALL(net->Transmit, net, 0, len, (void *)txbuf,
497 	    NULL, NULL, NULL);
498 	if (status != EFI_SUCCESS)
499 		goto out;
500 
501 	buf = NULL;
502 	while (status == EFI_SUCCESS) {
503 		status = EFI_CALL(net->GetStatus, net, NULL, &buf);
504 		if (buf)
505 			break;
506 	}
507 
508 	if (status == EFI_SUCCESS)
509 		ret = len;
510 
511 out:
512 	return ret;
513 }
514 
515 void
516 efinet_end(struct netif *nif)
517 {
518 	EFI_SIMPLE_NETWORK *net = NET;
519 
520 	if (net == NULL)
521 		return;
522 
523 	EFI_CALL(net->Shutdown, net);
524 }
525