summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/nvme/nvme.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/io/nvme/nvme.c')
-rw-r--r--usr/src/uts/common/io/nvme/nvme.c121
1 files changed, 95 insertions, 26 deletions
diff --git a/usr/src/uts/common/io/nvme/nvme.c b/usr/src/uts/common/io/nvme/nvme.c
index 1ea72fc792..391a6500b7 100644
--- a/usr/src/uts/common/io/nvme/nvme.c
+++ b/usr/src/uts/common/io/nvme/nvme.c
@@ -147,13 +147,14 @@
* - io-queue-len: the maximum length of the I/O queues (16-65536)
* - async-event-limit: the maximum number of asynchronous event requests to be
* posted by the driver
+ * - volatile-write-cache-enable: can be set to 0 to disable the volatile write
+ * cache
*
*
* TODO:
* - figure out sane default for I/O queue depth reported to blkdev
* - polled I/O support to support kernel core dumping
* - FMA handling of media errors
- * - support for the Volatile Write Cache
* - support for devices supporting very large I/O requests using chained PRPs
* - support for querying log pages from user space
* - support for configuring hardware parameters like interrupt coalescing
@@ -227,6 +228,9 @@ static void nvme_abort_cmd(nvme_cmd_t *);
static int nvme_async_event(nvme_t *);
static void *nvme_get_logpage(nvme_t *, uint8_t, ...);
static void *nvme_identify(nvme_t *, uint32_t);
+static boolean_t nvme_set_features(nvme_t *, uint32_t, uint8_t, uint32_t,
+ uint32_t *);
+static boolean_t nvme_write_cache_set(nvme_t *, boolean_t);
static int nvme_set_nqueues(nvme_t *, uint16_t);
static void nvme_free_dma(nvme_dma_t *);
@@ -1611,36 +1615,81 @@ fail:
return (buf);
}
-static int
-nvme_set_nqueues(nvme_t *nvme, uint16_t nqueues)
+static boolean_t
+nvme_set_features(nvme_t *nvme, uint32_t nsid, uint8_t feature, uint32_t val,
+ uint32_t *res)
{
+ _NOTE(ARGUNUSED(nsid));
nvme_cmd_t *cmd = nvme_alloc_cmd(nvme, KM_SLEEP);
- nvme_nqueue_t nq = { 0 };
+ boolean_t ret = B_FALSE;
- nq.b.nq_nsq = nq.b.nq_ncq = nqueues - 1;
+ ASSERT(res != NULL);
cmd->nc_sqid = 0;
cmd->nc_callback = nvme_wakeup_cmd;
cmd->nc_sqe.sqe_opc = NVME_OPC_SET_FEATURES;
- cmd->nc_sqe.sqe_cdw10 = NVME_FEAT_NQUEUES;
- cmd->nc_sqe.sqe_cdw11 = nq.r;
+ cmd->nc_sqe.sqe_cdw10 = feature;
+ cmd->nc_sqe.sqe_cdw11 = val;
+
+ switch (feature) {
+ case NVME_FEAT_WRITE_CACHE:
+ if (!nvme->n_write_cache_present)
+ goto fail;
+ break;
+
+ case NVME_FEAT_NQUEUES:
+ break;
+
+ default:
+ goto fail;
+ }
if (nvme_admin_cmd(cmd, nvme_admin_cmd_timeout) != DDI_SUCCESS) {
dev_err(nvme->n_dip, CE_WARN,
- "!nvme_admin_cmd failed for SET FEATURES (NQUEUES)");
- return (0);
+ "!nvme_admin_cmd failed for SET FEATURES");
+ return (ret);
}
if (nvme_check_cmd_status(cmd)) {
dev_err(nvme->n_dip, CE_WARN,
- "!SET FEATURES (NQUEUES) failed with sct = %x, sc = %x",
- cmd->nc_cqe.cqe_sf.sf_sct, cmd->nc_cqe.cqe_sf.sf_sc);
- nvme_free_cmd(cmd);
- return (0);
+ "!SET FEATURES %d failed with sct = %x, sc = %x",
+ feature, cmd->nc_cqe.cqe_sf.sf_sct,
+ cmd->nc_cqe.cqe_sf.sf_sc);
+ goto fail;
}
- nq.r = cmd->nc_cqe.cqe_dw0;
+ *res = cmd->nc_cqe.cqe_dw0;
+ ret = B_TRUE;
+
+fail:
nvme_free_cmd(cmd);
+ return (ret);
+}
+
+static boolean_t
+nvme_write_cache_set(nvme_t *nvme, boolean_t enable)
+{
+ nvme_write_cache_t nwc = { 0 };
+
+ if (enable)
+ nwc.b.wc_wce = 1;
+
+ if (!nvme_set_features(nvme, 0, NVME_FEAT_WRITE_CACHE, nwc.r, &nwc.r))
+ return (B_FALSE);
+
+ return (B_TRUE);
+}
+
+static int
+nvme_set_nqueues(nvme_t *nvme, uint16_t nqueues)
+{
+ nvme_nqueue_t nq = { 0 };
+
+ nq.b.nq_nsq = nq.b.nq_ncq = nqueues - 1;
+
+ if (!nvme_set_features(nvme, 0, NVME_FEAT_NQUEUES, nq.r, &nq.r)) {
+ return (0);
+ }
/*
* Always use the same number of submission and completion queues, and
@@ -2049,20 +2098,32 @@ nvme_init(nvme_t *nvme)
/*
* Check for the presence of a Volatile Write Cache. If present,
- * enable it by default.
+ * enable or disable based on the value of the property
+ * volatile-write-cache-enable (default is enabled).
*/
- if (nvme->n_idctl->id_vwc.vwc_present == 0) {
- nvme->n_volatile_write_cache_enabled = B_FALSE;
- nvme_bd_ops.o_sync_cache = NULL;
- } else {
+ nvme->n_write_cache_present =
+ nvme->n_idctl->id_vwc.vwc_present == 0 ? B_FALSE : B_TRUE;
+
+ (void) ddi_prop_update_int(DDI_DEV_T_NONE, nvme->n_dip,
+ "volatile-write-cache-present",
+ nvme->n_write_cache_present ? 1 : 0);
+
+ if (!nvme->n_write_cache_present) {
+ nvme->n_write_cache_enabled = B_FALSE;
+ } else if (!nvme_write_cache_set(nvme, nvme->n_write_cache_enabled)) {
+ dev_err(nvme->n_dip, CE_WARN,
+ "!failed to %sable volatile write cache",
+ nvme->n_write_cache_enabled ? "en" : "dis");
/*
- * TODO: send SET FEATURES to enable VWC
- * (have no hardware to test this)
+ * Assume the cache is (still) enabled.
*/
- nvme->n_volatile_write_cache_enabled = B_FALSE;
- nvme_bd_ops.o_sync_cache = NULL;
+ nvme->n_write_cache_enabled = B_TRUE;
}
+ (void) ddi_prop_update_int(DDI_DEV_T_NONE, nvme->n_dip,
+ "volatile-write-cache-enable",
+ nvme->n_write_cache_enabled ? 1 : 0);
+
/*
* Grab a copy of all mandatory log pages.
*
@@ -2427,6 +2488,9 @@ nvme_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
nvme->n_async_event_limit = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "async-event-limit",
NVME_DEFAULT_ASYNC_EVENT_LIMIT);
+ nvme->n_write_cache_enabled = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
+ DDI_PROP_DONTPASS, "volatile-write-cache-enable", 1) != 0 ?
+ B_TRUE : B_FALSE;
if (nvme->n_admin_queue_len < NVME_MIN_ADMIN_QUEUE_LEN)
nvme->n_admin_queue_len = NVME_MIN_ADMIN_QUEUE_LEN;
@@ -2889,14 +2953,19 @@ nvme_bd_sync(void *arg, bd_xfer_t *xfer)
return (EIO);
/*
- * If the volatile write cache isn't enabled the FLUSH command is a
- * no-op, so we can take a shortcut here.
+ * If the volatile write cache is not present or not enabled the FLUSH
+ * command is a no-op, so we can take a shortcut here.
*/
- if (ns->ns_nvme->n_volatile_write_cache_enabled == B_FALSE) {
+ if (!ns->ns_nvme->n_write_cache_present) {
bd_xfer_done(xfer, ENOTSUP);
return (0);
}
+ if (!ns->ns_nvme->n_write_cache_enabled) {
+ bd_xfer_done(xfer, 0);
+ return (0);
+ }
+
return (nvme_bd_cmd(ns, xfer, NVME_OPC_NVM_FLUSH));
}