From 4e9514d60fb4d69f691e54fb93f4cf466d5cb97c Mon Sep 17 00:00:00 2001 From: krw Date: Fri, 24 May 2024 12:04:07 +0000 Subject: [PATCH] Add support for NVMe passthrough commands IDENTIFY, GET_LOG_PG and SELFTEST. Enables suitably inquisitive software (e.g. smartmontools) to get information on nvme(4) disks. Based on work with dlg@ at h2k23, various at p2k24 and subsequent improvements and tests by jmatthew@. ok dlg@ for more permissive h2k23 version, jmatthew@ --- sys/dev/ic/nvme.c | 117 +++++++++++++++++++++++++++++++++++++++++-- sys/dev/ic/nvmeio.h | 47 +++++++++++++++++ sys/dev/ic/nvmereg.h | 3 +- 3 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 sys/dev/ic/nvmeio.h diff --git a/sys/dev/ic/nvme.c b/sys/dev/ic/nvme.c index 997c5e40d44..b04ab4d19c8 100644 --- a/sys/dev/ic/nvme.c +++ b/sys/dev/ic/nvme.c @@ -1,4 +1,4 @@ -/* $OpenBSD: nvme.c,v 1.111 2024/05/13 11:41:52 krw Exp $ */ +/* $OpenBSD: nvme.c,v 1.112 2024/05/24 12:04:07 krw Exp $ */ /* * Copyright (c) 2014 David Gwynne @@ -42,6 +42,7 @@ #include #include #include +#include struct cfdriver nvme_cd = { NULL, @@ -90,6 +91,9 @@ void nvme_minphys(struct buf *, struct scsi_link *); int nvme_scsi_probe(struct scsi_link *); void nvme_scsi_free(struct scsi_link *); uint64_t nvme_scsi_size(const struct nvm_identify_namespace *); +int nvme_scsi_ioctl(struct scsi_link *, u_long, caddr_t, int); +int nvme_passthrough_cmd(struct nvme_softc *, struct nvme_pt_cmd *, + int, int); #ifdef HIBERNATE #include @@ -111,7 +115,8 @@ int nvme_bioctl_disk(struct nvme_softc *, struct bioc_disk *); #endif /* NBIO > 0 */ const struct scsi_adapter nvme_switch = { - nvme_scsi_cmd, nvme_minphys, nvme_scsi_probe, nvme_scsi_free, NULL + nvme_scsi_cmd, nvme_minphys, nvme_scsi_probe, nvme_scsi_free, + nvme_scsi_ioctl }; void nvme_scsi_io(struct scsi_xfer *, int); @@ -924,6 +929,107 @@ nvme_scsi_size(const struct nvm_identify_namespace *ns) return nsze; } +int +nvme_passthrough_cmd(struct nvme_softc *sc, struct nvme_pt_cmd *pt, int dv_unit, + int nsid) +{ + struct nvme_pt_status pt_status; + struct nvme_sqe sqe; + struct nvme_dmamem *mem = NULL; + struct nvme_ccb *ccb = NULL; + int flags; + int rv = 0; + + ccb = nvme_ccb_get(sc); + if (ccb == NULL) + panic("nvme_passthrough_cmd: nvme_ccb_get returned NULL"); + + memset(&sqe, 0, sizeof(sqe)); + sqe.opcode = pt->pt_opcode; + htolem32(&sqe.nsid, pt->pt_nsid); + htolem32(&sqe.cdw10, pt->pt_cdw10); + htolem32(&sqe.cdw11, pt->pt_cdw11); + htolem32(&sqe.cdw12, pt->pt_cdw12); + htolem32(&sqe.cdw13, pt->pt_cdw13); + htolem32(&sqe.cdw14, pt->pt_cdw14); + htolem32(&sqe.cdw15, pt->pt_cdw15); + + ccb->ccb_done = nvme_empty_done; + ccb->ccb_cookie = &sqe; + + switch (pt->pt_opcode) { + case NVM_ADMIN_IDENTIFY: + case NVM_ADMIN_GET_LOG_PG: + case NVM_ADMIN_SELFTEST: + break; + + default: + rv = ENOTTY; + goto done; + } + + if (pt->pt_databuflen > 0) { + mem = nvme_dmamem_alloc(sc, pt->pt_databuflen); + if (mem == NULL) { + rv = ENOMEM; + goto done; + } + htolem64(&sqe.entry.prp[0], NVME_DMA_DVA(mem)); + nvme_dmamem_sync(sc, mem, BUS_DMASYNC_PREREAD); + } + + flags = nvme_poll(sc, sc->sc_admin_q, ccb, nvme_sqe_fill, NVME_TIMO_QOP); + + if (pt->pt_databuflen > 0) { + nvme_dmamem_sync(sc, mem, BUS_DMASYNC_POSTREAD); + if (flags == 0) + rv = copyout(NVME_DMA_KVA(mem), pt->pt_databuf, + pt->pt_databuflen); + } + + if (rv == 0 && pt->pt_statuslen > 0) { + pt_status.ps_dv_unit = dv_unit; + pt_status.ps_nsid = nsid; + pt_status.ps_flags = flags; + pt_status.ps_cc = nvme_read4(sc, NVME_CC); + pt_status.ps_csts = nvme_read4(sc, NVME_CSTS); + rv = copyout(&pt_status, pt->pt_status, pt->pt_statuslen); + } + + done: + if (mem) + nvme_dmamem_free(sc, mem); + if (ccb) + nvme_ccb_put(sc, ccb); + + return rv; +} + +int +nvme_scsi_ioctl(struct scsi_link *link, u_long cmd, caddr_t addr, int flag) +{ + struct nvme_softc *sc = link->bus->sb_adapter_softc; + struct nvme_pt_cmd *pt = (struct nvme_pt_cmd *)addr; + int rv; + + switch (cmd) { + case NVME_PASSTHROUGH_CMD: + break; + default: + return ENOTTY; + } + + if ((pt->pt_cdw10 & 0xff) == 0) + pt->pt_nsid = link->target; + + rv = nvme_passthrough_cmd(sc, pt, sc->sc_dev.dv_unit, link->target); + if (rv) + goto done; + + done: + return rv; +} + uint32_t nvme_op_sq_enter(struct nvme_softc *sc, struct nvme_queue *q, struct nvme_ccb *ccb) @@ -1744,7 +1850,8 @@ int nvme_bioctl(struct device *self, u_long cmd, caddr_t data) { struct nvme_softc *sc = (struct nvme_softc *)self; - int error = 0; + struct nvme_pt_cmd *pt; + int error = 0; rw_enter_write(&sc->sc_lock); @@ -1758,6 +1865,10 @@ nvme_bioctl(struct device *self, u_long cmd, caddr_t data) case BIOCDISK: error = nvme_bioctl_disk(sc, (struct bioc_disk *)data); break; + case NVME_PASSTHROUGH_CMD: + pt = (struct nvme_pt_cmd *)data; + error = nvme_passthrough_cmd(sc, pt, sc->sc_dev.dv_unit, -1); + break; default: printf("nvme_bioctl() Unknown command (%lu)\n", cmd); error = ENOTTY; diff --git a/sys/dev/ic/nvmeio.h b/sys/dev/ic/nvmeio.h new file mode 100644 index 00000000000..72c1811ca6f --- /dev/null +++ b/sys/dev/ic/nvmeio.h @@ -0,0 +1,47 @@ +/* $OpenBSD: nvmeio.h,v 1.1 2024/05/24 12:04:07 krw Exp $ */ +/* + * Copyright (c) 2023 Kenneth R Westerback + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define NVME_PASSTHROUGH_CMD _IOWR('n', 0, struct nvme_pt_cmd) + +struct nvme_pt_status { + int ps_dv_unit; + int ps_nsid; + int ps_flags; + uint32_t ps_csts; + uint32_t ps_cc; +}; + +struct nvme_pt_cmd { + /* Commands may arrive via /dev/bio. */ + struct bio pt_bio; + + /* The sqe fields that the caller may specify. */ + uint8_t pt_opcode; + uint32_t pt_nsid; + uint32_t pt_cdw10; + uint32_t pt_cdw11; + uint32_t pt_cdw12; + uint32_t pt_cdw13; + uint32_t pt_cdw14; + uint32_t pt_cdw15; + + caddr_t pt_status; + uint32_t pt_statuslen; + + caddr_t pt_databuf; /* User space address. */ + uint32_t pt_databuflen; /* Length of buffer. */ +}; diff --git a/sys/dev/ic/nvmereg.h b/sys/dev/ic/nvmereg.h index 41886c858fb..2a28c6af83e 100644 --- a/sys/dev/ic/nvmereg.h +++ b/sys/dev/ic/nvmereg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: nvmereg.h,v 1.14 2024/05/13 11:41:52 krw Exp $ */ +/* $OpenBSD: nvmereg.h,v 1.15 2024/05/24 12:04:07 krw Exp $ */ /* * Copyright (c) 2014 David Gwynne @@ -247,6 +247,7 @@ struct nvme_cqe { #define NVM_ADMIN_ASYNC_EV_REQ 0x0c /* Asynchronous Event Request */ #define NVM_ADMIN_FW_ACTIVATE 0x10 /* Firmware Activate */ #define NVM_ADMIN_FW_DOWNLOAD 0x11 /* Firmware Image Download */ +#define NVM_ADMIN_SELFTEST 0x14 /* Start self test */ #define NVM_CMD_FLUSH 0x00 /* Flush */ #define NVM_CMD_WRITE 0x01 /* Write */ -- 2.20.1