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