#include "nvme.h"
static int nvme_pci_attach(device_t);
static int nvme_pci_detach(device_t);
static const nvme_device_t nvme_devices[] = {
{ 0, 0, nvme_pci_attach, nvme_pci_detach, "NVME-PCIe" }
};
static int nvme_msix_enable = 1;
TUNABLE_INT("hw.nvme.msix.enable", &nvme_msix_enable);
static int nvme_msi_enable = 0;
TUNABLE_INT("hw.nvme.msi.enable", &nvme_msi_enable);
TAILQ_HEAD(, nvme_softc) nvme_sc_list = TAILQ_HEAD_INITIALIZER(nvme_sc_list);
struct lock nvme_master_lock = LOCK_INITIALIZER("nvmstr", 0, 0);
static int last_global_cpu;
const nvme_device_t *
nvme_lookup_device(device_t dev)
{
const nvme_device_t *ad;
uint16_t vendor = pci_get_vendor(dev);
uint16_t product = pci_get_device(dev);
uint8_t class = pci_get_class(dev);
uint8_t subclass = pci_get_subclass(dev);
uint8_t progif = pci_read_config(dev, PCIR_PROGIF, 1);
int is_nvme;
if (class == PCIC_STORAGE && subclass == PCIS_STORAGE_NVM &&
progif == PCIP_STORAGE_NVM_ENTERPRISE_NVMHCI_1_0) {
is_nvme = 1;
} else {
is_nvme = 0;
}
for (ad = &nvme_devices[0]; ad->vendor; ++ad) {
if (ad->vendor == vendor && ad->product == product)
return (ad);
}
if (is_nvme == 0)
ad = NULL;
return (ad);
}
static int
nvme_pci_attach(device_t dev)
{
nvme_softc_t *sc = device_get_softc(dev);
uint32_t reg;
int error;
int msi_enable;
int msix_enable;
#if 0
if (pci_read_config(dev, PCIR_COMMAND, 2) & 0x0400) {
device_printf(dev, "BIOS disabled PCI interrupt, "
"re-enabling\n");
pci_write_config(dev, PCIR_COMMAND,
pci_read_config(dev, PCIR_COMMAND, 2) & ~0x0400, 2);
}
#endif
sc->dev = dev;
sc->rid_regs = PCIR_BAR(0);
sc->regs = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&sc->rid_regs, RF_ACTIVE);
if (sc->regs == NULL) {
device_printf(dev, "unable to map registers\n");
nvme_pci_detach(dev);
return (ENXIO);
}
sc->iot = rman_get_bustag(sc->regs);
sc->ioh = rman_get_bushandle(sc->regs);
sc->rid_bar4 = PCIR_BAR(4);
sc->bar4 = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&sc->rid_bar4, RF_ACTIVE);
msi_enable = device_getenv_int(dev, "msi.enable", nvme_msi_enable);
msix_enable = device_getenv_int(dev, "msix.enable", nvme_msix_enable);
error = 0;
if (msix_enable) {
int i;
int cpu;
sc->nirqs = pci_msix_count(dev);
sc->irq_type = PCI_INTR_TYPE_MSIX;
if (sc->nirqs > ncpus + 1)
sc->nirqs = ncpus + 1;
error = pci_setup_msix(dev);
cpu = (last_global_cpu + 0) % ncpus;
for (i = 0; error == 0 && i < sc->nirqs; ++i) {
cpu = (last_global_cpu + i) % ncpus;
error = pci_alloc_msix_vector(dev, i,
&sc->rid_irq[i], cpu);
if (error)
break;
sc->irq[i] = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&sc->rid_irq[i],
RF_ACTIVE);
if (sc->cputovect[cpu] == 0)
sc->cputovect[cpu] = i;
}
while (error == 0) {
cpu = (cpu + 1) % ncpus;
i = (i + 1) % sc->nirqs;
if (i == 0)
i = 1;
if (sc->cputovect[cpu] != 0)
break;
sc->cputovect[cpu] = i;
}
if (error) {
while (--i >= 0) {
bus_release_resource(dev, SYS_RES_IRQ,
sc->rid_irq[i],
sc->irq[i]);
pci_release_msix_vector(dev, sc->rid_irq[i]);
sc->irq[i] = NULL;
}
} else {
last_global_cpu = (last_global_cpu + sc->nirqs) % ncpus;
pci_enable_msix(dev);
}
}
if (msix_enable == 0 || error) {
uint32_t irq_flags;
int i;
error = 0;
sc->nirqs = 1;
sc->irq_type = pci_alloc_1intr(dev, msi_enable,
&sc->rid_irq[0], &irq_flags);
sc->irq[0] = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&sc->rid_irq[0], irq_flags);
for (i = 0; i < ncpus; ++i)
sc->cputovect[i] = i + 1;
}
if (sc->irq[0] == NULL) {
device_printf(dev, "unable to map interrupt\n");
nvme_pci_detach(dev);
return (ENXIO);
} else {
const char *type;
switch(sc->irq_type) {
case PCI_INTR_TYPE_MSI:
type = "MSI";
break;
case PCI_INTR_TYPE_MSIX:
type = "MSIX";
break;
default:
type = "normal-int";
break;
}
device_printf(dev, "mapped %d %s IRQs\n", sc->nirqs, type);
}
sc->entimo = hz * 5;
error = nvme_enable(sc, 0);
if (error) {
nvme_pci_detach(dev);
return (ENXIO);
}
sc->vers = nvme_read(sc, NVME_REG_VERS);
sc->cap = nvme_read8(sc, NVME_REG_CAP);
sc->maxqe = NVME_CAP_MQES_GET(sc->cap);
sc->dstrd4 = NVME_CAP_DSTRD_GET(sc->cap);
device_printf(dev, "NVME Version %u.%u maxqe=%u caps=%016jx\n",
NVME_VERS_MAJOR_GET(sc->vers),
NVME_VERS_MINOR_GET(sc->vers),
sc->maxqe, sc->cap);
sc->entimo = NVME_CAP_TIMEOUT_GET(sc->cap) * hz / 2;
++sc->entimo;
if (sc->maxqe < 2) {
device_printf(dev,
"Attach failed, max queue entries (%d) "
"below minimum (2)\n", sc->maxqe);
nvme_pci_detach(dev);
return (ENXIO);
}
if (sc->maxqe > 256)
sc->maxqe = 256;
for (reg = 2; reg <= sc->maxqe; reg <<= 1)
;
sc->maxqe = reg >> 1;
sc->prp_bytes = sizeof(uint64_t) * (MAXPHYS / PAGE_SIZE) * sc->maxqe;
sc->cmd_bytes = sizeof(nvme_subq_item_t) * sc->maxqe;
sc->res_bytes = sizeof(nvme_comq_item_t) * sc->maxqe;
sc->adm_bytes = NVME_MAX_ADMIN_BUFFER * sc->maxqe;
error = 0;
error += bus_dma_tag_create(
NULL,
PAGE_SIZE,
4 * 1024 * 1024,
BUS_SPACE_MAXADDR,
BUS_SPACE_MAXADDR,
sc->prp_bytes,
1,
sc->prp_bytes,
0,
&sc->prps_tag);
error += bus_dma_tag_create(
NULL,
PAGE_SIZE,
4 * 1024 * 1024,
BUS_SPACE_MAXADDR,
BUS_SPACE_MAXADDR,
sc->cmd_bytes,
1,
sc->cmd_bytes,
0,
&sc->sque_tag);
error += bus_dma_tag_create(
NULL,
PAGE_SIZE,
4 * 1024 * 1024,
BUS_SPACE_MAXADDR,
BUS_SPACE_MAXADDR,
sc->res_bytes,
1,
sc->res_bytes,
0,
&sc->cque_tag);
error += bus_dma_tag_create(
NULL,
PAGE_SIZE,
4 * 1024 * 1024,
BUS_SPACE_MAXADDR,
BUS_SPACE_MAXADDR,
sc->adm_bytes,
1,
sc->adm_bytes,
0,
&sc->adm_tag);
if (error) {
device_printf(dev, "unable to create dma tags\n");
nvme_pci_detach(dev);
return (ENXIO);
}
error = nvme_alloc_subqueue(sc, 0);
if (error) {
device_printf(dev, "unable to allocate admin subqueue\n");
nvme_pci_detach(dev);
return (ENXIO);
}
error = nvme_alloc_comqueue(sc, 0);
if (error) {
device_printf(dev, "unable to allocate admin comqueue\n");
nvme_pci_detach(dev);
return (ENXIO);
}
reg = NVME_ATTR_COM_SET(sc->maxqe) | NVME_ATTR_SUB_SET(sc->maxqe);
nvme_write(sc, NVME_REG_ADM_ATTR, reg);
nvme_write8(sc, NVME_REG_ADM_SUBADR, (uint64_t)sc->subqueues[0].psubq);
nvme_write8(sc, NVME_REG_ADM_COMADR, (uint64_t)sc->comqueues[0].pcomq);
pci_enable_busmaster(dev);
reg = NVME_CONFIG_IOSUB_ES_SET(6) |
NVME_CONFIG_IOCOM_ES_SET(4) |
NVME_CONFIG_MEMPG_SET(PAGE_SHIFT) |
NVME_CONFIG_CSS_NVM;
nvme_write(sc, NVME_REG_CONFIG, reg);
reg = nvme_read(sc, NVME_REG_MEMSIZE);
error = nvme_enable(sc, 1);
if (error) {
nvme_enable(sc, 0);
nvme_pci_detach(dev);
return (ENXIO);
}
error = nvme_start_admin_thread(sc);
if (error) {
nvme_pci_detach(dev);
return (ENXIO);
}
lockmgr(&nvme_master_lock, LK_EXCLUSIVE);
sc->flags |= NVME_SC_ATTACHED;
TAILQ_INSERT_TAIL(&nvme_sc_list, sc, entry);
lockmgr(&nvme_master_lock, LK_RELEASE);
return(0);
}
static int
nvme_pci_detach(device_t dev)
{
nvme_softc_t *sc = device_get_softc(dev);
int i;
nvme_stop_admin_thread(sc);
nvme_issue_shutdown(sc, 0);
nvme_enable(sc, 0);
nvme_free_subqueue(sc, 0);
nvme_free_comqueue(sc, 0);
for (i = 0; i < sc->nirqs; ++i) {
if (sc->irq[i]) {
bus_release_resource(dev, SYS_RES_IRQ,
sc->rid_irq[i], sc->irq[i]);
sc->irq[i] = NULL;
if (sc->irq_type == PCI_INTR_TYPE_MSIX)
pci_release_msix_vector(dev, sc->rid_irq[i]);
}
}
switch(sc->irq_type) {
case PCI_INTR_TYPE_MSI:
pci_release_msi(dev);
break;
case PCI_INTR_TYPE_MSIX:
pci_teardown_msix(dev);
break;
default:
break;
}
if (sc->regs) {
bus_release_resource(dev, SYS_RES_MEMORY,
sc->rid_regs, sc->regs);
sc->regs = NULL;
}
if (sc->bar4) {
bus_release_resource(dev, SYS_RES_MEMORY,
sc->rid_bar4, sc->bar4);
sc->bar4 = NULL;
}
if (sc->prps_tag) {
bus_dma_tag_destroy(sc->prps_tag);
sc->prps_tag = NULL;
}
if (sc->sque_tag) {
bus_dma_tag_destroy(sc->sque_tag);
sc->sque_tag = NULL;
}
if (sc->cque_tag) {
bus_dma_tag_destroy(sc->cque_tag);
sc->cque_tag = NULL;
}
if (sc->adm_tag) {
bus_dma_tag_destroy(sc->adm_tag);
sc->adm_tag = NULL;
}
if (sc->flags & NVME_SC_ATTACHED) {
lockmgr(&nvme_master_lock, LK_EXCLUSIVE);
sc->flags &= ~NVME_SC_ATTACHED;
TAILQ_REMOVE(&nvme_sc_list, sc, entry);
lockmgr(&nvme_master_lock, LK_RELEASE);
}
return (0);
}