xref: /openbsd/sys/arch/amd64/stand/efiboot/efipxe.c (revision d415bd75)
1 /*	$OpenBSD: efipxe.c,v 1.10 2021/06/07 00:04:20 krw 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 <machine/biosvar.h>
21 
22 #include <libsa.h>
23 #include <lib/libsa/tftp.h>
24 
25 #include "disk.h"
26 
27 #include <efi.h>
28 #include <efiapi.h>
29 #include "efiboot.h"
30 
31 extern EFI_BOOT_SERVICES	*BS;
32 extern EFI_DEVICE_PATH		*efi_bootdp;
33 
34 extern char			*bootmac;
35 static UINT8			 boothw[16];
36 static EFI_IP_ADDRESS		 bootip, servip;
37 static EFI_GUID			 devp_guid = DEVICE_PATH_PROTOCOL;
38 static EFI_GUID			 pxe_guid = EFI_PXE_BASE_CODE_PROTOCOL;
39 static EFI_PXE_BASE_CODE	*PXE = NULL;
40 
41 extern int	 efi_device_path_depth(EFI_DEVICE_PATH *dp, int);
42 extern int	 efi_device_path_ncmp(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *, int);
43 
44 /*
45  * TFTP initial probe.  This function discovers PXE handles and tries
46  * to figure out if there has already been a successful PXE handshake.
47  * If so, set the PXE variable.
48  */
49 void
50 efi_pxeprobe(void)
51 {
52 	EFI_PXE_BASE_CODE *pxe;
53 	EFI_DEVICE_PATH *dp0;
54 	EFI_HANDLE *handles;
55 	EFI_STATUS status;
56 	UINTN nhandles;
57 	int i, depth;
58 
59 	if (efi_bootdp == NULL)
60 		return;
61 
62 	status = BS->LocateHandleBuffer(ByProtocol, &pxe_guid, NULL, &nhandles,
63 	    &handles);
64 	if (status != EFI_SUCCESS)
65 		return;
66 
67 	for (i = 0; i < nhandles; i++) {
68 		EFI_PXE_BASE_CODE_DHCPV4_PACKET *dhcp;
69 
70 		status = BS->HandleProtocol(handles[i], &devp_guid,
71 		    (void **)&dp0);
72 		if (status != EFI_SUCCESS)
73 			continue;
74 
75 		depth = efi_device_path_depth(efi_bootdp, MESSAGING_DEVICE_PATH);
76 		if (depth == -1 || efi_device_path_ncmp(efi_bootdp, dp0, depth))
77 			continue;
78 
79 		status = BS->HandleProtocol(handles[i], &pxe_guid,
80 		    (void **)&pxe);
81 		if (status != EFI_SUCCESS)
82 			continue;
83 
84 		if (pxe->Mode == NULL)
85 			continue;
86 
87 		dhcp = (EFI_PXE_BASE_CODE_DHCPV4_PACKET *)&pxe->Mode->DhcpAck;
88 		memcpy(&bootip, dhcp->BootpYiAddr, sizeof(bootip));
89 		memcpy(&servip, dhcp->BootpSiAddr, sizeof(servip));
90 		memcpy(boothw, dhcp->BootpHwAddr, sizeof(boothw));
91 		bootmac = boothw;
92 		PXE = pxe;
93 
94 		/*
95 		 * It is expected that bootdev_dip exists.  Usually
96 		 * efiopen() sets the pointer.  Create a fake disk
97 		 * for the TFTP case.
98 		 */
99 		bootdev_dip = alloc(sizeof(struct diskinfo));
100 		memset(bootdev_dip, 0, sizeof(struct diskinfo));
101 		memset(bootdev_dip->disklabel.d_uid, 0xff,
102 		    sizeof(bootdev_dip->disklabel.d_uid));
103 		break;
104 	}
105 }
106 
107 /*
108  * TFTP filesystem layer implementation.
109  */
110 struct tftp_handle {
111 	unsigned char	*inbuf;	/* input buffer */
112 	size_t		 inbufsize;
113 	off_t		 inbufoff;
114 };
115 
116 struct fs_ops tftp_fs = {
117 	tftp_open, tftp_close, tftp_read, tftp_write, tftp_seek,
118 	tftp_stat, tftp_readdir
119 };
120 
121 int
122 tftp_open(char *path, struct open_file *f)
123 {
124 	struct tftp_handle *tftpfile;
125 	EFI_PHYSICAL_ADDRESS addr;
126 	EFI_STATUS status;
127 	UINT64 size;
128 
129 	if (strcmp("TFTP", f->f_dev->dv_name) != 0)
130 		return ENXIO;
131 
132 	if (PXE == NULL)
133 		return ENXIO;
134 
135 	tftpfile = alloc(sizeof(*tftpfile));
136 	if (tftpfile == NULL)
137 		return ENOMEM;
138 	memset(tftpfile, 0, sizeof(*tftpfile));
139 
140 	status = PXE->Mtftp(PXE, EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE, NULL,
141 	    FALSE, &size, NULL, &servip, path, NULL, FALSE);
142 	if (status != EFI_SUCCESS) {
143 		free(tftpfile, sizeof(*tftpfile));
144 		return ENOENT;
145 	}
146 	tftpfile->inbufsize = size;
147 
148 	if (tftpfile->inbufsize == 0)
149 		goto out;
150 
151 	status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
152 	    EFI_SIZE_TO_PAGES(tftpfile->inbufsize), &addr);
153 	if (status != EFI_SUCCESS) {
154 		free(tftpfile, sizeof(*tftpfile));
155 		return ENOMEM;
156 	}
157 	tftpfile->inbuf = (unsigned char *)((paddr_t)addr);
158 
159 	status = PXE->Mtftp(PXE, EFI_PXE_BASE_CODE_TFTP_READ_FILE,
160 	    tftpfile->inbuf, FALSE, &size, NULL, &servip, path, NULL, FALSE);
161 	if (status != EFI_SUCCESS) {
162 		free(tftpfile, sizeof(*tftpfile));
163 		return ENXIO;
164 	}
165 out:
166 	f->f_fsdata = tftpfile;
167 	return 0;
168 }
169 
170 int
171 tftp_close(struct open_file *f)
172 {
173 	struct tftp_handle *tftpfile = f->f_fsdata;
174 
175 	if (tftpfile->inbuf != NULL)
176 		BS->FreePages((paddr_t)tftpfile->inbuf,
177 		    EFI_SIZE_TO_PAGES(tftpfile->inbufsize));
178 	free(tftpfile, sizeof(*tftpfile));
179 	return 0;
180 }
181 
182 int
183 tftp_read(struct open_file *f, void *addr, size_t size, size_t *resid)
184 {
185 	struct tftp_handle *tftpfile = f->f_fsdata;
186 	size_t toread;
187 
188 	if (size > tftpfile->inbufsize - tftpfile->inbufoff)
189 		toread = tftpfile->inbufsize - tftpfile->inbufoff;
190 	else
191 		toread = size;
192 
193 	if (toread != 0) {
194 		memcpy(addr, tftpfile->inbuf + tftpfile->inbufoff, toread);
195 		tftpfile->inbufoff += toread;
196 	}
197 
198 	if (resid != NULL)
199 		*resid = size - toread;
200 	return 0;
201 }
202 
203 int
204 tftp_write(struct open_file *f, void *start, size_t size, size_t *resid)
205 {
206 	return EROFS;
207 }
208 
209 off_t
210 tftp_seek(struct open_file *f, off_t offset, int where)
211 {
212 	struct tftp_handle *tftpfile = f->f_fsdata;
213 
214 	switch(where) {
215 	case SEEK_CUR:
216 		if (tftpfile->inbufoff + offset < 0 ||
217 		    tftpfile->inbufoff + offset > tftpfile->inbufsize) {
218 			errno = EOFFSET;
219 			break;
220 		}
221 		tftpfile->inbufoff += offset;
222 		return (tftpfile->inbufoff);
223 	case SEEK_SET:
224 		if (offset < 0 || offset > tftpfile->inbufsize) {
225 			errno = EOFFSET;
226 			break;
227 		}
228 		tftpfile->inbufoff = offset;
229 		return (tftpfile->inbufoff);
230 	case SEEK_END:
231 		tftpfile->inbufoff = tftpfile->inbufsize;
232 		return (tftpfile->inbufoff);
233 	default:
234 		errno = EINVAL;
235 	}
236 	return((off_t)-1);
237 }
238 
239 int
240 tftp_stat(struct open_file *f, struct stat *sb)
241 {
242 	struct tftp_handle *tftpfile = f->f_fsdata;
243 
244 	sb->st_mode = 0444;
245 	sb->st_nlink = 1;
246 	sb->st_uid = 0;
247 	sb->st_gid = 0;
248 	sb->st_size = tftpfile->inbufsize;
249 
250 	return 0;
251 }
252 
253 int
254 tftp_readdir(struct open_file *f, char *name)
255 {
256 	return EOPNOTSUPP;
257 }
258 
259 /*
260  * Dummy TFTP network device.
261  */
262 int
263 tftpopen(struct open_file *f, ...)
264 {
265 	char **fname, *p;
266 	va_list ap;
267 
268 	va_start(ap, f);
269 	fname = va_arg(ap, char **);
270 	va_end(ap);
271 
272 	/* No PXE set -> no PXE available */
273 	if (PXE == NULL)
274 		return 1;
275 
276 	/* Parse tftp:bsd into "tftp" and "bsd" */
277 	for (p = *fname; *p != ':' && *p != '\0'; p++)
278 		;
279 	if (*p != ':')
280 		return 1;
281 	if (strncmp(*fname, "tftp", p - *fname) != 0)
282 		return 1;
283 
284 	*fname = p + 1;
285 	return 0;
286 }
287 
288 int
289 tftpclose(struct open_file *f)
290 {
291 	return 0;
292 }
293 
294 int
295 tftpioctl(struct open_file *f, u_long cmd, void *data)
296 {
297 	return EOPNOTSUPP;
298 }
299 
300 int
301 tftpstrategy(void *devdata, int rw, daddr_t blk, size_t size, void *buf,
302 	size_t *rsize)
303 {
304 	return EOPNOTSUPP;
305 }
306