/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023-2024 Chelsio Communications, Inc. * Written by: John Baldwin */ #include #ifdef _KERNEL #include #else #include #include #include #endif #include #include "nvmft_subr.h" bool nvmf_nqn_valid(const char *nqn) { size_t len; len = strnlen(nqn, NVME_NQN_FIELD_SIZE); if (len == 0 || len > NVMF_NQN_MAX_LEN) return (false); #ifdef STRICT_CHECKS /* * Stricter checks from the spec. Linux does not seem to * require these. */ /* * NVMF_NQN_MIN_LEN does not include '.', and require at least * one character of a domain name. */ if (len < NVMF_NQN_MIN_LEN + 2) return (false); if (memcmp("nqn.", nqn, strlen("nqn.")) != 0) return (false); nqn += strlen("nqn."); /* Next 4 digits must be a year. */ for (u_int i = 0; i < 4; i++) { if (!isdigit(nqn[i])) return (false); } nqn += 4; /* '-' between year and month. */ if (nqn[0] != '-') return (false); nqn++; /* 2 digit month. */ for (u_int i = 0; i < 2; i++) { if (!isdigit(nqn[i])) return (false); } nqn += 2; /* '.' between month and reverse domain name. */ if (nqn[0] != '.') return (false); #endif return (true); } uint64_t _nvmf_controller_cap(uint32_t max_io_qsize, uint8_t enable_timeout) { uint32_t caphi, caplo; u_int mps; caphi = NVMEF(NVME_CAP_HI_REG_CMBS, 0) | NVMEF(NVME_CAP_HI_REG_PMRS, 0); if (max_io_qsize != 0) { mps = ffs(PAGE_SIZE) - 1; if (mps < NVME_MPS_SHIFT) mps = 0; else mps -= NVME_MPS_SHIFT; caphi |= NVMEF(NVME_CAP_HI_REG_MPSMAX, mps) | NVMEF(NVME_CAP_HI_REG_MPSMIN, mps); } caphi |= NVMEF(NVME_CAP_HI_REG_BPS, 0) | NVMEF(NVME_CAP_HI_REG_CSS, NVME_CAP_HI_REG_CSS_NVM_MASK) | NVMEF(NVME_CAP_HI_REG_NSSRS, 0) | NVMEF(NVME_CAP_HI_REG_DSTRD, 0); caplo = NVMEF(NVME_CAP_LO_REG_TO, enable_timeout) | NVMEF(NVME_CAP_LO_REG_AMS, 0) | NVMEF(NVME_CAP_LO_REG_CQR, 1); if (max_io_qsize != 0) caplo |= NVMEF(NVME_CAP_LO_REG_MQES, max_io_qsize - 1); return ((uint64_t)caphi << 32 | caplo); } bool _nvmf_validate_cc(uint32_t max_io_qsize __unused, uint64_t cap, uint32_t old_cc, uint32_t new_cc) { uint32_t caphi, changes, field; changes = old_cc ^ new_cc; field = NVMEV(NVME_CC_REG_IOCQES, new_cc); if (field != 0) { /* * XXX: Linux's initiator writes a non-zero value to * IOCQES when connecting to a discovery controller. */ #ifdef STRICT_CHECKS if (max_io_qsize == 0) return (false); #endif if (field != 4) return (false); } field = NVMEV(NVME_CC_REG_IOSQES, new_cc); if (field != 0) { /* * XXX: Linux's initiator writes a non-zero value to * IOCQES when connecting to a discovery controller. */ #ifdef STRICT_CHECKS if (max_io_qsize == 0) return (false); #endif if (field != 6) return (false); } field = NVMEV(NVME_CC_REG_SHN, new_cc); if (field == 3) return (false); field = NVMEV(NVME_CC_REG_AMS, new_cc); if (field != 0) return (false); caphi = cap >> 32; field = NVMEV(NVME_CC_REG_MPS, new_cc); if (field < NVMEV(NVME_CAP_HI_REG_MPSMAX, caphi) || field > NVMEV(NVME_CAP_HI_REG_MPSMIN, caphi)) return (false); field = NVMEV(NVME_CC_REG_CSS, new_cc); if (field != 0 && field != 0x7) return (false); /* AMS, MPS, and CSS can only be changed while CC.EN is 0. */ if (NVMEV(NVME_CC_REG_EN, old_cc) != 0 && (NVMEV(NVME_CC_REG_AMS, changes) != 0 || NVMEV(NVME_CC_REG_MPS, changes) != 0 || NVMEV(NVME_CC_REG_CSS, changes) != 0)) return (false); return (true); } void nvmf_controller_serial(char *buf, size_t len, u_long hostid) { snprintf(buf, len, "HI:%lu", hostid); } void nvmf_strpad(char *dst, const char *src, size_t len) { while (len > 0 && *src != '\0') { *dst++ = *src++; len--; } memset(dst, ' ', len); } void _nvmf_init_io_controller_data(uint16_t cntlid, uint32_t max_io_qsize, const char *serial, const char *model, const char *firmware_version, const char *subnqn, int nn, uint32_t ioccsz, uint32_t iorcsz, struct nvme_controller_data *cdata) { char *cp; nvmf_strpad(cdata->sn, serial, sizeof(cdata->sn)); nvmf_strpad(cdata->mn, model, sizeof(cdata->mn)); nvmf_strpad(cdata->fr, firmware_version, sizeof(cdata->fr)); cp = memchr(cdata->fr, '-', sizeof(cdata->fr)); if (cp != NULL) memset(cp, ' ', sizeof(cdata->fr) - (cp - (char *)cdata->fr)); /* FreeBSD OUI */ cdata->ieee[0] = 0xfc; cdata->ieee[1] = 0x9c; cdata->ieee[2] = 0x58; cdata->ctrlr_id = htole16(cntlid); cdata->ver = htole32(NVME_REV(1, 4)); cdata->ctratt = htole32( NVMEF(NVME_CTRLR_DATA_CTRATT_128BIT_HOSTID, 1) | NVMEF(NVME_CTRLR_DATA_CTRATT_TBKAS, 1)); cdata->cntrltype = 1; cdata->acl = 3; cdata->aerl = 3; /* 1 read-only firmware slot */ cdata->frmw = NVMEF(NVME_CTRLR_DATA_FRMW_SLOT1_RO, 1) | NVMEF(NVME_CTRLR_DATA_FRMW_NUM_SLOTS, 1); cdata->lpa = NVMEF(NVME_CTRLR_DATA_LPA_EXT_DATA, 1); /* Single power state */ cdata->npss = 0; /* * 1.2+ require a non-zero value for these even though it makes * no sense for Fabrics. */ cdata->wctemp = htole16(0x0157); cdata->cctemp = cdata->wctemp; /* 1 second granularity for KeepAlive */ cdata->kas = htole16(10); cdata->sqes = NVMEF(NVME_CTRLR_DATA_SQES_MAX, 6) | NVMEF(NVME_CTRLR_DATA_SQES_MIN, 6); cdata->cqes = NVMEF(NVME_CTRLR_DATA_CQES_MAX, 4) | NVMEF(NVME_CTRLR_DATA_CQES_MIN, 4); cdata->maxcmd = htole16(max_io_qsize); cdata->nn = htole32(nn); cdata->vwc = NVMEF(NVME_CTRLR_DATA_VWC_ALL, NVME_CTRLR_DATA_VWC_ALL_NO) | NVMEM(NVME_CTRLR_DATA_VWC_PRESENT); /* Transport-specific? */ cdata->sgls = htole32( NVMEF(NVME_CTRLR_DATA_SGLS_TRANSPORT_DATA_BLOCK, 1) | NVMEF(NVME_CTRLR_DATA_SGLS_ADDRESS_AS_OFFSET, 1) | NVMEF(NVME_CTRLR_DATA_SGLS_NVM_COMMAND_SET, 1)); strlcpy(cdata->subnqn, subnqn, sizeof(cdata->subnqn)); cdata->ioccsz = htole32(ioccsz / 16); cdata->iorcsz = htole32(iorcsz / 16); /* Transport-specific? */ cdata->icdoff = 0; cdata->fcatt = 0; /* Transport-specific? */ cdata->msdbd = 1; }