diff --git a/lib/libnvmf/libnvmf.h b/lib/libnvmf/libnvmf.h index 7cdd7e433455..6b38fd286596 100644 --- a/lib/libnvmf/libnvmf.h +++ b/lib/libnvmf/libnvmf.h @@ -1,382 +1,387 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022-2024 Chelsio Communications, Inc. * Written by: John Baldwin */ #ifndef __LIBNVMF_H__ #define __LIBNVMF_H__ #include #include #include #include #include #include #include struct nvmf_capsule; struct nvmf_association; struct nvmf_qpair; /* * Parameters shared by all queue-pairs of an association. Note that * this contains the requested values used to initiate transport * negotiation. */ struct nvmf_association_params { bool sq_flow_control; /* SQ flow control required. */ bool dynamic_controller_model; /* Controller only */ uint16_t max_admin_qsize; /* Controller only */ uint32_t max_io_qsize; /* Controller only, 0 for discovery */ union { struct { uint8_t pda; /* Tx-side PDA. */ bool header_digests; bool data_digests; uint32_t maxr2t; /* Host only */ uint32_t maxh2cdata; /* Controller only */ } tcp; }; }; /* Parameters specific to a single queue pair of an association. */ struct nvmf_qpair_params { bool admin; /* Host only */ union { struct { int fd; } tcp; }; }; /* Transport-independent APIs. */ /* * A host should allocate a new association for each association with * a controller. After the admin queue has been allocated and the * controller's data has been fetched, it should be passed to * nvmf_update_association to update internal transport-specific * parameters before allocating I/O queues. * * A controller uses a single association to manage all incoming * queues since it is not known until after parsing the CONNECT * command which transport queues are admin vs I/O and which * controller they are created against. */ struct nvmf_association *nvmf_allocate_association(enum nvmf_trtype trtype, bool controller, const struct nvmf_association_params *params); void nvmf_update_assocation(struct nvmf_association *na, const struct nvme_controller_data *cdata); void nvmf_free_association(struct nvmf_association *na); /* The most recent association-wide error message. */ const char *nvmf_association_error(const struct nvmf_association *na); /* * A queue pair represents either an Admin or I/O * submission/completion queue pair. * * Each open qpair holds a reference on its association. Once queue * pairs are allocated, callers can safely free the association to * ease bookkeeping. * * If nvmf_allocate_qpair fails, a detailed error message can be obtained * from nvmf_association_error. */ struct nvmf_qpair *nvmf_allocate_qpair(struct nvmf_association *na, const struct nvmf_qpair_params *params); void nvmf_free_qpair(struct nvmf_qpair *qp); /* * Capsules are either commands (host -> controller) or responses * (controller -> host). A single data buffer segment may be * associated with a command capsule. Transmitted data is not copied * by this API but instead must be preserved until the capsule is * transmitted and freed. */ struct nvmf_capsule *nvmf_allocate_command(struct nvmf_qpair *qp, const void *sqe); struct nvmf_capsule *nvmf_allocate_response(struct nvmf_qpair *qp, const void *cqe); void nvmf_free_capsule(struct nvmf_capsule *nc); int nvmf_capsule_append_data(struct nvmf_capsule *nc, void *buf, size_t len, bool send); int nvmf_transmit_capsule(struct nvmf_capsule *nc); int nvmf_receive_capsule(struct nvmf_qpair *qp, struct nvmf_capsule **ncp); const void *nvmf_capsule_sqe(const struct nvmf_capsule *nc); const void *nvmf_capsule_cqe(const struct nvmf_capsule *nc); /* Return a string name for a transport type. */ const char *nvmf_transport_type(uint8_t trtype); -/* Validate a NVMe Qualified Name. */ +/* + * Validate a NVMe Qualified Name. The second version enforces + * stricter checks inline with the specification. The first version + * enforces more minimal checks. + */ bool nvmf_nqn_valid(const char *nqn); +bool nvmf_nqn_valid_strict(const char *nqn); /* Controller-specific APIs. */ /* * A controller calls this function to check for any * transport-specific errors (invalid fields) in a received command * capsule. The callback returns a generic command status value: * NVME_SC_SUCCESS if no error is found. */ uint8_t nvmf_validate_command_capsule(const struct nvmf_capsule *nc); /* * A controller calls this function to query the amount of data * associated with a command capsule. */ size_t nvmf_capsule_data_len(const struct nvmf_capsule *cc); /* * A controller calls this function to receive data associated with a * command capsule (e.g. the data for a WRITE command). This can * either return in-capsule data or fetch data from the host * (e.g. using a R2T PDU over TCP). The received command capsule * should be passed in 'nc'. The received data is stored in '*buf'. */ int nvmf_receive_controller_data(const struct nvmf_capsule *nc, uint32_t data_offset, void *buf, size_t len); /* * A controller calls this function to send data in response to a * command along with a response capsule. If the data transfer * succeeds, a success response is sent. If the data transfer fails, * an appropriate error status capsule is sent. Regardless, a * response capsule is always sent. */ int nvmf_send_controller_data(const struct nvmf_capsule *nc, const void *buf, size_t len); /* * Construct a CQE for a reply to a command capsule in 'nc' with the * completion status 'status'. This is useful when additional CQE * info is required beyond the completion status. */ void nvmf_init_cqe(void *cqe, const struct nvmf_capsule *nc, uint16_t status); /* * Construct and send a response capsule to a command capsule with * the supplied CQE. */ int nvmf_send_response(const struct nvmf_capsule *nc, const void *cqe); /* * Wait for a single command capsule and return it in *ncp. This can * fail if an invalid capsule is received or an I/O error occurs. */ int nvmf_controller_receive_capsule(struct nvmf_qpair *qp, struct nvmf_capsule **ncp); /* Send a response capsule from a controller. */ int nvmf_controller_transmit_response(struct nvmf_capsule *nc); /* Construct and send an error response capsule. */ int nvmf_send_error(const struct nvmf_capsule *cc, uint8_t sc_type, uint8_t sc_status); /* * Construct and send an error response capsule using a generic status * code. */ int nvmf_send_generic_error(const struct nvmf_capsule *nc, uint8_t sc_status); /* Construct and send a simple success response capsule. */ int nvmf_send_success(const struct nvmf_capsule *nc); /* * Allocate a new queue pair and wait for the CONNECT command capsule. * If this fails, a detailed error message can be obtained from * nvmf_association_error. On success, the command capsule is saved * in '*ccp' and the connect data is saved in 'data'. The caller * must send an explicit response and free the the command capsule. */ struct nvmf_qpair *nvmf_accept(struct nvmf_association *na, const struct nvmf_qpair_params *params, struct nvmf_capsule **ccp, struct nvmf_fabric_connect_data *data); /* * Construct and send a response capsule with the Fabrics CONNECT * invalid parameters error status. If data is true the offset is * relative to the CONNECT data structure, otherwise the offset is * relative to the SQE. */ void nvmf_connect_invalid_parameters(const struct nvmf_capsule *cc, bool data, uint16_t offset); /* Construct and send a response capsule for a successful CONNECT. */ int nvmf_finish_accept(const struct nvmf_capsule *cc, uint16_t cntlid); /* Compute the initial state of CAP for a controller. */ uint64_t nvmf_controller_cap(struct nvmf_qpair *qp); /* Generate a serial number string from a host ID. */ void nvmf_controller_serial(char *buf, size_t len, u_long hostid); /* * Populate an Identify Controller data structure for a Discovery * controller. */ void nvmf_init_discovery_controller_data(struct nvmf_qpair *qp, struct nvme_controller_data *cdata); /* * Populate an Identify Controller data structure for an I/O * controller. */ void nvmf_init_io_controller_data(struct nvmf_qpair *qp, const char *serial, const char *subnqn, int nn, uint32_t ioccsz, struct nvme_controller_data *cdata); /* * Validate if a new value for CC is legal given the existing values of * CAP and CC. */ bool nvmf_validate_cc(struct nvmf_qpair *qp, uint64_t cap, uint32_t old_cc, uint32_t new_cc); /* Return the log page id (LID) of a GET_LOG_PAGE command. */ uint8_t nvmf_get_log_page_id(const struct nvme_command *cmd); /* Return the requested data length of a GET_LOG_PAGE command. */ uint64_t nvmf_get_log_page_length(const struct nvme_command *cmd); /* Return the requested data offset of a GET_LOG_PAGE command. */ uint64_t nvmf_get_log_page_offset(const struct nvme_command *cmd); /* Prepare to handoff a controller qpair. */ int nvmf_handoff_controller_qpair(struct nvmf_qpair *qp, const struct nvmf_fabric_connect_cmd *cmd, const struct nvmf_fabric_connect_data *data, struct nvmf_ioc_nv *nv); /* Host-specific APIs. */ /* * Connect to an admin or I/O queue. If this fails, a detailed error * message can be obtained from nvmf_association_error. */ struct nvmf_qpair *nvmf_connect(struct nvmf_association *na, const struct nvmf_qpair_params *params, uint16_t qid, u_int queue_size, const uint8_t hostid[16], uint16_t cntlid, const char *subnqn, const char *hostnqn, uint32_t kato); /* Return the CNTLID for a queue returned from CONNECT. */ uint16_t nvmf_cntlid(struct nvmf_qpair *qp); /* * Send a command to the controller. This can fail with EBUSY if the * submission queue is full. */ int nvmf_host_transmit_command(struct nvmf_capsule *nc); /* * Wait for a response to a command. If there are no outstanding * commands in the SQ, fails with EWOULDBLOCK. */ int nvmf_host_receive_response(struct nvmf_qpair *qp, struct nvmf_capsule **rcp); /* * Wait for a response to a specific command. The command must have been * succesfully sent previously. */ int nvmf_host_wait_for_response(struct nvmf_capsule *cc, struct nvmf_capsule **rcp); /* Build a KeepAlive command. */ struct nvmf_capsule *nvmf_keepalive(struct nvmf_qpair *qp); /* Read a controller property. */ int nvmf_read_property(struct nvmf_qpair *qp, uint32_t offset, uint8_t size, uint64_t *value); /* Write a controller property. */ int nvmf_write_property(struct nvmf_qpair *qp, uint32_t offset, uint8_t size, uint64_t value); /* Construct a 16-byte HostId from kern.hostuuid. */ int nvmf_hostid_from_hostuuid(uint8_t hostid[16]); /* Construct a NQN from kern.hostuuid. */ int nvmf_nqn_from_hostuuid(char nqn[NVMF_NQN_MAX_LEN]); /* Fetch controller data via IDENTIFY. */ int nvmf_host_identify_controller(struct nvmf_qpair *qp, struct nvme_controller_data *data); /* Fetch namespace data via IDENTIFY. */ int nvmf_host_identify_namespace(struct nvmf_qpair *qp, uint32_t nsid, struct nvme_namespace_data *nsdata); /* * Fetch discovery log page. The memory for the log page is allocated * by malloc() and returned in *logp. The caller must free the * memory. */ int nvmf_host_fetch_discovery_log_page(struct nvmf_qpair *qp, struct nvme_discovery_log **logp); /* * Construct a discovery log page entry that describes the connection * used by a host association's admin queue pair. */ int nvmf_init_dle_from_admin_qp(struct nvmf_qpair *qp, const struct nvme_controller_data *cdata, struct nvme_discovery_log_entry *dle); /* * Request a desired number of I/O queues via SET_FEATURES. The * number of actual I/O queues available is returned in *actual on * success. */ int nvmf_host_request_queues(struct nvmf_qpair *qp, u_int requested, u_int *actual); /* * Handoff active host association to the kernel. This frees the * qpairs (even on error). */ int nvmf_handoff_host(const struct nvme_discovery_log_entry *dle, const char *hostnqn, struct nvmf_qpair *admin_qp, u_int num_queues, struct nvmf_qpair **io_queues, const struct nvme_controller_data *cdata, uint32_t reconnect_delay, uint32_t controller_loss_timeout); /* * Disconnect an active host association previously handed off to the * kernel. *name is either the name of the device (nvmeX) for this * association or the remote subsystem NQN. */ int nvmf_disconnect_host(const char *host); /* * Disconnect all active host associations previously handed off to * the kernel. */ int nvmf_disconnect_all(void); /* * Fetch reconnect parameters from an existing kernel host to use for * establishing a new association. The caller must destroy the * returned nvlist. */ int nvmf_reconnect_params(int fd, nvlist_t **nvlp); /* * Handoff active host association to an existing host in the kernel. * This frees the qpairs (even on error). */ int nvmf_reconnect_host(int fd, const struct nvme_discovery_log_entry *dle, const char *hostnqn, struct nvmf_qpair *admin_qp, u_int num_queues, struct nvmf_qpair **io_queues, const struct nvme_controller_data *cdata, uint32_t reconnect_delay, uint32_t controller_loss_timeout); /* * Fetch connection status from an existing kernel host. */ int nvmf_connection_status(int fd, nvlist_t **nvlp); #endif /* !__LIBNVMF_H__ */ diff --git a/lib/libnvmf/nvmf_controller.c b/lib/libnvmf/nvmf_controller.c index 971dccbe039e..f26f11633e03 100644 --- a/lib/libnvmf/nvmf_controller.c +++ b/lib/libnvmf/nvmf_controller.c @@ -1,479 +1,529 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2024 Chelsio Communications, Inc. * Written by: John Baldwin */ #include #include +#include #include #include #include #include "libnvmf.h" #include "internal.h" #include "nvmft_subr.h" +bool +nvmf_nqn_valid_strict(const char *nqn) +{ + size_t len; + + if (!nvmf_nqn_valid(nqn)) + return (false); + + /* + * Stricter checks from the spec. Linux does not seem to + * require these. + */ + len = strlen(nqn); + + /* + * 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); + return (true); +} + void nvmf_init_cqe(void *cqe, const struct nvmf_capsule *nc, uint16_t status) { struct nvme_completion *cpl = cqe; const struct nvme_command *cmd = nvmf_capsule_sqe(nc); memset(cpl, 0, sizeof(*cpl)); cpl->cid = cmd->cid; cpl->status = htole16(status); } static struct nvmf_capsule * nvmf_simple_response(const struct nvmf_capsule *nc, uint8_t sc_type, uint8_t sc_status) { struct nvme_completion cpl; uint16_t status; status = NVMEF(NVME_STATUS_SCT, sc_type) | NVMEF(NVME_STATUS_SC, sc_status); nvmf_init_cqe(&cpl, nc, status); return (nvmf_allocate_response(nc->nc_qpair, &cpl)); } int nvmf_controller_receive_capsule(struct nvmf_qpair *qp, struct nvmf_capsule **ncp) { struct nvmf_capsule *nc; int error; uint8_t sc_status; *ncp = NULL; error = nvmf_receive_capsule(qp, &nc); if (error != 0) return (error); sc_status = nvmf_validate_command_capsule(nc); if (sc_status != NVME_SC_SUCCESS) { nvmf_send_generic_error(nc, sc_status); nvmf_free_capsule(nc); return (EPROTO); } *ncp = nc; return (0); } int nvmf_controller_transmit_response(struct nvmf_capsule *nc) { struct nvmf_qpair *qp = nc->nc_qpair; /* Set SQHD. */ if (qp->nq_flow_control) { qp->nq_sqhd = (qp->nq_sqhd + 1) % qp->nq_qsize; nc->nc_cqe.sqhd = htole16(qp->nq_sqhd); } else nc->nc_cqe.sqhd = 0; return (nvmf_transmit_capsule(nc)); } int nvmf_send_response(const struct nvmf_capsule *cc, const void *cqe) { struct nvmf_capsule *rc; int error; rc = nvmf_allocate_response(cc->nc_qpair, cqe); if (rc == NULL) return (ENOMEM); error = nvmf_controller_transmit_response(rc); nvmf_free_capsule(rc); return (error); } int nvmf_send_error(const struct nvmf_capsule *cc, uint8_t sc_type, uint8_t sc_status) { struct nvmf_capsule *rc; int error; rc = nvmf_simple_response(cc, sc_type, sc_status); error = nvmf_controller_transmit_response(rc); nvmf_free_capsule(rc); return (error); } int nvmf_send_generic_error(const struct nvmf_capsule *nc, uint8_t sc_status) { return (nvmf_send_error(nc, NVME_SCT_GENERIC, sc_status)); } int nvmf_send_success(const struct nvmf_capsule *nc) { return (nvmf_send_generic_error(nc, NVME_SC_SUCCESS)); } void nvmf_connect_invalid_parameters(const struct nvmf_capsule *cc, bool data, uint16_t offset) { struct nvmf_fabric_connect_rsp rsp; struct nvmf_capsule *rc; nvmf_init_cqe(&rsp, cc, NVMEF(NVME_STATUS_SCT, NVME_SCT_COMMAND_SPECIFIC) | NVMEF(NVME_STATUS_SC, NVMF_FABRIC_SC_INVALID_PARAM)); rsp.status_code_specific.invalid.ipo = htole16(offset); rsp.status_code_specific.invalid.iattr = data ? 1 : 0; rc = nvmf_allocate_response(cc->nc_qpair, &rsp); nvmf_transmit_capsule(rc); nvmf_free_capsule(rc); } struct nvmf_qpair * nvmf_accept(struct nvmf_association *na, const struct nvmf_qpair_params *params, struct nvmf_capsule **ccp, struct nvmf_fabric_connect_data *data) { static const char hostid_zero[sizeof(data->hostid)]; const struct nvmf_fabric_connect_cmd *cmd; struct nvmf_qpair *qp; struct nvmf_capsule *cc, *rc; u_int qsize; int error; uint16_t cntlid; uint8_t sc_status; qp = NULL; cc = NULL; rc = NULL; *ccp = NULL; na_clear_error(na); if (!na->na_controller) { na_error(na, "Cannot accept on a host"); goto error; } qp = nvmf_allocate_qpair(na, params); if (qp == NULL) goto error; /* Read the CONNECT capsule. */ error = nvmf_receive_capsule(qp, &cc); if (error != 0) { na_error(na, "Failed to receive CONNECT: %s", strerror(error)); goto error; } sc_status = nvmf_validate_command_capsule(cc); if (sc_status != 0) { na_error(na, "CONNECT command failed to validate: %u", sc_status); rc = nvmf_simple_response(cc, NVME_SCT_GENERIC, sc_status); goto error; } cmd = nvmf_capsule_sqe(cc); if (cmd->opcode != NVME_OPC_FABRICS_COMMANDS || cmd->fctype != NVMF_FABRIC_COMMAND_CONNECT) { na_error(na, "Invalid opcode in CONNECT (%u,%u)", cmd->opcode, cmd->fctype); rc = nvmf_simple_response(cc, NVME_SCT_GENERIC, NVME_SC_INVALID_OPCODE); goto error; } if (cmd->recfmt != htole16(0)) { na_error(na, "Unsupported CONNECT record format %u", le16toh(cmd->recfmt)); rc = nvmf_simple_response(cc, NVME_SCT_COMMAND_SPECIFIC, NVMF_FABRIC_SC_INCOMPATIBLE_FORMAT); goto error; } qsize = le16toh(cmd->sqsize) + 1; if (cmd->qid == 0) { /* Admin queue limits. */ if (qsize < NVME_MIN_ADMIN_ENTRIES || qsize > NVME_MAX_ADMIN_ENTRIES || qsize > na->na_params.max_admin_qsize) { na_error(na, "Invalid queue size %u", qsize); nvmf_connect_invalid_parameters(cc, false, offsetof(struct nvmf_fabric_connect_cmd, sqsize)); goto error; } qp->nq_admin = true; } else { /* I/O queues not allowed for discovery. */ if (na->na_params.max_io_qsize == 0) { na_error(na, "I/O queue on discovery controller"); nvmf_connect_invalid_parameters(cc, false, offsetof(struct nvmf_fabric_connect_cmd, qid)); goto error; } /* I/O queue limits. */ if (qsize < NVME_MIN_IO_ENTRIES || qsize > NVME_MAX_IO_ENTRIES || qsize > na->na_params.max_io_qsize) { na_error(na, "Invalid queue size %u", qsize); nvmf_connect_invalid_parameters(cc, false, offsetof(struct nvmf_fabric_connect_cmd, sqsize)); goto error; } /* KATO is reserved for I/O queues. */ if (cmd->kato != 0) { na_error(na, "KeepAlive timeout specified for I/O queue"); nvmf_connect_invalid_parameters(cc, false, offsetof(struct nvmf_fabric_connect_cmd, kato)); goto error; } qp->nq_admin = false; } qp->nq_qsize = qsize; /* Fetch CONNECT data. */ if (nvmf_capsule_data_len(cc) != sizeof(*data)) { na_error(na, "Invalid data payload length for CONNECT: %zu", nvmf_capsule_data_len(cc)); nvmf_connect_invalid_parameters(cc, false, offsetof(struct nvmf_fabric_connect_cmd, sgl1)); goto error; } error = nvmf_receive_controller_data(cc, 0, data, sizeof(*data)); if (error != 0) { na_error(na, "Failed to read data for CONNECT: %s", strerror(error)); rc = nvmf_simple_response(cc, NVME_SCT_GENERIC, NVME_SC_DATA_TRANSFER_ERROR); goto error; } /* The hostid must be non-zero. */ if (memcmp(data->hostid, hostid_zero, sizeof(hostid_zero)) == 0) { na_error(na, "HostID in CONNECT data is zero"); nvmf_connect_invalid_parameters(cc, true, offsetof(struct nvmf_fabric_connect_data, hostid)); goto error; } cntlid = le16toh(data->cntlid); if (cmd->qid == 0) { if (na->na_params.dynamic_controller_model) { if (cntlid != NVMF_CNTLID_DYNAMIC) { na_error(na, "Invalid controller ID %#x", cntlid); nvmf_connect_invalid_parameters(cc, true, offsetof(struct nvmf_fabric_connect_data, cntlid)); goto error; } } else { if (cntlid > NVMF_CNTLID_STATIC_MAX && cntlid != NVMF_CNTLID_STATIC_ANY) { na_error(na, "Invalid controller ID %#x", cntlid); nvmf_connect_invalid_parameters(cc, true, offsetof(struct nvmf_fabric_connect_data, cntlid)); goto error; } } } else { /* Wildcard Controller IDs are only valid on an Admin queue. */ if (cntlid > NVMF_CNTLID_STATIC_MAX) { na_error(na, "Invalid controller ID %#x", cntlid); nvmf_connect_invalid_parameters(cc, true, offsetof(struct nvmf_fabric_connect_data, cntlid)); goto error; } } /* Simple validation of each NQN. */ if (!nvmf_nqn_valid(data->subnqn)) { na_error(na, "Invalid SubNQN %.*s", (int)sizeof(data->subnqn), data->subnqn); nvmf_connect_invalid_parameters(cc, true, offsetof(struct nvmf_fabric_connect_data, subnqn)); goto error; } if (!nvmf_nqn_valid(data->hostnqn)) { na_error(na, "Invalid HostNQN %.*s", (int)sizeof(data->hostnqn), data->hostnqn); nvmf_connect_invalid_parameters(cc, true, offsetof(struct nvmf_fabric_connect_data, hostnqn)); goto error; } if (na->na_params.sq_flow_control || (cmd->cattr & NVMF_CONNECT_ATTR_DISABLE_SQ_FC) == 0) qp->nq_flow_control = true; else qp->nq_flow_control = false; qp->nq_sqhd = 0; qp->nq_kato = le32toh(cmd->kato); *ccp = cc; return (qp); error: if (rc != NULL) { nvmf_transmit_capsule(rc); nvmf_free_capsule(rc); } if (cc != NULL) nvmf_free_capsule(cc); if (qp != NULL) nvmf_free_qpair(qp); return (NULL); } int nvmf_finish_accept(const struct nvmf_capsule *cc, uint16_t cntlid) { struct nvmf_fabric_connect_rsp rsp; struct nvmf_qpair *qp = cc->nc_qpair; struct nvmf_capsule *rc; int error; nvmf_init_cqe(&rsp, cc, 0); if (qp->nq_flow_control) rsp.sqhd = htole16(qp->nq_sqhd); else rsp.sqhd = htole16(0xffff); rsp.status_code_specific.success.cntlid = htole16(cntlid); rc = nvmf_allocate_response(qp, &rsp); if (rc == NULL) return (ENOMEM); error = nvmf_transmit_capsule(rc); nvmf_free_capsule(rc); if (error == 0) qp->nq_cntlid = cntlid; return (error); } uint64_t nvmf_controller_cap(struct nvmf_qpair *qp) { const struct nvmf_association *na = qp->nq_association; return (_nvmf_controller_cap(na->na_params.max_io_qsize, NVMF_CC_EN_TIMEOUT)); } bool nvmf_validate_cc(struct nvmf_qpair *qp, uint64_t cap, uint32_t old_cc, uint32_t new_cc) { const struct nvmf_association *na = qp->nq_association; return (_nvmf_validate_cc(na->na_params.max_io_qsize, cap, old_cc, new_cc)); } void nvmf_init_discovery_controller_data(struct nvmf_qpair *qp, struct nvme_controller_data *cdata) { const struct nvmf_association *na = qp->nq_association; struct utsname utsname; char *cp; memset(cdata, 0, sizeof(*cdata)); /* * 5.2 Figure 37 states model name and serial are reserved, * but Linux includes them. Don't bother with serial, but * do set model name. */ uname(&utsname); nvmf_strpad(cdata->mn, utsname.sysname, sizeof(cdata->mn)); nvmf_strpad(cdata->fr, utsname.release, sizeof(cdata->fr)); cp = memchr(cdata->fr, '-', sizeof(cdata->fr)); if (cp != NULL) memset(cp, ' ', sizeof(cdata->fr) - (cp - (char *)cdata->fr)); cdata->ctrlr_id = htole16(qp->nq_cntlid); cdata->ver = htole32(NVME_REV(1, 4)); cdata->cntrltype = 2; cdata->lpa = NVMEF(NVME_CTRLR_DATA_LPA_EXT_DATA, 1); cdata->elpe = 0; cdata->maxcmd = htole16(na->na_params.max_admin_qsize); /* 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, NVMF_DISCOVERY_NQN, sizeof(cdata->subnqn)); } void nvmf_init_io_controller_data(struct nvmf_qpair *qp, const char *serial, const char *subnqn, int nn, uint32_t ioccsz, struct nvme_controller_data *cdata) { const struct nvmf_association *na = qp->nq_association; struct utsname utsname; uname(&utsname); memset(cdata, 0, sizeof(*cdata)); _nvmf_init_io_controller_data(qp->nq_cntlid, na->na_params.max_io_qsize, serial, utsname.sysname, utsname.release, subnqn, nn, ioccsz, sizeof(struct nvme_completion), cdata); } uint8_t nvmf_get_log_page_id(const struct nvme_command *cmd) { assert(cmd->opc == NVME_OPC_GET_LOG_PAGE); return (le32toh(cmd->cdw10) & 0xff); } uint64_t nvmf_get_log_page_length(const struct nvme_command *cmd) { uint32_t numd; assert(cmd->opc == NVME_OPC_GET_LOG_PAGE); numd = le32toh(cmd->cdw10) >> 16 | (le32toh(cmd->cdw11) & 0xffff) << 16; return ((numd + 1) * 4); } uint64_t nvmf_get_log_page_offset(const struct nvme_command *cmd) { assert(cmd->opc == NVME_OPC_GET_LOG_PAGE); return (le32toh(cmd->cdw12) | (uint64_t)le32toh(cmd->cdw13) << 32); } int nvmf_handoff_controller_qpair(struct nvmf_qpair *qp, const struct nvmf_fabric_connect_cmd *cmd, const struct nvmf_fabric_connect_data *data, struct nvmf_ioc_nv *nv) { nvlist_t *nvl, *nvl_qp; int error; error = nvmf_kernel_handoff_params(qp, &nvl_qp); if (error) return (error); nvl = nvlist_create(0); nvlist_add_number(nvl, "trtype", qp->nq_association->na_trtype); nvlist_move_nvlist(nvl, "params", nvl_qp); nvlist_add_binary(nvl, "cmd", cmd, sizeof(*cmd)); nvlist_add_binary(nvl, "data", data, sizeof(*data)); error = nvmf_pack_ioc_nvlist(nv, nvl); nvlist_destroy(nvl); return (error); } diff --git a/sys/dev/nvmf/controller/nvmft_subr.c b/sys/dev/nvmf/controller/nvmft_subr.c index bb2bc0988e81..245971813854 100644 --- a/sys/dev/nvmf/controller/nvmft_subr.c +++ b/sys/dev/nvmf/controller/nvmft_subr.c @@ -1,259 +1,219 @@ /*- * 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; }