diff --git a/stand/common/md.c b/stand/common/md.c index e9fcac668d93..61db56ece7ae 100644 --- a/stand/common/md.c +++ b/stand/common/md.c @@ -1,156 +1,157 @@ /*- * Copyright (c) 2009 Marcel Moolenaar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include "bootstrap.h" #define MD_BLOCK_SIZE 512 #ifndef MD_IMAGE_SIZE #error Must be compiled with MD_IMAGE_SIZE defined #endif #if (MD_IMAGE_SIZE == 0 || MD_IMAGE_SIZE % MD_BLOCK_SIZE) #error Image size must be a multiple of 512. #endif /* * Preloaded image gets put here. * Applications that patch the object with the image can determine * the size looking at the start and end markers (strings), * so we want them contiguous. */ static struct { u_char start[MD_IMAGE_SIZE]; u_char end[128]; } md_image = { .start = "MFS Filesystem goes here", .end = "MFS Filesystem had better STOP here", }; /* devsw I/F */ static int md_init(void); static int md_strategy(void *, int, daddr_t, size_t, char *, size_t *); static int md_open(struct open_file *, ...); static int md_close(struct open_file *); static int md_print(int); struct devsw md_dev = { .dv_name = "md", .dv_type = DEVT_DISK, .dv_init = md_init, .dv_strategy = md_strategy, .dv_open = md_open, .dv_close = md_close, .dv_ioctl = noioctl, .dv_print = md_print, + .dv_cleanup = nullsys, }; static int md_init(void) { return (0); } static int md_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf, size_t *rsize) { struct devdesc *dev = (struct devdesc *)devdata; size_t ofs; if (dev->d_unit != 0) return (ENXIO); if (blk < 0 || blk >= (MD_IMAGE_SIZE / MD_BLOCK_SIZE)) return (EIO); if (size % MD_BLOCK_SIZE) return (EIO); ofs = blk * MD_BLOCK_SIZE; if ((ofs + size) > MD_IMAGE_SIZE) size = MD_IMAGE_SIZE - ofs; if (rsize != NULL) *rsize = size; switch (rw & F_MASK) { case F_READ: bcopy(md_image.start + ofs, buf, size); return (0); case F_WRITE: bcopy(buf, md_image.start + ofs, size); return (0); } return (ENODEV); } static int md_open(struct open_file *f, ...) { va_list ap; struct devdesc *dev; va_start(ap, f); dev = va_arg(ap, struct devdesc *); va_end(ap); if (dev->d_unit != 0) return (ENXIO); return (0); } static int md_close(struct open_file *f) { struct devdesc *dev; dev = (struct devdesc *)(f->f_devdata); return ((dev->d_unit != 0) ? ENXIO : 0); } static int md_print(int verbose) { printf("%s devices:", md_dev.dv_name); if (pager_output("\n") != 0) return (1); printf("MD (%u bytes)", MD_IMAGE_SIZE); return (pager_output("\n")); } diff --git a/stand/common/vdisk.c b/stand/common/vdisk.c index 521ad498b194..c904613a8e91 100644 --- a/stand/common/vdisk.c +++ b/stand/common/vdisk.c @@ -1,417 +1,417 @@ /*- * Copyright 2019 Toomas Soome * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include static int vdisk_init(void); static int vdisk_strategy(void *, int, daddr_t, size_t, char *, size_t *); static int vdisk_open(struct open_file *, ...); static int vdisk_close(struct open_file *); static int vdisk_ioctl(struct open_file *, u_long, void *); static int vdisk_print(int); struct devsw vdisk_dev = { .dv_name = "vdisk", .dv_type = DEVT_DISK, .dv_init = vdisk_init, .dv_strategy = vdisk_strategy, .dv_open = vdisk_open, .dv_close = vdisk_close, .dv_ioctl = vdisk_ioctl, .dv_print = vdisk_print, - .dv_cleanup = NULL + .dv_cleanup = nullsys, }; typedef STAILQ_HEAD(vdisk_info_list, vdisk_info) vdisk_info_list_t; typedef struct vdisk_info { STAILQ_ENTRY(vdisk_info) vdisk_link; /* link in device list */ char *vdisk_path; int vdisk_unit; int vdisk_fd; uint64_t vdisk_size; /* size in bytes */ uint32_t vdisk_sectorsz; uint32_t vdisk_open; /* reference counter */ } vdisk_info_t; static vdisk_info_list_t vdisk_list; /* list of mapped vdisks. */ static vdisk_info_t * vdisk_get_info(struct devdesc *dev) { vdisk_info_t *vd; STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { if (vd->vdisk_unit == dev->d_unit) return (vd); } return (vd); } COMMAND_SET(map_vdisk, "map-vdisk", "map file as virtual disk", command_mapvd); static int command_mapvd(int argc, char *argv[]) { vdisk_info_t *vd, *p; struct stat sb; if (argc != 2) { printf("usage: %s filename\n", argv[0]); return (CMD_ERROR); } STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { if (strcmp(vd->vdisk_path, argv[1]) == 0) { printf("%s: file %s is already mapped as %s%d\n", argv[0], argv[1], vdisk_dev.dv_name, vd->vdisk_unit); return (CMD_ERROR); } } if (stat(argv[1], &sb) < 0) { /* * ENOSYS is really ENOENT because we did try to walk * through devsw list to try to open this file. */ if (errno == ENOSYS) errno = ENOENT; printf("%s: stat failed: %s\n", argv[0], strerror(errno)); return (CMD_ERROR); } /* * Avoid mapping small files. */ if (sb.st_size < 1024 * 1024) { printf("%s: file %s is too small.\n", argv[0], argv[1]); return (CMD_ERROR); } vd = calloc(1, sizeof (*vd)); if (vd == NULL) { printf("%s: out of memory\n", argv[0]); return (CMD_ERROR); } vd->vdisk_path = strdup(argv[1]); if (vd->vdisk_path == NULL) { free (vd); printf("%s: out of memory\n", argv[0]); return (CMD_ERROR); } vd->vdisk_fd = open(vd->vdisk_path, O_RDONLY); if (vd->vdisk_fd < 0) { printf("%s: open failed: %s\n", argv[0], strerror(errno)); free(vd->vdisk_path); free(vd); return (CMD_ERROR); } vd->vdisk_size = sb.st_size; vd->vdisk_sectorsz = DEV_BSIZE; STAILQ_FOREACH(p, &vdisk_list, vdisk_link) { vdisk_info_t *n; if (p->vdisk_unit == vd->vdisk_unit) { vd->vdisk_unit++; continue; } n = STAILQ_NEXT(p, vdisk_link); if (p->vdisk_unit < vd->vdisk_unit) { if (n == NULL) { /* p is last elem */ STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link); break; } if (n->vdisk_unit > vd->vdisk_unit) { /* p < vd < n */ STAILQ_INSERT_AFTER(&vdisk_list, p, vd, vdisk_link); break; } /* else n < vd or n == vd */ vd->vdisk_unit++; continue; } /* p > vd only if p is the first element */ STAILQ_INSERT_HEAD(&vdisk_list, vd, vdisk_link); break; } /* if the list was empty or contiguous */ if (p == NULL) STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link); printf("%s: file %s is mapped as %s%d\n", argv[0], vd->vdisk_path, vdisk_dev.dv_name, vd->vdisk_unit); return (CMD_OK); } COMMAND_SET(unmap_vdisk, "unmap-vdisk", "unmap virtual disk", command_unmapvd); /* * unmap-vdisk vdiskX */ static int command_unmapvd(int argc, char *argv[]) { size_t len; vdisk_info_t *vd; long unit; char *end; if (argc != 2) { printf("usage: %s %sN\n", argv[0], vdisk_dev.dv_name); return (CMD_ERROR); } len = strlen(vdisk_dev.dv_name); if (strncmp(vdisk_dev.dv_name, argv[1], len) != 0) { printf("%s: unknown device %s\n", argv[0], argv[1]); return (CMD_ERROR); } errno = 0; unit = strtol(argv[1] + len, &end, 10); if (errno != 0 || (*end != '\0' && strcmp(end, ":") != 0)) { printf("%s: unknown device %s\n", argv[0], argv[1]); return (CMD_ERROR); } STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { if (vd->vdisk_unit == unit) break; } if (vd == NULL) { printf("%s: unknown device %s\n", argv[0], argv[1]); return (CMD_ERROR); } if (vd->vdisk_open != 0) { printf("%s: %s is in use, unable to unmap.\n", argv[0], argv[1]); return (CMD_ERROR); } STAILQ_REMOVE(&vdisk_list, vd, vdisk_info, vdisk_link); (void) close(vd->vdisk_fd); printf("%s (%s) unmapped\n", argv[1], vd->vdisk_path); free(vd->vdisk_path); free(vd); return (CMD_OK); } static int vdisk_init(void) { STAILQ_INIT(&vdisk_list); return (0); } static int vdisk_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf, size_t *rsize) { struct disk_devdesc *dev; vdisk_info_t *vd; ssize_t rv; dev = devdata; if (dev == NULL) return (EINVAL); vd = vdisk_get_info((struct devdesc *)dev); if (vd == NULL) return (EINVAL); if (size == 0 || (size % 512) != 0) return (EIO); if (dev->dd.d_dev->dv_type == DEVT_DISK) { daddr_t offset; offset = dev->d_offset * vd->vdisk_sectorsz; offset /= 512; blk += offset; } if (lseek(vd->vdisk_fd, blk << 9, SEEK_SET) == -1) return (EIO); errno = 0; switch (rw & F_MASK) { case F_READ: rv = read(vd->vdisk_fd, buf, size); break; case F_WRITE: rv = write(vd->vdisk_fd, buf, size); break; default: return (ENOSYS); } if (errno == 0 && rsize != NULL) { *rsize = rv; } return (errno); } static int vdisk_open(struct open_file *f, ...) { va_list args; struct disk_devdesc *dev; vdisk_info_t *vd; int rc = 0; va_start(args, f); dev = va_arg(args, struct disk_devdesc *); va_end(args); if (dev == NULL) return (EINVAL); vd = vdisk_get_info((struct devdesc *)dev); if (vd == NULL) return (EINVAL); if (dev->dd.d_dev->dv_type == DEVT_DISK) { rc = disk_open(dev, vd->vdisk_size, vd->vdisk_sectorsz); } if (rc == 0) vd->vdisk_open++; return (rc); } static int vdisk_close(struct open_file *f) { struct disk_devdesc *dev; vdisk_info_t *vd; dev = (struct disk_devdesc *)(f->f_devdata); if (dev == NULL) return (EINVAL); vd = vdisk_get_info((struct devdesc *)dev); if (vd == NULL) return (EINVAL); vd->vdisk_open--; if (dev->dd.d_dev->dv_type == DEVT_DISK) return (disk_close(dev)); return (0); } static int vdisk_ioctl(struct open_file *f, u_long cmd, void *data) { struct disk_devdesc *dev; vdisk_info_t *vd; int rc; dev = (struct disk_devdesc *)(f->f_devdata); if (dev == NULL) return (EINVAL); vd = vdisk_get_info((struct devdesc *)dev); if (vd == NULL) return (EINVAL); if (dev->dd.d_dev->dv_type == DEVT_DISK) { rc = disk_ioctl(dev, cmd, data); if (rc != ENOTTY) return (rc); } switch (cmd) { case DIOCGSECTORSIZE: *(u_int *)data = vd->vdisk_sectorsz; break; case DIOCGMEDIASIZE: *(uint64_t *)data = vd->vdisk_size; break; default: return (ENOTTY); } return (0); } static int vdisk_print(int verbose) { int ret = 0; vdisk_info_t *vd; char line[80]; if (STAILQ_EMPTY(&vdisk_list)) return (ret); printf("%s devices:", vdisk_dev.dv_name); if ((ret = pager_output("\n")) != 0) return (ret); STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { struct disk_devdesc vd_dev; if (verbose) { printf(" %s", vd->vdisk_path); if ((ret = pager_output("\n")) != 0) break; } snprintf(line, sizeof(line), " %s%d", vdisk_dev.dv_name, vd->vdisk_unit); printf("%s: %" PRIu64 " X %u blocks", line, vd->vdisk_size / vd->vdisk_sectorsz, vd->vdisk_sectorsz); if ((ret = pager_output("\n")) != 0) break; vd_dev.dd.d_dev = &vdisk_dev; vd_dev.dd.d_unit = vd->vdisk_unit; vd_dev.d_slice = -1; vd_dev.d_partition = -1; ret = disk_open(&vd_dev, vd->vdisk_size, vd->vdisk_sectorsz); if (ret == 0) { ret = disk_print(&vd_dev, line, verbose); disk_close(&vd_dev); if (ret != 0) break; } else { ret = 0; } } return (ret); } diff --git a/stand/efi/libefi/efihttp.c b/stand/efi/libefi/efihttp.c index 05d338fbaf04..728f95a47b42 100644 --- a/stand/efi/libefi/efihttp.c +++ b/stand/efi/libefi/efihttp.c @@ -1,800 +1,800 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 Intel Corporation * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include /* Poll timeout in milliseconds */ static const int EFIHTTP_POLL_TIMEOUT = 300000; static EFI_GUID http_guid = EFI_HTTP_PROTOCOL_GUID; static EFI_GUID httpsb_guid = EFI_HTTP_SERVICE_BINDING_PROTOCOL_GUID; static EFI_GUID ip4config2_guid = EFI_IP4_CONFIG2_PROTOCOL_GUID; static bool efihttp_init_done = false; static int efihttp_dev_init(void); static int efihttp_dev_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf, size_t *rsize); static int efihttp_dev_open(struct open_file *f, ...); static int efihttp_dev_close(struct open_file *f); static int efihttp_fs_open(const char *path, struct open_file *f); static int efihttp_fs_close(struct open_file *f); static int efihttp_fs_read(struct open_file *f, void *buf, size_t size, size_t *resid); static int efihttp_fs_write(struct open_file *f, const void *buf, size_t size, size_t *resid); static off_t efihttp_fs_seek(struct open_file *f, off_t offset, int where); static int efihttp_fs_stat(struct open_file *f, struct stat *sb); static int efihttp_fs_readdir(struct open_file *f, struct dirent *d); struct open_efihttp { EFI_HTTP_PROTOCOL *http; EFI_HANDLE http_handle; EFI_HANDLE dev_handle; char *uri_base; }; struct file_efihttp { ssize_t size; off_t offset; char *path; bool is_dir; }; struct devsw efihttp_dev = { .dv_name = "http", .dv_type = DEVT_NET, .dv_init = efihttp_dev_init, .dv_strategy = efihttp_dev_strategy, .dv_open = efihttp_dev_open, .dv_close = efihttp_dev_close, .dv_ioctl = noioctl, .dv_print = NULL, - .dv_cleanup = NULL, + .dv_cleanup = nullsys, }; struct fs_ops efihttp_fsops = { .fs_name = "efihttp", .fo_open = efihttp_fs_open, .fo_close = efihttp_fs_close, .fo_read = efihttp_fs_read, .fo_write = efihttp_fs_write, .fo_seek = efihttp_fs_seek, .fo_stat = efihttp_fs_stat, .fo_readdir = efihttp_fs_readdir, }; static void EFIAPI notify(EFI_EVENT event __unused, void *context) { bool *b; b = (bool *)context; *b = true; } static int setup_ipv4_config2(EFI_HANDLE handle, MAC_ADDR_DEVICE_PATH *mac, IPv4_DEVICE_PATH *ipv4, DNS_DEVICE_PATH *dns) { EFI_IP4_CONFIG2_PROTOCOL *ip4config2; EFI_STATUS status; status = BS->OpenProtocol(handle, &ip4config2_guid, (void **)&ip4config2, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); if (ipv4 != NULL) { if (mac != NULL) { setenv("boot.netif.hwaddr", ether_sprintf((u_char *)mac->MacAddress.Addr), 1); } setenv("boot.netif.ip", inet_ntoa(*(struct in_addr *)ipv4->LocalIpAddress.Addr), 1); setenv("boot.netif.netmask", intoa(*(n_long *)ipv4->SubnetMask.Addr), 1); setenv("boot.netif.gateway", inet_ntoa(*(struct in_addr *)ipv4->GatewayIpAddress.Addr), 1); status = ip4config2->SetData(ip4config2, Ip4Config2DataTypePolicy, sizeof(EFI_IP4_CONFIG2_POLICY), &(EFI_IP4_CONFIG2_POLICY) { Ip4Config2PolicyStatic }); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); status = ip4config2->SetData(ip4config2, Ip4Config2DataTypeManualAddress, sizeof(EFI_IP4_CONFIG2_MANUAL_ADDRESS), &(EFI_IP4_CONFIG2_MANUAL_ADDRESS) { .Address = ipv4->LocalIpAddress, .SubnetMask = ipv4->SubnetMask }); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); if (ipv4->GatewayIpAddress.Addr[0] != 0) { status = ip4config2->SetData(ip4config2, Ip4Config2DataTypeGateway, sizeof(EFI_IPv4_ADDRESS), &ipv4->GatewayIpAddress); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); } if (dns != NULL) { status = ip4config2->SetData(ip4config2, Ip4Config2DataTypeDnsServer, sizeof(EFI_IPv4_ADDRESS), &dns->DnsServerIp); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); } } else { status = ip4config2->SetData(ip4config2, Ip4Config2DataTypePolicy, sizeof(EFI_IP4_CONFIG2_POLICY), &(EFI_IP4_CONFIG2_POLICY) { Ip4Config2PolicyDhcp }); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); } return (0); } static int efihttp_dev_init(void) { EFI_DEVICE_PATH *imgpath, *devpath; URI_DEVICE_PATH *uri; EFI_HANDLE handle; EFI_STATUS status; int err; bool found_http; imgpath = efi_lookup_image_devpath(IH); if (imgpath == NULL) return (ENXIO); devpath = imgpath; found_http = false; for (; !IsDevicePathEnd(devpath); devpath = NextDevicePathNode(devpath)) { if (DevicePathType(devpath) != MESSAGING_DEVICE_PATH || DevicePathSubType(devpath) != MSG_URI_DP) continue; uri = (URI_DEVICE_PATH *)devpath; if (strncmp("http", (const char *)uri->Uri, 4) == 0) found_http = true; } if (!found_http) return (ENXIO); status = BS->LocateDevicePath(&httpsb_guid, &imgpath, &handle); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); err = efi_register_handles(&efihttp_dev, &handle, NULL, 1); if (!err) efihttp_init_done = true; return (err); } static int efihttp_dev_strategy(void *devdata __unused, int rw __unused, daddr_t blk __unused, size_t size __unused, char *buf __unused, size_t *rsize __unused) { return (EIO); } static int efihttp_dev_open(struct open_file *f, ...) { EFI_HTTP_CONFIG_DATA config; EFI_HTTPv4_ACCESS_POINT config_access; DNS_DEVICE_PATH *dns; EFI_DEVICE_PATH *devpath, *imgpath; EFI_SERVICE_BINDING_PROTOCOL *sb; IPv4_DEVICE_PATH *ipv4; MAC_ADDR_DEVICE_PATH *mac; URI_DEVICE_PATH *uri; struct devdesc *dev; struct open_efihttp *oh; char *c; EFI_HANDLE handle; EFI_STATUS status; int err, len; if (!efihttp_init_done) return (ENXIO); imgpath = efi_lookup_image_devpath(IH); if (imgpath == NULL) return (ENXIO); devpath = imgpath; status = BS->LocateDevicePath(&httpsb_guid, &devpath, &handle); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); mac = NULL; ipv4 = NULL; dns = NULL; uri = NULL; for (; !IsDevicePathEnd(imgpath); imgpath = NextDevicePathNode(imgpath)) { if (DevicePathType(imgpath) != MESSAGING_DEVICE_PATH) continue; switch (DevicePathSubType(imgpath)) { case MSG_MAC_ADDR_DP: mac = (MAC_ADDR_DEVICE_PATH *)imgpath; break; case MSG_IPv4_DP: ipv4 = (IPv4_DEVICE_PATH *)imgpath; break; case MSG_DNS_DP: dns = (DNS_DEVICE_PATH *)imgpath; break; case MSG_URI_DP: uri = (URI_DEVICE_PATH *)imgpath; break; default: break; } } if (uri == NULL) return (ENXIO); err = setup_ipv4_config2(handle, mac, ipv4, dns); if (err) return (err); oh = calloc(1, sizeof(struct open_efihttp)); if (!oh) return (ENOMEM); oh->dev_handle = handle; dev = (struct devdesc *)f->f_devdata; dev->d_opendata = oh; status = BS->OpenProtocol(handle, &httpsb_guid, (void **)&sb, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) { err = efi_status_to_errno(status); goto end; } status = sb->CreateChild(sb, &oh->http_handle); if (EFI_ERROR(status)) { err = efi_status_to_errno(status); goto end; } status = BS->OpenProtocol(oh->http_handle, &http_guid, (void **)&oh->http, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) { sb->DestroyChild(sb, oh->http_handle); err = efi_status_to_errno(status); goto end; } config.HttpVersion = HttpVersion11; config.TimeOutMillisec = 0; config.LocalAddressIsIPv6 = FALSE; config.AccessPoint.IPv4Node = &config_access; config_access.UseDefaultAddress = TRUE; config_access.LocalPort = 0; status = oh->http->Configure(oh->http, &config); if (EFI_ERROR(status)) { sb->DestroyChild(sb, oh->http_handle); err = efi_status_to_errno(status); goto end; } /* * Here we make attempt to construct a "base" URI by stripping * the last two path components from the loaded URI under the * assumption that it is something like: * * http://127.0.0.1/foo/boot/loader.efi * * hoping to arriving at: * * http://127.0.0.1/foo/ */ len = DevicePathNodeLength(&uri->Header) - sizeof(URI_DEVICE_PATH); oh->uri_base = malloc(len + 1); if (oh->uri_base == NULL) { err = ENOMEM; goto end; } strncpy(oh->uri_base, (const char *)uri->Uri, len); oh->uri_base[len] = '\0'; c = strrchr(oh->uri_base, '/'); if (c != NULL) *c = '\0'; c = strrchr(oh->uri_base, '/'); if (c != NULL && *(c + 1) != '\0') *(c + 1) = '\0'; err = 0; end: if (err != 0) { free(dev->d_opendata); dev->d_opendata = NULL; } return (err); } static int efihttp_dev_close(struct open_file *f) { EFI_SERVICE_BINDING_PROTOCOL *sb; struct devdesc *dev; struct open_efihttp *oh; EFI_STATUS status; dev = (struct devdesc *)f->f_devdata; oh = (struct open_efihttp *)dev->d_opendata; status = BS->OpenProtocol(oh->dev_handle, &httpsb_guid, (void **)&sb, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); sb->DestroyChild(sb, oh->http_handle); free(oh->uri_base); free(oh); dev->d_opendata = NULL; return (0); } static int _efihttp_fs_open(const char *path, struct open_file *f) { EFI_HTTP_CONFIG_DATA config; EFI_HTTPv4_ACCESS_POINT config_access; EFI_HTTP_TOKEN token; EFI_HTTP_MESSAGE message; EFI_HTTP_REQUEST_DATA request; EFI_HTTP_RESPONSE_DATA response; EFI_HTTP_HEADER headers[3]; char *host, *hostp; char *c; struct devdesc *dev; struct open_efihttp *oh; struct file_efihttp *fh; EFI_STATUS status; UINTN i; int polltime; bool done; dev = (struct devdesc *)f->f_devdata; oh = (struct open_efihttp *)dev->d_opendata; fh = calloc(1, sizeof(struct file_efihttp)); if (fh == NULL) return (ENOMEM); f->f_fsdata = fh; fh->path = strdup(path); /* * Reset the HTTP state. * * EDK II's persistent HTTP connection handling is graceless, * assuming that all connections are persistent regardless of * any Connection: header or HTTP version reported by the * server, and failing to send requests when a more sane * implementation would seem to be just reestablishing the * closed connection. * * In the hopes of having some robustness, we indicate to the * server that we will close the connection by using a * Connection: close header. And then here we manually * unconfigure and reconfigure the http instance to force the * connection closed. */ memset(&config, 0, sizeof(config)); memset(&config_access, 0, sizeof(config_access)); config.AccessPoint.IPv4Node = &config_access; status = oh->http->GetModeData(oh->http, &config); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); status = oh->http->Configure(oh->http, NULL); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); status = oh->http->Configure(oh->http, &config); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); /* Send the read request */ done = false; status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify, &done, &token.Event); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); /* extract the host portion of the URL */ host = strdup(oh->uri_base); if (host == NULL) return (ENOMEM); hostp = host; /* Remove the protocol scheme */ c = strchr(host, '/'); if (c != NULL && *(c + 1) == '/') hostp = (c + 2); /* Remove any path information */ c = strchr(hostp, '/'); if (c != NULL) *c = '\0'; token.Status = EFI_NOT_READY; token.Message = &message; message.Data.Request = &request; message.HeaderCount = 3; message.Headers = headers; message.BodyLength = 0; message.Body = NULL; request.Method = HttpMethodGet; request.Url = calloc(strlen(oh->uri_base) + strlen(path) + 1, 2); headers[0].FieldName = (CHAR8 *)"Host"; headers[0].FieldValue = (CHAR8 *)hostp; headers[1].FieldName = (CHAR8 *)"Connection"; headers[1].FieldValue = (CHAR8 *)"close"; headers[2].FieldName = (CHAR8 *)"Accept"; headers[2].FieldValue = (CHAR8 *)"*/*"; cpy8to16(oh->uri_base, request.Url, strlen(oh->uri_base)); cpy8to16(path, request.Url + strlen(oh->uri_base), strlen(path)); status = oh->http->Request(oh->http, &token); free(request.Url); free(host); if (EFI_ERROR(status)) { BS->CloseEvent(token.Event); return (efi_status_to_errno(status)); } polltime = 0; while (!done && polltime < EFIHTTP_POLL_TIMEOUT) { status = oh->http->Poll(oh->http); if (EFI_ERROR(status)) break; if (!done) { delay(100 * 1000); polltime += 100; } } BS->CloseEvent(token.Event); if (EFI_ERROR(token.Status)) return (efi_status_to_errno(token.Status)); /* Wait for the read response */ done = false; status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify, &done, &token.Event); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); token.Status = EFI_NOT_READY; token.Message = &message; message.Data.Response = &response; message.HeaderCount = 0; message.Headers = NULL; message.BodyLength = 0; message.Body = NULL; response.StatusCode = HTTP_STATUS_UNSUPPORTED_STATUS; status = oh->http->Response(oh->http, &token); if (EFI_ERROR(status)) { BS->CloseEvent(token.Event); return (efi_status_to_errno(status)); } polltime = 0; while (!done && polltime < EFIHTTP_POLL_TIMEOUT) { status = oh->http->Poll(oh->http); if (EFI_ERROR(status)) break; if (!done) { delay(100 * 1000); polltime += 100; } } BS->CloseEvent(token.Event); if (EFI_ERROR(token.Status)) { BS->FreePool(message.Headers); return (efi_status_to_errno(token.Status)); } if (response.StatusCode != HTTP_STATUS_200_OK) { BS->FreePool(message.Headers); return (EIO); } fh->size = 0; fh->is_dir = false; for (i = 0; i < message.HeaderCount; i++) { if (strcasecmp((const char *)message.Headers[i].FieldName, "Content-Length") == 0) fh->size = strtoul((const char *) message.Headers[i].FieldValue, NULL, 10); else if (strcasecmp((const char *)message.Headers[i].FieldName, "Content-type") == 0) { if (strncmp((const char *)message.Headers[i].FieldValue, "text/html", 9) == 0) fh->is_dir = true; } } return (0); } static int efihttp_fs_open(const char *path, struct open_file *f) { char *path_slash; int err; if (!efihttp_init_done) return (ENXIO); /* * If any path fails to open, try with a trailing slash in * case it's a directory. */ err = _efihttp_fs_open(path, f); if (err != 0) { /* * Work around a bug in the EFI HTTP implementation which * causes a crash if the http instance isn't torn down * between requests. * See https://bugzilla.tianocore.org/show_bug.cgi?id=1917 */ efihttp_dev_close(f); efihttp_dev_open(f); path_slash = malloc(strlen(path) + 2); if (path_slash == NULL) return (ENOMEM); strcpy(path_slash, path); strcat(path_slash, "/"); err = _efihttp_fs_open(path_slash, f); free(path_slash); } return (err); } static int efihttp_fs_close(struct open_file *f __unused) { return (0); } static int _efihttp_fs_read(struct open_file *f, void *buf, size_t size, size_t *resid) { EFI_HTTP_TOKEN token; EFI_HTTP_MESSAGE message; EFI_STATUS status; struct devdesc *dev; struct open_efihttp *oh; struct file_efihttp *fh; bool done; int polltime; fh = (struct file_efihttp *)f->f_fsdata; if (fh->size > 0 && fh->offset >= fh->size) { if (resid != NULL) *resid = size; return 0; } dev = (struct devdesc *)f->f_devdata; oh = (struct open_efihttp *)dev->d_opendata; done = false; status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify, &done, &token.Event); if (EFI_ERROR(status)) { return (efi_status_to_errno(status)); } token.Status = EFI_NOT_READY; token.Message = &message; message.Data.Request = NULL; message.HeaderCount = 0; message.Headers = NULL; message.BodyLength = size; message.Body = buf; status = oh->http->Response(oh->http, &token); if (status == EFI_CONNECTION_FIN) { if (resid) *resid = size; return (0); } else if (EFI_ERROR(status)) { BS->CloseEvent(token.Event); return (efi_status_to_errno(status)); } polltime = 0; while (!done && polltime < EFIHTTP_POLL_TIMEOUT) { status = oh->http->Poll(oh->http); if (EFI_ERROR(status)) break; if (!done) { delay(100 * 1000); polltime += 100; } } BS->CloseEvent(token.Event); if (token.Status == EFI_CONNECTION_FIN) { if (resid) *resid = size; return (0); } else if (EFI_ERROR(token.Status)) return (efi_status_to_errno(token.Status)); if (resid) *resid = size - message.BodyLength; fh->offset += message.BodyLength; return (0); } static int efihttp_fs_read(struct open_file *f, void *buf, size_t size, size_t *resid) { size_t res; int err = 0; while (size > 0) { err = _efihttp_fs_read(f, buf, size, &res); if (err != 0 || res == size) goto end; buf += (size - res); size = res; } end: if (resid) *resid = size; return (err); } static int efihttp_fs_write(struct open_file *f __unused, const void *buf __unused, size_t size __unused, size_t *resid __unused) { return (EIO); } static off_t efihttp_fs_seek(struct open_file *f, off_t offset, int where) { struct file_efihttp *fh; char *path; void *buf; size_t res, res2; int err; fh = (struct file_efihttp *)f->f_fsdata; if (where == SEEK_SET && fh->offset == offset) return (0); if (where == SEEK_SET && fh->offset < offset) { buf = malloc(1500); if (buf == NULL) return (ENOMEM); res = offset - fh->offset; while (res > 0) { err = _efihttp_fs_read(f, buf, min(1500, res), &res2); if (err != 0) { free(buf); return (err); } res -= min(1500, res) - res2; } free(buf); return (0); } else if (where == SEEK_SET) { path = fh->path; fh->path = NULL; efihttp_fs_close(f); /* * Work around a bug in the EFI HTTP implementation which * causes a crash if the http instance isn't torn down * between requests. * See https://bugzilla.tianocore.org/show_bug.cgi?id=1917 */ efihttp_dev_close(f); efihttp_dev_open(f); err = efihttp_fs_open(path, f); free(path); if (err != 0) return (err); return efihttp_fs_seek(f, offset, where); } return (EIO); } static int efihttp_fs_stat(struct open_file *f, struct stat *sb) { struct file_efihttp *fh; fh = (struct file_efihttp *)f->f_fsdata; memset(sb, 0, sizeof(*sb)); sb->st_nlink = 1; sb->st_mode = 0777 | (fh->is_dir ? S_IFDIR : S_IFREG); sb->st_size = fh->size; return (0); } static int efihttp_fs_readdir(struct open_file *f, struct dirent *d) { static char *dirbuf = NULL, *db2, *cursor; static int dirbuf_len = 0; char *end; struct file_efihttp *fh; fh = (struct file_efihttp *)f->f_fsdata; if (dirbuf_len < fh->size) { db2 = realloc(dirbuf, fh->size); if (db2 == NULL) { free(dirbuf); return (ENOMEM); } else dirbuf = db2; dirbuf_len = fh->size; } if (fh->offset != fh->size) { efihttp_fs_seek(f, 0, SEEK_SET); efihttp_fs_read(f, dirbuf, dirbuf_len, NULL); cursor = dirbuf; } cursor = strstr(cursor, "d_type = DT_DIR; } else d->d_type = DT_REG; memcpy(d->d_name, cursor, end - cursor); d->d_name[end - cursor] = '\0'; return (0); } diff --git a/stand/efi/libefi/efinet.c b/stand/efi/libefi/efinet.c index c52b11d32ec8..a6582bd2c1b5 100644 --- a/stand/efi/libefi/efinet.c +++ b/stand/efi/libefi/efinet.c @@ -1,467 +1,467 @@ /*- * Copyright (c) 2001 Doug Rabson * Copyright (c) 2002, 2006 Marcel Moolenaar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include "dev_net.h" static EFI_GUID sn_guid = EFI_SIMPLE_NETWORK_PROTOCOL; static void efinet_end(struct netif *); static ssize_t efinet_get(struct iodesc *, void **, time_t); static void efinet_init(struct iodesc *, void *); static int efinet_match(struct netif *, void *); static int efinet_probe(struct netif *, void *); static ssize_t efinet_put(struct iodesc *, void *, size_t); struct netif_driver efinetif = { .netif_bname = "efinet", .netif_match = efinet_match, .netif_probe = efinet_probe, .netif_init = efinet_init, .netif_get = efinet_get, .netif_put = efinet_put, .netif_end = efinet_end, .netif_ifs = NULL, .netif_nifs = 0 }; #ifdef EFINET_DEBUG static void dump_mode(EFI_SIMPLE_NETWORK_MODE *mode) { int i; printf("State = %x\n", mode->State); printf("HwAddressSize = %u\n", mode->HwAddressSize); printf("MediaHeaderSize = %u\n", mode->MediaHeaderSize); printf("MaxPacketSize = %u\n", mode->MaxPacketSize); printf("NvRamSize = %u\n", mode->NvRamSize); printf("NvRamAccessSize = %u\n", mode->NvRamAccessSize); printf("ReceiveFilterMask = %x\n", mode->ReceiveFilterMask); printf("ReceiveFilterSetting = %u\n", mode->ReceiveFilterSetting); printf("MaxMCastFilterCount = %u\n", mode->MaxMCastFilterCount); printf("MCastFilterCount = %u\n", mode->MCastFilterCount); printf("MCastFilter = {"); for (i = 0; i < mode->MCastFilterCount; i++) printf(" %s", ether_sprintf(mode->MCastFilter[i].Addr)); printf(" }\n"); printf("CurrentAddress = %s\n", ether_sprintf(mode->CurrentAddress.Addr)); printf("BroadcastAddress = %s\n", ether_sprintf(mode->BroadcastAddress.Addr)); printf("PermanentAddress = %s\n", ether_sprintf(mode->PermanentAddress.Addr)); printf("IfType = %u\n", mode->IfType); printf("MacAddressChangeable = %d\n", mode->MacAddressChangeable); printf("MultipleTxSupported = %d\n", mode->MultipleTxSupported); printf("MediaPresentSupported = %d\n", mode->MediaPresentSupported); printf("MediaPresent = %d\n", mode->MediaPresent); } #endif static int efinet_match(struct netif *nif, void *machdep_hint) { struct devdesc *dev = machdep_hint; if (dev->d_unit == nif->nif_unit) return (1); return(0); } static int efinet_probe(struct netif *nif, void *machdep_hint) { EFI_SIMPLE_NETWORK *net; EFI_HANDLE h; EFI_STATUS status; h = nif->nif_driver->netif_ifs[nif->nif_unit].dif_private; /* * Open the network device in exclusive mode. Without this * we will be racing with the UEFI network stack. It will * pull packets off the network leading to lost packets. */ status = BS->OpenProtocol(h, &sn_guid, (void **)&net, IH, NULL, EFI_OPEN_PROTOCOL_EXCLUSIVE); if (status != EFI_SUCCESS) { printf("Unable to open network interface %d for " "exclusive access: %lu\n", nif->nif_unit, EFI_ERROR_CODE(status)); return (efi_status_to_errno(status)); } return (0); } static ssize_t efinet_put(struct iodesc *desc, void *pkt, size_t len) { struct netif *nif = desc->io_netif; EFI_SIMPLE_NETWORK *net; EFI_STATUS status; void *buf; net = nif->nif_devdata; if (net == NULL) return (-1); status = net->Transmit(net, 0, len, pkt, NULL, NULL, NULL); if (status != EFI_SUCCESS) return (-1); /* Wait for the buffer to be transmitted */ do { buf = NULL; /* XXX Is this needed? */ status = net->GetStatus(net, NULL, &buf); /* * XXX EFI1.1 and the E1000 card returns a different * address than we gave. Sigh. */ } while (status == EFI_SUCCESS && buf == NULL); /* XXX How do we deal with status != EFI_SUCCESS now? */ return ((status == EFI_SUCCESS) ? len : -1); } static ssize_t efinet_get(struct iodesc *desc, void **pkt, time_t timeout) { struct netif *nif = desc->io_netif; EFI_SIMPLE_NETWORK *net; EFI_STATUS status; UINTN bufsz; time_t t; char *buf, *ptr; ssize_t ret = -1; net = nif->nif_devdata; if (net == NULL) return (ret); bufsz = net->Mode->MaxPacketSize + ETHER_HDR_LEN + ETHER_CRC_LEN; buf = malloc(bufsz + ETHER_ALIGN); if (buf == NULL) return (ret); ptr = buf + ETHER_ALIGN; t = getsecs(); while ((getsecs() - t) < timeout) { status = net->Receive(net, NULL, &bufsz, ptr, NULL, NULL, NULL); if (status == EFI_SUCCESS) { *pkt = buf; ret = (ssize_t)bufsz; break; } if (status != EFI_NOT_READY) break; } if (ret == -1) free(buf); return (ret); } /* * Loader uses BOOTP/DHCP and also uses RARP as a fallback to populate * network parameters and problems with DHCP servers can cause the loader * to fail to populate them. Allow the device to ask about the basic * network parameters and if present use them. */ static void efi_env_net_params(struct iodesc *desc) { char *envstr; in_addr_t ipaddr, mask, gwaddr, serveraddr; n_long rootaddr; if ((envstr = getenv("rootpath")) != NULL) strlcpy(rootpath, envstr, sizeof(rootpath)); /* * Get network parameters. */ envstr = getenv("ipaddr"); ipaddr = (envstr != NULL) ? inet_addr(envstr) : 0; envstr = getenv("netmask"); mask = (envstr != NULL) ? inet_addr(envstr) : 0; envstr = getenv("gatewayip"); gwaddr = (envstr != NULL) ? inet_addr(envstr) : 0; envstr = getenv("serverip"); serveraddr = (envstr != NULL) ? inet_addr(envstr) : 0; /* No network params. */ if (ipaddr == 0 && mask == 0 && gwaddr == 0 && serveraddr == 0) return; /* Partial network params. */ if (ipaddr == 0 || mask == 0 || gwaddr == 0 || serveraddr == 0) { printf("Incomplete network settings from U-Boot\n"); return; } /* * Set network parameters. */ myip.s_addr = ipaddr; netmask = mask; gateip.s_addr = gwaddr; servip.s_addr = serveraddr; /* * There must be a rootpath. It may be ip:/path or it may be just the * path in which case the ip needs to be serverip. */ rootaddr = net_parse_rootpath(); if (rootaddr == INADDR_NONE) rootaddr = serveraddr; rootip.s_addr = rootaddr; #ifdef EFINET_DEBUG printf("%s: ip=%s\n", __func__, inet_ntoa(myip)); printf("%s: mask=%s\n", __func__, intoa(netmask)); printf("%s: gateway=%s\n", __func__, inet_ntoa(gateip)); printf("%s: server=%s\n", __func__, inet_ntoa(servip)); #endif desc->myip = myip; } static void efinet_init(struct iodesc *desc, void *machdep_hint) { struct netif *nif = desc->io_netif; EFI_SIMPLE_NETWORK *net; EFI_HANDLE h; EFI_STATUS status; UINT32 mask; /* Attempt to get netboot params from env */ efi_env_net_params(desc); if (nif->nif_driver->netif_ifs[nif->nif_unit].dif_unit < 0) { printf("Invalid network interface %d\n", nif->nif_unit); return; } h = nif->nif_driver->netif_ifs[nif->nif_unit].dif_private; status = OpenProtocolByHandle(h, &sn_guid, (void **)&nif->nif_devdata); if (status != EFI_SUCCESS) { printf("net%d: cannot fetch interface data (status=%lu)\n", nif->nif_unit, EFI_ERROR_CODE(status)); return; } net = nif->nif_devdata; if (net->Mode->State == EfiSimpleNetworkStopped) { status = net->Start(net); if (status != EFI_SUCCESS) { printf("net%d: cannot start interface (status=%lu)\n", nif->nif_unit, EFI_ERROR_CODE(status)); return; } } if (net->Mode->State != EfiSimpleNetworkInitialized) { status = net->Initialize(net, 0, 0); if (status != EFI_SUCCESS) { printf("net%d: cannot init. interface (status=%lu)\n", nif->nif_unit, EFI_ERROR_CODE(status)); return; } } mask = EFI_SIMPLE_NETWORK_RECEIVE_UNICAST | EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST; status = net->ReceiveFilters(net, mask, 0, FALSE, 0, NULL); if (status != EFI_SUCCESS) printf("net%d: cannot set rx. filters (status=%lu)\n", nif->nif_unit, EFI_ERROR_CODE(status)); #ifdef EFINET_DEBUG dump_mode(net->Mode); #endif bcopy(net->Mode->CurrentAddress.Addr, desc->myea, 6); desc->xid = 1; } static void efinet_end(struct netif *nif) { EFI_SIMPLE_NETWORK *net = nif->nif_devdata; if (net == NULL) return; net->Shutdown(net); } static int efinet_dev_init(void); static int efinet_dev_print(int); struct devsw efinet_dev = { .dv_name = "net", .dv_type = DEVT_NET, .dv_init = efinet_dev_init, .dv_strategy = NULL, /* Will be set in efinet_dev_init */ .dv_open = NULL, /* Will be set in efinet_dev_init */ .dv_close = NULL, /* Will be set in efinet_dev_init */ .dv_ioctl = noioctl, .dv_print = efinet_dev_print, - .dv_cleanup = NULL + .dv_cleanup = nullsys, }; static int efinet_dev_init() { struct netif_dif *dif; struct netif_stats *stats; EFI_DEVICE_PATH *devpath, *node; EFI_HANDLE *handles, *handles2; EFI_STATUS status; UINTN sz; int err, i, nifs; extern struct devsw netdev; sz = 0; handles = NULL; status = BS->LocateHandle(ByProtocol, &sn_guid, NULL, &sz, NULL); if (status == EFI_BUFFER_TOO_SMALL) { handles = (EFI_HANDLE *)malloc(sz); if (handles == NULL) return (ENOMEM); status = BS->LocateHandle(ByProtocol, &sn_guid, NULL, &sz, handles); if (EFI_ERROR(status)) free(handles); } if (EFI_ERROR(status)) return (efi_status_to_errno(status)); handles2 = (EFI_HANDLE *)malloc(sz); if (handles2 == NULL) { free(handles); return (ENOMEM); } nifs = 0; for (i = 0; i < sz / sizeof(EFI_HANDLE); i++) { devpath = efi_lookup_devpath(handles[i]); if (devpath == NULL) continue; if ((node = efi_devpath_last_node(devpath)) == NULL) continue; if (DevicePathType(node) != MESSAGING_DEVICE_PATH || DevicePathSubType(node) != MSG_MAC_ADDR_DP) continue; handles2[nifs] = handles[i]; nifs++; } free(handles); if (nifs == 0) { err = ENOENT; goto done; } err = efi_register_handles(&efinet_dev, handles2, NULL, nifs); if (err != 0) goto done; efinetif.netif_ifs = calloc(nifs, sizeof(struct netif_dif)); stats = calloc(nifs, sizeof(struct netif_stats)); if (efinetif.netif_ifs == NULL || stats == NULL) { free(efinetif.netif_ifs); free(stats); efinetif.netif_ifs = NULL; err = ENOMEM; goto done; } efinetif.netif_nifs = nifs; for (i = 0; i < nifs; i++) { dif = &efinetif.netif_ifs[i]; dif->dif_unit = i; dif->dif_nsel = 1; dif->dif_stats = &stats[i]; dif->dif_private = handles2[i]; } efinet_dev.dv_open = netdev.dv_open; efinet_dev.dv_close = netdev.dv_close; efinet_dev.dv_strategy = netdev.dv_strategy; done: free(handles2); return (err); } static int efinet_dev_print(int verbose) { CHAR16 *text; EFI_HANDLE h; int unit, ret = 0; printf("%s devices:", efinet_dev.dv_name); if ((ret = pager_output("\n")) != 0) return (ret); for (unit = 0, h = efi_find_handle(&efinet_dev, 0); h != NULL; h = efi_find_handle(&efinet_dev, ++unit)) { printf(" %s%d:", efinet_dev.dv_name, unit); if (verbose) { text = efi_devpath_name(efi_lookup_devpath(h)); if (text != NULL) { printf(" %S", text); efi_free_devpath_name(text); } } if ((ret = pager_output("\n")) != 0) break; } return (ret); } diff --git a/stand/efi/libefi/efipart.c b/stand/efi/libefi/efipart.c index 7807c17077a6..e5c9c88234b7 100644 --- a/stand/efi/libefi/efipart.c +++ b/stand/efi/libefi/efipart.c @@ -1,1252 +1,1252 @@ /*- * Copyright (c) 2010 Marcel Moolenaar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include static EFI_GUID blkio_guid = BLOCK_IO_PROTOCOL; typedef bool (*pd_test_cb_t)(pdinfo_t *, pdinfo_t *); static int efipart_initfd(void); static int efipart_initcd(void); static int efipart_inithd(void); static void efipart_cdinfo_add(pdinfo_t *); static int efipart_strategy(void *, int, daddr_t, size_t, char *, size_t *); static int efipart_realstrategy(void *, int, daddr_t, size_t, char *, size_t *); static int efipart_open(struct open_file *, ...); static int efipart_close(struct open_file *); static int efipart_ioctl(struct open_file *, u_long, void *); static int efipart_printfd(int); static int efipart_printcd(int); static int efipart_printhd(int); /* EISA PNP ID's for floppy controllers */ #define PNP0604 0x604 #define PNP0700 0x700 #define PNP0701 0x701 /* Bounce buffer max size */ #define BIO_BUFFER_SIZE 0x4000 struct devsw efipart_fddev = { .dv_name = "fd", .dv_type = DEVT_FD, .dv_init = efipart_initfd, .dv_strategy = efipart_strategy, .dv_open = efipart_open, .dv_close = efipart_close, .dv_ioctl = efipart_ioctl, .dv_print = efipart_printfd, - .dv_cleanup = NULL + .dv_cleanup = nullsys, }; struct devsw efipart_cddev = { .dv_name = "cd", .dv_type = DEVT_CD, .dv_init = efipart_initcd, .dv_strategy = efipart_strategy, .dv_open = efipart_open, .dv_close = efipart_close, .dv_ioctl = efipart_ioctl, .dv_print = efipart_printcd, - .dv_cleanup = NULL + .dv_cleanup = nullsys, }; struct devsw efipart_hddev = { .dv_name = "disk", .dv_type = DEVT_DISK, .dv_init = efipart_inithd, .dv_strategy = efipart_strategy, .dv_open = efipart_open, .dv_close = efipart_close, .dv_ioctl = efipart_ioctl, .dv_print = efipart_printhd, - .dv_cleanup = NULL + .dv_cleanup = nullsys, }; static pdinfo_list_t fdinfo = STAILQ_HEAD_INITIALIZER(fdinfo); static pdinfo_list_t cdinfo = STAILQ_HEAD_INITIALIZER(cdinfo); static pdinfo_list_t hdinfo = STAILQ_HEAD_INITIALIZER(hdinfo); /* * efipart_inithandles() is used to build up the pdinfo list from * block device handles. Then each devsw init callback is used to * pick items from pdinfo and move to proper device list. * In ideal world, we should end up with empty pdinfo once all * devsw initializers are called. */ static pdinfo_list_t pdinfo = STAILQ_HEAD_INITIALIZER(pdinfo); pdinfo_list_t * efiblk_get_pdinfo_list(struct devsw *dev) { if (dev->dv_type == DEVT_DISK) return (&hdinfo); if (dev->dv_type == DEVT_CD) return (&cdinfo); if (dev->dv_type == DEVT_FD) return (&fdinfo); return (NULL); } /* XXX this gets called way way too often, investigate */ pdinfo_t * efiblk_get_pdinfo(struct devdesc *dev) { pdinfo_list_t *pdi; pdinfo_t *pd = NULL; pdi = efiblk_get_pdinfo_list(dev->d_dev); if (pdi == NULL) return (pd); STAILQ_FOREACH(pd, pdi, pd_link) { if (pd->pd_unit == dev->d_unit) return (pd); } return (pd); } pdinfo_t * efiblk_get_pdinfo_by_device_path(EFI_DEVICE_PATH *path) { EFI_HANDLE h; EFI_STATUS status; EFI_DEVICE_PATH *devp = path; status = BS->LocateDevicePath(&blkio_guid, &devp, &h); if (EFI_ERROR(status)) return (NULL); return (efiblk_get_pdinfo_by_handle(h)); } static bool same_handle(pdinfo_t *pd, EFI_HANDLE h) { return (pd->pd_handle == h || pd->pd_alias == h); } pdinfo_t * efiblk_get_pdinfo_by_handle(EFI_HANDLE h) { pdinfo_t *dp, *pp; /* * Check hard disks, then cd, then floppy */ STAILQ_FOREACH(dp, &hdinfo, pd_link) { if (same_handle(dp, h)) return (dp); STAILQ_FOREACH(pp, &dp->pd_part, pd_link) { if (same_handle(pp, h)) return (pp); } } STAILQ_FOREACH(dp, &cdinfo, pd_link) { if (same_handle(dp, h)) return (dp); STAILQ_FOREACH(pp, &dp->pd_part, pd_link) { if (same_handle(pp, h)) return (pp); } } STAILQ_FOREACH(dp, &fdinfo, pd_link) { if (same_handle(dp, h)) return (dp); } return (NULL); } static int efiblk_pdinfo_count(pdinfo_list_t *pdi) { pdinfo_t *pd; int i = 0; STAILQ_FOREACH(pd, pdi, pd_link) { i++; } return (i); } static pdinfo_t * efipart_find_parent(pdinfo_list_t *pdi, EFI_DEVICE_PATH *devpath) { pdinfo_t *pd; EFI_DEVICE_PATH *parent; /* We want to find direct parent */ parent = efi_devpath_trim(devpath); /* We should not get out of memory here but be careful. */ if (parent == NULL) return (NULL); STAILQ_FOREACH(pd, pdi, pd_link) { /* We must have exact match. */ if (efi_devpath_match(pd->pd_devpath, parent)) break; } free(parent); return (pd); } /* * Return true when we should ignore this device. */ static bool efipart_ignore_device(EFI_HANDLE h, EFI_BLOCK_IO *blkio, EFI_DEVICE_PATH *devpath) { EFI_DEVICE_PATH *node, *parent; /* * We assume the block size 512 or greater power of 2. * Also skip devices with block size > 64k (16 is max * ashift supported by zfs). * iPXE is known to insert stub BLOCK IO device with * BlockSize 1. */ if (blkio->Media->BlockSize < 512 || blkio->Media->BlockSize > (1 << 16) || !powerof2(blkio->Media->BlockSize)) { efi_close_devpath(h); return (true); } /* Allowed values are 0, 1 and power of 2. */ if (blkio->Media->IoAlign > 1 && !powerof2(blkio->Media->IoAlign)) { efi_close_devpath(h); return (true); } /* * With device tree setup: * PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0)/USB(0x2,0x0) * PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0)/USB(0x2,0x0)/Unit(0x1) * PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0)/USB(0x2,0x0)/Unit(0x2) * PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0)/USB(0x2,0x0)/Unit(0x3) * PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0)/USB(0x2,0x0)/Unit(0x3)/CDROM.. * PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0)/USB(0x2,0x0)/Unit(0x3)/CDROM.. * PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0)/USB(0x2,0x0)/Unit(0x4) * PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0)/USB(0x2,0x0)/Unit(0x5) * PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0)/USB(0x2,0x0)/Unit(0x6) * PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0)/USB(0x2,0x0)/Unit(0x7) * * In above exmple only Unit(0x3) has media, all other nodes are * missing media and should not be used. * * No media does not always mean there is no device, but in above * case, we can not really assume there is any device. * Therefore, if this node is USB, or this node is Unit (LUN) and * direct parent is USB and we have no media, we will ignore this * device. * * Variation of the same situation, but with SCSI devices: * PciRoot(0x0)/Pci(0x1a,0x0)/USB(0x1,0)/USB(0x3,0x0)/SCSI(0x0,0x1) * PciRoot(0x0)/Pci(0x1a,0x0)/USB(0x1,0)/USB(0x3,0x0)/SCSI(0x0,0x2) * PciRoot(0x0)/Pci(0x1a,0x0)/USB(0x1,0)/USB(0x3,0x0)/SCSI(0x0,0x3) * PciRoot(0x0)/Pci(0x1a,0x0)/USB(0x1,0)/USB(0x3,0x0)/SCSI(0x0,0x3)/CD.. * PciRoot(0x0)/Pci(0x1a,0x0)/USB(0x1,0)/USB(0x3,0x0)/SCSI(0x0,0x3)/CD.. * PciRoot(0x0)/Pci(0x1a,0x0)/USB(0x1,0)/USB(0x3,0x0)/SCSI(0x0,0x4) * * Here above the SCSI luns 1,2 and 4 have no media. */ /* Do not ignore device with media. */ if (blkio->Media->MediaPresent) return (false); node = efi_devpath_last_node(devpath); if (node == NULL) return (false); /* USB without media present */ if (DevicePathType(node) == MESSAGING_DEVICE_PATH && DevicePathSubType(node) == MSG_USB_DP) { efi_close_devpath(h); return (true); } parent = efi_devpath_trim(devpath); if (parent != NULL) { bool parent_is_usb = false; node = efi_devpath_last_node(parent); if (node == NULL) { free(parent); return (false); } if (DevicePathType(node) == MESSAGING_DEVICE_PATH && DevicePathSubType(node) == MSG_USB_DP) parent_is_usb = true; free(parent); node = efi_devpath_last_node(devpath); if (node == NULL) return (false); if (parent_is_usb && DevicePathType(node) == MESSAGING_DEVICE_PATH) { /* * no media, parent is USB and devicepath is * LUN or SCSI. */ if (DevicePathSubType(node) == MSG_DEVICE_LOGICAL_UNIT_DP || DevicePathSubType(node) == MSG_SCSI_DP) { efi_close_devpath(h); return (true); } } } return (false); } int efipart_inithandles(void) { unsigned i, nin; UINTN sz; EFI_HANDLE *hin; EFI_DEVICE_PATH *devpath; EFI_BLOCK_IO *blkio; EFI_STATUS status; pdinfo_t *pd; if (!STAILQ_EMPTY(&pdinfo)) return (0); sz = 0; hin = NULL; status = BS->LocateHandle(ByProtocol, &blkio_guid, 0, &sz, hin); if (status == EFI_BUFFER_TOO_SMALL) { hin = malloc(sz); if (hin == NULL) return (ENOMEM); status = BS->LocateHandle(ByProtocol, &blkio_guid, 0, &sz, hin); if (EFI_ERROR(status)) free(hin); } if (EFI_ERROR(status)) return (efi_status_to_errno(status)); nin = sz / sizeof(*hin); #ifdef EFIPART_DEBUG printf("%s: Got %d BLOCK IO MEDIA handle(s)\n", __func__, nin); #endif for (i = 0; i < nin; i++) { /* * Get devpath and open protocol. * We should not get errors here */ if ((devpath = efi_lookup_devpath(hin[i])) == NULL) continue; status = OpenProtocolByHandle(hin[i], &blkio_guid, (void **)&blkio); if (EFI_ERROR(status)) { printf("error %lu\n", EFI_ERROR_CODE(status)); continue; } if (efipart_ignore_device(hin[i], blkio, devpath)) continue; /* This is bad. */ if ((pd = calloc(1, sizeof(*pd))) == NULL) { printf("efipart_inithandles: Out of memory.\n"); free(hin); return (ENOMEM); } STAILQ_INIT(&pd->pd_part); pd->pd_handle = hin[i]; pd->pd_devpath = devpath; pd->pd_blkio = blkio; STAILQ_INSERT_TAIL(&pdinfo, pd, pd_link); } /* * Walk pdinfo and set parents based on device path. */ STAILQ_FOREACH(pd, &pdinfo, pd_link) { pd->pd_parent = efipart_find_parent(&pdinfo, pd->pd_devpath); } free(hin); return (0); } /* * Get node identified by pd_test() from plist. */ static pdinfo_t * efipart_get_pd(pdinfo_list_t *plist, pd_test_cb_t pd_test, pdinfo_t *data) { pdinfo_t *pd; STAILQ_FOREACH(pd, plist, pd_link) { if (pd_test(pd, data)) break; } return (pd); } static ACPI_HID_DEVICE_PATH * efipart_floppy(EFI_DEVICE_PATH *node) { ACPI_HID_DEVICE_PATH *acpi; if (DevicePathType(node) == ACPI_DEVICE_PATH && DevicePathSubType(node) == ACPI_DP) { acpi = (ACPI_HID_DEVICE_PATH *) node; if (acpi->HID == EISA_PNP_ID(PNP0604) || acpi->HID == EISA_PNP_ID(PNP0700) || acpi->HID == EISA_PNP_ID(PNP0701)) { return (acpi); } } return (NULL); } static bool efipart_testfd(pdinfo_t *fd, pdinfo_t *data __unused) { EFI_DEVICE_PATH *node; node = efi_devpath_last_node(fd->pd_devpath); if (node == NULL) return (false); if (efipart_floppy(node) != NULL) return (true); return (false); } static int efipart_initfd(void) { EFI_DEVICE_PATH *node; ACPI_HID_DEVICE_PATH *acpi; pdinfo_t *parent, *fd; while ((fd = efipart_get_pd(&pdinfo, efipart_testfd, NULL)) != NULL) { if ((node = efi_devpath_last_node(fd->pd_devpath)) == NULL) continue; if ((acpi = efipart_floppy(node)) == NULL) continue; STAILQ_REMOVE(&pdinfo, fd, pdinfo, pd_link); parent = fd->pd_parent; if (parent != NULL) { STAILQ_REMOVE(&pdinfo, parent, pdinfo, pd_link); parent->pd_alias = fd->pd_handle; parent->pd_unit = acpi->UID; free(fd); fd = parent; } else { fd->pd_unit = acpi->UID; } fd->pd_devsw = &efipart_fddev; STAILQ_INSERT_TAIL(&fdinfo, fd, pd_link); } bcache_add_dev(efiblk_pdinfo_count(&fdinfo)); return (0); } /* * Add or update entries with new handle data. */ static void efipart_cdinfo_add(pdinfo_t *cd) { pdinfo_t *parent, *pd, *last; if (cd == NULL) return; parent = cd->pd_parent; /* Make sure we have parent added */ efipart_cdinfo_add(parent); STAILQ_FOREACH(pd, &pdinfo, pd_link) { if (efi_devpath_match(pd->pd_devpath, cd->pd_devpath)) { STAILQ_REMOVE(&pdinfo, cd, pdinfo, pd_link); break; } } if (pd == NULL) { /* This device is already added. */ return; } if (parent != NULL) { last = STAILQ_LAST(&parent->pd_part, pdinfo, pd_link); if (last != NULL) cd->pd_unit = last->pd_unit + 1; else cd->pd_unit = 0; cd->pd_devsw = &efipart_cddev; STAILQ_INSERT_TAIL(&parent->pd_part, cd, pd_link); return; } last = STAILQ_LAST(&cdinfo, pdinfo, pd_link); if (last != NULL) cd->pd_unit = last->pd_unit + 1; else cd->pd_unit = 0; cd->pd_devsw = &efipart_cddev; STAILQ_INSERT_TAIL(&cdinfo, cd, pd_link); } static bool efipart_testcd(pdinfo_t *cd, pdinfo_t *data __unused) { EFI_DEVICE_PATH *node; node = efi_devpath_last_node(cd->pd_devpath); if (node == NULL) return (false); if (efipart_floppy(node) != NULL) return (false); if (DevicePathType(node) == MEDIA_DEVICE_PATH && DevicePathSubType(node) == MEDIA_CDROM_DP) { return (true); } /* cd drive without the media. */ if (cd->pd_blkio->Media->RemovableMedia && !cd->pd_blkio->Media->MediaPresent) { return (true); } return (false); } /* * Test if pd is parent for device. */ static bool efipart_testchild(pdinfo_t *dev, pdinfo_t *pd) { /* device with no parent. */ if (dev->pd_parent == NULL) return (false); if (efi_devpath_match(dev->pd_parent->pd_devpath, pd->pd_devpath)) { return (true); } return (false); } static int efipart_initcd(void) { pdinfo_t *cd; while ((cd = efipart_get_pd(&pdinfo, efipart_testcd, NULL)) != NULL) efipart_cdinfo_add(cd); /* Find all children of CD devices we did add above. */ STAILQ_FOREACH(cd, &cdinfo, pd_link) { pdinfo_t *child; for (child = efipart_get_pd(&pdinfo, efipart_testchild, cd); child != NULL; child = efipart_get_pd(&pdinfo, efipart_testchild, cd)) efipart_cdinfo_add(child); } bcache_add_dev(efiblk_pdinfo_count(&cdinfo)); return (0); } static void efipart_hdinfo_add_node(pdinfo_t *hd, EFI_DEVICE_PATH *node) { pdinfo_t *parent, *ptr; if (node == NULL) return; parent = hd->pd_parent; /* * If the node is not MEDIA_HARDDRIVE_DP, it is sub-partition. * This can happen with Vendor nodes, and since we do not know * the more about those nodes, we just count them. */ if (DevicePathSubType(node) != MEDIA_HARDDRIVE_DP) { ptr = STAILQ_LAST(&parent->pd_part, pdinfo, pd_link); if (ptr != NULL) hd->pd_unit = ptr->pd_unit + 1; else hd->pd_unit = 0; } else { hd->pd_unit = ((HARDDRIVE_DEVICE_PATH *)node)->PartitionNumber; } hd->pd_devsw = &efipart_hddev; STAILQ_INSERT_TAIL(&parent->pd_part, hd, pd_link); } /* * The MEDIA_FILEPATH_DP has device name. * From U-Boot sources it looks like names are in the form * of typeN:M, where type is interface type, N is disk id * and M is partition id. */ static void efipart_hdinfo_add_filepath(pdinfo_t *hd, FILEPATH_DEVICE_PATH *node) { char *pathname, *p; int len; pdinfo_t *last; last = STAILQ_LAST(&hdinfo, pdinfo, pd_link); if (last != NULL) hd->pd_unit = last->pd_unit + 1; else hd->pd_unit = 0; /* FILEPATH_DEVICE_PATH has 0 terminated string */ len = ucs2len(node->PathName); if ((pathname = malloc(len + 1)) == NULL) { printf("Failed to add disk, out of memory\n"); free(hd); return; } cpy16to8(node->PathName, pathname, len + 1); p = strchr(pathname, ':'); /* * Assume we are receiving handles in order, first disk handle, * then partitions for this disk. If this assumption proves * false, this code would need update. */ if (p == NULL) { /* no colon, add the disk */ hd->pd_devsw = &efipart_hddev; STAILQ_INSERT_TAIL(&hdinfo, hd, pd_link); free(pathname); return; } p++; /* skip the colon */ errno = 0; hd->pd_unit = (int)strtol(p, NULL, 0); if (errno != 0) { printf("Bad unit number for partition \"%s\"\n", pathname); free(pathname); free(hd); return; } /* * We should have disk registered, if not, we are receiving * handles out of order, and this code should be reworked * to create "blank" disk for partition, and to find the * disk based on PathName compares. */ if (last == NULL) { printf("BUG: No disk for partition \"%s\"\n", pathname); free(pathname); free(hd); return; } /* Add the partition. */ hd->pd_parent = last; hd->pd_devsw = &efipart_hddev; STAILQ_INSERT_TAIL(&last->pd_part, hd, pd_link); free(pathname); } static void efipart_hdinfo_add(pdinfo_t *hd) { pdinfo_t *parent, *pd, *last; EFI_DEVICE_PATH *node; if (hd == NULL) return; parent = hd->pd_parent; /* Make sure we have parent added */ efipart_hdinfo_add(parent); STAILQ_FOREACH(pd, &pdinfo, pd_link) { if (efi_devpath_match(pd->pd_devpath, hd->pd_devpath)) { STAILQ_REMOVE(&pdinfo, hd, pdinfo, pd_link); break; } } if (pd == NULL) { /* This device is already added. */ return; } if ((node = efi_devpath_last_node(hd->pd_devpath)) == NULL) return; if (DevicePathType(node) == MEDIA_DEVICE_PATH && DevicePathSubType(node) == MEDIA_FILEPATH_DP) { efipart_hdinfo_add_filepath(hd, (FILEPATH_DEVICE_PATH *)node); return; } if (parent != NULL) { efipart_hdinfo_add_node(hd, node); return; } last = STAILQ_LAST(&hdinfo, pdinfo, pd_link); if (last != NULL) hd->pd_unit = last->pd_unit + 1; else hd->pd_unit = 0; /* Add the disk. */ hd->pd_devsw = &efipart_hddev; STAILQ_INSERT_TAIL(&hdinfo, hd, pd_link); } static bool efipart_testhd(pdinfo_t *hd, pdinfo_t *data __unused) { if (efipart_testfd(hd, NULL)) return (false); if (efipart_testcd(hd, NULL)) return (false); /* Anything else must be HD. */ return (true); } static int efipart_inithd(void) { pdinfo_t *hd; while ((hd = efipart_get_pd(&pdinfo, efipart_testhd, NULL)) != NULL) efipart_hdinfo_add(hd); bcache_add_dev(efiblk_pdinfo_count(&hdinfo)); return (0); } static int efipart_print_common(struct devsw *dev, pdinfo_list_t *pdlist, int verbose) { int ret = 0; EFI_BLOCK_IO *blkio; EFI_STATUS status; EFI_HANDLE h; pdinfo_t *pd; CHAR16 *text; struct disk_devdesc pd_dev; char line[80]; if (STAILQ_EMPTY(pdlist)) return (0); printf("%s devices:", dev->dv_name); if ((ret = pager_output("\n")) != 0) return (ret); STAILQ_FOREACH(pd, pdlist, pd_link) { h = pd->pd_handle; if (verbose) { /* Output the device path. */ text = efi_devpath_name(efi_lookup_devpath(h)); if (text != NULL) { printf(" %S", text); efi_free_devpath_name(text); if ((ret = pager_output("\n")) != 0) break; } } snprintf(line, sizeof(line), " %s%d", dev->dv_name, pd->pd_unit); printf("%s:", line); status = OpenProtocolByHandle(h, &blkio_guid, (void **)&blkio); if (!EFI_ERROR(status)) { printf(" %llu", blkio->Media->LastBlock == 0? 0: (unsigned long long) (blkio->Media->LastBlock + 1)); if (blkio->Media->LastBlock != 0) { printf(" X %u", blkio->Media->BlockSize); } printf(" blocks"); if (blkio->Media->MediaPresent) { if (blkio->Media->RemovableMedia) printf(" (removable)"); } else { printf(" (no media)"); } if ((ret = pager_output("\n")) != 0) break; if (!blkio->Media->MediaPresent) continue; pd->pd_blkio = blkio; pd_dev.dd.d_dev = dev; pd_dev.dd.d_unit = pd->pd_unit; pd_dev.d_slice = D_SLICENONE; pd_dev.d_partition = D_PARTNONE; ret = disk_open(&pd_dev, blkio->Media->BlockSize * (blkio->Media->LastBlock + 1), blkio->Media->BlockSize); if (ret == 0) { ret = disk_print(&pd_dev, line, verbose); disk_close(&pd_dev); if (ret != 0) return (ret); } else { /* Do not fail from disk_open() */ ret = 0; } } else { if ((ret = pager_output("\n")) != 0) break; } } return (ret); } static int efipart_printfd(int verbose) { return (efipart_print_common(&efipart_fddev, &fdinfo, verbose)); } static int efipart_printcd(int verbose) { return (efipart_print_common(&efipart_cddev, &cdinfo, verbose)); } static int efipart_printhd(int verbose) { return (efipart_print_common(&efipart_hddev, &hdinfo, verbose)); } static int efipart_open(struct open_file *f, ...) { va_list args; struct disk_devdesc *dev; pdinfo_t *pd; EFI_BLOCK_IO *blkio; EFI_STATUS status; va_start(args, f); dev = va_arg(args, struct disk_devdesc *); va_end(args); if (dev == NULL) return (EINVAL); pd = efiblk_get_pdinfo((struct devdesc *)dev); if (pd == NULL) return (EIO); if (pd->pd_blkio == NULL) { status = OpenProtocolByHandle(pd->pd_handle, &blkio_guid, (void **)&pd->pd_blkio); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); } blkio = pd->pd_blkio; if (!blkio->Media->MediaPresent) return (EAGAIN); pd->pd_open++; if (pd->pd_bcache == NULL) pd->pd_bcache = bcache_allocate(); if (dev->dd.d_dev->dv_type == DEVT_DISK) { int rc; rc = disk_open(dev, blkio->Media->BlockSize * (blkio->Media->LastBlock + 1), blkio->Media->BlockSize); if (rc != 0) { pd->pd_open--; if (pd->pd_open == 0) { pd->pd_blkio = NULL; bcache_free(pd->pd_bcache); pd->pd_bcache = NULL; } } return (rc); } return (0); } static int efipart_close(struct open_file *f) { struct disk_devdesc *dev; pdinfo_t *pd; dev = (struct disk_devdesc *)(f->f_devdata); if (dev == NULL) return (EINVAL); pd = efiblk_get_pdinfo((struct devdesc *)dev); if (pd == NULL) return (EINVAL); pd->pd_open--; if (pd->pd_open == 0) { pd->pd_blkio = NULL; if (dev->dd.d_dev->dv_type != DEVT_DISK) { bcache_free(pd->pd_bcache); pd->pd_bcache = NULL; } } if (dev->dd.d_dev->dv_type == DEVT_DISK) return (disk_close(dev)); return (0); } static int efipart_ioctl(struct open_file *f, u_long cmd, void *data) { struct disk_devdesc *dev; pdinfo_t *pd; int rc; dev = (struct disk_devdesc *)(f->f_devdata); if (dev == NULL) return (EINVAL); pd = efiblk_get_pdinfo((struct devdesc *)dev); if (pd == NULL) return (EINVAL); if (dev->dd.d_dev->dv_type == DEVT_DISK) { rc = disk_ioctl(dev, cmd, data); if (rc != ENOTTY) return (rc); } switch (cmd) { case DIOCGSECTORSIZE: *(u_int *)data = pd->pd_blkio->Media->BlockSize; break; case DIOCGMEDIASIZE: *(uint64_t *)data = pd->pd_blkio->Media->BlockSize * (pd->pd_blkio->Media->LastBlock + 1); break; default: return (ENOTTY); } return (0); } /* * efipart_readwrite() * Internal equivalent of efipart_strategy(), which operates on the * media-native block size. This function expects all I/O requests * to be within the media size and returns an error if such is not * the case. */ static int efipart_readwrite(EFI_BLOCK_IO *blkio, int rw, daddr_t blk, daddr_t nblks, char *buf) { EFI_STATUS status; TSENTER(); if (blkio == NULL) return (ENXIO); if (blk < 0 || blk > blkio->Media->LastBlock) return (EIO); if ((blk + nblks - 1) > blkio->Media->LastBlock) return (EIO); switch (rw & F_MASK) { case F_READ: status = blkio->ReadBlocks(blkio, blkio->Media->MediaId, blk, nblks * blkio->Media->BlockSize, buf); break; case F_WRITE: if (blkio->Media->ReadOnly) return (EROFS); status = blkio->WriteBlocks(blkio, blkio->Media->MediaId, blk, nblks * blkio->Media->BlockSize, buf); break; default: return (ENOSYS); } if (EFI_ERROR(status)) { printf("%s: rw=%d, blk=%ju size=%ju status=%lu\n", __func__, rw, blk, nblks, EFI_ERROR_CODE(status)); } TSEXIT(); return (efi_status_to_errno(status)); } static int efipart_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf, size_t *rsize) { struct bcache_devdata bcd; struct disk_devdesc *dev; pdinfo_t *pd; dev = (struct disk_devdesc *)devdata; if (dev == NULL) return (EINVAL); pd = efiblk_get_pdinfo((struct devdesc *)dev); if (pd == NULL) return (EINVAL); if (pd->pd_blkio->Media->RemovableMedia && !pd->pd_blkio->Media->MediaPresent) return (ENXIO); bcd.dv_strategy = efipart_realstrategy; bcd.dv_devdata = devdata; bcd.dv_cache = pd->pd_bcache; if (dev->dd.d_dev->dv_type == DEVT_DISK) { daddr_t offset; offset = dev->d_offset * pd->pd_blkio->Media->BlockSize; offset /= 512; return (bcache_strategy(&bcd, rw, blk + offset, size, buf, rsize)); } return (bcache_strategy(&bcd, rw, blk, size, buf, rsize)); } static int efipart_realstrategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf, size_t *rsize) { struct disk_devdesc *dev = (struct disk_devdesc *)devdata; pdinfo_t *pd; EFI_BLOCK_IO *blkio; uint64_t off, disk_blocks, d_offset = 0; char *blkbuf; size_t blkoff, blksz, bio_size; unsigned ioalign; bool need_buf; int rc; uint64_t diskend, readstart; if (dev == NULL || blk < 0) return (EINVAL); pd = efiblk_get_pdinfo((struct devdesc *)dev); if (pd == NULL) return (EINVAL); blkio = pd->pd_blkio; if (blkio == NULL) return (ENXIO); if (size == 0 || (size % 512) != 0) return (EIO); off = blk * 512; /* * Get disk blocks, this value is either for whole disk or for * partition. */ disk_blocks = 0; if (dev->dd.d_dev->dv_type == DEVT_DISK) { if (disk_ioctl(dev, DIOCGMEDIASIZE, &disk_blocks) == 0) { /* DIOCGMEDIASIZE does return bytes. */ disk_blocks /= blkio->Media->BlockSize; } d_offset = dev->d_offset; } if (disk_blocks == 0) disk_blocks = blkio->Media->LastBlock + 1 - d_offset; /* make sure we don't read past disk end */ if ((off + size) / blkio->Media->BlockSize > d_offset + disk_blocks) { diskend = d_offset + disk_blocks; readstart = off / blkio->Media->BlockSize; if (diskend <= readstart) { if (rsize != NULL) *rsize = 0; return (EIO); } size = diskend - readstart; size = size * blkio->Media->BlockSize; } need_buf = true; /* Do we need bounce buffer? */ if ((size % blkio->Media->BlockSize == 0) && (off % blkio->Media->BlockSize == 0)) need_buf = false; /* Do we have IO alignment requirement? */ ioalign = blkio->Media->IoAlign; if (ioalign == 0) ioalign++; if (ioalign > 1 && (uintptr_t)buf != roundup2((uintptr_t)buf, ioalign)) need_buf = true; if (need_buf) { for (bio_size = BIO_BUFFER_SIZE; bio_size > 0; bio_size -= blkio->Media->BlockSize) { blkbuf = memalign(ioalign, bio_size); if (blkbuf != NULL) break; } } else { blkbuf = buf; bio_size = size; } if (blkbuf == NULL) return (ENOMEM); if (rsize != NULL) *rsize = size; rc = 0; blk = off / blkio->Media->BlockSize; blkoff = off % blkio->Media->BlockSize; while (size > 0) { size_t x = min(size, bio_size); if (x < blkio->Media->BlockSize) x = 1; else x /= blkio->Media->BlockSize; switch (rw & F_MASK) { case F_READ: blksz = blkio->Media->BlockSize * x - blkoff; if (size < blksz) blksz = size; rc = efipart_readwrite(blkio, rw, blk, x, blkbuf); if (rc != 0) goto error; if (need_buf) bcopy(blkbuf + blkoff, buf, blksz); break; case F_WRITE: rc = 0; if (blkoff != 0) { /* * We got offset to sector, read 1 sector to * blkbuf. */ x = 1; blksz = blkio->Media->BlockSize - blkoff; blksz = min(blksz, size); rc = efipart_readwrite(blkio, F_READ, blk, x, blkbuf); } else if (size < blkio->Media->BlockSize) { /* * The remaining block is not full * sector. Read 1 sector to blkbuf. */ x = 1; blksz = size; rc = efipart_readwrite(blkio, F_READ, blk, x, blkbuf); } else { /* We can write full sector(s). */ blksz = blkio->Media->BlockSize * x; } if (rc != 0) goto error; /* * Put your Data In, Put your Data out, * Put your Data In, and shake it all about */ if (need_buf) bcopy(buf, blkbuf + blkoff, blksz); rc = efipart_readwrite(blkio, F_WRITE, blk, x, blkbuf); if (rc != 0) goto error; break; default: /* DO NOTHING */ rc = EROFS; goto error; } blkoff = 0; buf += blksz; size -= blksz; blk += x; } error: if (rsize != NULL) *rsize -= size; if (need_buf) free(blkbuf); return (rc); } diff --git a/stand/i386/libi386/biosdisk.c b/stand/i386/libi386/biosdisk.c index f8c712b324c6..353b25ff6816 100644 --- a/stand/i386/libi386/biosdisk.c +++ b/stand/i386/libi386/biosdisk.c @@ -1,1399 +1,1399 @@ /*- * Copyright (c) 1998 Michael Smith * Copyright (c) 2012 Andrey V. Elsukov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * BIOS disk device handling. * * Ideas and algorithms from: * * - NetBSD libi386/biosdisk.c * - FreeBSD biosboot/disk.c * */ #include #include #include #include #include #include #include #include #include #include #include "disk.h" #include "libi386.h" #define BIOS_NUMDRIVES 0x475 #define BIOSDISK_SECSIZE 512 #define BUFSIZE (1 * BIOSDISK_SECSIZE) #define DT_ATAPI 0x10 /* disk type for ATAPI floppies */ #define WDMAJOR 0 /* major numbers for devices we frontend for */ #define WFDMAJOR 1 #define FDMAJOR 2 #define DAMAJOR 4 #define ACDMAJOR 117 #define CDMAJOR 15 /* * INT13 commands */ #define CMD_RESET 0x0000 #define CMD_READ_CHS 0x0200 #define CMD_WRITE_CHS 0x0300 #define CMD_READ_PARAM 0x0800 #define CMD_DRIVE_TYPE 0x1500 #define CMD_CHECK_EDD 0x4100 #define CMD_READ_LBA 0x4200 #define CMD_WRITE_LBA 0x4300 #define CMD_EXT_PARAM 0x4800 #define CMD_CD_GET_STATUS 0x4b01 #define DISK_BIOS 0x13 #ifdef DISK_DEBUG #define DPRINTF(fmt, args...) printf("%s: " fmt "\n", __func__, ## args) #else #define DPRINTF(fmt, args...) ((void)0) #endif struct specification_packet { uint8_t sp_size; uint8_t sp_bootmedia; uint8_t sp_drive; uint8_t sp_controller; uint32_t sp_lba; uint16_t sp_devicespec; uint16_t sp_buffersegment; uint16_t sp_loadsegment; uint16_t sp_sectorcount; uint16_t sp_cylsec; uint8_t sp_head; uint8_t sp_dummy[16]; /* Avoid memory corruption */ }; /* * List of BIOS devices, translation from disk unit number to * BIOS unit number. */ typedef struct bdinfo { STAILQ_ENTRY(bdinfo) bd_link; /* link in device list */ int bd_unit; /* BIOS unit number */ int bd_cyl; /* BIOS geometry */ int bd_hds; int bd_sec; int bd_flags; #define BD_MODEINT13 0x0000 #define BD_MODEEDD1 0x0001 #define BD_MODEEDD3 0x0002 #define BD_MODEEDD (BD_MODEEDD1 | BD_MODEEDD3) #define BD_MODEMASK 0x0003 #define BD_FLOPPY 0x0004 #define BD_CDROM 0x0008 #define BD_NO_MEDIA 0x0010 int bd_type; /* BIOS 'drive type' (floppy only) */ uint16_t bd_sectorsize; /* Sector size */ uint64_t bd_sectors; /* Disk size */ int bd_open; /* reference counter */ void *bd_bcache; /* buffer cache data */ } bdinfo_t; #define BD_RD 0 #define BD_WR 1 typedef STAILQ_HEAD(bdinfo_list, bdinfo) bdinfo_list_t; static bdinfo_list_t fdinfo = STAILQ_HEAD_INITIALIZER(fdinfo); static bdinfo_list_t cdinfo = STAILQ_HEAD_INITIALIZER(cdinfo); static bdinfo_list_t hdinfo = STAILQ_HEAD_INITIALIZER(hdinfo); static void bd_io_workaround(bdinfo_t *); static int bd_io(struct disk_devdesc *, bdinfo_t *, daddr_t, int, caddr_t, int); static bool bd_int13probe(bdinfo_t *); static int bd_init(void); static int cd_init(void); static int fd_init(void); static int bd_strategy(void *devdata, int flag, daddr_t dblk, size_t size, char *buf, size_t *rsize); static int bd_realstrategy(void *devdata, int flag, daddr_t dblk, size_t size, char *buf, size_t *rsize); static int bd_open(struct open_file *f, ...); static int bd_close(struct open_file *f); static int bd_ioctl(struct open_file *f, u_long cmd, void *data); static int bd_print(int verbose); static int cd_print(int verbose); static int fd_print(int verbose); static void bd_reset_disk(int); static int bd_get_diskinfo_std(struct bdinfo *); struct devsw biosfd = { .dv_name = "fd", .dv_type = DEVT_FD, .dv_init = fd_init, .dv_strategy = bd_strategy, .dv_open = bd_open, .dv_close = bd_close, .dv_ioctl = bd_ioctl, .dv_print = fd_print, - .dv_cleanup = NULL + .dv_cleanup = nullsys, }; struct devsw bioscd = { .dv_name = "cd", .dv_type = DEVT_CD, .dv_init = cd_init, .dv_strategy = bd_strategy, .dv_open = bd_open, .dv_close = bd_close, .dv_ioctl = bd_ioctl, .dv_print = cd_print, - .dv_cleanup = NULL + .dv_cleanup = nullsys, }; struct devsw bioshd = { .dv_name = "disk", .dv_type = DEVT_DISK, .dv_init = bd_init, .dv_strategy = bd_strategy, .dv_open = bd_open, .dv_close = bd_close, .dv_ioctl = bd_ioctl, .dv_print = bd_print, - .dv_cleanup = NULL + .dv_cleanup = nullsys, }; static bdinfo_list_t * bd_get_bdinfo_list(struct devsw *dev) { if (dev->dv_type == DEVT_DISK) return (&hdinfo); if (dev->dv_type == DEVT_CD) return (&cdinfo); if (dev->dv_type == DEVT_FD) return (&fdinfo); return (NULL); } /* XXX this gets called way way too often, investigate */ static bdinfo_t * bd_get_bdinfo(struct devdesc *dev) { bdinfo_list_t *bdi; bdinfo_t *bd = NULL; int unit; bdi = bd_get_bdinfo_list(dev->d_dev); if (bdi == NULL) return (bd); unit = 0; STAILQ_FOREACH(bd, bdi, bd_link) { if (unit == dev->d_unit) return (bd); unit++; } return (bd); } /* * Translate between BIOS device numbers and our private unit numbers. */ int bd_bios2unit(int biosdev) { bdinfo_list_t *bdi[] = { &fdinfo, &cdinfo, &hdinfo, NULL }; bdinfo_t *bd; int i, unit; DPRINTF("looking for bios device 0x%x", biosdev); for (i = 0; bdi[i] != NULL; i++) { unit = 0; STAILQ_FOREACH(bd, bdi[i], bd_link) { if (bd->bd_unit == biosdev) { DPRINTF("bd unit %d is BIOS device 0x%x", unit, bd->bd_unit); return (unit); } unit++; } } return (-1); } int bd_unit2bios(struct i386_devdesc *dev) { bdinfo_list_t *bdi; bdinfo_t *bd; int unit; bdi = bd_get_bdinfo_list(dev->dd.d_dev); if (bdi == NULL) return (-1); unit = 0; STAILQ_FOREACH(bd, bdi, bd_link) { if (unit == dev->dd.d_unit) return (bd->bd_unit); unit++; } return (-1); } /* * Use INT13 AH=15 - Read Drive Type. */ static int fd_count(void) { int drive; for (drive = 0; drive < MAXBDDEV; drive++) { bd_reset_disk(drive); v86.ctl = V86_FLAGS; v86.addr = DISK_BIOS; v86.eax = CMD_DRIVE_TYPE; v86.edx = drive; v86int(); if (V86_CY(v86.efl)) break; if ((v86.eax & 0x300) == 0) break; } return (drive); } /* * Quiz the BIOS for disk devices, save a little info about them. */ static int fd_init(void) { int unit, numfd; bdinfo_t *bd; numfd = fd_count(); for (unit = 0; unit < numfd; unit++) { if ((bd = calloc(1, sizeof(*bd))) == NULL) break; bd->bd_sectorsize = BIOSDISK_SECSIZE; bd->bd_flags = BD_FLOPPY; bd->bd_unit = unit; /* Use std diskinfo for floppy drive */ if (bd_get_diskinfo_std(bd) != 0) { free(bd); break; } if (bd->bd_sectors == 0) bd->bd_flags |= BD_NO_MEDIA; printf("BIOS drive %c: is %s%d\n", ('A' + unit), biosfd.dv_name, unit); STAILQ_INSERT_TAIL(&fdinfo, bd, bd_link); } bcache_add_dev(unit); return (0); } static int bd_init(void) { int base, unit; bdinfo_t *bd; TSENTER(); base = 0x80; for (unit = 0; unit < *(unsigned char *)PTOV(BIOS_NUMDRIVES); unit++) { /* * Check the BIOS equipment list for number of fixed disks. */ if ((bd = calloc(1, sizeof(*bd))) == NULL) break; bd->bd_unit = base + unit; if (!bd_int13probe(bd)) { free(bd); break; } printf("BIOS drive %c: is %s%d\n", ('C' + unit), bioshd.dv_name, unit); STAILQ_INSERT_TAIL(&hdinfo, bd, bd_link); } bcache_add_dev(unit); TSEXIT(); return (0); } /* * We can't quiz, we have to be told what device to use, so this function * doesn't do anything. Instead, the loader calls bc_add() with the BIOS * device number to add. */ static int cd_init(void) { return (0); } /* * Information from bootable CD-ROM. */ static int bd_get_diskinfo_cd(struct bdinfo *bd) { struct specification_packet bc_sp; int ret = -1; (void) memset(&bc_sp, 0, sizeof (bc_sp)); /* Set sp_size as per specification. */ bc_sp.sp_size = sizeof (bc_sp) - sizeof (bc_sp.sp_dummy); v86.ctl = V86_FLAGS; v86.addr = DISK_BIOS; v86.eax = CMD_CD_GET_STATUS; v86.edx = bd->bd_unit; v86.ds = VTOPSEG(&bc_sp); v86.esi = VTOPOFF(&bc_sp); v86int(); if ((v86.eax & 0xff00) == 0 && bc_sp.sp_drive == bd->bd_unit) { bd->bd_cyl = ((bc_sp.sp_cylsec & 0xc0) << 2) + ((bc_sp.sp_cylsec & 0xff00) >> 8) + 1; bd->bd_sec = bc_sp.sp_cylsec & 0x3f; bd->bd_hds = bc_sp.sp_head + 1; bd->bd_sectors = (uint64_t)bd->bd_cyl * bd->bd_hds * bd->bd_sec; if (bc_sp.sp_bootmedia & 0x0F) { /* Floppy or hard-disk emulation */ bd->bd_sectorsize = BIOSDISK_SECSIZE; return (-1); } else { bd->bd_sectorsize = 2048; bd->bd_flags = BD_MODEEDD | BD_CDROM; ret = 0; } } /* * If this is the boot_drive, default to non-emulation bootable CD-ROM. */ if (ret != 0 && bd->bd_unit >= 0x88) { bd->bd_cyl = 0; bd->bd_hds = 1; bd->bd_sec = 15; bd->bd_sectorsize = 2048; bd->bd_flags = BD_MODEEDD | BD_CDROM; bd->bd_sectors = 0; ret = 0; } /* * Note we can not use bd_get_diskinfo_ext() nor bd_get_diskinfo_std() * here - some systems do get hung with those. */ /* * Still no size? use 7.961GB. The size does not really matter * as long as it is reasonably large to make our reads to pass * the sector count check. */ if (bd->bd_sectors == 0) bd->bd_sectors = 4173824; return (ret); } int bc_add(int biosdev) { bdinfo_t *bd; int nbcinfo = 0; if (!STAILQ_EMPTY(&cdinfo)) return (-1); if ((bd = calloc(1, sizeof(*bd))) == NULL) return (-1); bd->bd_unit = biosdev; if (bd_get_diskinfo_cd(bd) < 0) { free(bd); return (-1); } STAILQ_INSERT_TAIL(&cdinfo, bd, bd_link); printf("BIOS CD is cd%d\n", nbcinfo); nbcinfo++; bcache_add_dev(nbcinfo); /* register cd device in bcache */ return(0); } /* * Return EDD version or 0 if EDD is not supported on this drive. */ static int bd_check_extensions(int unit) { /* do not use ext calls for floppy devices */ if (unit < 0x80) return (0); /* Determine if we can use EDD with this device. */ v86.ctl = V86_FLAGS; v86.addr = DISK_BIOS; v86.eax = CMD_CHECK_EDD; v86.edx = unit; v86.ebx = EDD_QUERY_MAGIC; v86int(); if (V86_CY(v86.efl) || /* carry set */ (v86.ebx & 0xffff) != EDD_INSTALLED) /* signature */ return (0); /* extended disk access functions (AH=42h-44h,47h,48h) supported */ if ((v86.ecx & EDD_INTERFACE_FIXED_DISK) == 0) return (0); return ((v86.eax >> 8) & 0xff); } static void bd_reset_disk(int unit) { /* reset disk */ v86.ctl = V86_FLAGS; v86.addr = DISK_BIOS; v86.eax = CMD_RESET; v86.edx = unit; v86int(); } /* * Read CHS info. Return 0 on success, error otherwise. */ static int bd_get_diskinfo_std(struct bdinfo *bd) { bzero(&v86, sizeof(v86)); v86.ctl = V86_FLAGS; v86.addr = DISK_BIOS; v86.eax = CMD_READ_PARAM; v86.edx = bd->bd_unit; v86int(); if (V86_CY(v86.efl) && ((v86.eax & 0xff00) != 0)) return ((v86.eax & 0xff00) >> 8); /* return custom error on absurd sector number */ if ((v86.ecx & 0x3f) == 0) return (0x60); bd->bd_cyl = ((v86.ecx & 0xc0) << 2) + ((v86.ecx & 0xff00) >> 8) + 1; /* Convert max head # -> # of heads */ bd->bd_hds = ((v86.edx & 0xff00) >> 8) + 1; bd->bd_sec = v86.ecx & 0x3f; bd->bd_type = v86.ebx; bd->bd_sectors = (uint64_t)bd->bd_cyl * bd->bd_hds * bd->bd_sec; return (0); } /* * Read EDD info. Return 0 on success, error otherwise. * * Avoid stack corruption on some systems by adding extra bytes to * params block. */ static int bd_get_diskinfo_ext(struct bdinfo *bd) { struct disk_params { struct edd_params head; struct edd_device_path_v3 device_path; uint8_t dummy[16]; } __packed dparams; struct edd_params *params; uint64_t total; params = &dparams.head; /* Get disk params */ bzero(&dparams, sizeof(dparams)); params->len = sizeof(struct edd_params_v3); v86.ctl = V86_FLAGS; v86.addr = DISK_BIOS; v86.eax = CMD_EXT_PARAM; v86.edx = bd->bd_unit; v86.ds = VTOPSEG(&dparams); v86.esi = VTOPOFF(&dparams); v86int(); if (V86_CY(v86.efl) && ((v86.eax & 0xff00) != 0)) return ((v86.eax & 0xff00) >> 8); /* * Sector size must be a multiple of 512 bytes. * An alternate test would be to check power of 2, * powerof2(params.sector_size). * 16K is largest read buffer we can use at this time. */ if (params->sector_size >= 512 && params->sector_size <= 16384 && (params->sector_size % BIOSDISK_SECSIZE) == 0) bd->bd_sectorsize = params->sector_size; bd->bd_cyl = params->cylinders; bd->bd_hds = params->heads; bd->bd_sec = params->sectors_per_track; if (params->sectors != 0) { total = params->sectors; } else { total = (uint64_t)params->cylinders * params->heads * params->sectors_per_track; } bd->bd_sectors = total; return (0); } /* * Try to detect a device supported by the legacy int13 BIOS */ static bool bd_int13probe(bdinfo_t *bd) { int edd, ret; bd->bd_flags &= ~BD_NO_MEDIA; if ((bd->bd_flags & BD_CDROM) != 0) { return (bd_get_diskinfo_cd(bd) == 0); } edd = bd_check_extensions(bd->bd_unit); if (edd == 0) bd->bd_flags |= BD_MODEINT13; else if (edd < 0x30) bd->bd_flags |= BD_MODEEDD1; else bd->bd_flags |= BD_MODEEDD3; /* Default sector size */ if (bd->bd_sectorsize == 0) bd->bd_sectorsize = BIOSDISK_SECSIZE; /* * Test if the floppy device is present, so we can avoid receiving * bogus information from bd_get_diskinfo_std(). */ if (bd->bd_unit < 0x80) { /* reset disk */ bd_reset_disk(bd->bd_unit); /* Get disk type */ v86.ctl = V86_FLAGS; v86.addr = DISK_BIOS; v86.eax = CMD_DRIVE_TYPE; v86.edx = bd->bd_unit; v86int(); if (V86_CY(v86.efl) || (v86.eax & 0x300) == 0) return (false); } ret = 1; if (edd != 0) ret = bd_get_diskinfo_ext(bd); if (ret != 0 || bd->bd_sectors == 0) ret = bd_get_diskinfo_std(bd); if (ret != 0 && bd->bd_unit < 0x80) { /* Set defaults for 1.44 floppy */ bd->bd_cyl = 80; bd->bd_hds = 2; bd->bd_sec = 18; bd->bd_sectors = 2880; /* Since we are there, there most likely is no media */ bd->bd_flags |= BD_NO_MEDIA; ret = 0; } if (ret != 0) { if (bd->bd_sectors != 0 && edd != 0) { bd->bd_sec = 63; bd->bd_hds = 255; bd->bd_cyl = (bd->bd_sectors + bd->bd_sec * bd->bd_hds - 1) / bd->bd_sec * bd->bd_hds; } else { const char *dv_name; if ((bd->bd_flags & BD_FLOPPY) != 0) dv_name = biosfd.dv_name; else dv_name = bioshd.dv_name; printf("Can not get information about %s unit %#x\n", dv_name, bd->bd_unit); return (false); } } if (bd->bd_sec == 0) bd->bd_sec = 63; if (bd->bd_hds == 0) bd->bd_hds = 255; if (bd->bd_sectors == 0) bd->bd_sectors = (uint64_t)bd->bd_cyl * bd->bd_hds * bd->bd_sec; DPRINTF("unit 0x%x geometry %d/%d/%d\n", bd->bd_unit, bd->bd_cyl, bd->bd_hds, bd->bd_sec); return (true); } static int bd_count(bdinfo_list_t *bdi) { bdinfo_t *bd; int i; i = 0; STAILQ_FOREACH(bd, bdi, bd_link) i++; return (i); } /* * Print information about disks */ static int bd_print_common(struct devsw *dev, bdinfo_list_t *bdi, int verbose) { char line[80]; struct disk_devdesc devd; bdinfo_t *bd; int i, ret = 0; char drive; if (STAILQ_EMPTY(bdi)) return (0); printf("%s devices:", dev->dv_name); if ((ret = pager_output("\n")) != 0) return (ret); i = -1; STAILQ_FOREACH(bd, bdi, bd_link) { i++; switch (dev->dv_type) { case DEVT_FD: drive = 'A'; break; case DEVT_CD: drive = 'C' + bd_count(&hdinfo); break; default: drive = 'C'; break; } snprintf(line, sizeof(line), " %s%d: BIOS drive %c (%s%ju X %u):\n", dev->dv_name, i, drive + i, (bd->bd_flags & BD_NO_MEDIA) == BD_NO_MEDIA ? "no media, " : "", (uintmax_t)bd->bd_sectors, bd->bd_sectorsize); if ((ret = pager_output(line)) != 0) break; if ((bd->bd_flags & BD_NO_MEDIA) == BD_NO_MEDIA) continue; if (dev->dv_type != DEVT_DISK) continue; devd.dd.d_dev = dev; devd.dd.d_unit = i; devd.d_slice = D_SLICENONE; devd.d_partition = D_PARTNONE; if (disk_open(&devd, bd->bd_sectorsize * bd->bd_sectors, bd->bd_sectorsize) == 0) { snprintf(line, sizeof(line), " %s%d", dev->dv_name, i); ret = disk_print(&devd, line, verbose); disk_close(&devd); if (ret != 0) break; } } return (ret); } static int fd_print(int verbose) { return (bd_print_common(&biosfd, &fdinfo, verbose)); } static int bd_print(int verbose) { return (bd_print_common(&bioshd, &hdinfo, verbose)); } static int cd_print(int verbose) { return (bd_print_common(&bioscd, &cdinfo, verbose)); } /* * Read disk size from partition. * This is needed to work around buggy BIOS systems returning * wrong (truncated) disk media size. * During bd_probe() we tested if the multiplication of bd_sectors * would overflow so it should be safe to perform here. */ static uint64_t bd_disk_get_sectors(struct disk_devdesc *dev) { bdinfo_t *bd; struct disk_devdesc disk; uint64_t size; bd = bd_get_bdinfo(&dev->dd); if (bd == NULL) return (0); disk.dd.d_dev = dev->dd.d_dev; disk.dd.d_unit = dev->dd.d_unit; disk.d_slice = D_SLICENONE; disk.d_partition = D_PARTNONE; disk.d_offset = 0; size = bd->bd_sectors * bd->bd_sectorsize; if (disk_open(&disk, size, bd->bd_sectorsize) == 0) { (void) disk_ioctl(&disk, DIOCGMEDIASIZE, &size); disk_close(&disk); } return (size / bd->bd_sectorsize); } /* * Attempt to open the disk described by (dev) for use by (f). * * Note that the philosophy here is "give them exactly what * they ask for". This is necessary because being too "smart" * about what the user might want leads to complications. * (eg. given no slice or partition value, with a disk that is * sliced - are they after the first BSD slice, or the DOS * slice before it?) */ static int bd_open(struct open_file *f, ...) { bdinfo_t *bd; struct disk_devdesc *dev; va_list ap; int rc; TSENTER(); va_start(ap, f); dev = va_arg(ap, struct disk_devdesc *); va_end(ap); bd = bd_get_bdinfo(&dev->dd); if (bd == NULL) return (EIO); if ((bd->bd_flags & BD_NO_MEDIA) == BD_NO_MEDIA) { if (!bd_int13probe(bd)) return (EIO); if ((bd->bd_flags & BD_NO_MEDIA) == BD_NO_MEDIA) return (EIO); } if (bd->bd_bcache == NULL) bd->bd_bcache = bcache_allocate(); if (bd->bd_open == 0) bd->bd_sectors = bd_disk_get_sectors(dev); bd->bd_open++; rc = 0; if (dev->dd.d_dev->dv_type == DEVT_DISK) { rc = disk_open(dev, bd->bd_sectors * bd->bd_sectorsize, bd->bd_sectorsize); if (rc != 0) { bd->bd_open--; if (bd->bd_open == 0) { bcache_free(bd->bd_bcache); bd->bd_bcache = NULL; } } } TSEXIT(); return (rc); } static int bd_close(struct open_file *f) { struct disk_devdesc *dev; bdinfo_t *bd; int rc = 0; dev = (struct disk_devdesc *)f->f_devdata; bd = bd_get_bdinfo(&dev->dd); if (bd == NULL) return (EIO); bd->bd_open--; if (bd->bd_open == 0) { bcache_free(bd->bd_bcache); bd->bd_bcache = NULL; } if (dev->dd.d_dev->dv_type == DEVT_DISK) rc = disk_close(dev); return (rc); } static int bd_ioctl(struct open_file *f, u_long cmd, void *data) { bdinfo_t *bd; struct disk_devdesc *dev; int rc; dev = (struct disk_devdesc *)f->f_devdata; bd = bd_get_bdinfo(&dev->dd); if (bd == NULL) return (EIO); if (dev->dd.d_dev->dv_type == DEVT_DISK) { rc = disk_ioctl(dev, cmd, data); if (rc != ENOTTY) return (rc); } switch (cmd) { case DIOCGSECTORSIZE: *(uint32_t *)data = bd->bd_sectorsize; break; case DIOCGMEDIASIZE: *(uint64_t *)data = bd->bd_sectors * bd->bd_sectorsize; break; default: return (ENOTTY); } return (0); } static int bd_strategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf, size_t *rsize) { bdinfo_t *bd; struct bcache_devdata bcd; struct disk_devdesc *dev; daddr_t offset; dev = (struct disk_devdesc *)devdata; bd = bd_get_bdinfo(&dev->dd); if (bd == NULL) return (EINVAL); bcd.dv_strategy = bd_realstrategy; bcd.dv_devdata = devdata; bcd.dv_cache = bd->bd_bcache; offset = 0; if (dev->dd.d_dev->dv_type == DEVT_DISK) { offset = dev->d_offset * bd->bd_sectorsize; offset /= BIOSDISK_SECSIZE; } return (bcache_strategy(&bcd, rw, dblk + offset, size, buf, rsize)); } static int bd_realstrategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf, size_t *rsize) { struct disk_devdesc *dev = (struct disk_devdesc *)devdata; bdinfo_t *bd; uint64_t disk_blocks, offset, d_offset; size_t blks, blkoff, bsize, bio_size, rest; caddr_t bbuf = NULL; int rc; bd = bd_get_bdinfo(&dev->dd); if (bd == NULL || (bd->bd_flags & BD_NO_MEDIA) == BD_NO_MEDIA) return (EIO); /* * First make sure the IO size is a multiple of 512 bytes. While we do * process partial reads below, the strategy mechanism is built * assuming IO is a multiple of 512B blocks. If the request is not * a multiple of 512B blocks, it has to be some sort of bug. */ if (size == 0 || (size % BIOSDISK_SECSIZE) != 0) { printf("bd_strategy: %d bytes I/O not multiple of %d\n", size, BIOSDISK_SECSIZE); return (EIO); } DPRINTF("open_disk %p", dev); offset = dblk * BIOSDISK_SECSIZE; dblk = offset / bd->bd_sectorsize; blkoff = offset % bd->bd_sectorsize; /* * Check the value of the size argument. We do have quite small * heap (64MB), but we do not know good upper limit, so we check against * INT_MAX here. This will also protect us against possible overflows * while translating block count to bytes. */ if (size > INT_MAX) { DPRINTF("too large I/O: %zu bytes", size); return (EIO); } blks = size / bd->bd_sectorsize; if (blks == 0 || (size % bd->bd_sectorsize) != 0) blks++; if (dblk > dblk + blks) return (EIO); if (rsize) *rsize = 0; /* * Get disk blocks, this value is either for whole disk or for * partition. */ d_offset = 0; disk_blocks = 0; if (dev->dd.d_dev->dv_type == DEVT_DISK) { if (disk_ioctl(dev, DIOCGMEDIASIZE, &disk_blocks) == 0) { /* DIOCGMEDIASIZE does return bytes. */ disk_blocks /= bd->bd_sectorsize; } d_offset = dev->d_offset; } if (disk_blocks == 0) disk_blocks = bd->bd_sectors * (bd->bd_sectorsize / BIOSDISK_SECSIZE) - d_offset; /* Validate source block address. */ if (dblk < d_offset || dblk >= d_offset + disk_blocks) return (EIO); /* * Truncate if we are crossing disk or partition end. */ if (dblk + blks >= d_offset + disk_blocks) { blks = d_offset + disk_blocks - dblk; size = blks * bd->bd_sectorsize; DPRINTF("short I/O %d", blks); } bio_size = min(BIO_BUFFER_SIZE, size); while (bio_size > bd->bd_sectorsize) { bbuf = bio_alloc(bio_size); if (bbuf != NULL) break; bio_size -= bd->bd_sectorsize; } if (bbuf == NULL) { bio_size = V86_IO_BUFFER_SIZE; if (bio_size / bd->bd_sectorsize == 0) panic("BUG: Real mode buffer is too small"); /* Use alternate 4k buffer */ bbuf = PTOV(V86_IO_BUFFER); } rest = size; rc = 0; while (blks > 0) { int x = min(blks, bio_size / bd->bd_sectorsize); switch (rw & F_MASK) { case F_READ: DPRINTF("read %d from %lld to %p", x, dblk, buf); bsize = bd->bd_sectorsize * x - blkoff; if (rest < bsize) bsize = rest; if ((rc = bd_io(dev, bd, dblk, x, bbuf, BD_RD)) != 0) { rc = EIO; goto error; } bcopy(bbuf + blkoff, buf, bsize); break; case F_WRITE : DPRINTF("write %d from %lld to %p", x, dblk, buf); if (blkoff != 0) { /* * We got offset to sector, read 1 sector to * bbuf. */ x = 1; bsize = bd->bd_sectorsize - blkoff; bsize = min(bsize, rest); rc = bd_io(dev, bd, dblk, x, bbuf, BD_RD); } else if (rest < bd->bd_sectorsize) { /* * The remaining block is not full * sector. Read 1 sector to bbuf. */ x = 1; bsize = rest; rc = bd_io(dev, bd, dblk, x, bbuf, BD_RD); } else { /* We can write full sector(s). */ bsize = bd->bd_sectorsize * x; } /* * Put your Data In, Put your Data out, * Put your Data In, and shake it all about */ bcopy(buf, bbuf + blkoff, bsize); if ((rc = bd_io(dev, bd, dblk, x, bbuf, BD_WR)) != 0) { rc = EIO; goto error; } break; default: /* DO NOTHING */ rc = EROFS; goto error; } blkoff = 0; buf += bsize; rest -= bsize; blks -= x; dblk += x; } if (rsize != NULL) *rsize = size; error: if (bbuf != PTOV(V86_IO_BUFFER)) bio_free(bbuf, bio_size); return (rc); } static int bd_edd_io(bdinfo_t *bd, daddr_t dblk, int blks, caddr_t dest, int dowrite) { static struct edd_packet packet; TSENTER(); packet.len = sizeof(struct edd_packet); packet.count = blks; packet.off = VTOPOFF(dest); packet.seg = VTOPSEG(dest); packet.lba = dblk; v86.ctl = V86_FLAGS; v86.addr = DISK_BIOS; if (dowrite == BD_WR) v86.eax = CMD_WRITE_LBA; /* maybe Write with verify 0x4302? */ else v86.eax = CMD_READ_LBA; v86.edx = bd->bd_unit; v86.ds = VTOPSEG(&packet); v86.esi = VTOPOFF(&packet); v86int(); if (V86_CY(v86.efl)) return (v86.eax >> 8); TSEXIT(); return (0); } static int bd_chs_io(bdinfo_t *bd, daddr_t dblk, int blks, caddr_t dest, int dowrite) { uint32_t x, bpc, cyl, hd, sec; TSENTER(); bpc = bd->bd_sec * bd->bd_hds; /* blocks per cylinder */ x = dblk; cyl = x / bpc; /* block # / blocks per cylinder */ x %= bpc; /* block offset into cylinder */ hd = x / bd->bd_sec; /* offset / blocks per track */ sec = x % bd->bd_sec; /* offset into track */ /* correct sector number for 1-based BIOS numbering */ sec++; if (cyl > 1023) { /* CHS doesn't support cylinders > 1023. */ return (1); } v86.ctl = V86_FLAGS; v86.addr = DISK_BIOS; if (dowrite == BD_WR) v86.eax = CMD_WRITE_CHS | blks; else v86.eax = CMD_READ_CHS | blks; v86.ecx = ((cyl & 0xff) << 8) | ((cyl & 0x300) >> 2) | sec; v86.edx = (hd << 8) | bd->bd_unit; v86.es = VTOPSEG(dest); v86.ebx = VTOPOFF(dest); v86int(); if (V86_CY(v86.efl)) return (v86.eax >> 8); TSEXIT(); return (0); } static void bd_io_workaround(bdinfo_t *bd) { uint8_t buf[8 * 1024]; bd_edd_io(bd, 0xffffffff, 1, (caddr_t)buf, BD_RD); } static int bd_io(struct disk_devdesc *dev, bdinfo_t *bd, daddr_t dblk, int blks, caddr_t dest, int dowrite) { int result, retry; TSENTER(); /* Just in case some idiot actually tries to read/write -1 blocks... */ if (blks < 0) return (-1); /* * Workaround for a problem with some HP ProLiant BIOS failing to work * out the boot disk after installation. hrs and kuriyama discovered * this problem with an HP ProLiant DL320e Gen 8 with a 3TB HDD, and * discovered that an int13h call seems to cause a buffer overrun in * the bios. The problem is alleviated by doing an extra read before * the buggy read. It is not immediately known whether other models * are similarly affected. * Loop retrying the operation a couple of times. The BIOS * may also retry. */ if (dowrite == BD_RD && dblk >= 0x100000000) bd_io_workaround(bd); for (retry = 0; retry < 3; retry++) { if (bd->bd_flags & BD_MODEEDD) result = bd_edd_io(bd, dblk, blks, dest, dowrite); else result = bd_chs_io(bd, dblk, blks, dest, dowrite); if (result == 0) { if (bd->bd_flags & BD_NO_MEDIA) bd->bd_flags &= ~BD_NO_MEDIA; break; } bd_reset_disk(bd->bd_unit); /* * Error codes: * 20h controller failure * 31h no media in drive (IBM/MS INT 13 extensions) * 80h no media in drive, VMWare (Fusion) * There is no reason to repeat the IO with errors above. */ if (result == 0x20 || result == 0x31 || result == 0x80) { bd->bd_flags |= BD_NO_MEDIA; break; } } if (result != 0 && (bd->bd_flags & BD_NO_MEDIA) == 0) { if (dowrite == BD_WR) { printf("%s%d: Write %d sector(s) from %p (0x%x) " "to %lld: 0x%x\n", dev->dd.d_dev->dv_name, dev->dd.d_unit, blks, dest, VTOP(dest), dblk, result); } else { printf("%s%d: Read %d sector(s) from %lld to %p " "(0x%x): 0x%x\n", dev->dd.d_dev->dv_name, dev->dd.d_unit, blks, dblk, dest, VTOP(dest), result); } } TSEXIT(); return (result); } /* * Return the BIOS geometry of a given "fixed drive" in a format * suitable for the legacy bootinfo structure. Since the kernel is * expecting raw int 0x13/0x8 values for N_BIOS_GEOM drives, we * prefer to get the information directly, rather than rely on being * able to put it together from information already maintained for * different purposes and for a probably different number of drives. * * For valid drives, the geometry is expected in the format (31..0) * "000000cc cccccccc hhhhhhhh 00ssssss"; and invalid drives are * indicated by returning the geometry of a "1.2M" PC-format floppy * disk. And, incidentally, what is returned is not the geometry as * such but the highest valid cylinder, head, and sector numbers. */ uint32_t bd_getbigeom(int bunit) { v86.ctl = V86_FLAGS; v86.addr = DISK_BIOS; v86.eax = CMD_READ_PARAM; v86.edx = 0x80 + bunit; v86int(); if (V86_CY(v86.efl)) return (0x4f010f); return (((v86.ecx & 0xc0) << 18) | ((v86.ecx & 0xff00) << 8) | (v86.edx & 0xff00) | (v86.ecx & 0x3f)); } /* * Return a suitable dev_t value for (dev). * * In the case where it looks like (dev) is a SCSI disk, we allow the number of * IDE disks to be specified in $num_ide_disks. There should be a Better Way. */ int bd_getdev(struct i386_devdesc *d) { struct disk_devdesc *dev; bdinfo_t *bd; int biosdev; int major; int rootdev; char *nip, *cp; int i, unit, slice, partition; /* XXX: Assume partition 'a'. */ slice = 0; partition = 0; dev = (struct disk_devdesc *)d; bd = bd_get_bdinfo(&dev->dd); if (bd == NULL) return (-1); biosdev = bd_unit2bios(d); DPRINTF("unit %d BIOS device %d", dev->dd.d_unit, biosdev); if (biosdev == -1) /* not a BIOS device */ return (-1); if (dev->dd.d_dev->dv_type == DEVT_DISK) { if (disk_open(dev, bd->bd_sectors * bd->bd_sectorsize, bd->bd_sectorsize) != 0) /* oops, not a viable device */ return (-1); else disk_close(dev); slice = dev->d_slice + 1; partition = dev->d_partition; } if (biosdev < 0x80) { /* floppy (or emulated floppy) or ATAPI device */ if (bd->bd_type == DT_ATAPI) { /* is an ATAPI disk */ major = WFDMAJOR; } else { /* is a floppy disk */ major = FDMAJOR; } } else { /* assume an IDE disk */ major = WDMAJOR; } /* default root disk unit number */ unit = biosdev & 0x7f; if (dev->dd.d_dev->dv_type == DEVT_CD) { /* * XXX: Need to examine device spec here to figure out if * SCSI or ATAPI. No idea on how to figure out device number. * All we can really pass to the kernel is what bus and device * on which bus we were booted from, which dev_t isn't well * suited to since those number don't match to unit numbers * very well. We may just need to engage in a hack where * we pass -C to the boot args if we are the boot device. */ major = ACDMAJOR; unit = 0; /* XXX */ } /* XXX a better kludge to set the root disk unit number */ if ((nip = getenv("root_disk_unit")) != NULL) { i = strtol(nip, &cp, 0); /* check for parse error */ if ((cp != nip) && (*cp == 0)) unit = i; } rootdev = MAKEBOOTDEV(major, slice, unit, partition); DPRINTF("dev is 0x%x\n", rootdev); return (rootdev); } diff --git a/stand/i386/libi386/pxe.c b/stand/i386/libi386/pxe.c index e80a1961e191..3c6c5468fe37 100644 --- a/stand/i386/libi386/pxe.c +++ b/stand/i386/libi386/pxe.c @@ -1,631 +1,631 @@ /*- * Copyright (c) 2000 Alfred Perlstein * Copyright (c) 2000 Paul Saab * All rights reserved. * Copyright (c) 2000 John Baldwin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libi386.h" #include "btxv86.h" #include "pxe.h" static pxenv_t *pxenv_p = NULL; /* PXENV+ */ static pxe_t *pxe_p = NULL; /* !PXE */ #ifdef PXE_DEBUG static int pxe_debug = 0; #endif void pxe_enable(void *pxeinfo); static void (*pxe_call)(int func, void *ptr); static void pxenv_call(int func, void *ptr); static void bangpxe_call(int func, void *ptr); static int pxe_init(void); static int pxe_print(int verbose); static void pxe_cleanup(void); static void pxe_perror(int error); static int pxe_netif_match(struct netif *nif, void *machdep_hint); static int pxe_netif_probe(struct netif *nif, void *machdep_hint); static void pxe_netif_init(struct iodesc *desc, void *machdep_hint); static ssize_t pxe_netif_get(struct iodesc *, void **, time_t); static ssize_t pxe_netif_put(struct iodesc *desc, void *pkt, size_t len); static void pxe_netif_end(struct netif *nif); extern struct netif_stats pxe_st[]; extern uint16_t __bangpxeseg; extern uint16_t __bangpxeoff; extern void __bangpxeentry(void); extern uint16_t __pxenvseg; extern uint16_t __pxenvoff; extern void __pxenventry(void); struct netif_dif pxe_ifs[] = { /* dif_unit dif_nsel dif_stats dif_private */ {0, 1, &pxe_st[0], 0} }; struct netif_stats pxe_st[nitems(pxe_ifs)]; struct netif_driver pxenetif = { .netif_bname = "pxenet", .netif_match = pxe_netif_match, .netif_probe = pxe_netif_probe, .netif_init = pxe_netif_init, .netif_get = pxe_netif_get, .netif_put = pxe_netif_put, .netif_end = pxe_netif_end, .netif_ifs = pxe_ifs, .netif_nifs = nitems(pxe_ifs) }; struct netif_driver *netif_drivers[] = { &pxenetif, NULL }; struct devsw pxedisk = { .dv_name = "net", .dv_type = DEVT_NET, .dv_init = pxe_init, .dv_strategy = NULL, /* Will be set in pxe_init */ .dv_open = NULL, /* Will be set in pxe_init */ .dv_close = NULL, /* Will be set in pxe_init */ .dv_ioctl = noioctl, .dv_print = pxe_print, - .dv_cleanup = pxe_cleanup + .dv_cleanup = pxe_cleanup, }; /* * This function is called by the loader to enable PXE support if we * are booted by PXE. The passed in pointer is a pointer to the PXENV+ * structure. */ void pxe_enable(void *pxeinfo) { pxenv_p = (pxenv_t *)pxeinfo; pxe_p = (pxe_t *)PTOV(pxenv_p->PXEPtr.segment * 16 + pxenv_p->PXEPtr.offset); pxe_call = NULL; } /* * return true if pxe structures are found/initialized, * also figures out our IP information via the pxe cached info struct */ static int pxe_init(void) { t_PXENV_GET_CACHED_INFO *gci_p; int counter; uint8_t checksum; uint8_t *checkptr; extern struct devsw netdev; if (pxenv_p == NULL) return (0); /* look for "PXENV+" */ if (bcmp((void *)pxenv_p->Signature, S_SIZE("PXENV+"))) { pxenv_p = NULL; return (0); } /* make sure the size is something we can handle */ if (pxenv_p->Length > sizeof(*pxenv_p)) { printf("PXENV+ structure too large, ignoring\n"); pxenv_p = NULL; return (0); } /* * do byte checksum: * add up each byte in the structure, the total should be 0 */ checksum = 0; checkptr = (uint8_t *) pxenv_p; for (counter = 0; counter < pxenv_p->Length; counter++) checksum += *checkptr++; if (checksum != 0) { printf("PXENV+ structure failed checksum, ignoring\n"); pxenv_p = NULL; return (0); } /* * PXENV+ passed, so use that if !PXE is not available or * the checksum fails. */ pxe_call = pxenv_call; if (pxenv_p->Version >= 0x0200) { for (;;) { if (bcmp((void *)pxe_p->Signature, S_SIZE("!PXE"))) { pxe_p = NULL; break; } checksum = 0; checkptr = (uint8_t *)pxe_p; for (counter = 0; counter < pxe_p->StructLength; counter++) checksum += *checkptr++; if (checksum != 0) { pxe_p = NULL; break; } pxe_call = bangpxe_call; break; } } pxedisk.dv_open = netdev.dv_open; pxedisk.dv_close = netdev.dv_close; pxedisk.dv_strategy = netdev.dv_strategy; printf("\nPXE version %d.%d, real mode entry point ", (uint8_t) (pxenv_p->Version >> 8), (uint8_t) (pxenv_p->Version & 0xFF)); if (pxe_call == bangpxe_call) printf("@%04x:%04x\n", pxe_p->EntryPointSP.segment, pxe_p->EntryPointSP.offset); else printf("@%04x:%04x\n", pxenv_p->RMEntry.segment, pxenv_p->RMEntry.offset); gci_p = bio_alloc(sizeof(*gci_p)); if (gci_p == NULL) { pxe_p = NULL; return (0); } bzero(gci_p, sizeof(*gci_p)); gci_p->PacketType = PXENV_PACKET_TYPE_BINL_REPLY; pxe_call(PXENV_GET_CACHED_INFO, gci_p); if (gci_p->Status != 0) { pxe_perror(gci_p->Status); bio_free(gci_p, sizeof(*gci_p)); pxe_p = NULL; return (0); } free(bootp_response); if ((bootp_response = malloc(gci_p->BufferSize)) != NULL) { bootp_response_size = gci_p->BufferSize; bcopy(PTOV((gci_p->Buffer.segment << 4) + gci_p->Buffer.offset), bootp_response, bootp_response_size); } bio_free(gci_p, sizeof(*gci_p)); return (1); } static int pxe_print(int verbose) { if (pxe_call == NULL) return (0); printf("%s devices:", pxedisk.dv_name); if (pager_output("\n") != 0) return (1); printf(" %s0:", pxedisk.dv_name); if (verbose) { printf(" %s:%s", inet_ntoa(rootip), rootpath); } return (pager_output("\n")); } static void pxe_cleanup(void) { t_PXENV_UNLOAD_STACK *unload_stack_p; t_PXENV_UNDI_SHUTDOWN *undi_shutdown_p; if (pxe_call == NULL) return; undi_shutdown_p = bio_alloc(sizeof(*undi_shutdown_p)); if (undi_shutdown_p != NULL) { bzero(undi_shutdown_p, sizeof(*undi_shutdown_p)); pxe_call(PXENV_UNDI_SHUTDOWN, undi_shutdown_p); #ifdef PXE_DEBUG if (pxe_debug && undi_shutdown_p->Status != 0) printf("pxe_cleanup: UNDI_SHUTDOWN failed %x\n", undi_shutdown_p->Status); #endif bio_free(undi_shutdown_p, sizeof(*undi_shutdown_p)); } unload_stack_p = bio_alloc(sizeof(*unload_stack_p)); if (unload_stack_p != NULL) { bzero(unload_stack_p, sizeof(*unload_stack_p)); pxe_call(PXENV_UNLOAD_STACK, unload_stack_p); #ifdef PXE_DEBUG if (pxe_debug && unload_stack_p->Status != 0) printf("pxe_cleanup: UNLOAD_STACK failed %x\n", unload_stack_p->Status); #endif bio_free(unload_stack_p, sizeof(*unload_stack_p)); } } void pxe_perror(int err) { return; } void pxenv_call(int func, void *ptr) { #ifdef PXE_DEBUG if (pxe_debug) printf("pxenv_call %x\n", func); #endif bzero(&v86, sizeof(v86)); __pxenvseg = pxenv_p->RMEntry.segment; __pxenvoff = pxenv_p->RMEntry.offset; v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS; v86.es = VTOPSEG(ptr); v86.edi = VTOPOFF(ptr); v86.addr = (VTOPSEG(__pxenventry) << 16) | VTOPOFF(__pxenventry); v86.ebx = func; v86int(); v86.ctl = V86_FLAGS; } void bangpxe_call(int func, void *ptr) { #ifdef PXE_DEBUG if (pxe_debug) printf("bangpxe_call %x\n", func); #endif bzero(&v86, sizeof(v86)); __bangpxeseg = pxe_p->EntryPointSP.segment; __bangpxeoff = pxe_p->EntryPointSP.offset; v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS; v86.edx = VTOPSEG(ptr); v86.eax = VTOPOFF(ptr); v86.addr = (VTOPSEG(__bangpxeentry) << 16) | VTOPOFF(__bangpxeentry); v86.ebx = func; v86int(); v86.ctl = V86_FLAGS; } static int pxe_netif_match(struct netif *nif, void *machdep_hint) { return (1); } static int pxe_netif_probe(struct netif *nif, void *machdep_hint) { if (pxe_call == NULL) return (-1); return (0); } static void pxe_netif_end(struct netif *nif) { t_PXENV_UNDI_CLOSE *undi_close_p; undi_close_p = bio_alloc(sizeof(*undi_close_p)); if (undi_close_p != NULL) { bzero(undi_close_p, sizeof(*undi_close_p)); pxe_call(PXENV_UNDI_CLOSE, undi_close_p); if (undi_close_p->Status != 0) printf("undi close failed: %x\n", undi_close_p->Status); bio_free(undi_close_p, sizeof(*undi_close_p)); } } static void pxe_netif_init(struct iodesc *desc, void *machdep_hint) { t_PXENV_UNDI_GET_INFORMATION *undi_info_p; t_PXENV_UNDI_OPEN *undi_open_p; uint8_t *mac; int i, len; undi_info_p = bio_alloc(sizeof(*undi_info_p)); if (undi_info_p == NULL) return; bzero(undi_info_p, sizeof(*undi_info_p)); pxe_call(PXENV_UNDI_GET_INFORMATION, undi_info_p); if (undi_info_p->Status != 0) { printf("undi get info failed: %x\n", undi_info_p->Status); bio_free(undi_info_p, sizeof(*undi_info_p)); return; } /* Make sure the CurrentNodeAddress is valid. */ for (i = 0; i < undi_info_p->HwAddrLen; ++i) { if (undi_info_p->CurrentNodeAddress[i] != 0) break; } if (i < undi_info_p->HwAddrLen) { for (i = 0; i < undi_info_p->HwAddrLen; ++i) { if (undi_info_p->CurrentNodeAddress[i] != 0xff) break; } } if (i < undi_info_p->HwAddrLen) mac = undi_info_p->CurrentNodeAddress; else mac = undi_info_p->PermNodeAddress; len = min(sizeof (desc->myea), undi_info_p->HwAddrLen); for (i = 0; i < len; ++i) desc->myea[i] = mac[i]; if (bootp_response != NULL) desc->xid = bootp_response->bp_xid; else desc->xid = 0; bio_free(undi_info_p, sizeof(*undi_info_p)); undi_open_p = bio_alloc(sizeof(*undi_open_p)); if (undi_open_p == NULL) return; bzero(undi_open_p, sizeof(*undi_open_p)); undi_open_p->PktFilter = FLTR_DIRECTED | FLTR_BRDCST; pxe_call(PXENV_UNDI_OPEN, undi_open_p); if (undi_open_p->Status != 0) printf("undi open failed: %x\n", undi_open_p->Status); bio_free(undi_open_p, sizeof(*undi_open_p)); } static int pxe_netif_receive_isr(t_PXENV_UNDI_ISR *isr, void **pkt, ssize_t *retsize) { static bool data_pending; char *buf, *ptr, *frame; size_t size, rsize; buf = NULL; size = rsize = 0; /* * We can save ourselves the next two pxe calls because we already know * we weren't done grabbing everything. */ if (data_pending) { data_pending = false; goto nextbuf; } /* * We explicitly don't check for OURS/NOT_OURS as a result of START; * it's been reported that some cards are known to mishandle these. */ bzero(isr, sizeof(*isr)); isr->FuncFlag = PXENV_UNDI_ISR_IN_START; pxe_call(PXENV_UNDI_ISR, isr); /* We could translate Status... */ if (isr->Status != 0) { return (ENXIO); } bzero(isr, sizeof(*isr)); isr->FuncFlag = PXENV_UNDI_ISR_IN_PROCESS; pxe_call(PXENV_UNDI_ISR, isr); if (isr->Status != 0) { return (ENXIO); } if (isr->FuncFlag == PXENV_UNDI_ISR_OUT_BUSY) { /* * Let the caller decide if we need to be restarted. It will * currently blindly restart us, but it could check timeout in * the future. */ return (ERESTART); } /* * By design, we'll hardly ever hit this terminal condition unless we * pick up nothing but tx interrupts here. More frequently, we will * process rx buffers until we hit the terminal condition in the middle. */ while (isr->FuncFlag != PXENV_UNDI_ISR_OUT_DONE) { /* * This might have given us PXENV_UNDI_ISR_OUT_TRANSMIT, in * which case we can just disregard and move on to the next * buffer/frame. */ if (isr->FuncFlag != PXENV_UNDI_ISR_OUT_RECEIVE) goto nextbuf; if (buf == NULL) { /* * Grab size from the first Frame that we picked up, * allocate an rx buf to hold. Careful here, as we may * see a fragmented frame that's spread out across * multiple GET_NEXT calls. */ size = isr->FrameLength; buf = malloc(size + ETHER_ALIGN); if (buf == NULL) return (ENOMEM); ptr = buf + ETHER_ALIGN; } frame = (char *)((uintptr_t)isr->Frame.segment << 4); frame += isr->Frame.offset; bcopy(PTOV(frame), ptr, isr->BufferLength); ptr += isr->BufferLength; rsize += isr->BufferLength; /* * Stop here before we risk catching the start of another frame. * It would be nice to continue reading until we actually get a * PXENV_UNDI_ISR_OUT_DONE, but our network stack in libsa isn't * suitable for reading more than one packet at a time. */ if (rsize >= size) { data_pending = true; break; } nextbuf: bzero(isr, sizeof(*isr)); isr->FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT; pxe_call(PXENV_UNDI_ISR, isr); if (isr->Status != 0) { free(buf); return (ENXIO); } } /* * We may have never picked up a frame at all (all tx), in which case * the caller should restart us. */ if (rsize == 0) { return (ERESTART); } *pkt = buf; *retsize = rsize; return (0); } static int pxe_netif_receive(void **pkt, ssize_t *size) { t_PXENV_UNDI_ISR *isr; int ret; isr = bio_alloc(sizeof(*isr)); if (isr == NULL) return (ENOMEM); /* * This completely ignores the timeout specified in pxe_netif_get(), but * we shouldn't be running long enough here for that to make a * difference. */ for (;;) { /* We'll only really re-enter for PXENV_UNDI_ISR_OUT_BUSY. */ ret = pxe_netif_receive_isr(isr, pkt, size); if (ret != ERESTART) break; } bio_free(isr, sizeof(*isr)); return (ret); } static ssize_t pxe_netif_get(struct iodesc *desc, void **pkt, time_t timeout) { time_t t; void *ptr; int ret = -1; ssize_t size; t = getsecs(); size = 0; while ((getsecs() - t) < timeout) { ret = pxe_netif_receive(&ptr, &size); if (ret != -1) { *pkt = ptr; break; } } return (ret == 0 ? size : -1); } static ssize_t pxe_netif_put(struct iodesc *desc, void *pkt, size_t len) { t_PXENV_UNDI_TRANSMIT *trans_p; t_PXENV_UNDI_TBD *tbd_p; char *data; ssize_t rv = -1; trans_p = bio_alloc(sizeof(*trans_p)); tbd_p = bio_alloc(sizeof(*tbd_p)); data = bio_alloc(len); if (trans_p != NULL && tbd_p != NULL && data != NULL) { bzero(trans_p, sizeof(*trans_p)); bzero(tbd_p, sizeof(*tbd_p)); trans_p->TBD.segment = VTOPSEG(tbd_p); trans_p->TBD.offset = VTOPOFF(tbd_p); tbd_p->ImmedLength = len; tbd_p->Xmit.segment = VTOPSEG(data); tbd_p->Xmit.offset = VTOPOFF(data); bcopy(pkt, data, len); pxe_call(PXENV_UNDI_TRANSMIT, trans_p); if (trans_p->Status == 0) rv = len; } bio_free(data, len); bio_free(tbd_p, sizeof(*tbd_p)); bio_free(trans_p, sizeof(*trans_p)); return (rv); } diff --git a/stand/kboot/hostdisk.c b/stand/kboot/hostdisk.c index 25348c60fc97..cd125350ce9b 100644 --- a/stand/kboot/hostdisk.c +++ b/stand/kboot/hostdisk.c @@ -1,129 +1,130 @@ /*- * Copyright (C) 2014 Nathan Whitehorn * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include "bootstrap.h" #include "host_syscall.h" static int hostdisk_init(void); static int hostdisk_strategy(void *devdata, int flag, daddr_t dblk, size_t size, char *buf, size_t *rsize); static int hostdisk_open(struct open_file *f, ...); static int hostdisk_close(struct open_file *f); static int hostdisk_ioctl(struct open_file *f, u_long cmd, void *data); static int hostdisk_print(int verbose); struct devsw hostdisk = { .dv_name = "/dev", .dv_type = DEVT_DISK, .dv_init = hostdisk_init, .dv_strategy = hostdisk_strategy, .dv_open = hostdisk_open, .dv_close = hostdisk_close, .dv_ioctl = hostdisk_ioctl, .dv_print = hostdisk_print, + .dv_cleanup = nullsys, }; static int hostdisk_init(void) { return (0); } static int hostdisk_strategy(void *devdata, int flag, daddr_t dblk, size_t size, char *buf, size_t *rsize) { struct devdesc *desc = devdata; daddr_t pos; int n; uint64_t res; uint32_t posl, posh; pos = dblk * 512; posl = pos & 0xffffffff; posh = (pos >> 32) & 0xffffffff; if (host_llseek(desc->d_unit, posh, posl, &res, 0) < 0) { printf("Seek error\n"); return (EIO); } n = host_read(desc->d_unit, buf, size); if (n < 0) return (EIO); *rsize = n; return (0); } static int hostdisk_open(struct open_file *f, ...) { struct devdesc *desc; va_list vl; va_start(vl, f); desc = va_arg(vl, struct devdesc *); va_end(vl); desc->d_unit = host_open(desc->d_opendata, O_RDONLY, 0); if (desc->d_unit <= 0) { printf("hostdisk_open: couldn't open %s: %d\n", (char *)desc->d_opendata, desc->d_unit); return (ENOENT); } return (0); } static int hostdisk_close(struct open_file *f) { struct devdesc *desc = f->f_devdata; host_close(desc->d_unit); return (0); } static int hostdisk_ioctl(struct open_file *f, u_long cmd, void *data) { return (EINVAL); } static int hostdisk_print(int verbose) { return (0); } diff --git a/stand/libofw/ofw_disk.c b/stand/libofw/ofw_disk.c index 8af5750b13bc..086b16a9ecdc 100644 --- a/stand/libofw/ofw_disk.c +++ b/stand/libofw/ofw_disk.c @@ -1,184 +1,185 @@ /*- * Copyright (C) 2000 Benno Rice. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY Benno Rice ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Disk I/O routines using Open Firmware */ #include #include #include #include #include #include "bootstrap.h" #include "libofw.h" static int ofwd_init(void); static int ofwd_strategy(void *devdata, int flag, daddr_t dblk, size_t size, char *buf, size_t *rsize); static int ofwd_open(struct open_file *f, ...); static int ofwd_close(struct open_file *f); static int ofwd_ioctl(struct open_file *f, u_long cmd, void *data); static int ofwd_print(int verbose); struct devsw ofwdisk = { .dv_name = "block", .dv_type = DEVT_DISK, .dv_init = ofwd_init, .dv_strategy = ofwd_strategy, .dv_open = ofwd_open, .dv_close = ofwd_close, .dv_ioctl = ofwd_ioctl, .dv_print = ofwd_print, + .dv_cleanup = nullsys, }; /* * We're not guaranteed to be able to open a device more than once and there * is no OFW standard method to determine whether a device is already opened. * Opening a device multiple times simultaneously happens to work with most * OFW block device drivers but triggers a trap with at least the driver for * the on-board controllers of Sun Fire V100 and Ultra 1. Upper layers and MI * code expect to be able to open a device more than once however. Given that * different partitions of the same device might be opened at the same time as * done by ZFS, we can't generally just keep track of the opened devices and * reuse the instance handle when asked to open an already opened device. So * the best we can do is to cache the lastly used device path and close and * open devices in ofwd_strategy() as needed. */ static struct ofw_devdesc *kdp; static int ofwd_init(void) { return (0); } static int ofwd_strategy(void *devdata, int flag __unused, daddr_t dblk, size_t size, char *buf, size_t *rsize) { struct ofw_devdesc *dp = (struct ofw_devdesc *)devdata; daddr_t pos; int n; if (dp != kdp) { if (kdp != NULL) { #if !defined(__powerpc__) OF_close(kdp->d_handle); #endif kdp = NULL; } if ((dp->d_handle = OF_open(dp->d_path)) == -1) return (ENOENT); kdp = dp; } pos = dblk * 512; do { if (OF_seek(dp->d_handle, pos) < 0) return (EIO); n = OF_read(dp->d_handle, buf, size); if (n < 0 && n != -2) return (EIO); } while (n == -2); *rsize = size; return (0); } static int ofwd_open(struct open_file *f, ...) { struct ofw_devdesc *dp; va_list vl; va_start(vl, f); dp = va_arg(vl, struct ofw_devdesc *); va_end(vl); if (dp != kdp) { if (kdp != NULL) { OF_close(kdp->d_handle); kdp = NULL; } if ((dp->d_handle = OF_open(dp->d_path)) == -1) { printf("%s: Could not open %s\n", __func__, dp->d_path); return (ENOENT); } kdp = dp; } return (0); } static int ofwd_close(struct open_file *f) { struct ofw_devdesc *dev = f->f_devdata; if (dev == kdp) { #if !defined(__powerpc__) OF_close(dev->d_handle); #endif kdp = NULL; } return (0); } static int ofwd_ioctl(struct open_file *f, u_long cmd, void *data) { struct ofw_devdesc *dev = f->f_devdata; int block_size; unsigned int n; switch (cmd) { case DIOCGSECTORSIZE: block_size = OF_block_size(dev->d_handle); *(u_int *)data = block_size; break; case DIOCGMEDIASIZE: block_size = OF_block_size(dev->d_handle); n = OF_blocks(dev->d_handle); *(uint64_t *)data = (uint64_t)(n * block_size); break; default: return (ENOTTY); } return (0); } static int ofwd_print(int verbose __unused) { return (0); } diff --git a/stand/libsa/zfs/zfs.c b/stand/libsa/zfs/zfs.c index 633ef3b18784..71e3a49c2929 100644 --- a/stand/libsa/zfs/zfs.c +++ b/stand/libsa/zfs/zfs.c @@ -1,2042 +1,2042 @@ /*- * Copyright (c) 2007 Doug Rabson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); /* * Stand-alone file reading package. */ #include #include #include #include #include #include #include #include #include #include #include "libzfs.h" #include "zfsimpl.c" /* Define the range of indexes to be populated with ZFS Boot Environments */ #define ZFS_BE_FIRST 4 #define ZFS_BE_LAST 8 static int zfs_open(const char *path, struct open_file *f); static int zfs_close(struct open_file *f); static int zfs_read(struct open_file *f, void *buf, size_t size, size_t *resid); static off_t zfs_seek(struct open_file *f, off_t offset, int where); static int zfs_stat(struct open_file *f, struct stat *sb); static int zfs_readdir(struct open_file *f, struct dirent *d); static int zfs_mount(const char *dev, const char *path, void **data); static int zfs_unmount(const char *dev, void *data); static void zfs_bootenv_initial(const char *envname, spa_t *spa, const char *name, const char *dsname, int checkpoint); static void zfs_checkpoints_initial(spa_t *spa, const char *name, const char *dsname); struct devsw zfs_dev; struct fs_ops zfs_fsops = { .fs_name = "zfs", .fo_open = zfs_open, .fo_close = zfs_close, .fo_read = zfs_read, .fo_write = null_write, .fo_seek = zfs_seek, .fo_stat = zfs_stat, .fo_readdir = zfs_readdir, .fo_mount = zfs_mount, .fo_unmount = zfs_unmount }; /* * In-core open file. */ struct file { off_t f_seekp; /* seek pointer */ dnode_phys_t f_dnode; uint64_t f_zap_type; /* zap type for readdir */ uint64_t f_num_leafs; /* number of fzap leaf blocks */ zap_leaf_phys_t *f_zap_leaf; /* zap leaf buffer */ }; static int zfs_env_index; static int zfs_env_count; SLIST_HEAD(zfs_be_list, zfs_be_entry) zfs_be_head = SLIST_HEAD_INITIALIZER(zfs_be_head); struct zfs_be_list *zfs_be_headp; struct zfs_be_entry { char *name; SLIST_ENTRY(zfs_be_entry) entries; } *zfs_be, *zfs_be_tmp; /* * Open a file. */ static int zfs_open(const char *upath, struct open_file *f) { struct zfsmount *mount = (struct zfsmount *)f->f_devdata; struct file *fp; int rc; if (f->f_dev != &zfs_dev) return (EINVAL); /* allocate file system specific data structure */ fp = calloc(1, sizeof(struct file)); if (fp == NULL) return (ENOMEM); f->f_fsdata = fp; rc = zfs_lookup(mount, upath, &fp->f_dnode); fp->f_seekp = 0; if (rc) { f->f_fsdata = NULL; free(fp); } return (rc); } static int zfs_close(struct open_file *f) { struct file *fp = (struct file *)f->f_fsdata; dnode_cache_obj = NULL; f->f_fsdata = NULL; free(fp); return (0); } /* * Copy a portion of a file into kernel memory. * Cross block boundaries when necessary. */ static int zfs_read(struct open_file *f, void *start, size_t size, size_t *resid /* out */) { const spa_t *spa = ((struct zfsmount *)f->f_devdata)->spa; struct file *fp = (struct file *)f->f_fsdata; struct stat sb; size_t n; int rc; rc = zfs_stat(f, &sb); if (rc) return (rc); n = size; if (fp->f_seekp + n > sb.st_size) n = sb.st_size - fp->f_seekp; rc = dnode_read(spa, &fp->f_dnode, fp->f_seekp, start, n); if (rc) return (rc); if (0) { int i; for (i = 0; i < n; i++) putchar(((char*) start)[i]); } fp->f_seekp += n; if (resid) *resid = size - n; return (0); } static off_t zfs_seek(struct open_file *f, off_t offset, int where) { struct file *fp = (struct file *)f->f_fsdata; switch (where) { case SEEK_SET: fp->f_seekp = offset; break; case SEEK_CUR: fp->f_seekp += offset; break; case SEEK_END: { struct stat sb; int error; error = zfs_stat(f, &sb); if (error != 0) { errno = error; return (-1); } fp->f_seekp = sb.st_size - offset; break; } default: errno = EINVAL; return (-1); } return (fp->f_seekp); } static int zfs_stat(struct open_file *f, struct stat *sb) { const spa_t *spa = ((struct zfsmount *)f->f_devdata)->spa; struct file *fp = (struct file *)f->f_fsdata; return (zfs_dnode_stat(spa, &fp->f_dnode, sb)); } static int zfs_readdir(struct open_file *f, struct dirent *d) { const spa_t *spa = ((struct zfsmount *)f->f_devdata)->spa; struct file *fp = (struct file *)f->f_fsdata; mzap_ent_phys_t mze; struct stat sb; size_t bsize = fp->f_dnode.dn_datablkszsec << SPA_MINBLOCKSHIFT; int rc; rc = zfs_stat(f, &sb); if (rc) return (rc); if (!S_ISDIR(sb.st_mode)) return (ENOTDIR); /* * If this is the first read, get the zap type. */ if (fp->f_seekp == 0) { rc = dnode_read(spa, &fp->f_dnode, 0, &fp->f_zap_type, sizeof(fp->f_zap_type)); if (rc) return (rc); if (fp->f_zap_type == ZBT_MICRO) { fp->f_seekp = offsetof(mzap_phys_t, mz_chunk); } else { rc = dnode_read(spa, &fp->f_dnode, offsetof(zap_phys_t, zap_num_leafs), &fp->f_num_leafs, sizeof(fp->f_num_leafs)); if (rc) return (rc); fp->f_seekp = bsize; fp->f_zap_leaf = malloc(bsize); if (fp->f_zap_leaf == NULL) return (ENOMEM); rc = dnode_read(spa, &fp->f_dnode, fp->f_seekp, fp->f_zap_leaf, bsize); if (rc) return (rc); } } if (fp->f_zap_type == ZBT_MICRO) { mzap_next: if (fp->f_seekp >= bsize) return (ENOENT); rc = dnode_read(spa, &fp->f_dnode, fp->f_seekp, &mze, sizeof(mze)); if (rc) return (rc); fp->f_seekp += sizeof(mze); if (!mze.mze_name[0]) goto mzap_next; d->d_fileno = ZFS_DIRENT_OBJ(mze.mze_value); d->d_type = ZFS_DIRENT_TYPE(mze.mze_value); strcpy(d->d_name, mze.mze_name); d->d_namlen = strlen(d->d_name); return (0); } else { zap_leaf_t zl; zap_leaf_chunk_t *zc, *nc; int chunk; size_t namelen; char *p; uint64_t value; /* * Initialise this so we can use the ZAP size * calculating macros. */ zl.l_bs = ilog2(bsize); zl.l_phys = fp->f_zap_leaf; /* * Figure out which chunk we are currently looking at * and consider seeking to the next leaf. We use the * low bits of f_seekp as a simple chunk index. */ fzap_next: chunk = fp->f_seekp & (bsize - 1); if (chunk == ZAP_LEAF_NUMCHUNKS(&zl)) { fp->f_seekp = rounddown2(fp->f_seekp, bsize) + bsize; chunk = 0; /* * Check for EOF and read the new leaf. */ if (fp->f_seekp >= bsize * fp->f_num_leafs) return (ENOENT); rc = dnode_read(spa, &fp->f_dnode, fp->f_seekp, fp->f_zap_leaf, bsize); if (rc) return (rc); } zc = &ZAP_LEAF_CHUNK(&zl, chunk); fp->f_seekp++; if (zc->l_entry.le_type != ZAP_CHUNK_ENTRY) goto fzap_next; namelen = zc->l_entry.le_name_numints; if (namelen > sizeof(d->d_name)) namelen = sizeof(d->d_name); /* * Paste the name back together. */ nc = &ZAP_LEAF_CHUNK(&zl, zc->l_entry.le_name_chunk); p = d->d_name; while (namelen > 0) { int len; len = namelen; if (len > ZAP_LEAF_ARRAY_BYTES) len = ZAP_LEAF_ARRAY_BYTES; memcpy(p, nc->l_array.la_array, len); p += len; namelen -= len; nc = &ZAP_LEAF_CHUNK(&zl, nc->l_array.la_next); } d->d_name[sizeof(d->d_name) - 1] = 0; /* * Assume the first eight bytes of the value are * a uint64_t. */ value = fzap_leaf_value(&zl, zc); d->d_fileno = ZFS_DIRENT_OBJ(value); d->d_type = ZFS_DIRENT_TYPE(value); d->d_namlen = strlen(d->d_name); return (0); } } /* * if path is NULL, create mount structure, but do not add it to list. */ static int zfs_mount(const char *dev, const char *path, void **data) { struct zfs_devdesc *zfsdev; spa_t *spa; struct zfsmount *mnt; int rv; errno = 0; zfsdev = malloc(sizeof(*zfsdev)); if (zfsdev == NULL) return (errno); rv = zfs_parsedev(zfsdev, dev + 3, NULL); if (rv != 0) { free(zfsdev); return (rv); } spa = spa_find_by_dev(zfsdev); if (spa == NULL) return (ENXIO); mnt = calloc(1, sizeof(*mnt)); if (mnt != NULL && path != NULL) mnt->path = strdup(path); rv = errno; if (mnt != NULL) rv = zfs_mount_impl(spa, zfsdev->root_guid, mnt); free(zfsdev); if (rv == 0 && mnt != NULL && mnt->objset.os_type != DMU_OST_ZFS) { printf("Unexpected object set type %ju\n", (uintmax_t)mnt->objset.os_type); rv = EIO; } if (rv != 0) { if (mnt != NULL) free(mnt->path); free(mnt); return (rv); } if (mnt != NULL) { *data = mnt; if (path != NULL) STAILQ_INSERT_TAIL(&zfsmount, mnt, next); } return (rv); } static int zfs_unmount(const char *dev, void *data) { struct zfsmount *mnt = data; STAILQ_REMOVE(&zfsmount, mnt, zfsmount, next); free(mnt->path); free(mnt); return (0); } static int vdev_read(vdev_t *vdev, void *priv, off_t offset, void *buf, size_t bytes) { int fd, ret; size_t res, head, tail, total_size, full_sec_size; unsigned secsz, do_tail_read; off_t start_sec; char *outbuf, *bouncebuf; fd = (uintptr_t) priv; outbuf = (char *) buf; bouncebuf = NULL; ret = ioctl(fd, DIOCGSECTORSIZE, &secsz); if (ret != 0) return (ret); /* * Handling reads of arbitrary offset and size - multi-sector case * and single-sector case. * * Multi-sector Case * (do_tail_read = true if tail > 0) * * |<----------------------total_size--------------------->| * | | * |<--head-->|<--------------bytes------------>|<--tail-->| * | | | | * | | |<~full_sec_size~>| | | * +------------------+ +------------------+ * | |0101010| . . . |0101011| | * +------------------+ +------------------+ * start_sec start_sec + n * * * Single-sector Case * (do_tail_read = false) * * |<------total_size = secsz----->| * | | * |<-head->|<---bytes--->|<-tail->| * +-------------------------------+ * | |0101010101010| | * +-------------------------------+ * start_sec */ start_sec = offset / secsz; head = offset % secsz; total_size = roundup2(head + bytes, secsz); tail = total_size - (head + bytes); do_tail_read = ((tail > 0) && (head + bytes > secsz)); full_sec_size = total_size; if (head > 0) full_sec_size -= secsz; if (do_tail_read) full_sec_size -= secsz; /* Return of partial sector data requires a bounce buffer. */ if ((head > 0) || do_tail_read || bytes < secsz) { bouncebuf = malloc(secsz); if (bouncebuf == NULL) { printf("vdev_read: out of memory\n"); return (ENOMEM); } } if (lseek(fd, start_sec * secsz, SEEK_SET) == -1) { ret = errno; goto error; } /* Partial data return from first sector */ if (head > 0) { res = read(fd, bouncebuf, secsz); if (res != secsz) { ret = EIO; goto error; } memcpy(outbuf, bouncebuf + head, min(secsz - head, bytes)); outbuf += min(secsz - head, bytes); } /* * Full data return from read sectors. * Note, there is still corner case where we read * from sector boundary, but less than sector size, e.g. reading 512B * from 4k sector. */ if (full_sec_size > 0) { if (bytes < full_sec_size) { res = read(fd, bouncebuf, secsz); if (res != secsz) { ret = EIO; goto error; } memcpy(outbuf, bouncebuf, bytes); } else { res = read(fd, outbuf, full_sec_size); if (res != full_sec_size) { ret = EIO; goto error; } outbuf += full_sec_size; } } /* Partial data return from last sector */ if (do_tail_read) { res = read(fd, bouncebuf, secsz); if (res != secsz) { ret = EIO; goto error; } memcpy(outbuf, bouncebuf, secsz - tail); } ret = 0; error: free(bouncebuf); return (ret); } static int vdev_write(vdev_t *vdev, off_t offset, void *buf, size_t bytes) { int fd, ret; size_t head, tail, total_size, full_sec_size; unsigned secsz, do_tail_write; off_t start_sec; ssize_t res; char *outbuf, *bouncebuf; fd = (uintptr_t)vdev->v_priv; outbuf = (char *)buf; bouncebuf = NULL; ret = ioctl(fd, DIOCGSECTORSIZE, &secsz); if (ret != 0) return (ret); start_sec = offset / secsz; head = offset % secsz; total_size = roundup2(head + bytes, secsz); tail = total_size - (head + bytes); do_tail_write = ((tail > 0) && (head + bytes > secsz)); full_sec_size = total_size; if (head > 0) full_sec_size -= secsz; if (do_tail_write) full_sec_size -= secsz; /* Partial sector write requires a bounce buffer. */ if ((head > 0) || do_tail_write || bytes < secsz) { bouncebuf = malloc(secsz); if (bouncebuf == NULL) { printf("vdev_write: out of memory\n"); return (ENOMEM); } } if (lseek(fd, start_sec * secsz, SEEK_SET) == -1) { ret = errno; goto error; } /* Partial data for first sector */ if (head > 0) { res = read(fd, bouncebuf, secsz); if ((unsigned)res != secsz) { ret = EIO; goto error; } memcpy(bouncebuf + head, outbuf, min(secsz - head, bytes)); (void) lseek(fd, -secsz, SEEK_CUR); res = write(fd, bouncebuf, secsz); if ((unsigned)res != secsz) { ret = EIO; goto error; } outbuf += min(secsz - head, bytes); } /* * Full data write to sectors. * Note, there is still corner case where we write * to sector boundary, but less than sector size, e.g. write 512B * to 4k sector. */ if (full_sec_size > 0) { if (bytes < full_sec_size) { res = read(fd, bouncebuf, secsz); if ((unsigned)res != secsz) { ret = EIO; goto error; } memcpy(bouncebuf, outbuf, bytes); (void) lseek(fd, -secsz, SEEK_CUR); res = write(fd, bouncebuf, secsz); if ((unsigned)res != secsz) { ret = EIO; goto error; } } else { res = write(fd, outbuf, full_sec_size); if ((unsigned)res != full_sec_size) { ret = EIO; goto error; } outbuf += full_sec_size; } } /* Partial data write to last sector */ if (do_tail_write) { res = read(fd, bouncebuf, secsz); if ((unsigned)res != secsz) { ret = EIO; goto error; } memcpy(bouncebuf, outbuf, secsz - tail); (void) lseek(fd, -secsz, SEEK_CUR); res = write(fd, bouncebuf, secsz); if ((unsigned)res != secsz) { ret = EIO; goto error; } } ret = 0; error: free(bouncebuf); return (ret); } static int zfs_dev_init(void) { spa_t *spa; spa_t *next; spa_t *prev; zfs_init(); if (archsw.arch_zfs_probe == NULL) return (ENXIO); archsw.arch_zfs_probe(); prev = NULL; spa = STAILQ_FIRST(&zfs_pools); while (spa != NULL) { next = STAILQ_NEXT(spa, spa_link); if (zfs_spa_init(spa)) { if (prev == NULL) STAILQ_REMOVE_HEAD(&zfs_pools, spa_link); else STAILQ_REMOVE_AFTER(&zfs_pools, prev, spa_link); } else prev = spa; spa = next; } return (0); } struct zfs_probe_args { int fd; const char *devname; uint64_t *pool_guid; u_int secsz; }; static int zfs_diskread(void *arg, void *buf, size_t blocks, uint64_t offset) { struct zfs_probe_args *ppa; ppa = (struct zfs_probe_args *)arg; return (vdev_read(NULL, (void *)(uintptr_t)ppa->fd, offset * ppa->secsz, buf, blocks * ppa->secsz)); } static int zfs_probe(int fd, uint64_t *pool_guid) { spa_t *spa; int ret; spa = NULL; ret = vdev_probe(vdev_read, vdev_write, (void *)(uintptr_t)fd, &spa); if (ret == 0 && pool_guid != NULL) if (*pool_guid == 0) *pool_guid = spa->spa_guid; return (ret); } static int zfs_probe_partition(void *arg, const char *partname, const struct ptable_entry *part) { struct zfs_probe_args *ppa, pa; struct ptable *table; char devname[32]; int ret; /* Probe only freebsd-zfs and freebsd partitions */ if (part->type != PART_FREEBSD && part->type != PART_FREEBSD_ZFS) return (0); ppa = (struct zfs_probe_args *)arg; strncpy(devname, ppa->devname, strlen(ppa->devname) - 1); devname[strlen(ppa->devname) - 1] = '\0'; snprintf(devname, sizeof(devname), "%s%s:", devname, partname); pa.fd = open(devname, O_RDWR); if (pa.fd == -1) return (0); ret = zfs_probe(pa.fd, ppa->pool_guid); if (ret == 0) return (0); /* Do we have BSD label here? */ if (part->type == PART_FREEBSD) { pa.devname = devname; pa.pool_guid = ppa->pool_guid; pa.secsz = ppa->secsz; table = ptable_open(&pa, part->end - part->start + 1, ppa->secsz, zfs_diskread); if (table != NULL) { ptable_iterate(table, &pa, zfs_probe_partition); ptable_close(table); } } close(pa.fd); return (0); } /* * Return bootenv nvlist from pool label. */ int zfs_get_bootenv(void *vdev, nvlist_t **benvp) { struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; nvlist_t *benv = NULL; vdev_t *vd; spa_t *spa; if (dev->dd.d_dev->dv_type != DEVT_ZFS) return (ENOTSUP); if ((spa = spa_find_by_dev(dev)) == NULL) return (ENXIO); if (spa->spa_bootenv == NULL) { STAILQ_FOREACH(vd, &spa->spa_root_vdev->v_children, v_childlink) { benv = vdev_read_bootenv(vd); if (benv != NULL) break; } spa->spa_bootenv = benv; } else { benv = spa->spa_bootenv; } if (benv == NULL) return (ENOENT); *benvp = benv; return (0); } /* * Store nvlist to pool label bootenv area. Also updates cached pointer in spa. */ int zfs_set_bootenv(void *vdev, nvlist_t *benv) { struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; spa_t *spa; vdev_t *vd; if (dev->dd.d_dev->dv_type != DEVT_ZFS) return (ENOTSUP); if ((spa = spa_find_by_dev(dev)) == NULL) return (ENXIO); STAILQ_FOREACH(vd, &spa->spa_root_vdev->v_children, v_childlink) { vdev_write_bootenv(vd, benv); } spa->spa_bootenv = benv; return (0); } /* * Get bootonce value by key. The bootonce pair is removed * from the bootenv nvlist and the remaining nvlist is committed back to disk. */ int zfs_get_bootonce(void *vdev, const char *key, char *buf, size_t size) { nvlist_t *benv; char *result = NULL; int result_size, rv; if ((rv = zfs_get_bootenv(vdev, &benv)) != 0) return (rv); if ((rv = nvlist_find(benv, key, DATA_TYPE_STRING, NULL, &result, &result_size)) == 0) { if (result_size == 0) { /* ignore empty string */ rv = ENOENT; } else { size = MIN((size_t)result_size + 1, size); strlcpy(buf, result, size); } (void) nvlist_remove(benv, key, DATA_TYPE_STRING); (void) zfs_set_bootenv(vdev, benv); } return (rv); } /* * nvstore backend. */ static int zfs_nvstore_setter(void *, int, const char *, const void *, size_t); static int zfs_nvstore_setter_str(void *, const char *, const char *, const char *); static int zfs_nvstore_unset_impl(void *, const char *, bool); static int zfs_nvstore_setenv(void *, void *); /* * nvstore is only present for current rootfs pool. */ static int zfs_nvstore_sethook(struct env_var *ev, int flags __unused, const void *value) { struct zfs_devdesc *dev; int rv; archsw.arch_getdev((void **)&dev, NULL, NULL); if (dev == NULL) return (ENXIO); rv = zfs_nvstore_setter_str(dev, NULL, ev->ev_name, value); free(dev); return (rv); } /* * nvstore is only present for current rootfs pool. */ static int zfs_nvstore_unsethook(struct env_var *ev) { struct zfs_devdesc *dev; int rv; archsw.arch_getdev((void **)&dev, NULL, NULL); if (dev == NULL) return (ENXIO); rv = zfs_nvstore_unset_impl(dev, ev->ev_name, false); free(dev); return (rv); } static int zfs_nvstore_getter(void *vdev, const char *name, void **data) { struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; spa_t *spa; nvlist_t *nv; char *str, **ptr; int size; int rv; if (dev->dd.d_dev->dv_type != DEVT_ZFS) return (ENOTSUP); if ((spa = spa_find_by_dev(dev)) == NULL) return (ENXIO); if (spa->spa_bootenv == NULL) return (ENXIO); if (nvlist_find(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST, NULL, &nv, NULL) != 0) return (ENOENT); rv = nvlist_find(nv, name, DATA_TYPE_STRING, NULL, &str, &size); if (rv == 0) { ptr = (char **)data; asprintf(ptr, "%.*s", size, str); if (*data == NULL) rv = ENOMEM; } nvlist_destroy(nv); return (rv); } static int zfs_nvstore_setter(void *vdev, int type, const char *name, const void *data, size_t size) { struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; spa_t *spa; nvlist_t *nv; int rv; bool env_set = true; if (dev->dd.d_dev->dv_type != DEVT_ZFS) return (ENOTSUP); if ((spa = spa_find_by_dev(dev)) == NULL) return (ENXIO); if (spa->spa_bootenv == NULL) return (ENXIO); if (nvlist_find(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST, NULL, &nv, NULL) != 0) { nv = nvlist_create(NV_UNIQUE_NAME); if (nv == NULL) return (ENOMEM); } rv = 0; switch (type) { case DATA_TYPE_INT8: if (size != sizeof (int8_t)) { rv = EINVAL; break; } rv = nvlist_add_int8(nv, name, *(int8_t *)data); break; case DATA_TYPE_INT16: if (size != sizeof (int16_t)) { rv = EINVAL; break; } rv = nvlist_add_int16(nv, name, *(int16_t *)data); break; case DATA_TYPE_INT32: if (size != sizeof (int32_t)) { rv = EINVAL; break; } rv = nvlist_add_int32(nv, name, *(int32_t *)data); break; case DATA_TYPE_INT64: if (size != sizeof (int64_t)) { rv = EINVAL; break; } rv = nvlist_add_int64(nv, name, *(int64_t *)data); break; case DATA_TYPE_BYTE: if (size != sizeof (uint8_t)) { rv = EINVAL; break; } rv = nvlist_add_byte(nv, name, *(int8_t *)data); break; case DATA_TYPE_UINT8: if (size != sizeof (uint8_t)) { rv = EINVAL; break; } rv = nvlist_add_uint8(nv, name, *(int8_t *)data); break; case DATA_TYPE_UINT16: if (size != sizeof (uint16_t)) { rv = EINVAL; break; } rv = nvlist_add_uint16(nv, name, *(uint16_t *)data); break; case DATA_TYPE_UINT32: if (size != sizeof (uint32_t)) { rv = EINVAL; break; } rv = nvlist_add_uint32(nv, name, *(uint32_t *)data); break; case DATA_TYPE_UINT64: if (size != sizeof (uint64_t)) { rv = EINVAL; break; } rv = nvlist_add_uint64(nv, name, *(uint64_t *)data); break; case DATA_TYPE_STRING: rv = nvlist_add_string(nv, name, data); break; case DATA_TYPE_BOOLEAN_VALUE: if (size != sizeof (boolean_t)) { rv = EINVAL; break; } rv = nvlist_add_boolean_value(nv, name, *(boolean_t *)data); break; default: rv = EINVAL; break; } if (rv == 0) { rv = nvlist_add_nvlist(spa->spa_bootenv, OS_NVSTORE, nv); if (rv == 0) { rv = zfs_set_bootenv(vdev, spa->spa_bootenv); } if (rv == 0) { if (env_set) { rv = zfs_nvstore_setenv(vdev, nvpair_find(nv, name)); } else { env_discard(env_getenv(name)); rv = 0; } } } nvlist_destroy(nv); return (rv); } static int get_int64(const char *data, int64_t *ip) { char *end; int64_t val; errno = 0; val = strtoll(data, &end, 0); if (errno != 0 || *data == '\0' || *end != '\0') return (EINVAL); *ip = val; return (0); } static int get_uint64(const char *data, uint64_t *ip) { char *end; uint64_t val; errno = 0; val = strtoull(data, &end, 0); if (errno != 0 || *data == '\0' || *end != '\0') return (EINVAL); *ip = val; return (0); } /* * Translate textual data to data type. If type is not set, and we are * creating new pair, use DATA_TYPE_STRING. */ static int zfs_nvstore_setter_str(void *vdev, const char *type, const char *name, const char *data) { struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; spa_t *spa; nvlist_t *nv; int rv; data_type_t dt; int64_t val; uint64_t uval; if (dev->dd.d_dev->dv_type != DEVT_ZFS) return (ENOTSUP); if ((spa = spa_find_by_dev(dev)) == NULL) return (ENXIO); if (spa->spa_bootenv == NULL) return (ENXIO); if (nvlist_find(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST, NULL, &nv, NULL) != 0) { nv = NULL; } if (type == NULL) { nvp_header_t *nvh; /* * if there is no existing pair, default to string. * Otherwise, use type from existing pair. */ nvh = nvpair_find(nv, name); if (nvh == NULL) { dt = DATA_TYPE_STRING; } else { nv_string_t *nvp_name; nv_pair_data_t *nvp_data; nvp_name = (nv_string_t *)(nvh + 1); nvp_data = (nv_pair_data_t *)(&nvp_name->nv_data[0] + NV_ALIGN4(nvp_name->nv_size)); dt = nvp_data->nv_type; } } else { dt = nvpair_type_from_name(type); } nvlist_destroy(nv); rv = 0; switch (dt) { case DATA_TYPE_INT8: rv = get_int64(data, &val); if (rv == 0) { int8_t v = val; rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); } break; case DATA_TYPE_INT16: rv = get_int64(data, &val); if (rv == 0) { int16_t v = val; rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); } break; case DATA_TYPE_INT32: rv = get_int64(data, &val); if (rv == 0) { int32_t v = val; rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); } break; case DATA_TYPE_INT64: rv = get_int64(data, &val); if (rv == 0) { rv = zfs_nvstore_setter(vdev, dt, name, &val, sizeof (val)); } break; case DATA_TYPE_BYTE: rv = get_uint64(data, &uval); if (rv == 0) { uint8_t v = uval; rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); } break; case DATA_TYPE_UINT8: rv = get_uint64(data, &uval); if (rv == 0) { uint8_t v = uval; rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); } break; case DATA_TYPE_UINT16: rv = get_uint64(data, &uval); if (rv == 0) { uint16_t v = uval; rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); } break; case DATA_TYPE_UINT32: rv = get_uint64(data, &uval); if (rv == 0) { uint32_t v = uval; rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); } break; case DATA_TYPE_UINT64: rv = get_uint64(data, &uval); if (rv == 0) { rv = zfs_nvstore_setter(vdev, dt, name, &uval, sizeof (uval)); } break; case DATA_TYPE_STRING: rv = zfs_nvstore_setter(vdev, dt, name, data, strlen(data) + 1); break; case DATA_TYPE_BOOLEAN_VALUE: rv = get_int64(data, &val); if (rv == 0) { boolean_t v = val; rv = zfs_nvstore_setter(vdev, dt, name, &v, sizeof (v)); } default: rv = EINVAL; } return (rv); } static int zfs_nvstore_unset_impl(void *vdev, const char *name, bool unset_env) { struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; spa_t *spa; nvlist_t *nv; int rv; if (dev->dd.d_dev->dv_type != DEVT_ZFS) return (ENOTSUP); if ((spa = spa_find_by_dev(dev)) == NULL) return (ENXIO); if (spa->spa_bootenv == NULL) return (ENXIO); if (nvlist_find(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST, NULL, &nv, NULL) != 0) return (ENOENT); rv = nvlist_remove(nv, name, DATA_TYPE_UNKNOWN); if (rv == 0) { if (nvlist_next_nvpair(nv, NULL) == NULL) { rv = nvlist_remove(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST); } else { rv = nvlist_add_nvlist(spa->spa_bootenv, OS_NVSTORE, nv); } if (rv == 0) rv = zfs_set_bootenv(vdev, spa->spa_bootenv); } if (unset_env) env_discard(env_getenv(name)); return (rv); } static int zfs_nvstore_unset(void *vdev, const char *name) { return (zfs_nvstore_unset_impl(vdev, name, true)); } static int zfs_nvstore_print(void *vdev __unused, void *ptr) { nvpair_print(ptr, 0); return (0); } /* * Create environment variable from nvpair. * set hook will update nvstore with new value, unset hook will remove * variable from nvstore. */ static int zfs_nvstore_setenv(void *vdev __unused, void *ptr) { nvp_header_t *nvh = ptr; nv_string_t *nvp_name, *nvp_value; nv_pair_data_t *nvp_data; char *name, *value; int rv = 0; if (nvh == NULL) return (ENOENT); nvp_name = (nv_string_t *)(nvh + 1); nvp_data = (nv_pair_data_t *)(&nvp_name->nv_data[0] + NV_ALIGN4(nvp_name->nv_size)); if ((name = nvstring_get(nvp_name)) == NULL) return (ENOMEM); value = NULL; switch (nvp_data->nv_type) { case DATA_TYPE_BYTE: case DATA_TYPE_UINT8: (void) asprintf(&value, "%uc", *(unsigned *)&nvp_data->nv_data[0]); if (value == NULL) rv = ENOMEM; break; case DATA_TYPE_INT8: (void) asprintf(&value, "%c", *(int *)&nvp_data->nv_data[0]); if (value == NULL) rv = ENOMEM; break; case DATA_TYPE_INT16: (void) asprintf(&value, "%hd", *(short *)&nvp_data->nv_data[0]); if (value == NULL) rv = ENOMEM; break; case DATA_TYPE_UINT16: (void) asprintf(&value, "%hu", *(unsigned short *)&nvp_data->nv_data[0]); if (value == NULL) rv = ENOMEM; break; case DATA_TYPE_BOOLEAN_VALUE: case DATA_TYPE_INT32: (void) asprintf(&value, "%d", *(int *)&nvp_data->nv_data[0]); if (value == NULL) rv = ENOMEM; break; case DATA_TYPE_UINT32: (void) asprintf(&value, "%u", *(unsigned *)&nvp_data->nv_data[0]); if (value == NULL) rv = ENOMEM; break; case DATA_TYPE_INT64: (void) asprintf(&value, "%jd", (intmax_t)*(int64_t *)&nvp_data->nv_data[0]); if (value == NULL) rv = ENOMEM; break; case DATA_TYPE_UINT64: (void) asprintf(&value, "%ju", (uintmax_t)*(uint64_t *)&nvp_data->nv_data[0]); if (value == NULL) rv = ENOMEM; break; case DATA_TYPE_STRING: nvp_value = (nv_string_t *)&nvp_data->nv_data[0]; if ((value = nvstring_get(nvp_value)) == NULL) { rv = ENOMEM; break; } break; default: rv = EINVAL; break; } if (value != NULL) { rv = env_setenv(name, EV_VOLATILE | EV_NOHOOK, value, zfs_nvstore_sethook, zfs_nvstore_unsethook); free(value); } free(name); return (rv); } static int zfs_nvstore_iterate(void *vdev, int (*cb)(void *, void *)) { struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; spa_t *spa; nvlist_t *nv; nvp_header_t *nvh; int rv; if (dev->dd.d_dev->dv_type != DEVT_ZFS) return (ENOTSUP); if ((spa = spa_find_by_dev(dev)) == NULL) return (ENXIO); if (spa->spa_bootenv == NULL) return (ENXIO); if (nvlist_find(spa->spa_bootenv, OS_NVSTORE, DATA_TYPE_NVLIST, NULL, &nv, NULL) != 0) return (ENOENT); rv = 0; nvh = NULL; while ((nvh = nvlist_next_nvpair(nv, nvh)) != NULL) { rv = cb(vdev, nvh); if (rv != 0) break; } return (rv); } nvs_callbacks_t nvstore_zfs_cb = { .nvs_getter = zfs_nvstore_getter, .nvs_setter = zfs_nvstore_setter, .nvs_setter_str = zfs_nvstore_setter_str, .nvs_unset = zfs_nvstore_unset, .nvs_print = zfs_nvstore_print, .nvs_iterate = zfs_nvstore_iterate }; int zfs_attach_nvstore(void *vdev) { struct zfs_devdesc *dev = vdev; spa_t *spa; uint64_t version; int rv; if (dev->dd.d_dev->dv_type != DEVT_ZFS) return (ENOTSUP); if ((spa = spa_find_by_dev(dev)) == NULL) return (ENXIO); rv = nvlist_find(spa->spa_bootenv, BOOTENV_VERSION, DATA_TYPE_UINT64, NULL, &version, NULL); if (rv != 0 || version != VB_NVLIST) { return (ENXIO); } dev = malloc(sizeof (*dev)); if (dev == NULL) return (ENOMEM); memcpy(dev, vdev, sizeof (*dev)); rv = nvstore_init(spa->spa_name, &nvstore_zfs_cb, dev); if (rv != 0) free(dev); else rv = zfs_nvstore_iterate(dev, zfs_nvstore_setenv); return (rv); } int zfs_probe_dev(const char *devname, uint64_t *pool_guid) { struct ptable *table; struct zfs_probe_args pa; uint64_t mediasz; int ret; if (pool_guid) *pool_guid = 0; pa.fd = open(devname, O_RDWR); if (pa.fd == -1) return (ENXIO); /* Probe the whole disk */ ret = zfs_probe(pa.fd, pool_guid); if (ret == 0) return (0); /* Probe each partition */ ret = ioctl(pa.fd, DIOCGMEDIASIZE, &mediasz); if (ret == 0) ret = ioctl(pa.fd, DIOCGSECTORSIZE, &pa.secsz); if (ret == 0) { pa.devname = devname; pa.pool_guid = pool_guid; table = ptable_open(&pa, mediasz / pa.secsz, pa.secsz, zfs_diskread); if (table != NULL) { ptable_iterate(table, &pa, zfs_probe_partition); ptable_close(table); } } close(pa.fd); if (pool_guid && *pool_guid == 0) ret = ENXIO; return (ret); } /* * Print information about ZFS pools */ static int zfs_dev_print(int verbose) { spa_t *spa; char line[80]; int ret = 0; if (STAILQ_EMPTY(&zfs_pools)) return (0); printf("%s devices:", zfs_dev.dv_name); if ((ret = pager_output("\n")) != 0) return (ret); if (verbose) { return (spa_all_status()); } STAILQ_FOREACH(spa, &zfs_pools, spa_link) { snprintf(line, sizeof(line), " zfs:%s\n", spa->spa_name); ret = pager_output(line); if (ret != 0) break; } return (ret); } /* * Attempt to open the pool described by (dev) for use by (f). */ static int zfs_dev_open(struct open_file *f, ...) { va_list args; struct zfs_devdesc *dev; struct zfsmount *mount; spa_t *spa; int rv; va_start(args, f); dev = va_arg(args, struct zfs_devdesc *); va_end(args); if ((spa = spa_find_by_dev(dev)) == NULL) return (ENXIO); STAILQ_FOREACH(mount, &zfsmount, next) { if (spa->spa_guid == mount->spa->spa_guid) break; } rv = 0; /* This device is not set as currdev, mount us private copy. */ if (mount == NULL) rv = zfs_mount(zfs_fmtdev(dev), NULL, (void **)&mount); if (rv == 0) { f->f_devdata = mount; free(dev); } return (rv); } static int zfs_dev_close(struct open_file *f) { struct zfsmount *mnt, *mount; mnt = f->f_devdata; STAILQ_FOREACH(mount, &zfsmount, next) { if (mnt->spa->spa_guid == mount->spa->spa_guid) break; } /* * devclose() will free f->f_devdata, but since we do have * pointer to zfsmount structure in f->f_devdata, and * zfs_unmount() will also free the zfsmount structure, * we will get double free. To prevent double free, * we must set f_devdata to NULL there. */ if (mount != NULL) f->f_devdata = NULL; return (0); } static int zfs_dev_strategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf, size_t *rsize) { return (ENOSYS); } struct devsw zfs_dev = { .dv_name = "zfs", .dv_type = DEVT_ZFS, .dv_init = zfs_dev_init, .dv_strategy = zfs_dev_strategy, .dv_open = zfs_dev_open, .dv_close = zfs_dev_close, .dv_ioctl = noioctl, .dv_print = zfs_dev_print, - .dv_cleanup = NULL + .dv_cleanup = nullsys, }; int zfs_parsedev(struct zfs_devdesc *dev, const char *devspec, const char **path) { static char rootname[ZFS_MAXNAMELEN]; static char poolname[ZFS_MAXNAMELEN]; spa_t *spa; const char *end; const char *np; const char *sep; int rv; np = devspec; if (*np != ':') return (EINVAL); np++; end = strrchr(np, ':'); if (end == NULL) return (EINVAL); sep = strchr(np, '/'); if (sep == NULL || sep >= end) sep = end; memcpy(poolname, np, sep - np); poolname[sep - np] = '\0'; if (sep < end) { sep++; memcpy(rootname, sep, end - sep); rootname[end - sep] = '\0'; } else rootname[0] = '\0'; spa = spa_find_by_name(poolname); if (!spa) return (ENXIO); dev->pool_guid = spa->spa_guid; rv = zfs_lookup_dataset(spa, rootname, &dev->root_guid); if (rv != 0) return (rv); if (path != NULL) *path = (*end == '\0') ? end : end + 1; dev->dd.d_dev = &zfs_dev; return (0); } char * zfs_fmtdev(void *vdev) { static char rootname[ZFS_MAXNAMELEN]; static char buf[2 * ZFS_MAXNAMELEN + 8]; struct zfs_devdesc *dev = (struct zfs_devdesc *)vdev; spa_t *spa; buf[0] = '\0'; if (dev->dd.d_dev->dv_type != DEVT_ZFS) return (buf); /* Do we have any pools? */ spa = STAILQ_FIRST(&zfs_pools); if (spa == NULL) return (buf); if (dev->pool_guid == 0) dev->pool_guid = spa->spa_guid; else spa = spa_find_by_guid(dev->pool_guid); if (spa == NULL) { printf("ZFS: can't find pool by guid\n"); return (buf); } if (dev->root_guid == 0 && zfs_get_root(spa, &dev->root_guid)) { printf("ZFS: can't find root filesystem\n"); return (buf); } if (zfs_rlookup(spa, dev->root_guid, rootname)) { printf("ZFS: can't find filesystem by guid\n"); return (buf); } if (rootname[0] == '\0') snprintf(buf, sizeof(buf), "%s:%s:", dev->dd.d_dev->dv_name, spa->spa_name); else snprintf(buf, sizeof(buf), "%s:%s/%s:", dev->dd.d_dev->dv_name, spa->spa_name, rootname); return (buf); } static int split_devname(const char *name, char *poolname, size_t size, const char **dsnamep) { const char *dsname; size_t len; ASSERT(name != NULL); ASSERT(poolname != NULL); len = strlen(name); dsname = strchr(name, '/'); if (dsname != NULL) { len = dsname - name; dsname++; } else dsname = ""; if (len + 1 > size) return (EINVAL); strlcpy(poolname, name, len + 1); if (dsnamep != NULL) *dsnamep = dsname; return (0); } int zfs_list(const char *name) { static char poolname[ZFS_MAXNAMELEN]; uint64_t objid; spa_t *spa; const char *dsname; int rv; if (split_devname(name, poolname, sizeof(poolname), &dsname) != 0) return (EINVAL); spa = spa_find_by_name(poolname); if (!spa) return (ENXIO); rv = zfs_lookup_dataset(spa, dsname, &objid); if (rv != 0) return (rv); return (zfs_list_dataset(spa, objid)); } void init_zfs_boot_options(const char *currdev_in) { char poolname[ZFS_MAXNAMELEN]; char *beroot, *currdev; spa_t *spa; int currdev_len; const char *dsname; currdev = NULL; currdev_len = strlen(currdev_in); if (currdev_len == 0) return; if (strncmp(currdev_in, "zfs:", 4) != 0) return; currdev = strdup(currdev_in); if (currdev == NULL) return; /* Remove the trailing : */ currdev[currdev_len - 1] = '\0'; setenv("zfs_be_active", currdev, 1); setenv("zfs_be_currpage", "1", 1); /* Remove the last element (current bootenv) */ beroot = strrchr(currdev, '/'); if (beroot != NULL) beroot[0] = '\0'; beroot = strchr(currdev, ':') + 1; setenv("zfs_be_root", beroot, 1); if (split_devname(beroot, poolname, sizeof(poolname), &dsname) != 0) return; spa = spa_find_by_name(poolname); if (spa == NULL) return; zfs_bootenv_initial("bootenvs", spa, beroot, dsname, 0); zfs_checkpoints_initial(spa, beroot, dsname); free(currdev); } static void zfs_checkpoints_initial(spa_t *spa, const char *name, const char *dsname) { char envname[32]; if (spa->spa_uberblock_checkpoint.ub_checkpoint_txg != 0) { snprintf(envname, sizeof(envname), "zpool_checkpoint"); setenv(envname, name, 1); spa->spa_uberblock = &spa->spa_uberblock_checkpoint; spa->spa_mos = &spa->spa_mos_checkpoint; zfs_bootenv_initial("bootenvs_check", spa, name, dsname, 1); spa->spa_uberblock = &spa->spa_uberblock_master; spa->spa_mos = &spa->spa_mos_master; } } static void zfs_bootenv_initial(const char *envprefix, spa_t *spa, const char *rootname, const char *dsname, int checkpoint) { char envname[32], envval[256]; uint64_t objid; int bootenvs_idx, rv; SLIST_INIT(&zfs_be_head); zfs_env_count = 0; rv = zfs_lookup_dataset(spa, dsname, &objid); if (rv != 0) return; rv = zfs_callback_dataset(spa, objid, zfs_belist_add); bootenvs_idx = 0; /* Populate the initial environment variables */ SLIST_FOREACH_SAFE(zfs_be, &zfs_be_head, entries, zfs_be_tmp) { /* Enumerate all bootenvs for general usage */ snprintf(envname, sizeof(envname), "%s[%d]", envprefix, bootenvs_idx); snprintf(envval, sizeof(envval), "zfs:%s%s/%s", checkpoint ? "!" : "", rootname, zfs_be->name); rv = setenv(envname, envval, 1); if (rv != 0) break; bootenvs_idx++; } snprintf(envname, sizeof(envname), "%s_count", envprefix); snprintf(envval, sizeof(envval), "%d", bootenvs_idx); setenv(envname, envval, 1); /* Clean up the SLIST of ZFS BEs */ while (!SLIST_EMPTY(&zfs_be_head)) { zfs_be = SLIST_FIRST(&zfs_be_head); SLIST_REMOVE_HEAD(&zfs_be_head, entries); free(zfs_be->name); free(zfs_be); } } int zfs_bootenv(const char *name) { char poolname[ZFS_MAXNAMELEN], *root; const char *dsname; char becount[4]; uint64_t objid; spa_t *spa; int rv, pages, perpage, currpage; if (name == NULL) return (EINVAL); if ((root = getenv("zfs_be_root")) == NULL) return (EINVAL); if (strcmp(name, root) != 0) { if (setenv("zfs_be_root", name, 1) != 0) return (ENOMEM); } SLIST_INIT(&zfs_be_head); zfs_env_count = 0; if (split_devname(name, poolname, sizeof(poolname), &dsname) != 0) return (EINVAL); spa = spa_find_by_name(poolname); if (!spa) return (ENXIO); rv = zfs_lookup_dataset(spa, dsname, &objid); if (rv != 0) return (rv); rv = zfs_callback_dataset(spa, objid, zfs_belist_add); /* Calculate and store the number of pages of BEs */ perpage = (ZFS_BE_LAST - ZFS_BE_FIRST + 1); pages = (zfs_env_count / perpage) + ((zfs_env_count % perpage) > 0 ? 1 : 0); snprintf(becount, 4, "%d", pages); if (setenv("zfs_be_pages", becount, 1) != 0) return (ENOMEM); /* Roll over the page counter if it has exceeded the maximum */ currpage = strtol(getenv("zfs_be_currpage"), NULL, 10); if (currpage > pages) { if (setenv("zfs_be_currpage", "1", 1) != 0) return (ENOMEM); } /* Populate the menu environment variables */ zfs_set_env(); /* Clean up the SLIST of ZFS BEs */ while (!SLIST_EMPTY(&zfs_be_head)) { zfs_be = SLIST_FIRST(&zfs_be_head); SLIST_REMOVE_HEAD(&zfs_be_head, entries); free(zfs_be->name); free(zfs_be); } return (rv); } int zfs_belist_add(const char *name, uint64_t value __unused) { /* Skip special datasets that start with a $ character */ if (strncmp(name, "$", 1) == 0) { return (0); } /* Add the boot environment to the head of the SLIST */ zfs_be = malloc(sizeof(struct zfs_be_entry)); if (zfs_be == NULL) { return (ENOMEM); } zfs_be->name = strdup(name); if (zfs_be->name == NULL) { free(zfs_be); return (ENOMEM); } SLIST_INSERT_HEAD(&zfs_be_head, zfs_be, entries); zfs_env_count++; return (0); } int zfs_set_env(void) { char envname[32], envval[256]; char *beroot, *pagenum; int rv, page, ctr; beroot = getenv("zfs_be_root"); if (beroot == NULL) { return (1); } pagenum = getenv("zfs_be_currpage"); if (pagenum != NULL) { page = strtol(pagenum, NULL, 10); } else { page = 1; } ctr = 1; rv = 0; zfs_env_index = ZFS_BE_FIRST; SLIST_FOREACH_SAFE(zfs_be, &zfs_be_head, entries, zfs_be_tmp) { /* Skip to the requested page number */ if (ctr <= ((ZFS_BE_LAST - ZFS_BE_FIRST + 1) * (page - 1))) { ctr++; continue; } snprintf(envname, sizeof(envname), "bootenvmenu_caption[%d]", zfs_env_index); snprintf(envval, sizeof(envval), "%s", zfs_be->name); rv = setenv(envname, envval, 1); if (rv != 0) { break; } snprintf(envname, sizeof(envname), "bootenvansi_caption[%d]", zfs_env_index); rv = setenv(envname, envval, 1); if (rv != 0){ break; } snprintf(envname, sizeof(envname), "bootenvmenu_command[%d]", zfs_env_index); rv = setenv(envname, "set_bootenv", 1); if (rv != 0){ break; } snprintf(envname, sizeof(envname), "bootenv_root[%d]", zfs_env_index); snprintf(envval, sizeof(envval), "zfs:%s/%s", beroot, zfs_be->name); rv = setenv(envname, envval, 1); if (rv != 0){ break; } zfs_env_index++; if (zfs_env_index > ZFS_BE_LAST) { break; } } for (; zfs_env_index <= ZFS_BE_LAST; zfs_env_index++) { snprintf(envname, sizeof(envname), "bootenvmenu_caption[%d]", zfs_env_index); (void)unsetenv(envname); snprintf(envname, sizeof(envname), "bootenvansi_caption[%d]", zfs_env_index); (void)unsetenv(envname); snprintf(envname, sizeof(envname), "bootenvmenu_command[%d]", zfs_env_index); (void)unsetenv(envname); snprintf(envname, sizeof(envname), "bootenv_root[%d]", zfs_env_index); (void)unsetenv(envname); } return (rv); } diff --git a/stand/userboot/userboot/host.c b/stand/userboot/userboot/host.c index 74727fd8ce3b..d7f01f45c0ab 100644 --- a/stand/userboot/userboot/host.c +++ b/stand/userboot/userboot/host.c @@ -1,178 +1,178 @@ /*- * Copyright (c) 2011 Google, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Read from the host filesystem */ #include #include #include #include #include #include #include #include "libuserboot.h" /* * Open a file. */ static int host_open(const char *upath, struct open_file *f) { if (f->f_dev != &host_dev) return (EINVAL); return (CALLBACK(open, upath, &f->f_fsdata)); } static int host_close(struct open_file *f) { CALLBACK(close, f->f_fsdata); f->f_fsdata = (void *)0; return (0); } /* * Copy a portion of a file into memory. */ static int host_read(struct open_file *f, void *start, size_t size, size_t *resid) { return (CALLBACK(read, f->f_fsdata, start, size, resid)); } static off_t host_seek(struct open_file *f, off_t offset, int where) { return (CALLBACK(seek, f->f_fsdata, offset, where)); } static int host_stat(struct open_file *f, struct stat *sb) { CALLBACK(stat, f->f_fsdata, sb); return (0); } static int host_readdir(struct open_file *f, struct dirent *d) { uint32_t fileno; uint8_t type; size_t namelen; int rc; rc = CALLBACK(readdir, f->f_fsdata, &fileno, &type, &namelen, d->d_name); if (rc) return (rc); d->d_fileno = fileno; d->d_type = type; d->d_namlen = namelen; return (0); } static int host_dev_init(void) { return (0); } static int host_dev_print(int verbose) { char line[80]; printf("%s devices:", host_dev.dv_name); if (pager_output("\n") != 0) return (1); snprintf(line, sizeof(line), " host%d: Host filesystem\n", 0); return (pager_output(line)); } /* * 'Open' the host device. */ static int host_dev_open(struct open_file *f, ...) { return (0); } static int host_dev_close(struct open_file *f) { return (0); } static int host_dev_strategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf, size_t *rsize) { return (ENOSYS); } struct fs_ops host_fsops = { .fs_name = "host", .fo_open = host_open, .fo_close = host_close, .fo_read = host_read, .fo_write = null_write, .fo_seek = host_seek, .fo_stat = host_stat, .fo_readdir = host_readdir, }; struct devsw host_dev = { .dv_name = "host", .dv_type = DEVT_NET, .dv_init = host_dev_init, .dv_strategy = host_dev_strategy, .dv_open = host_dev_open, .dv_close = host_dev_close, .dv_ioctl = noioctl, .dv_print = host_dev_print, - .dv_cleanup = NULL + .dv_cleanup = nullsys, };