#include "nvme.h"
static void nvme_admin_thread(void *arg);
static int nvme_admin_state_identify_ctlr(nvme_softc_t *sc);
static int nvme_admin_state_make_queues(nvme_softc_t *sc);
static int nvme_admin_state_identify_ns(nvme_softc_t *sc);
static int nvme_admin_state_operating(nvme_softc_t *sc);
static int nvme_admin_state_failed(nvme_softc_t *sc);
int
nvme_start_admin_thread(nvme_softc_t *sc)
{
int error, intr_flags;
lockinit(&sc->admin_lk, "admlk", 0, 0);
lockinit(&sc->ioctl_lk, "nvioc", 0, 0);
sc->admin_signal = 0;
intr_flags = INTR_MPSAFE;
if (sc->nirqs == 1) {
intr_flags |= INTR_HIFREQ;
}
error = bus_setup_intr(sc->dev, sc->irq[0], intr_flags,
nvme_intr, &sc->comqueues[0],
&sc->irq_handle[0], NULL);
if (error) {
device_printf(sc->dev, "unable to install interrupt\n");
return error;
}
lockmgr(&sc->admin_lk, LK_EXCLUSIVE);
kthread_create(nvme_admin_thread, sc, &sc->admintd, "nvme_admin");
while ((sc->admin_signal & ADMIN_SIG_RUNNING) == 0)
lksleep(&sc->admin_signal, &sc->admin_lk, 0, "nvwbeg", 0);
lockmgr(&sc->admin_lk, LK_RELEASE);
return 0;
}
void
nvme_stop_admin_thread(nvme_softc_t *sc)
{
uint32_t i;
atomic_set_int(&sc->admin_signal, ADMIN_SIG_STOP);
lockmgr(&sc->admin_lk, LK_EXCLUSIVE);
while ((sc->admin_signal & ADMIN_SIG_PROBED) == 0) {
if ((sc->admin_signal & ADMIN_SIG_RUNNING) == 0)
break;
lksleep(&sc->admin_signal, &sc->admin_lk, 0, "nvwend", 0);
}
lockmgr(&sc->admin_lk, LK_RELEASE);
for (i = 0; i < NVME_MAX_NAMESPACES; ++i) {
nvme_softns_t *nsc;
if ((nsc = sc->nscary[i]) != NULL) {
nvme_disk_detach(nsc);
kfree(nsc, M_NVME);
sc->nscary[i] = NULL;
}
}
lockmgr(&sc->admin_lk, LK_EXCLUSIVE);
wakeup(&sc->admin_signal);
while (sc->admin_signal & ADMIN_SIG_RUNNING)
lksleep(&sc->admin_signal, &sc->admin_lk, 0, "nvwend", 0);
lockmgr(&sc->admin_lk, LK_RELEASE);
if (sc->irq_handle[0]) {
bus_teardown_intr(sc->dev, sc->irq[0], sc->irq_handle[0]);
sc->irq_handle[0] = NULL;
}
lockuninit(&sc->ioctl_lk);
lockuninit(&sc->admin_lk);
nvme_os_sleep(1);
}
static
void
nvme_admin_thread(void *arg)
{
nvme_softc_t *sc = arg;
uint32_t i;
lockmgr(&sc->admin_lk, LK_EXCLUSIVE);
atomic_set_int(&sc->admin_signal, ADMIN_SIG_RUNNING);
wakeup(&sc->admin_signal);
sc->admin_func = nvme_admin_state_identify_ctlr;
while ((sc->admin_signal & ADMIN_SIG_STOP) == 0) {
for (i = 0; i <= sc->niocomqs; ++i) {
nvme_comqueue_t *comq = &sc->comqueues[i];
if (comq->nqe == 0)
continue;
lockmgr(&comq->lk, LK_EXCLUSIVE);
nvme_poll_completions(comq, &comq->lk);
lockmgr(&comq->lk, LK_RELEASE);
}
if (sc->admin_signal & ADMIN_SIG_REQUEUE) {
atomic_clear_int(&sc->admin_signal, ADMIN_SIG_REQUEUE);
nvme_disk_requeues(sc);
}
if (sc->admin_func(sc) == 0 &&
(sc->admin_signal & ADMIN_SIG_RUN_MASK) == 0) {
lksleep(&sc->admin_signal, &sc->admin_lk, 0,
"nvidle", hz);
}
}
for (i = 1; i <= sc->niosubqs; ++i) {
nvme_delete_subqueue(sc, i);
nvme_free_subqueue(sc, i);
}
for (i = 1; i <= sc->niocomqs; ++i) {
nvme_delete_comqueue(sc, i);
nvme_free_comqueue(sc, i);
}
atomic_clear_int(&sc->admin_signal, ADMIN_SIG_RUNNING);
wakeup(&sc->admin_signal);
lockmgr(&sc->admin_lk, LK_RELEASE);
}
static
int
nvme_admin_state_identify_ctlr(nvme_softc_t *sc)
{
nvme_request_t *req;
nvme_ident_ctlr_data_t *rp;
int status;
uint64_t mempgsize;
char serial[20+16];
char model[40+16];
mempgsize = NVME_CAP_MEMPG_MIN_GET(sc->cap);
req = nvme_get_admin_request(sc, NVME_OP_IDENTIFY);
req->cmd.identify.cns = NVME_CNS_CTLR;
req->cmd.identify.cntid = 0;
bzero(req->info, sizeof(*req->info));
nvme_submit_request(req);
status = nvme_wait_request(req);
sc->idctlr = req->info->idctlr;
nvme_put_request(req);
rp = &sc->idctlr;
KKASSERT(sizeof(sc->idctlr.serialno) == 20);
KKASSERT(sizeof(sc->idctlr.modelno) == 40);
bzero(serial, sizeof(serial));
bzero(model, sizeof(model));
bcopy(rp->serialno, serial, sizeof(rp->serialno));
bcopy(rp->modelno, model, sizeof(rp->modelno));
string_cleanup(serial, 0);
string_cleanup(model, 0);
device_printf(sc->dev, "Model %s BaseSerial %s nscount=%d\n",
model, serial, rp->ns_count);
sc->admin_func = nvme_admin_state_make_queues;
return 1;
}
#define COMQFIXUP(msix, ncomqs) ((((msix) - 1) % ncomqs) + 1)
static
int
nvme_admin_state_make_queues(nvme_softc_t *sc)
{
nvme_request_t *req;
uint16_t niosubqs, subq_err_idx;
uint16_t niocomqs, comq_err_idx;
uint32_t i;
uint16_t qno;
int status;
int error;
req = nvme_get_admin_request(sc, NVME_OP_SET_FEATURES);
niosubqs = ncpus * 2 + 0;
niocomqs = ncpus + 0;
if (niosubqs >= NVME_MAX_QUEUES)
niosubqs = NVME_MAX_QUEUES - 1;
if (niocomqs >= NVME_MAX_QUEUES)
niocomqs = NVME_MAX_QUEUES - 1;
if (sc->nirqs == 1 && niocomqs > 4) {
niocomqs = 4;
device_printf(sc->dev, "no MSI-X support, limit comqs to %d\n",
niocomqs);
}
device_printf(sc->dev, "Request %u/%u queues, ", niosubqs, niocomqs);
req->cmd.setfeat.flags = NVME_FID_NUMQUEUES;
req->cmd.setfeat.numqs.nsqr = niosubqs - 1;
req->cmd.setfeat.numqs.ncqr = niocomqs - 1;
nvme_submit_request(req);
status = nvme_wait_request(req);
if (status == 0) {
sc->niosubqs = 1 + (req->res.setfeat.dw0 & 0xFFFFU);
sc->niocomqs = 1 + ((req->res.setfeat.dw0 >> 16) & 0xFFFFU);
if (sc->niosubqs > niosubqs)
sc->niosubqs = niosubqs;
if (sc->niocomqs > niocomqs)
sc->niocomqs = niocomqs;
} else {
sc->niosubqs = 0;
sc->niocomqs = 0;
}
kprintf("Returns %u/%u queues, ", sc->niosubqs, sc->niocomqs);
nvme_put_request(req);
tryagain:
sc->dumpqno = 0;
sc->eventqno = 0;
if (sc->niosubqs >= ncpus * 2 + 0 && sc->niocomqs >= ncpus + 0) {
kprintf("optimal map\n");
qno = 1;
for (i = 0; i < ncpus; ++i) {
int cpuqno = COMQFIXUP(sc->cputovect[i], ncpus);
KKASSERT(cpuqno != 0);
sc->qmap[i][0] = qno + 0;
sc->qmap[i][1] = qno + 1;
sc->subqueues[qno + 0].comqid = cpuqno;
sc->subqueues[qno + 1].comqid = cpuqno;
qno += 2;
}
sc->niosubqs = ncpus * 2 + 0;
sc->niocomqs = ncpus + 0;
} else if (sc->niosubqs >= ncpus && sc->niocomqs >= ncpus) {
kprintf("nominal map 1:1 cpu\n");
for (i = 0; i < ncpus; ++i) {
qno = sc->cputovect[i];
KKASSERT(qno != 0);
sc->qmap[i][0] = qno;
sc->qmap[i][1] = qno;
sc->subqueues[qno].comqid = COMQFIXUP(qno, ncpus);
}
sc->niosubqs = ncpus;
sc->niocomqs = ncpus;
} else if (sc->niosubqs >= 2 && sc->niocomqs >= 2) {
kprintf("rw-sep map (%d, %d)\n", sc->niosubqs, sc->niocomqs);
for (i = 0; i < ncpus; ++i) {
int cpuqno = COMQFIXUP(sc->cputovect[i], sc->niocomqs);
int qno = COMQFIXUP((i + 1), sc->niosubqs);
KKASSERT(qno != 0);
sc->qmap[i][0] = qno;
sc->qmap[i][1] = qno;
sc->subqueues[qno].comqid = cpuqno;
}
#if 0
sc->niosubqs = 2;
sc->niocomqs = 2;
#endif
} else if (sc->niosubqs >= 2) {
kprintf("basic map\n");
qno = 1;
for (i = 0; i < ncpus; ++i) {
int cpuqno = COMQFIXUP(sc->cputovect[i], 1);
KKASSERT(qno != 0);
sc->qmap[i][0] = qno + 0;
sc->qmap[i][1] = qno + 1;
if (i <= 0)
sc->subqueues[qno + 0].comqid = cpuqno;
if (i <= 1)
sc->subqueues[qno + 1].comqid = cpuqno;
}
sc->niosubqs = 2;
sc->niocomqs = 1;
} else {
kprintf("minimal map\n");
sc->dumpqno = 0;
sc->eventqno = 0;
for (i = 0; i < ncpus; ++i) {
sc->qmap[i][0] = 1;
sc->qmap[i][1] = 1;
}
sc->subqueues[1].comqid = 1;
sc->niosubqs = 1;
sc->niocomqs = 1;
}
error = 0;
for (i = 1; i <= sc->niocomqs; ++i) {
error += nvme_alloc_comqueue(sc, i);
if (error) {
device_printf(sc->dev, "Unable to alloc comq %d/%d\n",
i, sc->niocomqs);
break;
}
error += nvme_create_comqueue(sc, i);
if (error) {
device_printf(sc->dev, "Unable to create comq %d/%d\n",
i, sc->niocomqs);
++i;
break;
}
}
comq_err_idx = i;
for (i = 1; i <= sc->niosubqs; ++i) {
error += nvme_alloc_subqueue(sc, i);
if (error) {
device_printf(sc->dev, "Unable to alloc subq %d/%d\n",
i, sc->niosubqs);
break;
}
error += nvme_create_subqueue(sc, i);
if (error) {
device_printf(sc->dev, "Unable to create subq %d/%d\n",
i, sc->niosubqs);
++i;
break;
}
}
subq_err_idx = i;
if (error) {
device_printf(sc->dev, "Failed to initialize device!\n");
for (i = subq_err_idx - 1; i >= 1; --i) {
nvme_delete_subqueue(sc, i);
nvme_free_subqueue(sc, i);
}
for (i = comq_err_idx - 1; i >= 1; --i) {
nvme_delete_comqueue(sc, i);
nvme_free_comqueue(sc, i);
}
sc->admin_func = nvme_admin_state_failed;
if (sc->niosubqs > 1 || sc->niocomqs > 1) {
int trywith = 1;
device_printf(sc->dev,
"Retrying with fewer queues (%d/%d) "
"just in case the device lied to us\n",
trywith, trywith);
if (sc->niosubqs > trywith)
sc->niosubqs = trywith;
if (sc->niocomqs > trywith)
sc->niocomqs = trywith;
goto tryagain;
}
} else {
sc->admin_func = nvme_admin_state_identify_ns;
}
req = nvme_get_admin_request(sc, NVME_OP_SET_FEATURES);
device_printf(sc->dev, "Interrupt Coalesce: 100uS / 4 qentries\n");
req->cmd.setfeat.flags = NVME_FID_INTCOALESCE;
req->cmd.setfeat.intcoal.thr = 0;
req->cmd.setfeat.intcoal.time = 0;
nvme_submit_request(req);
status = nvme_wait_request(req);
if (status) {
device_printf(sc->dev,
"Interrupt coalesce failed status=%d\n",
status);
}
nvme_put_request(req);
return 1;
}
static
int
nvme_admin_state_identify_ns(nvme_softc_t *sc)
{
nvme_request_t *req;
nvme_ident_ns_list_t *rp;
int status;
int i;
int j;
if (bootverbose) {
if (sc->idctlr.admin_cap & NVME_ADMIN_NSMANAGE)
device_printf(sc->dev,
"Namespace management supported\n");
else
device_printf(sc->dev,
"Namespace management not supported\n");
}
#if 0
if (sc->idctlr.admin_cap & NVME_ADMIN_NSMANAGE) {
req = nvme_get_admin_request(sc, NVME_OP_IDENTIFY);
req->cmd.identify.cns = NVME_CNS_ANY_CTLR_LIST;
req->cmd.identify.cntid = 0;
bzero(req->info, sizeof(*req->info));
nvme_submit_request(req);
status = nvme_wait_request(req);
kprintf("nsquery status %08x\n", status);
#if 0
for (i = 0; i < req->info->ctlrlist.idcount; ++i) {
kprintf("CTLR %04x\n", req->info->ctlrlist.ctlrids[i]);
}
#endif
nvme_put_request(req);
}
#endif
rp = kmalloc(sizeof(*rp), M_NVME, M_WAITOK | M_ZERO);
if (sc->idctlr.admin_cap & NVME_ADMIN_NSMANAGE) {
req = nvme_get_admin_request(sc, NVME_OP_IDENTIFY);
req->cmd.identify.cns = NVME_CNS_ACT_NSLIST;
req->cmd.identify.cntid = 0;
bzero(req->info, sizeof(*req->info));
nvme_submit_request(req);
status = nvme_wait_request(req);
kprintf("nsquery status %08x\n", status);
cpu_lfence();
*rp = req->info->nslist;
nvme_put_request(req);
} else {
for (i = 1; i <= (int)sc->idctlr.ns_count && i <= 1024; ++i)
rp->nsids[i-1] = i;
}
for (i = 0; i < 1024; ++i) {
nvme_softns_t *nsc;
nvme_lba_fmt_data_t *lbafmt;
if (rp->nsids[i] == 0)
continue;
req = nvme_get_admin_request(sc, NVME_OP_IDENTIFY);
req->cmd.identify.cns = NVME_CNS_ACT_NS;
req->cmd.identify.cntid = 0;
req->cmd.identify.head.nsid = rp->nsids[i];
bzero(req->info, sizeof(*req->info));
nvme_submit_request(req);
status = nvme_wait_request(req);
if (status != 0) {
kprintf("NS FAILED %08x\n", status);
continue;
}
for (j = 0; j < NVME_MAX_NAMESPACES; ++j) {
if (sc->nscary[j] &&
sc->nscary[j]->nsid == rp->nsids[i])
break;
}
if (j == NVME_MAX_NAMESPACES) {
j = i;
if (sc->nscary[j] != NULL) {
for (j = NVME_MAX_NAMESPACES - 1; j >= 0; --j) {
if (sc->nscary[j] == NULL)
break;
}
}
}
if (j < 0) {
device_printf(sc->dev, "not enough room in nscary for "
"namespace %08x\n", rp->nsids[i]);
nvme_put_request(req);
continue;
}
nsc = sc->nscary[j];
if (nsc == NULL) {
nsc = kmalloc(sizeof(*nsc), M_NVME, M_WAITOK | M_ZERO);
nsc->unit = nvme_alloc_disk_unit();
sc->nscary[j] = nsc;
}
if (sc->nscmax <= j)
sc->nscmax = j + 1;
nsc->sc = sc;
nsc->nsid = rp->nsids[i];
nsc->state = NVME_NSC_STATE_UNATTACHED;
nsc->idns = req->info->idns;
bioq_init(&nsc->bioq);
lockinit(&nsc->lk, "nvnsc", 0, 0);
nvme_put_request(req);
j = NVME_FLBAS_SEL_GET(nsc->idns.flbas);
lbafmt = &nsc->idns.lba_fmt[j];
nsc->blksize = 1 << lbafmt->sect_size;
nvme_disk_attach(nsc);
}
kfree(rp, M_NVME);
sc->admin_func = nvme_admin_state_operating;
return 1;
}
static
int
nvme_admin_state_operating(nvme_softc_t *sc)
{
if ((sc->admin_signal & ADMIN_SIG_PROBED) == 0) {
atomic_set_int(&sc->admin_signal, ADMIN_SIG_PROBED);
wakeup(&sc->admin_signal);
}
return 0;
}
static
int
nvme_admin_state_failed(nvme_softc_t *sc)
{
if ((sc->admin_signal & ADMIN_SIG_PROBED) == 0) {
atomic_set_int(&sc->admin_signal, ADMIN_SIG_PROBED);
wakeup(&sc->admin_signal);
}
return 0;
}