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