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