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