diff options
Diffstat (limited to 'usr.sbin/nvmfd/devices.c')
-rw-r--r-- | usr.sbin/nvmfd/devices.c | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/usr.sbin/nvmfd/devices.c b/usr.sbin/nvmfd/devices.c new file mode 100644 index 000000000000..fafc1077f207 --- /dev/null +++ b/usr.sbin/nvmfd/devices.c @@ -0,0 +1,386 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023-2024 Chelsio Communications, Inc. + * Written by: John Baldwin <jhb@FreeBSD.org> + */ + +#include <sys/disk.h> +#include <sys/gsb_crc32.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <net/ieee_oui.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libnvmf.h> +#include <libutil.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "internal.h" + +#define RAMDISK_PREFIX "ramdisk:" + +struct backing_device { + enum { RAMDISK, FILE, CDEV } type; + union { + int fd; /* FILE, CDEV */ + void *mem; /* RAMDISK */ + }; + u_int sector_size; + uint64_t nlbas; + uint64_t eui64; +}; + +static struct backing_device *devices; +static u_int ndevices; + +static uint64_t +generate_eui64(uint32_t low) +{ + return (OUI_FREEBSD_NVME_LOW << 16 | low); +} + +static uint32_t +crc32(const void *buf, size_t len) +{ + return (calculate_crc32c(0xffffffff, buf, len) ^ 0xffffffff); +} + +static void +init_ramdisk(const char *config, struct backing_device *dev) +{ + static uint32_t ramdisk_idx = 1; + uint64_t num; + + dev->type = RAMDISK; + dev->sector_size = 512; + if (expand_number(config, &num)) + errx(1, "Invalid ramdisk specification: %s", config); + if ((num % dev->sector_size) != 0) + errx(1, "Invalid ramdisk size %ju", (uintmax_t)num); + dev->mem = calloc(num, 1); + dev->nlbas = num / dev->sector_size; + dev->eui64 = generate_eui64('M' << 24 | ramdisk_idx++); +} + +static void +init_filedevice(const char *config, int fd, struct stat *sb, + struct backing_device *dev) +{ + dev->type = FILE; + dev->fd = fd; + dev->sector_size = 512; + if ((sb->st_size % dev->sector_size) != 0) + errx(1, "File size is not a multiple of 512: %s", config); + dev->nlbas = sb->st_size / dev->sector_size; + dev->eui64 = generate_eui64('F' << 24 | + (crc32(config, strlen(config)) & 0xffffff)); +} + +static void +init_chardevice(const char *config, int fd, struct backing_device *dev) +{ + off_t len; + + dev->type = CDEV; + dev->fd = fd; + if (ioctl(fd, DIOCGSECTORSIZE, &dev->sector_size) != 0) + err(1, "Failed to fetch sector size for %s", config); + if (ioctl(fd, DIOCGMEDIASIZE, &len) != 0) + err(1, "Failed to fetch sector size for %s", config); + dev->nlbas = len / dev->sector_size; + dev->eui64 = generate_eui64('C' << 24 | + (crc32(config, strlen(config)) & 0xffffff)); +} + +static void +init_device(const char *config, struct backing_device *dev) +{ + struct stat sb; + int fd; + + /* Check for a RAM disk. */ + if (strncmp(RAMDISK_PREFIX, config, strlen(RAMDISK_PREFIX)) == 0) { + init_ramdisk(config + strlen(RAMDISK_PREFIX), dev); + return; + } + + fd = open(config, O_RDWR); + if (fd == -1) + err(1, "Failed to open %s", config); + if (fstat(fd, &sb) == -1) + err(1, "fstat"); + switch (sb.st_mode & S_IFMT) { + case S_IFCHR: + init_chardevice(config, fd, dev); + break; + case S_IFREG: + init_filedevice(config, fd, &sb, dev); + break; + default: + errx(1, "Invalid file type for %s", config); + } +} + +void +register_devices(int ac, char **av) +{ + ndevices = ac; + devices = calloc(ndevices, sizeof(*devices)); + + for (int i = 0; i < ac; i++) + init_device(av[i], &devices[i]); +} + +u_int +device_count(void) +{ + return (ndevices); +} + +static struct backing_device * +lookup_device(uint32_t nsid) +{ + if (nsid == 0 || nsid > ndevices) + return (NULL); + return (&devices[nsid - 1]); +} + +void +device_active_nslist(uint32_t nsid, struct nvme_ns_list *nslist) +{ + u_int count; + + memset(nslist, 0, sizeof(*nslist)); + count = 0; + nsid++; + while (nsid <= ndevices) { + nslist->ns[count] = htole32(nsid); + count++; + if (count == nitems(nslist->ns)) + break; + nsid++; + } +} + +bool +device_identification_descriptor(uint32_t nsid, void *buf) +{ + struct backing_device *dev; + char *p; + + dev = lookup_device(nsid); + if (dev == NULL) + return (false); + + memset(buf, 0, 4096); + + p = buf; + + /* EUI64 */ + *p++ = 1; + *p++ = 8; + p += 2; + be64enc(p, dev->eui64); + return (true); +} + +bool +device_namespace_data(uint32_t nsid, struct nvme_namespace_data *nsdata) +{ + struct backing_device *dev; + + dev = lookup_device(nsid); + if (dev == NULL) + return (false); + + memset(nsdata, 0, sizeof(*nsdata)); + nsdata->nsze = htole64(dev->nlbas); + nsdata->ncap = nsdata->nsze; + nsdata->nuse = nsdata->ncap; + nsdata->nlbaf = 1 - 1; + nsdata->flbas = NVMEF(NVME_NS_DATA_FLBAS_FORMAT, 0); + nsdata->lbaf[0] = NVMEF(NVME_NS_DATA_LBAF_LBADS, + ffs(dev->sector_size) - 1); + + be64enc(nsdata->eui64, dev->eui64); + return (true); +} + +static bool +read_buffer(int fd, void *buf, size_t len, off_t offset) +{ + ssize_t nread; + char *dst; + + dst = buf; + while (len > 0) { + nread = pread(fd, dst, len, offset); + if (nread == -1 && errno == EINTR) + continue; + if (nread <= 0) + return (false); + dst += nread; + len -= nread; + offset += nread; + } + return (true); +} + +void +device_read(uint32_t nsid, uint64_t lba, u_int nlb, + const struct nvmf_capsule *nc) +{ + struct backing_device *dev; + char *p, *src; + off_t off; + size_t len; + + dev = lookup_device(nsid); + if (dev == NULL) { + nvmf_send_generic_error(nc, + NVME_SC_INVALID_NAMESPACE_OR_FORMAT); + return; + } + + if (lba + nlb < lba || lba + nlb > dev->nlbas) { + nvmf_send_generic_error(nc, NVME_SC_LBA_OUT_OF_RANGE); + return; + } + + off = lba * dev->sector_size; + len = nlb * dev->sector_size; + if (nvmf_capsule_data_len(nc) != len) { + nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); + return; + } + + if (dev->type == RAMDISK) { + p = NULL; + src = (char *)dev->mem + off; + } else { + p = malloc(len); + if (!read_buffer(dev->fd, p, len, off)) { + free(p); + nvmf_send_generic_error(nc, + NVME_SC_INTERNAL_DEVICE_ERROR); + return; + } + src = p; + } + + nvmf_send_controller_data(nc, src, len); + free(p); +} + +static bool +write_buffer(int fd, const void *buf, size_t len, off_t offset) +{ + ssize_t nwritten; + const char *src; + + src = buf; + while (len > 0) { + nwritten = pwrite(fd, src, len, offset); + if (nwritten == -1 && errno == EINTR) + continue; + if (nwritten <= 0) + return (false); + src += nwritten; + len -= nwritten; + offset += nwritten; + } + return (true); +} + +void +device_write(uint32_t nsid, uint64_t lba, u_int nlb, + const struct nvmf_capsule *nc) +{ + struct backing_device *dev; + char *p, *dst; + off_t off; + size_t len; + int error; + + dev = lookup_device(nsid); + if (dev == NULL) { + nvmf_send_generic_error(nc, + NVME_SC_INVALID_NAMESPACE_OR_FORMAT); + return; + } + + if (lba + nlb < lba || lba + nlb > dev->nlbas) { + nvmf_send_generic_error(nc, NVME_SC_LBA_OUT_OF_RANGE); + return; + } + + off = lba * dev->sector_size; + len = nlb * dev->sector_size; + if (nvmf_capsule_data_len(nc) != len) { + nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); + return; + } + + if (dev->type == RAMDISK) { + p = NULL; + dst = (char *)dev->mem + off; + } else { + p = malloc(len); + dst = p; + } + + error = nvmf_receive_controller_data(nc, 0, dst, len); + if (error != 0) { + nvmf_send_generic_error(nc, NVME_SC_TRANSIENT_TRANSPORT_ERROR); + free(p); + return; + } + + if (dev->type != RAMDISK) { + if (!write_buffer(dev->fd, p, len, off)) { + free(p); + nvmf_send_generic_error(nc, + NVME_SC_INTERNAL_DEVICE_ERROR); + return; + } + } + free(p); + nvmf_send_success(nc); +} + +void +device_flush(uint32_t nsid, const struct nvmf_capsule *nc) +{ + struct backing_device *dev; + + dev = lookup_device(nsid); + if (dev == NULL) { + nvmf_send_generic_error(nc, + NVME_SC_INVALID_NAMESPACE_OR_FORMAT); + return; + } + + switch (dev->type) { + case RAMDISK: + break; + case FILE: + if (fdatasync(dev->fd) == -1) { + nvmf_send_error(nc, NVME_SCT_MEDIA_ERROR, + NVME_SC_WRITE_FAULTS); + return; + } + break; + case CDEV: + if (ioctl(dev->fd, DIOCGFLUSH) == -1) { + nvmf_send_error(nc, NVME_SCT_MEDIA_ERROR, + NVME_SC_WRITE_FAULTS); + return; + } + } + + nvmf_send_success(nc); +} |