#include <stdlib.h>
#include <stdarg.h>
#include <libdevinfo.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <upanic.h>
#include "libnvme_impl.h"
bool
nvme_vers_ctrl_atleast(const nvme_ctrl_t *ctrl, const nvme_version_t *targ)
{
return (nvme_vers_atleast(&ctrl->nc_vers, targ));
}
bool
nvme_vers_ctrl_info_atleast(const nvme_ctrl_info_t *ci,
const nvme_version_t *targ)
{
return (nvme_vers_atleast(&ci->nci_vers, targ));
}
bool
nvme_vers_ns_info_atleast(const nvme_ns_info_t *info,
const nvme_version_t *targ)
{
return (nvme_vers_atleast(&info->nni_vers, targ));
}
bool
nvme_guid_valid(const nvme_ctrl_t *ctrl, const uint8_t guid[16])
{
const uint8_t zero_guid[16] = { 0 };
return (nvme_vers_ctrl_atleast(ctrl, &nvme_vers_1v2) &&
memcmp(zero_guid, guid, sizeof (zero_guid)) != 0);
}
bool
nvme_eui64_valid(const nvme_ctrl_t *ctrl, const uint8_t eui64[8])
{
const uint8_t zero_eui[8] = { 0 };
return (nvme_vers_ctrl_atleast(ctrl, &nvme_vers_1v1) &&
memcmp(zero_eui, eui64, sizeof (zero_eui)) != 0);
}
int
nvme_format_nguid(const uint8_t nguid[16], char *buf, size_t len)
{
return (snprintf(buf, len, "%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X"
"%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X",
nguid[0], nguid[1], nguid[2], nguid[3], nguid[4], nguid[5],
nguid[6], nguid[7], nguid[8], nguid[9], nguid[10], nguid[11],
nguid[12], nguid[13], nguid[14], nguid[15]));
}
int
nvme_format_eui64(const uint8_t eui64[8], char *buf, size_t len)
{
return (snprintf(buf, len, "%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X",
eui64[0], eui64[1], eui64[2], eui64[3], eui64[4], eui64[5],
eui64[6], eui64[7]));
}
void
nvme_fini(nvme_t *nvme)
{
if (nvme == NULL)
return;
if (nvme->nh_devinfo != DI_NODE_NIL) {
di_fini(nvme->nh_devinfo);
}
free(nvme);
}
nvme_t *
nvme_init(void)
{
nvme_t *nvme;
nvme = calloc(1, sizeof (nvme_t));
if (nvme == NULL) {
return (NULL);
}
nvme->nh_devinfo = di_init("/", DINFOCPYALL);
if (nvme->nh_devinfo == DI_NODE_NIL) {
nvme_fini(nvme);
return (NULL);
}
return (nvme);
}
void
nvme_ctrl_discover_fini(nvme_ctrl_iter_t *iter)
{
free(iter);
}
nvme_iter_t
nvme_ctrl_discover_step(nvme_ctrl_iter_t *iter, const nvme_ctrl_disc_t **discp)
{
di_minor_t m;
*discp = NULL;
if (iter->ni_done) {
return (NVME_ITER_DONE);
}
for (;;) {
if (iter->ni_cur == NULL) {
iter->ni_cur = di_drv_first_node("nvme",
iter->ni_nvme->nh_devinfo);
} else {
iter->ni_cur = di_drv_next_node(iter->ni_cur);
}
if (iter->ni_cur == NULL) {
iter->ni_done = true;
return (NVME_ITER_DONE);
}
for (m = di_minor_next(iter->ni_cur, DI_MINOR_NIL);
m != DI_MINOR_NIL; m = di_minor_next(iter->ni_cur, m)) {
if (strcmp(di_minor_nodetype(m),
DDI_NT_NVME_NEXUS) == 0) {
break;
}
}
if (m == DI_MINOR_NIL) {
continue;
}
iter->ni_disc.ncd_devi = iter->ni_cur;
iter->ni_disc.ncd_minor = m;
*discp = &iter->ni_disc;
return (NVME_ITER_VALID);
}
return (NVME_ITER_DONE);
}
bool
nvme_ctrl_discover_init(nvme_t *nvme, nvme_ctrl_iter_t **iterp)
{
nvme_ctrl_iter_t *iter;
if (iterp == NULL) {
return (nvme_error(nvme, NVME_ERR_BAD_PTR, 0, "encountered "
"invalid nvme_ctrl_iter_t output pointer: %p", iterp));
}
iter = calloc(1, sizeof (nvme_ctrl_iter_t));
if (iter == NULL) {
int e = errno;
return (nvme_error(nvme, NVME_ERR_NO_MEM, e, "failed to "
"allocate memory for a new nvme_ctrl_iter_t: %s",
strerror(e)));
}
iter->ni_nvme = nvme;
*iterp = iter;
return (nvme_success(nvme));
}
bool
nvme_ctrl_discover(nvme_t *nvme, nvme_ctrl_disc_f func, void *arg)
{
nvme_ctrl_iter_t *iter;
const nvme_ctrl_disc_t *disc;
nvme_iter_t ret;
if (func == NULL) {
return (nvme_error(nvme, NVME_ERR_BAD_PTR, 0, "encountered "
"invalid nvme_ctrl_disc_f function pointer: %p", func));
}
if (!nvme_ctrl_discover_init(nvme, &iter)) {
return (false);
}
while ((ret = nvme_ctrl_discover_step(iter, &disc)) ==
NVME_ITER_VALID) {
if (!func(nvme, disc, arg))
break;
}
nvme_ctrl_discover_fini(iter);
if (ret == NVME_ITER_ERROR) {
return (false);
}
return (nvme_success(nvme));
}
di_node_t
nvme_ctrl_disc_devi(const nvme_ctrl_disc_t *discp)
{
return (discp->ncd_devi);
}
di_minor_t
nvme_ctrl_disc_minor(const nvme_ctrl_disc_t *discp)
{
return (discp->ncd_minor);
}
void
nvme_ctrl_fini(nvme_ctrl_t *ctrl)
{
if (ctrl == NULL) {
return;
}
if (ctrl->nc_sup_logs != NULL) {
free(ctrl->nc_sup_logs);
}
if (ctrl->nc_sup_logs_err != NULL) {
free(ctrl->nc_sup_logs_err);
}
if (ctrl->nc_devi_path != NULL) {
di_devfs_path_free(ctrl->nc_devi_path);
}
if (ctrl->nc_fd >= 0) {
(void) close(ctrl->nc_fd);
ctrl->nc_fd = -1;
}
free(ctrl);
}
bool
nvme_ctrl_init(nvme_t *nvme, di_node_t di, nvme_ctrl_t **outp)
{
const char *drv;
int32_t inst;
di_minor_t minor;
char *path, buf[PATH_MAX];
nvme_ctrl_t *ctrl;
nvme_ioctl_ctrl_info_t ctrl_info;
if (di == DI_NODE_NIL) {
return (nvme_error(nvme, NVME_ERR_BAD_PTR, 0, "encountered "
"invalid di_node_t: %p", di));
}
if (outp == NULL) {
return (nvme_error(nvme, NVME_ERR_BAD_PTR, 0, "encountered "
"invalid nvme_ctrl_t output pointer: %p", outp));
}
*outp = NULL;
drv = di_driver_name(di);
inst = di_instance(di);
if (drv == NULL || inst < 0) {
return (nvme_error(nvme, NVME_ERR_BAD_DEVI, 0, "devi %s has "
"no driver attached", di_node_name(di)));
}
if (strcmp(drv, "nvme") != 0) {
return (nvme_error(nvme, NVME_ERR_BAD_DEVI, 0, "devi %s isn't "
"attached to nvme, found %s", di_node_name(di), drv));
}
minor = DI_MINOR_NIL;
while ((minor = di_minor_next(di, minor)) != DI_MINOR_NIL) {
if (strcmp(di_minor_nodetype(minor), DDI_NT_NVME_NEXUS) == 0) {
break;
}
}
if (minor == DI_MINOR_NIL) {
return (nvme_error(nvme, NVME_ERR_BAD_DEVI, 0, "devi %s isn't "
"attached to nvme, found %s", di_node_name(di), drv));
}
path = di_devfs_minor_path(minor);
if (path == NULL) {
int e = errno;
return (nvme_error(nvme, NVME_ERR_LIBDEVINFO, e, "failed to "
"obtain /devices path for the requested minor: %s",
strerror(e)));
}
if (snprintf(buf, sizeof (buf), "/devices%s", path) >= sizeof (buf)) {
di_devfs_path_free(path);
return (nvme_error(nvme, NVME_ERR_INTERNAL, 0, "failed to "
"construct full /devices minor path, would have overflown "
"internal buffer"));
}
di_devfs_path_free(path);
ctrl = calloc(1, sizeof (*ctrl));
if (ctrl == NULL) {
int e = errno;
return (nvme_error(nvme, NVME_ERR_NO_MEM, e, "failed to "
"allocate memory for a new nvme_ctrl_t: %s", strerror(e)));
}
ctrl->nc_nvme = nvme;
ctrl->nc_devi = di;
ctrl->nc_minor = minor;
ctrl->nc_inst = inst;
ctrl->nc_fd = open(buf, O_RDWR | O_CLOEXEC);
if (ctrl->nc_fd < 0) {
int e = errno;
nvme_ctrl_fini(ctrl);
return (nvme_error(nvme, NVME_ERR_OPEN_DEV, e, "failed to open "
"device path %s: %s", buf, strerror(e)));
}
ctrl->nc_devi_path = di_devfs_path(di);
if (ctrl->nc_devi_path == NULL) {
int e = errno;
nvme_ctrl_fini(ctrl);
return (nvme_error(nvme, NVME_ERR_LIBDEVINFO, e, "failed to "
"obtain /devices path for the controller: %s",
strerror(e)));
}
if (!nvme_ioc_ctrl_info(ctrl, &ctrl_info)) {
nvme_err_data_t err;
nvme_ctrl_err_save(ctrl, &err);
nvme_err_set(nvme, &err);
nvme_ctrl_fini(ctrl);
return (false);
}
ctrl->nc_vers = ctrl_info.nci_vers;
ctrl->nc_info = ctrl_info.nci_ctrl_id;
nvme_vendor_map_ctrl(ctrl);
*outp = ctrl;
return (nvme_success(nvme));
}
typedef struct {
bool ncia_found;
int32_t ncia_inst;
nvme_ctrl_t *ncia_ctrl;
nvme_err_data_t ncia_err;
} nvme_ctrl_init_arg_t;
bool
nvme_ctrl_init_by_instance_cb(nvme_t *nvme, const nvme_ctrl_disc_t *disc,
void *arg)
{
nvme_ctrl_init_arg_t *init = arg;
if (di_instance(disc->ncd_devi) != init->ncia_inst) {
return (true);
}
init->ncia_found = true;
if (!nvme_ctrl_init(nvme, disc->ncd_devi, &init->ncia_ctrl)) {
nvme_err_save(nvme, &init->ncia_err);
}
return (false);
}
bool
nvme_ctrl_init_by_instance(nvme_t *nvme, int32_t inst, nvme_ctrl_t **outp)
{
nvme_ctrl_init_arg_t init;
if (inst < 0) {
return (nvme_error(nvme, NVME_ERR_ILLEGAL_INSTANCE, 0,
"encountered illegal negative instance number: %d", inst));
}
if (outp == NULL) {
return (nvme_error(nvme, NVME_ERR_BAD_PTR, 0, "encountered "
"invalid nvme_ctrl_t output pointer: %p", outp));
}
init.ncia_found = false;
init.ncia_inst = inst;
init.ncia_ctrl = NULL;
if (!nvme_ctrl_discover(nvme, nvme_ctrl_init_by_instance_cb, &init)) {
return (false);
}
if (!init.ncia_found) {
return (nvme_error(nvme, NVME_ERR_BAD_CONTROLLER, 0,
"failed to find NVMe controller nvme%d", inst));
}
if (init.ncia_ctrl == NULL) {
nvme_err_set(nvme, &init.ncia_err);
return (false);
}
*outp = init.ncia_ctrl;
return (nvme_success(nvme));
}
bool
nvme_ctrl_devi(nvme_ctrl_t *ctrl, di_node_t *devip)
{
*devip = ctrl->nc_devi;
return (nvme_ctrl_success(ctrl));
}
bool
nvme_ioc_ctrl_info(nvme_ctrl_t *ctrl, nvme_ioctl_ctrl_info_t *info)
{
(void) memset(info, 0, sizeof (nvme_ioctl_ctrl_info_t));
if (ioctl(ctrl->nc_fd, NVME_IOC_CTRL_INFO, info) != 0) {
int e = errno;
return (nvme_ioctl_syserror(ctrl, e, "controller info"));
}
if (info->nci_common.nioc_drv_err != NVME_IOCTL_E_OK) {
return (nvme_ioctl_error(ctrl, &info->nci_common,
"controller info"));
}
return (true);
}
bool
nvme_ioc_ns_info(nvme_ctrl_t *ctrl, uint32_t nsid, nvme_ioctl_ns_info_t *info)
{
(void) memset(info, 0, sizeof (nvme_ioctl_ns_info_t));
info->nni_common.nioc_nsid = nsid;
if (ioctl(ctrl->nc_fd, NVME_IOC_NS_INFO, info) != 0) {
int e = errno;
return (nvme_ioctl_syserror(ctrl, e, "namespace info"));
}
if (info->nni_common.nioc_drv_err != NVME_IOCTL_E_OK) {
return (nvme_ioctl_error(ctrl, &info->nni_common,
"namespace info"));
}
return (true);
}
const char *
nvme_tporttostr(nvme_ctrl_transport_t tport)
{
switch (tport) {
case NVME_CTRL_TRANSPORT_PCI:
return ("PCI");
case NVME_CTRL_TRANSPORT_TCP:
return ("TCP");
case NVME_CTRL_TRANSPORT_RDMA:
return ("RDMA");
default:
return ("unknown transport");
}
}
static bool
nvme_ns_discover_validate(nvme_ctrl_t *ctrl, nvme_ns_disc_level_t level)
{
switch (level) {
case NVME_NS_DISC_F_ALL:
case NVME_NS_DISC_F_ALLOCATED:
case NVME_NS_DISC_F_ACTIVE:
case NVME_NS_DISC_F_NOT_IGNORED:
case NVME_NS_DISC_F_BLKDEV:
return (true);
default:
return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_FLAG, 0, "invalid "
"namespace discovery level specified: 0x%x", level));
}
}
void
nvme_ns_discover_fini(nvme_ns_iter_t *iter)
{
free(iter);
}
const char *
nvme_nsleveltostr(nvme_ns_disc_level_t level)
{
switch (level) {
case NVME_NS_DISC_F_ALL:
return ("unallocated");
case NVME_NS_DISC_F_ALLOCATED:
return ("allocated");
case NVME_NS_DISC_F_ACTIVE:
return ("active");
case NVME_NS_DISC_F_NOT_IGNORED:
return ("not ignored");
case NVME_NS_DISC_F_BLKDEV:
return ("blkdev");
default:
return ("unknown level");
}
}
nvme_ns_disc_level_t
nvme_ns_state_to_disc_level(nvme_ns_state_t state)
{
char msg[1024];
size_t len;
switch (state) {
case NVME_NS_STATE_UNALLOCATED:
return (NVME_NS_DISC_F_ALL);
case NVME_NS_STATE_ALLOCATED:
return (NVME_NS_DISC_F_ALLOCATED);
case NVME_NS_STATE_ACTIVE:
return (NVME_NS_DISC_F_ACTIVE);
case NVME_NS_STATE_NOT_IGNORED:
return (NVME_NS_DISC_F_NOT_IGNORED);
case NVME_NS_STATE_ATTACHED:
return (NVME_NS_DISC_F_BLKDEV);
default:
len = snprintf(msg, sizeof (msg), "encountered unknown kernel "
"state: 0x%x", state);
upanic(msg, len + 1);
}
}
nvme_iter_t
nvme_ns_discover_step(nvme_ns_iter_t *iter, const nvme_ns_disc_t **discp)
{
nvme_ctrl_t *ctrl = iter->nni_ctrl;
if (iter->nni_err) {
return (NVME_ITER_ERROR);
}
if (iter->nni_done) {
return (NVME_ITER_DONE);
}
while (iter->nni_cur_idx <= ctrl->nc_info.id_nn) {
uint32_t nsid = iter->nni_cur_idx;
nvme_ioctl_ns_info_t ns_info = { 0 };
nvme_ns_disc_level_t level;
if (!nvme_ioc_ns_info(ctrl, nsid, &ns_info)) {
iter->nni_err = true;
return (NVME_ITER_ERROR);
}
iter->nni_cur_idx++;
level = nvme_ns_state_to_disc_level(ns_info.nni_state);
if (iter->nni_level > level) {
continue;
}
(void) memset(&iter->nni_disc, 0, sizeof (nvme_ns_disc_t));
iter->nni_disc.nnd_nsid = nsid;
iter->nni_disc.nnd_level = level;
if (nvme_guid_valid(ctrl, ns_info.nni_id.id_nguid)) {
iter->nni_disc.nnd_flags |= NVME_NS_DISC_F_NGUID_VALID;
(void) memcpy(iter->nni_disc.nnd_nguid,
ns_info.nni_id.id_nguid,
sizeof (ns_info.nni_id.id_nguid));
}
if (nvme_eui64_valid(ctrl, ns_info.nni_id.id_eui64)) {
iter->nni_disc.nnd_flags |= NVME_NS_DISC_F_EUI64_VALID;
(void) memcpy(iter->nni_disc.nnd_eui64,
ns_info.nni_id.id_eui64,
sizeof (ns_info.nni_id.id_eui64));
}
*discp = &iter->nni_disc;
return (NVME_ITER_VALID);
}
iter->nni_done = true;
return (NVME_ITER_DONE);
}
bool
nvme_ns_discover_init(nvme_ctrl_t *ctrl, nvme_ns_disc_level_t level,
nvme_ns_iter_t **iterp)
{
nvme_ns_iter_t *iter;
if (!nvme_ns_discover_validate(ctrl, level)) {
return (false);
}
if (iterp == NULL) {
return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
"encountered invalid nvme_ns_iter_t output pointer: %p",
iterp));
}
iter = calloc(1, sizeof (nvme_ns_iter_t));
if (iter == NULL) {
int e = errno;
return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
"allocate memory for a new nvme_ns_iter_t: %s",
strerror(e)));
}
iter->nni_ctrl = ctrl;
iter->nni_level = level;
iter->nni_cur_idx = 1;
*iterp = iter;
return (nvme_ctrl_success(ctrl));
}
bool
nvme_ns_discover(nvme_ctrl_t *ctrl, nvme_ns_disc_level_t level,
nvme_ns_disc_f func, void *arg)
{
nvme_ns_iter_t *iter;
nvme_iter_t ret;
const nvme_ns_disc_t *disc;
if (!nvme_ns_discover_validate(ctrl, level)) {
return (false);
}
if (func == NULL) {
return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
"encountered invalid nvme_ns_disc_f function pointer: %p",
func));
}
if (!nvme_ns_discover_init(ctrl, level, &iter)) {
return (false);
}
while ((ret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
if (!func(ctrl, disc, arg))
break;
}
nvme_ns_discover_fini(iter);
if (ret == NVME_ITER_ERROR) {
return (false);
}
return (nvme_ctrl_success(ctrl));
}
uint32_t
nvme_ns_disc_nsid(const nvme_ns_disc_t *discp)
{
return (discp->nnd_nsid);
}
nvme_ns_disc_level_t
nvme_ns_disc_level(const nvme_ns_disc_t *discp)
{
return (discp->nnd_level);
}
nvme_ns_disc_flags_t
nvme_ns_disc_flags(const nvme_ns_disc_t *discp)
{
return (discp->nnd_flags);
}
const uint8_t *
nvme_ns_disc_eui64(const nvme_ns_disc_t *discp)
{
if ((discp->nnd_flags & NVME_NS_DISC_F_EUI64_VALID) == 0) {
return (NULL);
}
return (discp->nnd_eui64);
}
const uint8_t *
nvme_ns_disc_nguid(const nvme_ns_disc_t *discp)
{
if ((discp->nnd_flags & NVME_NS_DISC_F_NGUID_VALID) == 0) {
return (NULL);
}
return (discp->nnd_nguid);
}
void
nvme_ns_fini(nvme_ns_t *ns)
{
free(ns);
}
bool
nvme_ns_init(nvme_ctrl_t *ctrl, uint32_t nsid, nvme_ns_t **nsp)
{
nvme_ns_t *ns;
if (nsp == NULL) {
return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
"encountered invalid nvme_ns_t output pointer: %p", nsp));
}
if (nsid < NVME_NSID_MIN || nsid > ctrl->nc_info.id_nn) {
return (nvme_ctrl_error(ctrl, NVME_ERR_NS_RANGE, 0, "requested "
"namespace 0x%x is invalid, valid namespaces are [0x%x, "
"0x%x]", nsid, NVME_NSID_MIN, ctrl->nc_info.id_nn));
}
ns = calloc(1, sizeof (nvme_ns_t));
if (ns == NULL) {
int e = errno;
return (nvme_ctrl_error(ctrl, NVME_ERR_NO_MEM, e, "failed to "
"allocate memory for a new nvme_ns_t: %s", strerror(e)));
}
ns->nn_ctrl = ctrl;
ns->nn_nsid = nsid;
*nsp = ns;
return (nvme_ctrl_success(ctrl));
}
typedef struct {
nvme_ctrl_t *nnia_ctrl;
const char *nnia_name;
bool nnia_found;
nvme_ns_t *nnia_ns;
nvme_err_data_t nnia_err;
} nvme_ns_init_arg_t;
static bool
nvme_ns_init_by_name_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc,
void *arg)
{
nvme_ns_init_arg_t *init = arg;
char buf[NVME_NGUID_NAMELEN];
CTASSERT(NVME_NGUID_NAMELEN > NVME_EUI64_NAMELEN);
if ((disc->nnd_flags & NVME_NS_DISC_F_NGUID_VALID) != 0) {
(void) nvme_format_nguid(disc->nnd_nguid, buf, sizeof (buf));
if (strcasecmp(init->nnia_name, buf) == 0)
goto match;
}
if ((disc->nnd_flags & NVME_NS_DISC_F_EUI64_VALID) != 0) {
(void) nvme_format_eui64(disc->nnd_eui64, buf, sizeof (buf));
if (strcasecmp(init->nnia_name, buf) == 0)
goto match;
}
(void) snprintf(buf, sizeof (buf), "%u", disc->nnd_nsid);
if (strcasecmp(init->nnia_name, buf) == 0)
goto match;
return (true);
match:
init->nnia_found = true;
if (!nvme_ns_init(ctrl, disc->nnd_nsid, &init->nnia_ns)) {
nvme_ctrl_err_save(ctrl, &init->nnia_err);
}
return (false);
}
bool
nvme_ns_init_by_name(nvme_ctrl_t *ctrl, const char *ns_name, nvme_ns_t **nsp)
{
nvme_ns_init_arg_t init;
if (ns_name == NULL) {
return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
"encountered invalid namespace name: %p", ns_name));
}
if (nsp == NULL) {
return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
"encountered invalid nvme_ns_t output pointer: %p", nsp));
}
init.nnia_ctrl = ctrl;
init.nnia_name = ns_name;
init.nnia_found = false;
init.nnia_ns = NULL;
if (!nvme_ns_discover(ctrl, NVME_NS_DISC_F_ALL, nvme_ns_init_by_name_cb,
&init)) {
return (false);
}
if (!init.nnia_found) {
return (nvme_ctrl_error(ctrl, NVME_ERR_NS_RANGE, 0, "failed to "
"find NVMe namespace %s on nvme%d", ns_name,
ctrl->nc_inst));
}
if (init.nnia_ns == NULL) {
nvme_ctrl_err_set(ctrl, &init.nnia_err);
return (false);
}
*nsp = init.nnia_ns;
return (nvme_ctrl_success(ctrl));
}
bool
nvme_ctrl_ns_init(nvme_t *nvme, const char *name, nvme_ctrl_t **ctrlp,
nvme_ns_t **nsp)
{
const char *slash, *ns_name;
char *eptr;
nvme_ctrl_t *ctrl;
nvme_ns_t *ns;
unsigned long inst;
size_t ctrl_namelen;
if (name == NULL) {
return (nvme_error(nvme, NVME_ERR_BAD_PTR, 0, "encountered "
"invalid name to search for: %p", name));
}
if (ctrlp == NULL) {
return (nvme_error(nvme, NVME_ERR_BAD_PTR, 0, "encountered "
"invalid nvme_ctrl_t output pointer: %p", ctrlp));
}
slash = strchr(name, '/');
if (slash != NULL) {
ctrl_namelen = (uintptr_t)slash - (uintptr_t)name;
ns_name = slash + 1;
if (nsp == NULL) {
return (nvme_error(nvme, NVME_ERR_BAD_PTR, 0,
"encountered invalid nvme_ns_t output pointer: %p",
nsp));
}
} else {
ctrl_namelen = strlen(name);
ns_name = NULL;
}
*ctrlp = NULL;
if (nsp != NULL) {
*nsp = NULL;
}
if (strncmp(name, "nvme", 4) != 0) {
return (nvme_error(nvme, NVME_ERR_BAD_CONTROLLER, 0, "unable "
"to map controller '%.*s' to a known device class, "
"expected the controller to start with 'nvme'",
(int)ctrl_namelen, name));
}
if (ctrl_namelen == 4) {
return (nvme_error(nvme, NVME_ERR_BAD_CONTROLLER, 0,
"no controller instance specified in %.*s",
(int)ctrl_namelen, name));
}
if (name[4] == '0' && ctrl_namelen > 5) {
return (nvme_error(nvme, NVME_ERR_BAD_CONTROLLER, 0,
"leading zeros aren't allowed for the instance specified "
"in %.*s", (int)ctrl_namelen, name));
}
errno = 0;
inst = strtoul(name + 4, &eptr, 10);
if (errno != 0 || (*eptr != '\0' && eptr != slash)) {
return (nvme_error(nvme, NVME_ERR_BAD_CONTROLLER, 0,
"failed to parse controller instance from %.*s",
(int)ctrl_namelen, name));
}
if (inst > INT32_MAX) {
return (nvme_error(nvme, NVME_ERR_ILLEGAL_INSTANCE, 0,
"parsed controller instance %lu is outside the valid "
"range [0, %d]", inst, INT32_MAX));
}
if (!nvme_ctrl_init_by_instance(nvme, (int32_t)inst, &ctrl)) {
return (false);
}
if (ns_name == NULL) {
*ctrlp = ctrl;
return (nvme_success(nvme));
}
if (!nvme_ns_init_by_name(ctrl, ns_name, &ns)) {
nvme_err_data_t err;
nvme_ctrl_err_save(ctrl, &err);
nvme_err_set(nvme, &err);
nvme_ctrl_fini(ctrl);
return (false);
}
*ctrlp = ctrl;
*nsp = ns;
return (nvme_success(nvme));
}
bool
nvme_ns_bd_attach(nvme_ns_t *ns)
{
nvme_ctrl_t *ctrl = ns->nn_ctrl;
nvme_ioctl_common_t com;
(void) memset(&com, 0, sizeof (com));
com.nioc_nsid = ns->nn_nsid;
if (ioctl(ns->nn_ctrl->nc_fd, NVME_IOC_BD_ATTACH, &com) != 0) {
int e = errno;
return (nvme_ioctl_syserror(ctrl, e, "namespace attach"));
}
if (com.nioc_drv_err != NVME_IOCTL_E_OK) {
return (nvme_ioctl_error(ctrl, &com, "namespace attach"));
}
return (nvme_ctrl_success(ctrl));
}
bool
nvme_ns_bd_detach(nvme_ns_t *ns)
{
nvme_ctrl_t *ctrl = ns->nn_ctrl;
nvme_ioctl_common_t com;
(void) memset(&com, 0, sizeof (com));
com.nioc_nsid = ns->nn_nsid;
if (ioctl(ns->nn_ctrl->nc_fd, NVME_IOC_BD_DETACH, &com) != 0) {
int e = errno;
return (nvme_ioctl_syserror(ctrl, e, "namespace detach"));
}
if (com.nioc_drv_err != NVME_IOCTL_E_OK) {
return (nvme_ioctl_error(ctrl, &com, "namespace detach"));
}
return (nvme_ctrl_success(ctrl));
}
static void
nvme_lock_check(nvme_ctrl_t *ctrl)
{
char msg[1024];
int ret;
const char *up;
size_t ulen;
const char *base = "fatal libnvme locking error detected";
if (ctrl->nc_err.ne_err != NVME_ERR_LOCK_PROG) {
return;
}
ret = snprintf(msg, sizeof (msg), "%s: %s (controller %p)", base,
ctrl->nc_err.ne_errmsg, ctrl);
if (ret >= sizeof (msg)) {
ulen = sizeof (msg);
up = msg;
} else if (ret <= 0) {
ulen = strlen(base) + 1;
up = base;
} else {
ulen = (size_t)ret + 1;
up = msg;
}
upanic(up, ulen);
}
static bool
nvme_lock_common(nvme_ctrl_t *ctrl, uint32_t nsid, nvme_lock_level_t level,
nvme_lock_flags_t flags)
{
nvme_ioctl_lock_t lock;
const nvme_lock_flags_t all_flags = NVME_LOCK_F_DONT_BLOCK;
if (level != NVME_LOCK_L_READ && level != NVME_LOCK_L_WRITE) {
return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_FLAG, 0, "unknown "
"lock level: 0x%x", level));
}
if ((flags & ~all_flags) != 0) {
return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_FLAG, 0, "unknown "
"lock flags: 0x%x", flags & ~all_flags));
}
(void) memset(&lock, 0, sizeof (lock));
lock.nil_common.nioc_nsid = nsid;
if (nsid != 0) {
lock.nil_ent = NVME_LOCK_E_NS;
} else {
lock.nil_ent = NVME_LOCK_E_CTRL;
}
lock.nil_level = level;
lock.nil_flags = flags;
if (ioctl(ctrl->nc_fd, NVME_IOC_LOCK, &lock) != 0) {
int e = errno;
return (nvme_ioctl_syserror(ctrl, e, "lock"));
}
if (lock.nil_common.nioc_drv_err != NVME_IOCTL_E_OK) {
(void) nvme_ioctl_error(ctrl, &lock.nil_common, "lock");
nvme_lock_check(ctrl);
return (false);
}
return (nvme_ctrl_success(ctrl));
}
void
nvme_unlock_common(nvme_ctrl_t *ctrl, uint32_t nsid)
{
nvme_ioctl_unlock_t unlock;
(void) memset(&unlock, 0, sizeof (unlock));
unlock.niu_common.nioc_nsid = nsid;
if (nsid != 0) {
unlock.niu_ent = NVME_LOCK_E_NS;
} else {
unlock.niu_ent = NVME_LOCK_E_CTRL;
}
if (ioctl(ctrl->nc_fd, NVME_IOC_UNLOCK, &unlock) != 0) {
int e = errno;
(void) nvme_ctrl_error(ctrl, NVME_ERR_LOCK_PROG, e, "internal "
"programming error: failed to issue unlock ioctl: %s",
strerror(e));
nvme_lock_check(ctrl);
return;
}
if (unlock.niu_common.nioc_drv_err != NVME_IOCTL_E_OK) {
(void) nvme_ioctl_error(ctrl, &unlock.niu_common, "unlock");
if (ctrl->nc_err.ne_err != NVME_ERR_LOCK_PROG) {
nvme_err_data_t err;
nvme_ctrl_err_save(ctrl, &err);
(void) nvme_ctrl_error(ctrl, NVME_ERR_LOCK_PROG, 0,
"internal programming error: received unexpected "
"libnvme error 0x%x: %s", err.ne_err,
err.ne_errmsg);
}
nvme_lock_check(ctrl);
return;
}
(void) nvme_ctrl_success(ctrl);
}
bool
nvme_ctrl_lock(nvme_ctrl_t *ctrl, nvme_lock_level_t level,
nvme_lock_flags_t flags)
{
return (nvme_lock_common(ctrl, 0, level, flags));
}
bool
nvme_ns_lock(nvme_ns_t *ns, nvme_lock_level_t level,
nvme_lock_flags_t flags)
{
return (nvme_lock_common(ns->nn_ctrl, ns->nn_nsid, level, flags));
}
void
nvme_ctrl_unlock(nvme_ctrl_t *ctrl)
{
nvme_unlock_common(ctrl, 0);
}
void
nvme_ns_unlock(nvme_ns_t *ns)
{
nvme_unlock_common(ns->nn_ctrl, ns->nn_nsid);
}