diff --git a/libnvme/src/meson.build b/libnvme/src/meson.build index 1f93a616d3..901d74a69a 100644 --- a/libnvme/src/meson.build +++ b/libnvme/src/meson.build @@ -33,6 +33,15 @@ elif host_system == 'windows' 'nvme/scan-win.c', 'nvme/tree-win.c', ] +elif host_system == 'freebsd' + sources += [ + 'nvme/ctrl-sysfs-custom-freebsd.c', + 'nvme/ioctl-freebsd.c', + 'nvme/lib-freebsd.c', + 'nvme/mem-freebsd.c', + 'nvme/scan-freebsd.c', + 'nvme/tree-freebsd.c', + ] endif headers = [ 'nvme/accessors.h', diff --git a/libnvme/src/nvme/ctrl-sysfs-custom-freebsd.c b/libnvme/src/nvme/ctrl-sysfs-custom-freebsd.c new file mode 100644 index 0000000000..b0b31874ae --- /dev/null +++ b/libnvme/src/nvme/ctrl-sysfs-custom-freebsd.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2026 SUSE Software Solutions + * + * Authors: Daniel Wagner + */ + +#include +#include + +#include + +#include + +#include "lib.h" + +#include "ctrl-sysfs.c" + +#ifdef CONFIG_FABRICS +# include "ctrl-sysfs-custom-fabrics.c" +#else +# include "ctrl-sysfs-custom-no-fabrics.c" +#endif + +int libnvme_ctrl_load_identity(struct libnvme_ctrl *c) +{ + struct nvme_id_ctrl id_ctrl; + int ret; + + ret = libnvme_ctrl_identify(c, &id_ctrl); + if (ret != 0) + return ret; + + if (asprintf(&c->sysfs->cntrltype, "%u", id_ctrl.cntrltype) < 0) + return -ENOMEM; + + if (asprintf(&c->sysfs->cntlid, "%u", le16_to_cpu(id_ctrl.cntlid)) < 0) + return -ENOMEM; + + if (asprintf(&c->sysfs->dctype, "%u", id_ctrl.dctype) < 0) + return -ENOMEM; + + return 0; +} + +int libnvme_ctrl_load_phy_slot(__shr_unused struct libnvme_ctrl *c) +{ + /* FreeBSD has no PCIe physical slot sysfs equivalent. */ + return 0; +} diff --git a/libnvme/src/nvme/ioctl-freebsd.c b/libnvme/src/nvme/ioctl-freebsd.c new file mode 100644 index 0000000000..aaa838ee1a --- /dev/null +++ b/libnvme/src/nvme/ioctl-freebsd.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2026 SUSE Software Solutions + * + * Authors: Daniel Wagner + */ + +#include + +#include + +#include + +#include "private.h" + +__shr_public int libnvme_reset_subsystem( + __shr_unused struct libnvme_transport_handle *hdl) +{ + return -ENOTSUP; +} + +__shr_public int libnvme_reset_ctrl( + __shr_unused struct libnvme_transport_handle *hdl) +{ + return -ENOTSUP; +} + +__shr_public int libnvme_rescan_ns( + __shr_unused struct libnvme_transport_handle *hdl) +{ + return -ENOTSUP; +} + +__shr_public int libnvme_get_nsid( + __shr_unused struct libnvme_transport_handle *hdl, + __shr_unused __u32 *nsid) +{ + return -ENOTSUP; +} + +__shr_public int libnvme_update_block_size( + __shr_unused struct libnvme_transport_handle *hdl, + __shr_unused int block_size) +{ + return -ENOTSUP; +} + +__shr_public int libnvme_exec_admin_passthru( + __shr_unused struct libnvme_transport_handle *hdl, + __shr_unused struct libnvme_passthru_cmd *cmd) +{ + return -ENOTSUP; +} + +__shr_public int libnvme_exec_io_passthru( + __shr_unused struct libnvme_transport_handle *hdl, + __shr_unused struct libnvme_passthru_cmd *cmd) +{ + return -ENOTSUP; +} diff --git a/libnvme/src/nvme/lib-freebsd.c b/libnvme/src/nvme/lib-freebsd.c new file mode 100644 index 0000000000..53cbc6c3db --- /dev/null +++ b/libnvme/src/nvme/lib-freebsd.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2026 SUSE Software Solutions + * + * Authors: Daniel Wagner + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "private.h" + +/* + * FreeBSD: opening a device node is plain POSIX and portable, so this + * mirrors lib-linux.c's approach without assuming Linux's "nvme%dn%d" + * naming scheme. Actual command submission is stubbed out in + * ioctl-freebsd.c. + */ + +static int __libnvme_transport_handle_open_direct( + struct libnvme_transport_handle *hdl, const char *devname) +{ + int ret; + + hdl->type = LIBNVME_TRANSPORT_HANDLE_TYPE_DIRECT; + + hdl->fd = open(devname, O_RDONLY); + if (hdl->fd < 0) + return -errno; + + ret = fstat(hdl->fd, &hdl->stat); + if (ret < 0) { + close(hdl->fd); + return -errno; + } + + if (!S_ISCHR(hdl->stat.st_mode) && !S_ISBLK(hdl->stat.st_mode)) { + close(hdl->fd); + return -EINVAL; + } + + return 0; +} + +void __libnvme_transport_handle_close_direct( + struct libnvme_transport_handle *hdl) +{ + libnvme_close_uring(hdl); + close(hdl->fd); + free(hdl); +} + +__shr_public int libnvme_open( + struct libnvme_global_ctx *ctx, const char *name, + struct libnvme_transport_handle **hdlp) +{ + struct libnvme_transport_handle *hdl; + int ret; + + hdl = __libnvme_create_transport_handle(ctx); + if (!hdl) + return -ENOMEM; + + hdl->name = strdup(name); + if (!hdl->name) { + free(hdl); + return -ENOMEM; + } + + if (!strncmp(name, "NVME_TEST_FD", 12)) { + hdl->type = LIBNVME_TRANSPORT_HANDLE_TYPE_DIRECT; + hdl->fd = LIBNVME_TEST_FD; + + if (!strcmp(name, "NVME_TEST_FD64")) + hdl->ioctl_admin_state = IOCTL_STATE_IOCTL64; + + *hdlp = hdl; + return 0; + } + + if (!strncmp(name, "mctp:", strlen("mctp:"))) { + libnvme_close(hdl); + return -ENOTSUP; + } + + ret = __libnvme_transport_handle_open_direct(hdl, name); + if (ret) { + libnvme_close(hdl); + return ret; + } + + *hdlp = hdl; + + return 0; +} + +__shr_public void libnvme_close(struct libnvme_transport_handle *hdl) +{ + if (!hdl) + return; + + free(hdl->name); + + switch (hdl->type) { + case LIBNVME_TRANSPORT_HANDLE_TYPE_DIRECT: + __libnvme_transport_handle_close_direct(hdl); + break; + case LIBNVME_TRANSPORT_HANDLE_TYPE_MI: + case LIBNVME_TRANSPORT_HANDLE_TYPE_UNKNOWN: + free(hdl); + break; + } +} diff --git a/libnvme/src/nvme/mem-freebsd.c b/libnvme/src/nvme/mem-freebsd.c new file mode 100644 index 0000000000..06be2eca03 --- /dev/null +++ b/libnvme/src/nvme/mem-freebsd.c @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libnvme. + * Copyright (c) 2026 SUSE Software Solutions + * + * Authors: Daniel Wagner + */ + +#include +#include +#include +#include + +#include + +#include + +#include "mem.h" +#include "private.h" + +/* + * FreeBSD: plain page-aligned allocations. Linux's transparent-hugepage + * mmap/madvise flags (MAP_HUGETLB, MADV_HUGEPAGE) have no FreeBSD + * equivalent, so libnvme_alloc_huge() always takes the small-allocation + * posix_memalign() path regardless of size -- correct, just without the + * performance benefit of real huge pages. + */ + +__shr_public void *libnvme_alloc(size_t len) +{ + size_t _len = round_up(len, 0x1000); + void *p; + + if (posix_memalign((void *)&p, getpagesize(), _len)) + return NULL; + + memset(p, 0, _len); + return p; +} + +__shr_public void *libnvme_realloc(void *p, size_t len) +{ + size_t old_len = malloc_usable_size(p); + void *result = libnvme_alloc(len); + + if (p && result) { + memcpy(result, p, min_t(size_t, old_len, len)); + free(p); + } + + return result; +} + +__shr_public void libnvme_free(void *p) +{ + free(p); +} + +__shr_public void *libnvme_alloc_huge(size_t len, + struct libnvme_mem_huge *mh) +{ + memset(mh, 0, sizeof(*mh)); + + len = round_up(len, 0x1000); + + mh->p = libnvme_alloc(len); + if (!mh->p) + return NULL; + + mh->libnvme_alloc = true; + mh->len = len; + return mh->p; +} + +__shr_public void libnvme_free_huge(struct libnvme_mem_huge *mh) +{ + if (!mh || mh->len == 0) + return; + + free(mh->p); + + mh->len = 0; + mh->p = NULL; +} diff --git a/libnvme/src/nvme/scan-freebsd.c b/libnvme/src/nvme/scan-freebsd.c new file mode 100644 index 0000000000..69901f0fc5 --- /dev/null +++ b/libnvme/src/nvme/scan-freebsd.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2026 SUSE Software Solutions + * + * Authors: Daniel Wagner + */ + +#include + +#include + +#include + +#include "private.h" + +__shr_public int libnvme_scan_subsystems( + __shr_unused struct libnvme_global_ctx *ctx, + __shr_unused struct dirent ***subsys) +{ + return 0; +} + +__shr_public int libnvme_scan_subsystem_namespaces( + __shr_unused libnvme_subsystem_t s, + __shr_unused struct dirent ***ns) +{ + return 0; +} + +__shr_public int libnvme_scan_ctrls( + __shr_unused struct libnvme_global_ctx *ctx, + __shr_unused struct dirent ***ctrls) +{ + return 0; +} + +__shr_public int libnvme_scan_ctrl_namespace_paths( + __shr_unused libnvme_ctrl_t c, + __shr_unused struct dirent ***paths) +{ + return 0; +} + +__shr_public int libnvme_scan_ctrl_namespaces( + __shr_unused libnvme_ctrl_t c, + __shr_unused struct dirent ***ns) +{ + return 0; +} + +__shr_public int libnvme_scan_ns_head_paths( + __shr_unused libnvme_ns_head_t head, + __shr_unused struct dirent ***paths) +{ + return 0; +} diff --git a/libnvme/src/nvme/tree-freebsd.c b/libnvme/src/nvme/tree-freebsd.c new file mode 100644 index 0000000000..f77eda210f --- /dev/null +++ b/libnvme/src/nvme/tree-freebsd.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2026 SUSE Software Solutions + * + * Authors: Daniel Wagner + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include "cleanup.h" +#include "private-tree.h" +#include "private.h" +#include "util.h" + +int libnvme_reconfigure_ctrl(__shr_unused struct libnvme_global_ctx *ctx, + libnvme_ctrl_t c, const char *path, const char *name) +{ + /* + * It's necessary to release any resources first because a ctrl + * can be reused. + */ + libnvme_ctrl_release_transport_handle(c); + FREE_CTRL_ATTR(c->name); + FREE_CTRL_ATTR(c->sysfs_dir); + libnvme_ctrl_sysfs_reset(c->sysfs); + + c->hdl = NULL; + c->name = shr_xstrdup(name); + c->sysfs_dir = shr_xstrdup(path); + if (!c->name || !c->sysfs_dir) { + FREE_CTRL_ATTR(c->name); + FREE_CTRL_ATTR(c->sysfs_dir); + return -ENOMEM; + } + + if (!libnvme_ctrl_get_transport_handle(c)) + return -ENODEV; + + return 0; +} + +__shr_public const char *libnvme_ctrl_get_state(libnvme_ctrl_t c) +{ + char *state = c->state; + + c->state = strdup(""); + free(state); + return c->state; +} + +__shr_public int libnvme_init_ctrl(__shr_unused libnvme_host_t h, + __shr_unused libnvme_ctrl_t c, + __shr_unused int instance) +{ + return -ENOTSUP; +} + +int libnvme_get_ctrl_transport(__shr_unused struct libnvme_global_ctx *ctx, + __shr_unused const char *path, + __shr_unused const char *name, char **transport, + char **traddr, char **addr, char **trsvcid, + char **host_traddr, char **host_iface) +{ + *transport = NULL; + *traddr = NULL; + *addr = NULL; + *trsvcid = NULL; + *host_traddr = NULL; + *host_iface = NULL; + + return -ENODEV; +} + +__shr_public int libnvme_scan_ctrl( + __shr_unused struct libnvme_global_ctx *ctx, + __shr_unused const char *name, + __shr_unused libnvme_ctrl_t *cp) +{ + return -ENODEV; +} + +__shr_public char *libnvme_get_subsys_attr( + __shr_unused libnvme_subsystem_t s, + __shr_unused const char *attr) +{ + return NULL; +} + +__shr_public char *libnvme_get_path_attr( + __shr_unused libnvme_path_t p, + __shr_unused const char *attr) +{ + return NULL; +} + +__shr_public char *libnvme_get_attr( + __shr_unused const char *dir, + __shr_unused const char *attr) +{ + return NULL; +} + +__shr_public char *libnvme_get_ctrl_attr( + __shr_unused libnvme_ctrl_t c, + __shr_unused const char *attr) +{ + return NULL; +} + +__shr_public char *libnvme_get_ns_attr( + __shr_unused libnvme_ns_t n, + __shr_unused const char *attr) +{ + return NULL; +} + +const char *libnvme_subsys_sysfs_dir( + __shr_unused struct libnvme_global_ctx *ctx) +{ + return NULL; +} + +const char *libnvme_ns_sysfs_dir( + __shr_unused struct libnvme_global_ctx *ctx) +{ + return NULL; +} + +int libnvme_ns_init(__shr_unused const char *path, struct libnvme_ns *ns) +{ + __cleanup_libnvme_free struct nvme_id_ns *id = NULL; + uint8_t flbas; + int ret; + + id = libnvme_alloc(sizeof(*id)); + if (!id) + return -ENOMEM; + + ret = libnvme_ns_identify(ns, id); + if (ret) + return ret; + + nvme_id_ns_flbas_to_lbaf_inuse(id->flbas, &flbas); + ns->lba_size = 1 << id->lbaf[flbas].ds; + ns->lba_count = le64_to_cpu(id->nsze); + ns->lba_util = le64_to_cpu(id->nuse); + ns->meta_size = le16_to_cpu(id->lbaf[flbas].ms); + + return 0; +} + +int libnvme_ns_open(struct libnvme_global_ctx *ctx, + __shr_unused const char *sys_path, + const char *name, libnvme_ns_t *ns) +{ + struct libnvme_transport_handle *hdl; + struct libnvme_ns_head *head; + struct libnvme_ns *n; + int ret; + + n = calloc(1, sizeof(*n)); + if (!n) + return -ENOMEM; + + head = calloc(1, sizeof(*head)); + if (!head) { + free(n); + return -ENOMEM; + } + + head->n = n; + list_head_init(&head->paths); + + n->ctx = ctx; + n->head = head; + n->hdl = NULL; + n->name = strdup(name); + n->generic_name = strdup(name); + if (!n->name || !n->generic_name) { + ret = -ENOMEM; + goto free_ns; + } + + /* Open the device to query the namespace ID */ + ret = libnvme_open(ctx, name, &hdl); + if (ret) + goto free_ns; + + ret = libnvme_get_nsid(hdl, &n->nsid); + libnvme_close(hdl); + if (ret) + goto free_ns; + + ret = libnvme_ns_init(name, n); + if (ret) + goto free_ns; + + list_node_init(&n->entry); + + libnvme_ns_release_transport_handle(n); + + *ns = n; + return 0; + +free_ns: + free(n->generic_name); + free(n->name); + free(head); + free(n); + return ret; +} + +int __libnvme_scan_namespace(struct libnvme_global_ctx *ctx, + __shr_unused const char *sysfs_dir, + const char *name, libnvme_ns_t *ns) +{ + struct libnvme_ns *n = NULL; + int ret; + + ret = libnvme_ns_open(ctx, NULL, name, &n); + if (ret) + return ret; + + n->sysfs_dir = strdup(name); + if (!n->sysfs_dir) { + libnvme_free_ns(n); + return -ENOMEM; + } + + *ns = n; + return 0; +} + +int libnvme_init_subsystem(libnvme_subsystem_t s, const char *name) +{ + s->subsystype = strdup("nvm"); + if (!s->subsystype) + return -ENOMEM; + + s->name = strdup(name); + if (!s->name) { + free(s->subsystype); + return -ENOMEM; + } + + return 0; +} diff --git a/meson.build b/meson.build index e833b3837e..e36d74b91a 100644 --- a/meson.build +++ b/meson.build @@ -622,6 +622,10 @@ if want_nvme sources += [ 'nvme-pci-ids-win.c', ] + elif host_system == 'freebsd' + sources += [ + 'nvme-pci-ids-freebsd.c', + ] endif if json_c_dep.found() diff --git a/nvme-pci-ids-freebsd.c b/nvme-pci-ids-freebsd.c new file mode 100644 index 0000000000..582ee37664 --- /dev/null +++ b/nvme-pci-ids-freebsd.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This file is part of nvme-cli. + * Copyright (c) 2026 SUSE Software Solutions + * + * Authors: Daniel Wagner + */ + +#include + +#include + +#include "nvme-pci-ids.h" + +int __nvme_get_sysfs_dir(struct libnvme_global_ctx *ctx, + const char *ctrl_name, char **sysfs_dir) +{ + return -ENOTSUP; +} + +int __nvme_get_pci_ids(const char *sysfs_dir, + __u32 *vid, __u32 *did, + __u32 *subsys_vid, __u32 *subsys_did, + __u32 *class_code) +{ + return -ENOTSUP; +} diff --git a/util/hash-freebsd.c b/util/hash-freebsd.c new file mode 100644 index 0000000000..a2d35084b7 --- /dev/null +++ b/util/hash-freebsd.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This file is part of nvme-cli. + * Copyright (c) 2026 SUSE Software Solutions + * + * Authors: Daniel Wagner + */ + +#include + +#include "hash.h" + +unsigned char *create_hmac_sha256(unsigned char *data, int datalen, + unsigned char *key, int keylen) +{ + return NULL; +} + +unsigned char *create_md5(unsigned char *data, int datalen) +{ + return NULL; +} diff --git a/util/meson.build b/util/meson.build index 2747baed68..e6ad438c97 100644 --- a/util/meson.build +++ b/util/meson.build @@ -14,6 +14,11 @@ if host_system == 'linux' 'util/dashboard.c', 'util/hash-linux.c', ] +elif host_system == 'freebsd' + util_sources += [ + 'util/sighdl-freebsd.c', + 'util/hash-freebsd.c', + ] elif host_system == 'windows' util_sources += [ 'util/sighdl-win.c', diff --git a/util/sighdl-freebsd.c b/util/sighdl-freebsd.c new file mode 100644 index 0000000000..1724f49fbe --- /dev/null +++ b/util/sighdl-freebsd.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This file is part of nvme-cli. + * Copyright (c) 2026 SUSE Software Solutions + * + * Authors: Daniel Wagner + */ + +#include +#include +#include + +#include "sighdl.h" + +volatile sig_atomic_t nvme_sigint_received; +volatile sig_atomic_t nvme_sigwinch_received; + +static void nvme_sigint_handler(int signum) +{ + nvme_sigint_received = true; +} + +static void nvme_sigwinch_handler(int signum) +{ + nvme_sigwinch_received = true; +} + +int nvme_install_sigint_handler(void) +{ + struct sigaction act = {0}; + + sigemptyset(&act.sa_mask); + act.sa_handler = nvme_sigint_handler; + act.sa_flags = 0; + + nvme_sigint_received = false; + if (sigaction(SIGINT, &act, NULL) == -1) + return -errno; + + return 0; +} + +int nvme_install_sigwinch_handler(void) +{ + struct sigaction act = {0}; + + sigemptyset(&act.sa_mask); + act.sa_handler = nvme_sigwinch_handler; + act.sa_flags = 0; + + nvme_sigwinch_received = false; + if (sigaction(SIGWINCH, &act, NULL) == -1) + return -errno; + + return 0; +}