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
efi_pxeprobe(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
tftp_open(char * path,struct open_file * f)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
tftp_close(struct open_file * f)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
tftp_read(struct open_file * f,void * addr,size_t size,size_t * resid)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
tftp_write(struct open_file * f,void * start,size_t size,size_t * resid)204 tftp_write(struct open_file *f, void *start, size_t size, size_t *resid)
205 {
206 return EROFS;
207 }
208
209 off_t
tftp_seek(struct open_file * f,off_t offset,int where)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
tftp_stat(struct open_file * f,struct stat * sb)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
tftp_readdir(struct open_file * f,char * name)254 tftp_readdir(struct open_file *f, char *name)
255 {
256 return EOPNOTSUPP;
257 }
258
259 /*
260 * Dummy TFTP network device.
261 */
262 int
tftpopen(struct open_file * f,...)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
tftpclose(struct open_file * f)289 tftpclose(struct open_file *f)
290 {
291 return 0;
292 }
293
294 int
tftpioctl(struct open_file * f,u_long cmd,void * data)295 tftpioctl(struct open_file *f, u_long cmd, void *data)
296 {
297 return EOPNOTSUPP;
298 }
299
300 int
tftpstrategy(void * devdata,int rw,daddr_t blk,size_t size,void * buf,size_t * rsize)301 tftpstrategy(void *devdata, int rw, daddr_t blk, size_t size, void *buf,
302 size_t *rsize)
303 {
304 return EOPNOTSUPP;
305 }
306