#include "nvme.h"
MALLOC_DEFINE(M_NVME, "NVMe Driver", "NVME");
static
void
nvme_dmamem_saveseg(void *info, bus_dma_segment_t *segs, int nsegs, int error)
{
KKASSERT(error == 0);
KKASSERT(nsegs == 1);
*(bus_addr_t *)info = segs->ds_addr;
}
int
nvme_enable(nvme_softc_t *sc, int enable)
{
uint32_t reg;
int error = 0;
int base_ticks;
reg = nvme_read(sc, NVME_REG_CONFIG);
if (enable == 0 && (reg & NVME_CONFIG_EN)) {
reg &= ~NVME_CONFIG_EN;
nvme_write(sc, NVME_REG_CONFIG, reg);
} else if (enable && (reg & NVME_CONFIG_EN) == 0) {
reg |= NVME_CONFIG_EN;
nvme_write(sc, NVME_REG_CONFIG, reg);
}
error = ENXIO;
base_ticks = ticks;
while ((int)(ticks - base_ticks) < sc->entimo) {
reg = nvme_read(sc, NVME_REG_STATUS);
if (enable == 0 && (reg & NVME_STATUS_RDY) == 0) {
error = 0;
break;
}
if (enable && (reg & NVME_STATUS_RDY)) {
error = 0;
break;
}
nvme_os_sleep(50);
}
if (error == 0 && sc->nirqs == 1) {
if (enable) {
nvme_write(sc, NVME_REG_INTSET, ~1);
nvme_write(sc, NVME_REG_INTCLR, 1);
} else {
nvme_write(sc, NVME_REG_INTSET, ~1);
}
}
if (error) {
device_printf(sc->dev, "Cannot %s device\n",
(enable ? "enable" : "disable"));
} else {
#if 0
kprintf("gratuitous 15 second sleep\n");
nvme_os_sleep(15000);
kprintf("gratuitous 15 second sleep done\n");
#endif
}
return error;
}
int
nvme_alloc_subqueue(nvme_softc_t *sc, uint16_t qid)
{
nvme_subqueue_t *queue = &sc->subqueues[qid];
int error = 0;
lockinit(&queue->lk, "nvqlk", 0, 0);
queue->sc = sc;
queue->nqe = sc->maxqe;
queue->qid = qid;
queue->subq_doorbell_reg = NVME_REG_SUBQ_BELL(qid, sc->dstrd4);
if (error == 0) {
error = bus_dmamem_alloc(sc->sque_tag, (void **)&queue->ksubq,
BUS_DMA_ZERO, &queue->sque_map);
}
if (error == 0) {
error = bus_dmamap_load(sc->sque_tag, queue->sque_map,
queue->ksubq,
bus_dma_tag_getmaxsize(sc->sque_tag),
nvme_dmamem_saveseg, &queue->psubq,
0);
}
if (error == 0) {
error = bus_dmamem_alloc(sc->prps_tag, (void **)&queue->kprps,
BUS_DMA_ZERO, &queue->prps_map);
}
if (error == 0) {
error = bus_dmamap_load(sc->prps_tag, queue->prps_map,
queue->kprps,
bus_dma_tag_getmaxsize(sc->prps_tag),
nvme_dmamem_saveseg, &queue->pprps,
0);
}
if (qid == 0 && error == 0) {
error = bus_dmamem_alloc(sc->adm_tag,
(void **)&queue->kdatapgs,
BUS_DMA_ZERO, &queue->adm_map);
}
if (qid == 0 && error == 0) {
error = bus_dmamap_load(sc->adm_tag, queue->adm_map,
queue->kdatapgs,
bus_dma_tag_getmaxsize(sc->adm_tag),
nvme_dmamem_saveseg, &queue->pdatapgs,
0);
}
if (error == 0) {
nvme_request_t *req;
uint32_t i;
queue->reqary = kmalloc(sizeof(nvme_request_t) * queue->nqe,
M_NVME, M_WAITOK | M_ZERO);
for (i = 0; i < queue->nqe; ++i) {
req = &queue->reqary[i];
if (i == 0) {
queue->dump_req = req;
} else {
req->next_avail = queue->first_avail;
queue->first_avail = req;
}
req->subq = queue;
req->comq = &sc->comqueues[queue->comqid];
req->cmd_id = i;
if (qid == 0) {
req->info = &queue->kdatapgs[i];
req->pinfo = queue->pdatapgs +
i * sizeof(nvme_admin_data_t);
}
}
}
if (error)
nvme_free_subqueue(sc, qid);
return error;
}
int
nvme_alloc_comqueue(nvme_softc_t *sc, uint16_t qid)
{
nvme_comqueue_t *queue = &sc->comqueues[qid];
int error = 0;
lockinit(&queue->lk, "nvqlk", 0, 0);
queue->sc = sc;
queue->qid = qid;
queue->phase = NVME_COMQ_STATUS_PHASE;
queue->comq_doorbell_reg = NVME_REG_COMQ_BELL(qid, sc->dstrd4);
if (error == 0) {
error = bus_dmamem_alloc(sc->cque_tag, (void **)&queue->kcomq,
BUS_DMA_ZERO, &queue->cque_map);
}
if (error == 0) {
error = bus_dmamap_load(sc->cque_tag, queue->cque_map,
queue->kcomq,
bus_dma_tag_getmaxsize(sc->cque_tag),
nvme_dmamem_saveseg, &queue->pcomq,
0);
}
if (error == 0)
queue->nqe = sc->maxqe;
if (error)
nvme_free_comqueue(sc, qid);
return error;
}
void
nvme_free_subqueue(nvme_softc_t *sc, uint16_t qid)
{
nvme_subqueue_t *queue = &sc->subqueues[qid];
queue->first_avail = NULL;
if (queue->reqary) {
kfree(queue->reqary, M_NVME);
queue->reqary = NULL;
}
if (queue->ksubq) {
bus_dmamem_free(sc->sque_tag, queue->ksubq, queue->sque_map);
bus_dmamap_unload(sc->sque_tag, queue->sque_map);
bus_dmamap_destroy(sc->sque_tag, queue->sque_map);
}
if (queue->kprps) {
bus_dmamem_free(sc->prps_tag, queue->kprps, queue->prps_map);
bus_dmamap_unload(sc->prps_tag, queue->prps_map);
bus_dmamap_destroy(sc->prps_tag, queue->prps_map);
}
if (queue->kdatapgs) {
bus_dmamem_free(sc->adm_tag, queue->kdatapgs, queue->adm_map);
bus_dmamap_unload(sc->adm_tag, queue->adm_map);
bus_dmamap_destroy(sc->adm_tag, queue->adm_map);
}
bzero(queue, sizeof(*queue));
}
void
nvme_free_comqueue(nvme_softc_t *sc, uint16_t qid)
{
nvme_comqueue_t *queue = &sc->comqueues[qid];
queue->nqe = 0;
if (queue->kcomq) {
bus_dmamem_free(sc->cque_tag, queue->kcomq, queue->cque_map);
bus_dmamap_unload(sc->cque_tag, queue->cque_map);
bus_dmamap_destroy(sc->cque_tag, queue->cque_map);
}
bzero(queue, sizeof(*queue));
}
nvme_request_t *
nvme_get_admin_request(nvme_softc_t *sc, uint8_t opcode)
{
nvme_request_t *req;
req = nvme_get_request(&sc->subqueues[0], opcode, NULL, 0);
req->cmd.head.prp1 = req->pinfo;
req->callback = NULL;
return req;
}
static __inline
void
_nvme_fill_request(nvme_subqueue_t *queue, uint8_t opcode,
char *kva, size_t bytes,
nvme_request_t *req)
{
req->next_avail = NULL;
KKASSERT(req->state == NVME_REQ_AVAIL);
req->state = NVME_REQ_ALLOCATED;
req->callback = NULL;
req->waiting = 0;
req->cmd.head.opcode = opcode;
req->cmd.head.flags = NVME_SUBQFLG_PRP | NVME_SUBQFLG_NORM;
req->cmd.head.cid = req->cmd_id;
req->cmd.head.nsid = 0;
req->cmd.head.mptr = 0;
req->cmd.head.prp1 = 0;
req->cmd.head.prp2 = 0;
req->cmd.dw10 = 0;
req->cmd.dw11 = 0;
req->cmd.dw12 = 0;
req->cmd.dw13 = 0;
req->cmd.dw14 = 0;
req->cmd.dw15 = 0;
if (kva) {
size_t count = 0;
size_t idx = 0;
vm_paddr_t paddr;
vm_paddr_t pprptab;
uint64_t *kprptab;
KKASSERT(bytes >= 0 && bytes <= MAXPHYS);
kprptab = queue->kprps +
(MAXPHYS / PAGE_SIZE) * req->cmd_id;
pprptab = queue->pprps +
(MAXPHYS / PAGE_SIZE) * req->cmd_id *
sizeof(uint64_t);
while (count < bytes) {
paddr = vtophys(kva + count);
if (idx == 0) {
KKASSERT((paddr & 3) == 0);
req->cmd.head.prp1 = paddr;
count += (((intptr_t)kva + PAGE_SIZE) &
~(intptr_t)PAGE_MASK) -
(intptr_t)kva;
} else if (idx == 1 && count + PAGE_SIZE >= bytes) {
KKASSERT((paddr & PAGE_MASK) == 0);
req->cmd.head.prp2 = paddr;
count += PAGE_SIZE;
} else {
KKASSERT((paddr & PAGE_MASK) == 0);
req->cmd.head.prp2 = pprptab;
kprptab[idx - 1] = paddr;
count += PAGE_SIZE;
}
++idx;
}
}
}
nvme_request_t *
nvme_get_request(nvme_subqueue_t *queue, uint8_t opcode,
char *kva, size_t bytes)
{
nvme_request_t *req;
nvme_request_t *next;
lockmgr(&queue->lk, LK_EXCLUSIVE);
if ((queue->subq_tail + queue->unsubmitted + 1) % queue->nqe ==
queue->subq_head) {
lockmgr(&queue->lk, LK_RELEASE);
KKASSERT(queue->qid != 0);
atomic_swap_int(&queue->signal_requeue, 1);
return NULL;
}
for (;;) {
req = queue->first_avail;
cpu_ccfence();
if (req == NULL) {
lockmgr(&queue->lk, LK_RELEASE);
KKASSERT(queue->qid != 0);
atomic_swap_int(&queue->signal_requeue, 1);
return NULL;
}
next = req->next_avail;
if (atomic_cmpset_ptr(&queue->first_avail, req, next))
break;
}
++queue->unsubmitted;
lockmgr(&queue->lk, LK_RELEASE);
_nvme_fill_request(queue, opcode, kva, bytes, req);
return req;
}
nvme_request_t *
nvme_get_dump_request(nvme_subqueue_t *queue, uint8_t opcode,
char *kva, size_t bytes)
{
nvme_request_t *req;
int error;
error = lockmgr(&queue->lk, LK_EXCLUSIVE | LK_NOWAIT);
req = queue->dump_req;
++queue->unsubmitted;
if (error == 0)
lockmgr(&queue->lk, LK_RELEASE);
_nvme_fill_request(queue, opcode, kva, bytes, req);
return req;
}
void
nvme_submit_request(nvme_request_t *req)
{
nvme_subqueue_t *queue = req->subq;
nvme_allcmd_t *cmd;
cmd = &queue->ksubq[queue->subq_tail];
--queue->unsubmitted;
if (++queue->subq_tail == queue->nqe)
queue->subq_tail = 0;
KKASSERT(queue->subq_tail != queue->subq_head);
*cmd = req->cmd;
cpu_sfence();
req->state = NVME_REQ_SUBMITTED;
nvme_write(queue->sc, queue->subq_doorbell_reg, queue->subq_tail);
}
int
nvme_wait_request(nvme_request_t *req)
{
struct lock *lk;
int code;
req->waiting = 1;
if (req->state != NVME_REQ_COMPLETED) {
lk = &req->comq->lk;
cpu_lfence();
lockmgr(lk, LK_EXCLUSIVE);
while (req->state == NVME_REQ_SUBMITTED) {
nvme_poll_completions(req->comq, lk);
if (req->state != NVME_REQ_SUBMITTED)
break;
lksleep(req, lk, 0, "nvwait", hz);
}
lockmgr(lk, LK_RELEASE);
KKASSERT(req->state == NVME_REQ_COMPLETED);
}
cpu_lfence();
code = NVME_COMQ_STATUS_CODE_GET(req->res.tail.status);
return code;
}
int
nvme_poll_request(nvme_request_t *req)
{
struct lock *lk;
int code;
int didlock = 500;
req->waiting = 1;
if (req->state != NVME_REQ_COMPLETED) {
lk = &req->comq->lk;
cpu_lfence();
while (lockmgr(lk, LK_EXCLUSIVE | LK_NOWAIT) != 0) {
if (--didlock == 0)
break;
tsc_delay(1000);
}
while (req->state == NVME_REQ_SUBMITTED) {
nvme_poll_completions(req->comq, lk);
if (req->state != NVME_REQ_SUBMITTED)
break;
lwkt_switch();
}
if (didlock)
lockmgr(lk, LK_RELEASE);
KKASSERT(req->state == NVME_REQ_COMPLETED);
}
cpu_lfence();
code = NVME_COMQ_STATUS_CODE_GET(req->res.tail.status);
return code;
}
void
nvme_put_request(nvme_request_t *req)
{
nvme_subqueue_t *queue = req->subq;
nvme_request_t *next;
KKASSERT(req->state == NVME_REQ_COMPLETED);
req->state = NVME_REQ_AVAIL;
for (;;) {
next = queue->first_avail;
cpu_ccfence();
req->next_avail = next;
if (atomic_cmpset_ptr(&queue->first_avail, next, req))
break;
}
if (atomic_swap_int(&queue->signal_requeue, 0)) {
atomic_set_int(&queue->sc->admin_signal, ADMIN_SIG_REQUEUE);
wakeup(&queue->sc->admin_signal);
}
}
void
nvme_put_dump_request(nvme_request_t *req)
{
KKASSERT(req->state == NVME_REQ_COMPLETED);
req->state = NVME_REQ_AVAIL;
}
void
nvme_poll_completions(nvme_comqueue_t *comq, struct lock *lk)
{
nvme_softc_t *sc = comq->sc;
nvme_request_t *req;
nvme_subqueue_t *subq;
nvme_allres_t *res;
#if 0
int didwork = 0;
#endif
KKASSERT(comq->comq_tail < comq->nqe);
cpu_lfence();
for (;;) {
res = &comq->kcomq[comq->comq_tail];
if ((res->tail.status ^ comq->phase) & NVME_COMQ_STATUS_PHASE)
break;
if (++comq->comq_tail == comq->nqe) {
comq->comq_tail = 0;
comq->phase ^= NVME_COMQ_STATUS_PHASE;
}
#if 0
if (++didwork == (comq->nqe >> 2)) {
didwork = 0;
nvme_write(comq->sc, comq->comq_doorbell_reg,
comq->comq_tail);
}
#endif
cpu_lfence();
subq = &sc->subqueues[res->tail.subq_id];
subq->subq_head = res->tail.subq_head_ptr;
req = &subq->reqary[res->tail.cmd_id];
KKASSERT(req->state == NVME_REQ_SUBMITTED &&
req->comq == comq);
req->res = *res;
nvme_write(comq->sc, comq->comq_doorbell_reg, comq->comq_tail);
cpu_sfence();
req->state = NVME_REQ_COMPLETED;
if (req->callback) {
req->callback(req, lk);
} else if (req->waiting) {
wakeup(req);
}
}
#if 0
if (didwork)
nvme_write(comq->sc, comq->comq_doorbell_reg, comq->comq_tail);
#endif
}
void
nvme_intr(void *arg)
{
nvme_comqueue_t *comq = arg;
nvme_softc_t *sc;
int i;
int skip;
sc = comq->sc;
if (sc->nirqs == 1)
skip = 1;
else
skip = sc->nirqs - 1;
for (i = comq->qid; i <= sc->niocomqs; i += skip) {
if (comq->nqe) {
lockmgr(&comq->lk, LK_EXCLUSIVE);
nvme_poll_completions(comq, &comq->lk);
lockmgr(&comq->lk, LK_RELEASE);
}
comq += skip;
}
}
int
nvme_create_subqueue(nvme_softc_t *sc, uint16_t qid)
{
nvme_request_t *req;
nvme_subqueue_t *subq = &sc->subqueues[qid];
int status;
req = nvme_get_admin_request(sc, NVME_OP_CREATE_SUBQ);
req->cmd.head.prp1 = subq->psubq;
req->cmd.crsub.subq_id = qid;
req->cmd.crsub.subq_size = subq->nqe - 1;
req->cmd.crsub.flags = NVME_CREATESUB_PC | NVME_CREATESUB_PRI_URG;
req->cmd.crsub.comq_id = subq->comqid;
nvme_submit_request(req);
status = nvme_wait_request(req);
nvme_put_request(req);
return status;
}
int
nvme_create_comqueue(nvme_softc_t *sc, uint16_t qid)
{
nvme_request_t *req;
nvme_comqueue_t *comq = &sc->comqueues[qid];
int status;
int error;
uint16_t ivect;
error = 0;
if (sc->nirqs > 1) {
ivect = 1 + (qid - 1) % (sc->nirqs - 1);
if (qid && ivect == qid) {
error = bus_setup_intr(sc->dev, sc->irq[ivect],
INTR_MPSAFE | INTR_HIFREQ,
nvme_intr,
&sc->comqueues[ivect],
&sc->irq_handle[ivect],
NULL);
}
} else {
ivect = 0;
}
if (error)
return error;
req = nvme_get_admin_request(sc, NVME_OP_CREATE_COMQ);
req->cmd.head.prp1 = comq->pcomq;
req->cmd.crcom.comq_id = qid;
req->cmd.crcom.comq_size = comq->nqe - 1;
req->cmd.crcom.ivect = ivect;
req->cmd.crcom.flags = NVME_CREATECOM_PC | NVME_CREATECOM_IEN;
nvme_submit_request(req);
status = nvme_wait_request(req);
nvme_put_request(req);
if (sc->nirqs > 1 && status) {
ivect = 1 + (qid - 1) % (sc->nirqs - 1);
if (qid && ivect == qid) {
bus_teardown_intr(sc->dev,
sc->irq[ivect],
sc->irq_handle[ivect]);
sc->irq_handle[ivect] = NULL;
}
}
return status;
}
int
nvme_delete_subqueue(nvme_softc_t *sc, uint16_t qid)
{
nvme_request_t *req;
int status;
req = nvme_get_admin_request(sc, NVME_OP_DELETE_SUBQ);
req->cmd.head.prp1 = 0;
req->cmd.delete.qid = qid;
nvme_submit_request(req);
status = nvme_wait_request(req);
nvme_put_request(req);
return status;
}
int
nvme_delete_comqueue(nvme_softc_t *sc, uint16_t qid)
{
nvme_request_t *req;
nvme_comqueue_t *comq = &sc->comqueues[qid];
int status;
uint16_t ivect;
if (comq->sc == NULL)
return 0;
req = nvme_get_admin_request(sc, NVME_OP_DELETE_COMQ);
req->cmd.head.prp1 = 0;
req->cmd.delete.qid = qid;
nvme_submit_request(req);
status = nvme_wait_request(req);
nvme_put_request(req);
if (qid && sc->nirqs > 1) {
ivect = 1 + (qid - 1) % (sc->nirqs - 1);
if (ivect == qid && sc->irq_handle[ivect]) {
bus_teardown_intr(sc->dev,
sc->irq[ivect],
sc->irq_handle[ivect]);
sc->irq_handle[ivect] = NULL;
}
}
return status;
}
int
nvme_issue_shutdown(nvme_softc_t *sc, int dopoll)
{
uint32_t reg;
int base_ticks;
int error;
reg = nvme_read(sc, NVME_REG_CONFIG);
reg &= ~NVME_CONFIG_SHUT_MASK;
reg |= NVME_CONFIG_SHUT_NORM;
nvme_write(sc, NVME_REG_CONFIG, reg);
error = ENXIO;
base_ticks = ticks;
while ((int)(ticks - base_ticks) < 10 * 20) {
reg = nvme_read(sc, NVME_REG_STATUS);
if ((reg & NVME_STATUS_SHUT_MASK) & NVME_STATUS_SHUT_DONE) {
error = 0;
break;
}
if (dopoll == 0)
nvme_os_sleep(50);
}
if (error)
device_printf(sc->dev, "Unable to shutdown chip nicely\n");
else
device_printf(sc->dev, "Normal chip shutdown succeeded\n");
return error;
}
size_t
string_cleanup(char *str, int domiddle)
{
size_t i;
size_t j;
int atbeg = 1;
for (i = j = 0; str[i]; ++i) {
if ((str[i] == ' ' || str[i] == '\r') &&
(atbeg || domiddle)) {
continue;
} else {
atbeg = 0;
}
str[j] = str[i];
++j;
}
while (domiddle == 0 && j > 0 && (str[j-1] == ' ' || str[j-1] == '\r'))
--j;
str[j] = 0;
if (domiddle == 0) {
for (j = 0; str[j]; ++j) {
if (str[j] == ' ')
str[j] = '_';
}
}
return j;
}