#include <sys/param.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/domainset.h>
#include <sys/proc.h>
#include <sys/sbuf.h>
#include <dev/pci/pcivar.h>
#include "nvme_private.h"
typedef enum error_print { ERROR_PRINT_NONE, ERROR_PRINT_NO_RETRY, ERROR_PRINT_ALL } error_print_t;
#define DO_NOT_RETRY 1
static void _nvme_qpair_submit_request(struct nvme_qpair *qpair,
struct nvme_request *req);
static void nvme_qpair_destroy(struct nvme_qpair *qpair);
static const char *
get_opcode_string(bool admin, uint8_t opc, char *buf, size_t len)
{
struct sbuf sb;
sbuf_new(&sb, buf, len, SBUF_FIXEDLEN);
nvme_opcode_sbuf(admin, opc, &sb);
if (sbuf_finish(&sb) != 0)
return ("");
return (buf);
}
static void
nvme_admin_qpair_print_command(struct nvme_qpair *qpair,
struct nvme_command *cmd)
{
char buf[64];
nvme_printf(qpair->ctrlr, "%s sqid:%d cid:%d nsid:%x "
"cdw10:%08x cdw11:%08x\n",
get_opcode_string(true, cmd->opc, buf, sizeof(buf)), qpair->id,
cmd->cid, le32toh(cmd->nsid), le32toh(cmd->cdw10),
le32toh(cmd->cdw11));
}
static void
nvme_io_qpair_print_command(struct nvme_qpair *qpair,
struct nvme_command *cmd)
{
char buf[64];
switch (cmd->opc) {
case NVME_OPC_WRITE:
case NVME_OPC_READ:
case NVME_OPC_WRITE_UNCORRECTABLE:
case NVME_OPC_COMPARE:
case NVME_OPC_WRITE_ZEROES:
case NVME_OPC_VERIFY:
nvme_printf(qpair->ctrlr, "%s sqid:%d cid:%d nsid:%d "
"lba:%llu len:%d\n",
get_opcode_string(false, cmd->opc, buf, sizeof(buf)),
qpair->id, cmd->cid, le32toh(cmd->nsid),
((unsigned long long)le32toh(cmd->cdw11) << 32) + le32toh(cmd->cdw10),
(le32toh(cmd->cdw12) & 0xFFFF) + 1);
break;
default:
nvme_printf(qpair->ctrlr, "%s sqid:%d cid:%d nsid:%d\n",
get_opcode_string(false, cmd->opc, buf, sizeof(buf)),
qpair->id, cmd->cid, le32toh(cmd->nsid));
break;
}
}
void
nvme_qpair_print_command(struct nvme_qpair *qpair, struct nvme_command *cmd)
{
if (qpair->id == 0)
nvme_admin_qpair_print_command(qpair, cmd);
else
nvme_io_qpair_print_command(qpair, cmd);
if (nvme_verbose_cmd_dump) {
nvme_printf(qpair->ctrlr,
"nsid:%#x rsvd2:%#x rsvd3:%#x mptr:%#jx prp1:%#jx prp2:%#jx\n",
cmd->nsid, cmd->rsvd2, cmd->rsvd3, (uintmax_t)cmd->mptr,
(uintmax_t)cmd->prp1, (uintmax_t)cmd->prp2);
nvme_printf(qpair->ctrlr,
"cdw10: %#x cdw11:%#x cdw12:%#x cdw13:%#x cdw14:%#x cdw15:%#x\n",
cmd->cdw10, cmd->cdw11, cmd->cdw12, cmd->cdw13, cmd->cdw14,
cmd->cdw15);
}
}
static const char *
get_status_string(const struct nvme_completion *cpl, char *buf, size_t len)
{
struct sbuf sb;
sbuf_new(&sb, buf, len, SBUF_FIXEDLEN);
nvme_sc_sbuf(cpl, &sb);
if (sbuf_finish(&sb) != 0)
return ("");
return (buf);
}
void
nvme_qpair_print_completion(struct nvme_qpair *qpair,
struct nvme_completion *cpl)
{
char buf[64];
uint8_t crd, m, dnr, p;
crd = NVME_STATUS_GET_CRD(cpl->status);
m = NVME_STATUS_GET_M(cpl->status);
dnr = NVME_STATUS_GET_DNR(cpl->status);
p = NVME_STATUS_GET_P(cpl->status);
nvme_printf(qpair->ctrlr, "%s crd:%x m:%x dnr:%x p:%d "
"sqid:%d cid:%d cdw0:%x\n",
get_status_string(cpl, buf, sizeof(buf)), crd, m, dnr, p,
cpl->sqid, cpl->cid, cpl->cdw0);
}
static bool
nvme_completion_is_retry(const struct nvme_completion *cpl)
{
uint8_t sct, sc, dnr;
sct = NVME_STATUS_GET_SCT(cpl->status);
sc = NVME_STATUS_GET_SC(cpl->status);
dnr = NVME_STATUS_GET_DNR(cpl->status);
switch (sct) {
case NVME_SCT_GENERIC:
switch (sc) {
case NVME_SC_ABORTED_BY_REQUEST:
case NVME_SC_NAMESPACE_NOT_READY:
if (dnr)
return (0);
else
return (1);
case NVME_SC_INVALID_OPCODE:
case NVME_SC_INVALID_FIELD:
case NVME_SC_COMMAND_ID_CONFLICT:
case NVME_SC_DATA_TRANSFER_ERROR:
case NVME_SC_ABORTED_POWER_LOSS:
case NVME_SC_INTERNAL_DEVICE_ERROR:
case NVME_SC_ABORTED_SQ_DELETION:
case NVME_SC_ABORTED_FAILED_FUSED:
case NVME_SC_ABORTED_MISSING_FUSED:
case NVME_SC_INVALID_NAMESPACE_OR_FORMAT:
case NVME_SC_COMMAND_SEQUENCE_ERROR:
case NVME_SC_LBA_OUT_OF_RANGE:
case NVME_SC_CAPACITY_EXCEEDED:
default:
return (0);
}
case NVME_SCT_COMMAND_SPECIFIC:
case NVME_SCT_MEDIA_ERROR:
return (0);
case NVME_SCT_PATH_RELATED:
switch (sc) {
case NVME_SC_INTERNAL_PATH_ERROR:
if (dnr)
return (0);
else
return (1);
default:
return (0);
}
case NVME_SCT_VENDOR_SPECIFIC:
default:
return (0);
}
}
static void
nvme_qpair_complete_tracker(struct nvme_tracker *tr,
struct nvme_completion *cpl, error_print_t print_on_error)
{
struct nvme_qpair *qpair = tr->qpair;
struct nvme_request *req;
bool retry, error, retriable;
mtx_assert(&qpair->lock, MA_NOTOWNED);
req = tr->req;
error = nvme_completion_is_error(cpl);
retriable = nvme_completion_is_retry(cpl);
retry = error && retriable && req->retries < nvme_retry_count;
if (retry)
qpair->num_retries++;
if (error && req->retries >= nvme_retry_count && retriable)
qpair->num_failures++;
if (error && (print_on_error == ERROR_PRINT_ALL ||
(!retry && print_on_error == ERROR_PRINT_NO_RETRY))) {
nvme_qpair_print_command(qpair, &req->cmd);
nvme_qpair_print_completion(qpair, cpl);
}
qpair->act_tr[cpl->cid - qpair->cid_base] = NULL;
KASSERT(cpl->cid == req->cmd.cid, ("cpl cid does not match cmd cid\n"));
if (!retry) {
if (req->payload_valid) {
bus_dmamap_sync(qpair->dma_tag_payload,
tr->payload_dma_map,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
}
if (req->cb_fn)
req->cb_fn(req->cb_arg, cpl);
}
mtx_lock(&qpair->lock);
if (retry) {
req->retries++;
nvme_qpair_submit_tracker(qpair, tr);
} else {
if (req->payload_valid) {
bus_dmamap_unload(qpair->dma_tag_payload,
tr->payload_dma_map);
}
nvme_free_request(req);
tr->req = NULL;
TAILQ_REMOVE(&qpair->outstanding_tr, tr, tailq);
TAILQ_INSERT_HEAD(&qpair->free_tr, tr, tailq);
if (!STAILQ_EMPTY(&qpair->queued_req) &&
!qpair->ctrlr->is_resetting) {
req = STAILQ_FIRST(&qpair->queued_req);
STAILQ_REMOVE_HEAD(&qpair->queued_req, stailq);
_nvme_qpair_submit_request(qpair, req);
}
}
mtx_unlock(&qpair->lock);
}
static uint32_t
nvme_qpair_make_status(uint32_t sct, uint32_t sc, uint32_t dnr)
{
uint32_t status = 0;
status |= NVMEF(NVME_STATUS_SCT, sct);
status |= NVMEF(NVME_STATUS_SC, sc);
status |= NVMEF(NVME_STATUS_DNR, dnr);
return (status);
}
static void
nvme_qpair_manual_complete_tracker(
struct nvme_tracker *tr, uint32_t sct, uint32_t sc, uint32_t dnr,
error_print_t print_on_error)
{
struct nvme_completion cpl;
struct nvme_qpair * qpair = tr->qpair;
mtx_assert(&qpair->lock, MA_NOTOWNED);
memset(&cpl, 0, sizeof(cpl));
cpl.sqid = qpair->id;
cpl.cid = qpair->cid_base + tr->cid;
cpl.status = nvme_qpair_make_status(sct, sc, dnr);
nvme_qpair_complete_tracker(tr, &cpl, print_on_error);
}
static void
nvme_qpair_manual_complete_request(struct nvme_qpair *qpair,
struct nvme_request *req, uint32_t sct, uint32_t sc, uint32_t dnr,
error_print_t print_on_error)
{
struct nvme_completion cpl;
bool error;
memset(&cpl, 0, sizeof(cpl));
cpl.sqid = qpair->id;
cpl.status = nvme_qpair_make_status(sct, sc, dnr);
error = nvme_completion_is_error(&cpl);
if (error && print_on_error == ERROR_PRINT_ALL) {
nvme_qpair_print_command(qpair, &req->cmd);
nvme_qpair_print_completion(qpair, &cpl);
}
if (req->cb_fn)
req->cb_fn(req->cb_arg, &cpl);
nvme_free_request(req);
}
static bool
_nvme_qpair_process_completions(struct nvme_qpair *qpair)
{
struct nvme_tracker *tr;
struct nvme_completion cpl;
bool done = false;
bool in_panic = dumping || SCHEDULER_STOPPED();
mtx_assert(&qpair->recovery, MA_OWNED);
if (qpair->recovery_state != RECOVERY_NONE) {
qpair->num_ignored++;
return (false);
}
KASSERT(!(qpair->num_intr_handler_calls == 0 && qpair->phase == 0),
("%s: Phase wrong for first interrupt call.",
device_get_nameunit(qpair->ctrlr->dev)));
qpair->num_intr_handler_calls++;
bus_dmamap_sync(qpair->dma_tag, qpair->queuemem_map,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
if (__predict_false(in_panic)) {
if (qpair->cq_head == qpair->num_entries) {
qpair->cq_head = 0;
qpair->phase = !qpair->phase;
} else if (qpair->cq_head == 0) {
cpl = qpair->cpl[qpair->num_entries - 1];
nvme_completion_swapbytes(&cpl);
qpair->phase = !NVME_STATUS_GET_P(cpl.status);
}
}
while (1) {
uint16_t status;
status = le16toh(qpair->cpl[qpair->cq_head].status);
if (NVME_STATUS_GET_P(status) != qpair->phase)
break;
bus_dmamap_sync(qpair->dma_tag, qpair->queuemem_map,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
cpl = qpair->cpl[qpair->cq_head];
nvme_completion_swapbytes(&cpl);
KASSERT(
NVME_STATUS_GET_P(status) == NVME_STATUS_GET_P(cpl.status),
("Phase unexpectedly inconsistent"));
if (cpl.cid >= qpair->cid_base &&
cpl.cid < qpair->cid_base + qpair->num_trackers)
tr = qpair->act_tr[cpl.cid - qpair->cid_base];
else
tr = NULL;
done = true;
if (tr != NULL) {
nvme_qpair_complete_tracker(tr, &cpl, ERROR_PRINT_ALL);
qpair->sq_head = cpl.sqhd;
} else if (!in_panic) {
nvme_printf(qpair->ctrlr,
"cpl (cid = %u) does not map to outstanding cmd\n",
cpl.cid);
nvme_qpair_print_completion(qpair,
&qpair->cpl[qpair->cq_head]);
KASSERT(0, ("received completion for unknown cmd"));
}
if (++qpair->cq_head == qpair->num_entries) {
atomic_store_rel_int(&qpair->cq_head, 0);
qpair->phase = !qpair->phase;
}
}
if (done) {
bus_write_4(qpair->ctrlr->resource, qpair->cq_hdbl_off,
qpair->cq_head);
}
return (done);
}
bool
nvme_qpair_process_completions(struct nvme_qpair *qpair)
{
bool done = false;
mtx_lock(&qpair->recovery);
if (__predict_true(qpair->recovery_state == RECOVERY_NONE))
done = _nvme_qpair_process_completions(qpair);
else
qpair->num_recovery_nolock++;
mtx_unlock(&qpair->recovery);
return (done);
}
static void
nvme_qpair_msi_handler(void *arg)
{
struct nvme_qpair *qpair = arg;
nvme_qpair_process_completions(qpair);
}
int
nvme_qpair_construct(struct nvme_qpair *qpair,
uint32_t num_entries, uint32_t num_trackers,
struct nvme_controller *ctrlr)
{
struct nvme_tracker *tr;
size_t cmdsz, cplsz, prpsz, allocsz, prpmemsz;
uint64_t queuemem_phys, prpmem_phys, list_phys;
uint8_t *queuemem, *prpmem, *prp_list;
int i, err;
qpair->vector = ctrlr->msi_count > 1 ? qpair->id : 0;
qpair->num_entries = num_entries;
qpair->num_trackers = num_trackers;
qpair->ctrlr = ctrlr;
if (qpair->id != 0) {
uint8_t sqes_max = (ctrlr->cdata.sqes >> 4) & 0xf;
qpair->sqe_shift = (sqes_max > 6) ? (sqes_max - 6) : 0;
} else {
qpair->sqe_shift = 0;
}
if ((ctrlr->quirks & QUIRK_APPLE_SHARED_CID_SPACE) && qpair->id != 0)
qpair->cid_base = ctrlr->adminq.num_trackers;
else
qpair->cid_base = 0;
mtx_init(&qpair->lock, "nvme qpair lock", NULL, MTX_DEF);
mtx_init(&qpair->recovery, "nvme qpair recovery", NULL, MTX_DEF);
callout_init_mtx(&qpair->timer, &qpair->recovery, 0);
qpair->timer_armed = false;
qpair->recovery_state = RECOVERY_WAITING;
err = bus_dma_tag_create(bus_get_dma_tag(ctrlr->dev),
4, ctrlr->page_size, BUS_SPACE_MAXADDR,
BUS_SPACE_MAXADDR, NULL, NULL, ctrlr->max_xfer_size,
howmany(ctrlr->max_xfer_size, ctrlr->page_size) + 1,
ctrlr->page_size, 0,
NULL, NULL, &qpair->dma_tag_payload);
if (err != 0) {
nvme_printf(ctrlr, "payload tag create failed %d\n", err);
goto out;
}
cmdsz = qpair->num_entries * sizeof(struct nvme_command) << qpair->sqe_shift;
cmdsz = roundup2(cmdsz, ctrlr->page_size);
cplsz = qpair->num_entries * sizeof(struct nvme_completion);
cplsz = roundup2(cplsz, ctrlr->page_size);
prpsz = sizeof(uint64_t) *
howmany(ctrlr->max_xfer_size, ctrlr->page_size);
prpmemsz = qpair->num_trackers * prpsz;
allocsz = cmdsz + cplsz + prpmemsz;
err = bus_dma_tag_create(bus_get_dma_tag(ctrlr->dev),
ctrlr->page_size, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL,
allocsz, 1, allocsz, 0, NULL, NULL, &qpair->dma_tag);
if (err != 0) {
nvme_printf(ctrlr, "tag create failed %d\n", err);
goto out;
}
bus_dma_tag_set_domain(qpair->dma_tag, qpair->domain);
if (bus_dmamem_alloc(qpair->dma_tag, (void **)&queuemem,
BUS_DMA_COHERENT | BUS_DMA_NOWAIT, &qpair->queuemem_map)) {
nvme_printf(ctrlr, "failed to alloc qpair memory\n");
goto out;
}
if (bus_dmamap_load(qpair->dma_tag, qpair->queuemem_map,
queuemem, allocsz, nvme_single_map, &queuemem_phys, 0) != 0) {
nvme_printf(ctrlr, "failed to load qpair memory\n");
bus_dmamem_free(qpair->dma_tag, qpair->cmd,
qpair->queuemem_map);
goto out;
}
qpair->num_cmds = 0;
qpair->num_intr_handler_calls = 0;
qpair->num_retries = 0;
qpair->num_failures = 0;
qpair->num_ignored = 0;
qpair->cmd = (struct nvme_command *)queuemem;
qpair->cpl = (struct nvme_completion *)(queuemem + cmdsz);
prpmem = (uint8_t *)(queuemem + cmdsz + cplsz);
qpair->cmd_bus_addr = queuemem_phys;
qpair->cpl_bus_addr = queuemem_phys + cmdsz;
prpmem_phys = queuemem_phys + cmdsz + cplsz;
qpair->sq_tdbl_off = nvme_mmio_offsetof(doorbell[0]) +
(qpair->id << (ctrlr->dstrd + 1));
qpair->cq_hdbl_off = nvme_mmio_offsetof(doorbell[0]) +
(qpair->id << (ctrlr->dstrd + 1)) + (1 << ctrlr->dstrd);
TAILQ_INIT(&qpair->free_tr);
TAILQ_INIT(&qpair->outstanding_tr);
STAILQ_INIT(&qpair->queued_req);
list_phys = prpmem_phys;
prp_list = prpmem;
for (i = 0; i < qpair->num_trackers; i++) {
if (list_phys + prpsz > prpmem_phys + prpmemsz) {
qpair->num_trackers = i;
break;
}
if (trunc_page(list_phys) !=
trunc_page(list_phys + prpsz - 1)) {
list_phys = roundup2(list_phys, ctrlr->page_size);
prp_list =
(uint8_t *)roundup2((uintptr_t)prp_list, ctrlr->page_size);
}
tr = malloc_domainset(sizeof(*tr), M_NVME,
DOMAINSET_PREF(qpair->domain), M_ZERO | M_WAITOK);
bus_dmamap_create(qpair->dma_tag_payload, 0,
&tr->payload_dma_map);
tr->cid = i;
tr->qpair = qpair;
tr->prp = (uint64_t *)prp_list;
tr->prp_bus_addr = list_phys;
TAILQ_INSERT_HEAD(&qpair->free_tr, tr, tailq);
list_phys += prpsz;
prp_list += prpsz;
}
if (qpair->num_trackers == 0) {
nvme_printf(ctrlr, "failed to allocate enough trackers\n");
goto out;
}
qpair->act_tr = malloc_domainset(sizeof(struct nvme_tracker *) *
qpair->num_entries, M_NVME, DOMAINSET_PREF(qpair->domain),
M_ZERO | M_WAITOK);
if (ctrlr->msi_count > 1) {
qpair->rid = qpair->vector + 1;
qpair->res = bus_alloc_resource_any(ctrlr->dev, SYS_RES_IRQ,
&qpair->rid, RF_ACTIVE);
if (qpair->res == NULL) {
nvme_printf(ctrlr, "unable to allocate MSI\n");
goto out;
}
if (bus_setup_intr(ctrlr->dev, qpair->res,
INTR_TYPE_MISC | INTR_MPSAFE, NULL,
nvme_qpair_msi_handler, qpair, &qpair->tag) != 0) {
nvme_printf(ctrlr, "unable to setup MSI\n");
goto out;
}
if (qpair->id == 0) {
bus_describe_intr(ctrlr->dev, qpair->res, qpair->tag,
"admin");
} else {
bus_describe_intr(ctrlr->dev, qpair->res, qpair->tag,
"io%d", qpair->id - 1);
}
}
return (0);
out:
nvme_qpair_destroy(qpair);
return (ENOMEM);
}
static void
nvme_qpair_destroy(struct nvme_qpair *qpair)
{
struct nvme_tracker *tr;
mtx_lock(&qpair->recovery);
qpair->timer_armed = false;
mtx_unlock(&qpair->recovery);
callout_drain(&qpair->timer);
if (qpair->tag) {
bus_teardown_intr(qpair->ctrlr->dev, qpair->res, qpair->tag);
qpair->tag = NULL;
}
if (qpair->act_tr) {
free(qpair->act_tr, M_NVME);
qpair->act_tr = NULL;
}
while (!TAILQ_EMPTY(&qpair->free_tr)) {
tr = TAILQ_FIRST(&qpair->free_tr);
TAILQ_REMOVE(&qpair->free_tr, tr, tailq);
bus_dmamap_destroy(qpair->dma_tag_payload,
tr->payload_dma_map);
free(tr, M_NVME);
}
if (qpair->cmd != NULL) {
bus_dmamap_unload(qpair->dma_tag, qpair->queuemem_map);
bus_dmamem_free(qpair->dma_tag, qpair->cmd,
qpair->queuemem_map);
qpair->cmd = NULL;
}
if (qpair->dma_tag) {
bus_dma_tag_destroy(qpair->dma_tag);
qpair->dma_tag = NULL;
}
if (qpair->dma_tag_payload) {
bus_dma_tag_destroy(qpair->dma_tag_payload);
qpair->dma_tag_payload = NULL;
}
if (mtx_initialized(&qpair->lock))
mtx_destroy(&qpair->lock);
if (mtx_initialized(&qpair->recovery))
mtx_destroy(&qpair->recovery);
if (qpair->res) {
bus_release_resource(qpair->ctrlr->dev, SYS_RES_IRQ,
rman_get_rid(qpair->res), qpair->res);
qpair->res = NULL;
}
}
static void
nvme_admin_qpair_abort_aers(struct nvme_qpair *qpair)
{
struct nvme_tracker *tr;
mtx_assert(&qpair->lock, MA_NOTOWNED);
mtx_lock(&qpair->lock);
tr = TAILQ_FIRST(&qpair->outstanding_tr);
while (tr != NULL) {
if (tr->req->cmd.opc != NVME_OPC_ASYNC_EVENT_REQUEST) {
tr = TAILQ_NEXT(tr, tailq);
continue;
}
mtx_unlock(&qpair->lock);
nvme_qpair_manual_complete_tracker(tr,
NVME_SCT_GENERIC, NVME_SC_ABORTED_SQ_DELETION, 0,
ERROR_PRINT_NONE);
mtx_lock(&qpair->lock);
tr = TAILQ_FIRST(&qpair->outstanding_tr);
}
mtx_unlock(&qpair->lock);
}
void
nvme_admin_qpair_destroy(struct nvme_qpair *qpair)
{
mtx_assert(&qpair->lock, MA_NOTOWNED);
nvme_admin_qpair_abort_aers(qpair);
nvme_qpair_destroy(qpair);
}
void
nvme_io_qpair_destroy(struct nvme_qpair *qpair)
{
nvme_qpair_destroy(qpair);
}
static void
nvme_abort_complete(void *arg, const struct nvme_completion *status)
{
struct nvme_tracker *tr = arg;
mtx_lock(&tr->qpair->lock);
if ((status->cdw0 & 1) == 1 && tr->qpair->act_tr[tr->cid] != NULL) {
nvme_printf(tr->qpair->ctrlr,
"abort command failed, aborting command manually\n");
nvme_qpair_manual_complete_tracker(tr,
NVME_SCT_GENERIC, NVME_SC_ABORTED_BY_REQUEST, 0, ERROR_PRINT_ALL);
}
mtx_unlock(&tr->qpair->lock);
}
static void
nvme_qpair_timeout(void *arg)
{
struct nvme_qpair *qpair = arg;
struct nvme_controller *ctrlr = qpair->ctrlr;
struct nvme_tracker *tr;
sbintime_t now;
bool idle = true;
bool is_admin = qpair == &ctrlr->adminq;
bool fast;
uint32_t csts;
uint8_t cfs;
mtx_assert(&qpair->recovery, MA_OWNED);
if (is_admin ? qpair->ctrlr->is_failed_admin : qpair->ctrlr->is_failed) {
nvme_printf(qpair->ctrlr,
"%sFailed controller, stopping watchdog timeout.\n",
is_admin ? "Complete " : "");
qpair->timer_armed = false;
return;
}
if (!qpair->timer_armed) {
nvme_printf(qpair->ctrlr,
"Timeout fired during nvme_qpair_destroy\n");
return;
}
switch (qpair->recovery_state) {
case RECOVERY_NONE:
csts = nvme_mmio_read_4(ctrlr, csts);
cfs = NVMEV(NVME_CSTS_REG_CFS, csts);
if (csts == NVME_GONE || cfs == 1) {
do_reset:
nvme_printf(ctrlr, "Resetting controller due to a timeout%s.\n",
(csts == 0xffffffff) ? " and possible hot unplug" :
(cfs ? " and fatal error status" : ""));
qpair->recovery_state = RECOVERY_WAITING;
nvme_ctrlr_reset(ctrlr);
idle = false;
break;
}
fast = false;
mtx_lock(&qpair->lock);
now = getsbinuptime();
TAILQ_FOREACH(tr, &qpair->outstanding_tr, tailq) {
if (tr->deadline == SBT_MAX)
continue;
idle = false;
if (now <= tr->deadline)
fast = true;
break;
}
mtx_unlock(&qpair->lock);
if (idle || fast)
break;
if (_nvme_qpair_process_completions(qpair) && !ctrlr->isr_warned) {
nvme_printf(ctrlr, "System interrupt issues?\n");
ctrlr->isr_warned = true;
}
mtx_lock(&qpair->lock);
idle = true;
TAILQ_FOREACH(tr, &qpair->outstanding_tr, tailq) {
if (tr->deadline == SBT_MAX)
continue;
idle = false;
if (now <= tr->deadline)
break;
if (ctrlr->enable_aborts &&
tr->req->cb_fn != nvme_abort_complete) {
nvme_ctrlr_cmd_abort(ctrlr,
qpair->cid_base + tr->cid, qpair->id,
nvme_abort_complete, tr);
} else {
mtx_unlock(&qpair->lock);
goto do_reset;
}
}
mtx_unlock(&qpair->lock);
break;
case RECOVERY_WAITING:
if (!device_is_suspended(ctrlr->dev))
nvme_printf(ctrlr, "Waiting for reset to complete\n");
idle = false;
break;
}
if (!idle) {
callout_schedule_sbt(&qpair->timer, SBT_1S / 2, SBT_1S / 2, 0);
} else {
qpair->timer_armed = false;
}
}
void
nvme_qpair_submit_tracker(struct nvme_qpair *qpair, struct nvme_tracker *tr)
{
struct nvme_request *req;
struct nvme_controller *ctrlr;
int timeout;
mtx_assert(&qpair->lock, MA_OWNED);
req = tr->req;
req->cmd.cid = qpair->cid_base + tr->cid;
qpair->act_tr[tr->cid] = tr;
ctrlr = qpair->ctrlr;
if (req->timeout) {
if (req->cb_fn == nvme_completion_poll_cb)
timeout = 1;
else if (qpair->id == 0)
timeout = ctrlr->admin_timeout_period;
else
timeout = ctrlr->timeout_period;
tr->deadline = getsbinuptime() + timeout * SBT_1S;
if (!qpair->timer_armed) {
qpair->timer_armed = true;
callout_reset_sbt_on(&qpair->timer, SBT_1S / 2, SBT_1S / 2,
nvme_qpair_timeout, qpair, qpair->cpu, 0);
}
} else
tr->deadline = SBT_MAX;
memcpy(NVME_SQE(qpair, qpair->sq_tail), &req->cmd, sizeof(req->cmd));
if (++qpair->sq_tail == qpair->num_entries)
qpair->sq_tail = 0;
bus_dmamap_sync(qpair->dma_tag, qpair->queuemem_map,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
bus_write_4(ctrlr->resource, qpair->sq_tdbl_off, qpair->sq_tail);
qpair->num_cmds++;
}
static void
nvme_payload_map(void *arg, bus_dma_segment_t *seg, int nseg, int error)
{
struct nvme_tracker *tr = arg;
uint32_t cur_nseg;
if (error != 0) {
nvme_printf(tr->qpair->ctrlr,
"nvme_payload_map err %d\n", error);
return;
}
tr->req->cmd.prp1 = htole64(seg[0].ds_addr);
if (nseg == 2) {
tr->req->cmd.prp2 = htole64(seg[1].ds_addr);
} else if (nseg > 2) {
cur_nseg = 1;
tr->req->cmd.prp2 = htole64((uint64_t)tr->prp_bus_addr);
while (cur_nseg < nseg) {
tr->prp[cur_nseg-1] =
htole64((uint64_t)seg[cur_nseg].ds_addr);
cur_nseg++;
}
} else {
tr->req->cmd.prp2 = 0;
}
bus_dmamap_sync(tr->qpair->dma_tag_payload, tr->payload_dma_map,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
nvme_qpair_submit_tracker(tr->qpair, tr);
}
static void
_nvme_qpair_submit_request(struct nvme_qpair *qpair, struct nvme_request *req)
{
struct nvme_tracker *tr;
int err = 0;
bool is_admin = qpair == &qpair->ctrlr->adminq;
mtx_assert(&qpair->lock, MA_OWNED);
tr = TAILQ_FIRST(&qpair->free_tr);
req->qpair = qpair;
if (is_admin ? qpair->ctrlr->is_failed_admin : qpair->ctrlr->is_failed) {
nvme_qpair_manual_complete_request(qpair, req,
NVME_SCT_GENERIC, NVME_SC_ABORTED_BY_REQUEST, 1,
ERROR_PRINT_NONE);
return;
}
if (tr == NULL || qpair->recovery_state != RECOVERY_NONE) {
STAILQ_INSERT_TAIL(&qpair->queued_req, req, stailq);
return;
}
TAILQ_REMOVE(&qpair->free_tr, tr, tailq);
TAILQ_INSERT_TAIL(&qpair->outstanding_tr, tr, tailq);
tr->deadline = SBT_MAX;
tr->req = req;
if (!req->payload_valid) {
nvme_qpair_submit_tracker(tr->qpair, tr);
return;
}
err = bus_dmamap_load_mem(tr->qpair->dma_tag_payload,
tr->payload_dma_map, &req->payload, nvme_payload_map, tr, 0);
if (err != 0) {
nvme_printf(qpair->ctrlr,
"bus_dmamap_load_mem returned 0x%x!\n", err);
mtx_unlock(&qpair->lock);
nvme_qpair_manual_complete_tracker(tr, NVME_SCT_GENERIC,
NVME_SC_DATA_TRANSFER_ERROR, DO_NOT_RETRY, ERROR_PRINT_ALL);
mtx_lock(&qpair->lock);
}
}
void
nvme_qpair_submit_request(struct nvme_qpair *qpair, struct nvme_request *req)
{
mtx_lock(&qpair->lock);
_nvme_qpair_submit_request(qpair, req);
mtx_unlock(&qpair->lock);
}
static void
nvme_qpair_enable(struct nvme_qpair *qpair)
{
bool is_admin __diagused = qpair == &qpair->ctrlr->adminq;
if (mtx_initialized(&qpair->recovery))
mtx_assert(&qpair->recovery, MA_OWNED);
if (mtx_initialized(&qpair->lock))
mtx_assert(&qpair->lock, MA_OWNED);
KASSERT(!(is_admin ? qpair->ctrlr->is_failed_admin : qpair->ctrlr->is_failed),
("Enabling a failed qpair\n"));
qpair->recovery_state = RECOVERY_NONE;
}
void
nvme_qpair_reset(struct nvme_qpair *qpair)
{
qpair->sq_head = qpair->sq_tail = qpair->cq_head = 0;
qpair->phase = 1;
memset(qpair->cmd, 0,
qpair->num_entries * sizeof(struct nvme_command) << qpair->sqe_shift);
memset(qpair->cpl, 0,
qpair->num_entries * sizeof(struct nvme_completion));
}
void
nvme_admin_qpair_enable(struct nvme_qpair *qpair)
{
struct nvme_tracker *tr;
struct nvme_tracker *tr_temp;
bool rpt;
rpt = !TAILQ_EMPTY(&qpair->outstanding_tr);
if (rpt)
nvme_printf(qpair->ctrlr,
"aborting outstanding admin command\n");
TAILQ_FOREACH_SAFE(tr, &qpair->outstanding_tr, tailq, tr_temp) {
nvme_qpair_manual_complete_tracker(tr, NVME_SCT_GENERIC,
NVME_SC_ABORTED_BY_REQUEST, DO_NOT_RETRY, ERROR_PRINT_ALL);
}
if (rpt)
nvme_printf(qpair->ctrlr,
"done aborting outstanding admin\n");
mtx_lock(&qpair->recovery);
mtx_lock(&qpair->lock);
nvme_qpair_enable(qpair);
mtx_unlock(&qpair->lock);
mtx_unlock(&qpair->recovery);
}
void
nvme_io_qpair_enable(struct nvme_qpair *qpair)
{
STAILQ_HEAD(, nvme_request) temp;
struct nvme_tracker *tr;
struct nvme_tracker *tr_temp;
struct nvme_request *req;
bool report;
report = !TAILQ_EMPTY(&qpair->outstanding_tr);
if (report)
nvme_printf(qpair->ctrlr, "aborting outstanding i/o\n");
TAILQ_FOREACH_SAFE(tr, &qpair->outstanding_tr, tailq, tr_temp) {
nvme_qpair_manual_complete_tracker(tr, NVME_SCT_GENERIC,
NVME_SC_ABORTED_BY_REQUEST, 0, ERROR_PRINT_NO_RETRY);
}
if (report)
nvme_printf(qpair->ctrlr, "done aborting outstanding i/o\n");
mtx_lock(&qpair->recovery);
mtx_lock(&qpair->lock);
nvme_qpair_enable(qpair);
STAILQ_INIT(&temp);
STAILQ_SWAP(&qpair->queued_req, &temp, nvme_request);
report = !STAILQ_EMPTY(&temp);
if (report)
nvme_printf(qpair->ctrlr, "resubmitting queued i/o\n");
while (!STAILQ_EMPTY(&temp)) {
req = STAILQ_FIRST(&temp);
STAILQ_REMOVE_HEAD(&temp, stailq);
nvme_qpair_print_command(qpair, &req->cmd);
_nvme_qpair_submit_request(qpair, req);
}
if (report)
nvme_printf(qpair->ctrlr, "done resubmitting i/o\n");
mtx_unlock(&qpair->lock);
mtx_unlock(&qpair->recovery);
}
static void
nvme_qpair_disable(struct nvme_qpair *qpair)
{
struct nvme_tracker *tr, *tr_temp;
if (mtx_initialized(&qpair->recovery))
mtx_assert(&qpair->recovery, MA_OWNED);
if (mtx_initialized(&qpair->lock))
mtx_assert(&qpair->lock, MA_OWNED);
qpair->recovery_state = RECOVERY_WAITING;
TAILQ_FOREACH_SAFE(tr, &qpair->outstanding_tr, tailq, tr_temp) {
tr->deadline = SBT_MAX;
}
}
void
nvme_admin_qpair_disable(struct nvme_qpair *qpair)
{
mtx_lock(&qpair->recovery);
mtx_lock(&qpair->lock);
nvme_qpair_disable(qpair);
mtx_unlock(&qpair->lock);
nvme_admin_qpair_abort_aers(qpair);
mtx_unlock(&qpair->recovery);
}
void
nvme_io_qpair_disable(struct nvme_qpair *qpair)
{
mtx_lock(&qpair->recovery);
mtx_lock(&qpair->lock);
nvme_qpair_disable(qpair);
mtx_unlock(&qpair->lock);
mtx_unlock(&qpair->recovery);
}
void
nvme_qpair_fail(struct nvme_qpair *qpair)
{
struct nvme_tracker *tr;
struct nvme_request *req;
if (!mtx_initialized(&qpair->lock))
return;
mtx_lock(&qpair->lock);
if (!STAILQ_EMPTY(&qpair->queued_req)) {
nvme_printf(qpair->ctrlr, "failing queued i/o\n");
}
while (!STAILQ_EMPTY(&qpair->queued_req)) {
req = STAILQ_FIRST(&qpair->queued_req);
STAILQ_REMOVE_HEAD(&qpair->queued_req, stailq);
mtx_unlock(&qpair->lock);
nvme_qpair_manual_complete_request(qpair, req, NVME_SCT_GENERIC,
NVME_SC_ABORTED_BY_REQUEST, 1, ERROR_PRINT_ALL);
mtx_lock(&qpair->lock);
}
if (!TAILQ_EMPTY(&qpair->outstanding_tr)) {
nvme_printf(qpair->ctrlr, "failing outstanding i/o\n");
}
while (!TAILQ_EMPTY(&qpair->outstanding_tr)) {
tr = TAILQ_FIRST(&qpair->outstanding_tr);
mtx_unlock(&qpair->lock);
nvme_qpair_manual_complete_tracker(tr, NVME_SCT_GENERIC,
NVME_SC_ABORTED_BY_REQUEST, DO_NOT_RETRY, ERROR_PRINT_ALL);
mtx_lock(&qpair->lock);
}
mtx_unlock(&qpair->lock);
}