* [PATCH 0/2] Add Opal unlock support to NVMe.
@ 2016-04-22 23:12 Rafael Antognolli
2016-04-22 23:12 ` [PATCH 1/2] Add optane OPAL unlocking code Rafael Antognolli
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Rafael Antognolli @ 2016-04-22 23:12 UTC (permalink / raw)
To: linux-nvme; +Cc: linux-kernel, linux-block, Rafael Antognolli
This patch series implement a small set of the Opal protocol for self
encrypting devices. It's implemented only what is needed for saving a password
and unlocking a given "locking range". The password is saved on the driver and
replayed back to the device on resume from suspend to RAM. It is specifically
supporting the single user mode.
It is not planned to implement the full Opal protocol (at least not for now).
Rafael Antognolli (2):
Add optane OPAL unlocking code.
NVMe: Add ioctls to save and unlock an Opal locking range.
drivers/nvme/host/Kconfig | 7 +
drivers/nvme/host/Makefile | 1 +
drivers/nvme/host/core.c | 9 +
drivers/nvme/host/opal.c | 1270 +++++++++++++++++++++++++++++++++++++
drivers/nvme/host/opal.h | 73 +++
drivers/nvme/host/opal_internal.h | 501 +++++++++++++++
include/uapi/linux/nvme_ioctl.h | 7 +
7 files changed, 1868 insertions(+)
create mode 100644 drivers/nvme/host/opal.c
create mode 100644 drivers/nvme/host/opal.h
create mode 100644 drivers/nvme/host/opal_internal.h
--
1.9.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/2] Add optane OPAL unlocking code.
2016-04-22 23:12 [PATCH 0/2] Add Opal unlock support to NVMe Rafael Antognolli
@ 2016-04-22 23:12 ` Rafael Antognolli
2016-04-22 23:12 ` [PATCH 2/2] NVMe: Add ioctls to save and unlock an Opal locking range Rafael Antognolli
2016-04-25 8:24 ` [PATCH 0/2] Add Opal unlock support to NVMe Christoph Hellwig
2 siblings, 0 replies; 7+ messages in thread
From: Rafael Antognolli @ 2016-04-22 23:12 UTC (permalink / raw)
To: linux-nvme; +Cc: linux-kernel, linux-block, Rafael Antognolli
This code is used to unlock a device during resume from "suspend to RAM". It
allows the userspace to set a key for a locking range. This key is stored in
the module memory, and will be replayed later (using the OPAL protocol, through
the NVMe driver) to unlock the locking range.
The nvme_opal_unlock() will search through the list of saved devices +
locking_range + namespaces + keys and check if it is a match for this
namespace. For every match, it adds an "unlocking job" to a list, and after
this, these jobs are "consumed" by running the respective OPAL "unlock range"
commands (from the OPAL spec):
* STARTSESSION
* SET(locking range, readwrite)
* ENDSESSION
Signed-off-by: Rafael Antognolli <rafael.antognolli@intel.com>
---
drivers/nvme/host/Kconfig | 7 +
drivers/nvme/host/Makefile | 1 +
drivers/nvme/host/opal.c | 1270 +++++++++++++++++++++++++++++++++++++
drivers/nvme/host/opal.h | 73 +++
drivers/nvme/host/opal_internal.h | 501 +++++++++++++++
5 files changed, 1852 insertions(+)
create mode 100644 drivers/nvme/host/opal.c
create mode 100644 drivers/nvme/host/opal.h
create mode 100644 drivers/nvme/host/opal_internal.h
diff --git a/drivers/nvme/host/Kconfig b/drivers/nvme/host/Kconfig
index c894841..f326c40 100644
--- a/drivers/nvme/host/Kconfig
+++ b/drivers/nvme/host/Kconfig
@@ -24,3 +24,10 @@ config BLK_DEV_NVME_SCSI
to say N here, unless you run a distro that abuses the SCSI
emulation to provide stable device names for mount by id, like
some OpenSuSE and SLES versions.
+
+config BLK_DEV_NVME_OPAL
+ bool "Opal encryption resume support"
+ depends on BLK_DEV_NVME
+ ---help---
+ Self encrypting devices that support the Opal specification need this
+ option enabled to support resume from S3 and S0ix states.
diff --git a/drivers/nvme/host/Makefile b/drivers/nvme/host/Makefile
index 9a3ca89..fdaa3cb 100644
--- a/drivers/nvme/host/Makefile
+++ b/drivers/nvme/host/Makefile
@@ -4,5 +4,6 @@ obj-$(CONFIG_BLK_DEV_NVME) += nvme.o
nvme-core-y := core.o
nvme-core-$(CONFIG_BLK_DEV_NVME_SCSI) += scsi.o
nvme-core-$(CONFIG_NVM) += lightnvm.o
+nvme-core-$(CONFIG_BLK_DEV_NVME_OPAL) += opal.o
nvme-y += pci.o
diff --git a/drivers/nvme/host/opal.c b/drivers/nvme/host/opal.c
new file mode 100644
index 0000000..6f14636
--- /dev/null
+++ b/drivers/nvme/host/opal.c
@@ -0,0 +1,1270 @@
+/*
+ * Copyright © 2016 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rafael Antognolli <rafael.antognolli@intel.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include "opal.h"
+#include "opal_internal.h"
+#include "nvme.h"
+
+#define KEY_MAX 256
+
+/* From include/linux/nvme.h. */
+#define SERIAL_MAX 20
+#define MODEL_MAX 40
+#define IO_BUFFER_LENGTH 2048
+
+#define MAX_TOKS 64
+
+struct nvme_opal_dev {
+ uint8_t serial[SERIAL_MAX];
+ uint8_t model[MODEL_MAX];
+ uint8_t key[KEY_MAX];
+ unsigned nsid;
+ uint8_t locking_range;
+ uint16_t comID;
+ struct list_head node;
+ struct kref refcount;
+};
+
+struct opal_job {
+ struct nvme_ns *ns;
+ struct nvme_opal_dev *dev;
+ struct list_head node;
+};
+
+struct opal_cmd {
+ uint32_t pos;
+ uint8_t cmd[IO_BUFFER_LENGTH];
+ uint8_t resp[IO_BUFFER_LENGTH];
+};
+
+/*
+ * On the parsed response, we don't store again the toks that are already
+ * stored in the response buffer. Instead, for each token, we just store a
+ * pointer to the position in the buffer where the token starts, and the size
+ * of the token in bytes.
+ */
+struct opal_resp_tok {
+ const uint8_t *pos;
+ size_t len;
+ enum OPAL_RESPONSE_TOKEN type;
+ enum OPAL_ATOM_WIDTH width;
+ union {
+ uint64_t u;
+ int64_t s;
+ } stored;
+};
+
+/*
+ * From the response header it's not possible to know how many tokens there are
+ * on the payload. So we hardcode that the maximum will be MAX_TOKS, and later
+ * if we start dealing with messages that have more than that, we can increase
+ * this number. This is done to avoid having to make two passes through the
+ * response, the first one counting how many tokens we have and the second one
+ * actually storing the positions.
+ */
+struct parsed_resp {
+ int num;
+ uint8_t buf[IO_BUFFER_LENGTH];
+ struct opal_resp_tok toks[MAX_TOKS];
+};
+
+static LIST_HEAD(opal_list);
+static DEFINE_MUTEX(opal_list_mutex);
+
+static struct opal_cmd *alloc_opal_cmd(void)
+{
+ struct opal_cmd *cmd;
+ cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+ if (!cmd)
+ return ERR_PTR(-ENOMEM);
+
+ cmd->pos = sizeof(struct opal_header);
+ return cmd;
+}
+
+static int _nvme_opal_submit_cmd(struct nvme_ns *ns, uint8_t opcode,
+ uint16_t comID, void *buffer,
+ size_t buflen)
+{
+ struct nvme_command c;
+ uint32_t protocol = 0x01;
+
+ memset(&c, 0, sizeof(c));
+ c.common.opcode = opcode;
+ c.common.nsid = ns->ns_id;
+ c.common.cdw10[0] = protocol << 24 | comID << 8;
+ c.common.cdw10[1] = buflen;
+
+ return nvme_submit_sync_cmd(ns->ctrl->admin_q, &c, buffer, buflen);
+}
+
+static void print_buffer(const uint8_t *ptr, uint32_t length)
+{
+#ifdef DEBUG
+ uint32_t i;
+
+ printk("OPAL: Printing buffer:\n");
+ for (i = 0; i < length; i++) {
+ printk("%02x", ptr[i]);
+ if ((i + 1) % 16 == 0)
+ printk("\n");
+ else if ((i + 1) % 4 == 0)
+ printk(" ");
+ }
+ printk("\n");
+#endif
+}
+
+static bool check_tper(const void *data)
+{
+ const struct d0_tper_features *tper = data;
+ uint8_t flags = tper->supported_features;
+
+ if (!(flags & 0x1)) {
+ pr_err("OPAL: TPer sync not supported. flags = %d\n",
+ tper->supported_features);
+ return false;
+ }
+
+ return true;
+}
+
+static bool check_locking(const void *data)
+{
+ const struct d0_locking_features *locking = data;
+ uint8_t flags = locking->supported_features;
+
+ pr_debug("OPAL: locking features:\n");
+ pr_debug("OPAL: supported: %d, enabled: %d, locked: %d\n",
+ flags & 0x1, (flags >> 1) & 0x1, (flags >> 2) & 0x1);
+ pr_debug("OPAL: media encryption: %d, MBR enabled: %d, MBR done: %d\n",
+ (flags >> 3) & 0x1, (flags >> 4) & 0x1, (flags >> 5) & 0x1);
+
+ return true;
+}
+
+static bool check_SUM(const void *data)
+{
+ const struct d0_single_user_mode *sum = data;
+ uint32_t nlo = be32_to_cpu(sum->num_locking_objects);
+
+ if (nlo == 0) {
+ pr_err("OPAL: need at least one locking object.\n");
+ return false;
+ }
+
+ pr_debug("OPAL: number of locking objects: %d\n", nlo);
+
+ return true;
+}
+
+static uint16_t get_comID_v100(const void *data)
+{
+ const struct d0_opal_v100 *v100 = data;
+ return be16_to_cpu(v100->baseComID);
+}
+
+static uint16_t get_comID_v200(const void *data)
+{
+ const struct d0_opal_v200 *v200 = data;
+ return be16_to_cpu(v200->baseComID);
+}
+
+static int __nvme_opal_discovery0(struct nvme_ns *ns,
+ uint16_t *comid,
+ uint8_t *d0_response)
+{
+ uint8_t lastRC;
+ const uint8_t *epos, *cpos;
+ const struct d0_header *hdr;
+ uint16_t comID = 0;
+ bool foundComID = false, supported = true, single_user = false;
+
+ if ((lastRC = _nvme_opal_submit_cmd(
+ ns, nvme_admin_security_recv, 0x0001,
+ d0_response, IO_BUFFER_LENGTH)) != 0) {
+ dev_err(ns->ctrl->dev, "OPAL: Sending discovery0 failed\n");
+ return -EFAULT;
+ }
+
+ epos = d0_response;
+ cpos = d0_response;
+ hdr = (struct d0_header *)d0_response;
+
+ print_buffer(d0_response, be32_to_cpu(hdr->length));
+
+ epos = epos + be32_to_cpu(hdr->length); /* end of buffer */
+ cpos = cpos + 48; /* current position on buffer */
+
+ while (cpos < epos && supported) {
+
+ const struct d0_features *body =
+ (const struct d0_features *)cpos;
+ switch (be16_to_cpu(body->code)) {
+ case FC_TPER:
+ supported = check_tper(body->features);
+ break;
+ case FC_LOCKING:
+ supported = check_locking(body->features);
+ break;
+ case FC_SINGLEUSER:
+ single_user = check_SUM(body->features);
+ break;
+ case FC_GEOMETRY:
+ case FC_ENTERPRISE:
+ case FC_DATASTORE:
+ /* We are only interested on the comID for now.
+ * Later when we need to check for more
+ * features, the check should be added here.
+ */
+ dev_dbg(ns->ctrl->dev,
+ "Found OPAL feature description: %d\n",
+ be16_to_cpu(body->code));
+ break;
+ case FC_OPALV100:
+ comID = get_comID_v100(body->features);
+ foundComID = true;
+ dev_info(ns->ctrl->dev, "Found OPAL v1\n");
+ break;
+ case FC_OPALV200:
+ comID = get_comID_v200(body->features);
+ foundComID = true;
+ dev_info(ns->ctrl->dev, "Found OPAL v2\n");
+ break;
+ default:
+ if (0xbfff <
+ be16_to_cpu(body->code)) {
+ /* vendor specific, just ignore */
+ } else {
+ dev_warn(ns->ctrl->dev,
+ "OPAL Unknown feature: %d\n",
+ be16_to_cpu(body->code));
+ }
+ }
+ cpos = cpos + (body->length + 4);
+ }
+
+ if (!supported) {
+ dev_err(ns->ctrl->dev,
+ "Device not supported\n");
+ return -EINVAL;
+ }
+
+ if (!single_user) {
+ dev_err(ns->ctrl->dev,
+ "Device doesn't support single user mode\n");
+ return -EINVAL;
+ }
+
+ if (!foundComID) {
+ dev_err(ns->ctrl->dev,
+ "Could not find OPAL comID for device\n");
+ dev_err(ns->ctrl->dev,
+ "OPAL kernel unlocking will be disabled\n");
+ return -EPERM;
+ }
+
+ *comid = comID;
+ return 0;
+}
+
+static int nvme_opal_discovery0(struct nvme_ns *ns,
+ uint16_t *comid)
+{
+ int ret;
+ uint8_t *d0_response = NULL;
+
+ d0_response = kzalloc(IO_BUFFER_LENGTH, GFP_KERNEL);
+ if (!d0_response)
+ return -ENOMEM;
+
+ ret = __nvme_opal_discovery0(ns, comid, d0_response);
+
+ kfree(d0_response);
+ return ret;
+}
+
+static int nvme_opal_send_cmd(struct nvme_ns *ns,
+ uint16_t comID, void *buf,
+ size_t buflen,
+ void *resp, size_t resplen)
+{
+ int ret;
+ struct opal_header *hdr = resp;
+ ret = _nvme_opal_submit_cmd(ns, nvme_admin_security_send, comID, buf,
+ buflen);
+
+ if (ret)
+ return ret;
+
+ do {
+ msleep(25);
+ memset(resp, 0, resplen);
+ ret = _nvme_opal_submit_cmd(ns,
+ nvme_admin_security_recv,
+ comID, resp, resplen);
+ } while ((ret == 0) && (hdr->cp.outstandingData != 0) &&
+ (hdr->cp.minTransfer == 0));
+
+ dev_dbg(ns->ctrl->dev,
+ "Sent OPAL command: ret=%d, outstanding=%d, minTransfer=%d\n",
+ ret, hdr->cp.outstandingData, hdr->cp.minTransfer);
+
+ return ret;
+}
+
+static ssize_t add_token_u8(struct opal_cmd *cmd, uint8_t tok)
+{
+ if (cmd->pos + sizeof(tok) >= IO_BUFFER_LENGTH)
+ return -EFAULT;
+ cmd->cmd[cmd->pos++] = tok;
+ return sizeof(tok);
+}
+
+#define ON_TOKEN_ERROR_RETURN(type, cmd, tok, ret) \
+ if (add_token_##type(cmd, tok) < 0) { \
+ pr_err("OPAL: [%s:%d] error building command buffer\n", \
+ __func__, __LINE__); \
+ return ret; \
+ }
+
+#define ON_TOKEN_ERROR_GOTO(type, cmd, tok, tag) \
+ if (add_token_##type(cmd, tok) < 0) { \
+ pr_err("OPAL: [%s:%d] error building command buffer\n", \
+ __func__, __LINE__); \
+ goto tag; \
+ }
+
+static uint8_t create_short_atom(int bytestring, int has_sign, int len)
+{
+ uint8_t atom;
+
+ atom = (1 << 7) | (bytestring << 5) | (has_sign << 4) | (len & 0xf);
+
+ return atom;
+}
+
+static ssize_t add_token_u64(struct opal_cmd *cmd, uint64_t number)
+{
+ int len;
+ uint8_t atom;
+
+ if (number < 64)
+ return add_token_u8(cmd, number);
+ else {
+ int i, startat;
+
+ if (number < 0x100)
+ len = 1;
+ else if (number < 0x10000)
+ len = 2;
+ else if (number < 0x100000000)
+ len = 4;
+ else
+ len = 8;
+
+ startat = len - 1;
+ atom = create_short_atom(0, 0, len);
+ ON_TOKEN_ERROR_RETURN(u8, cmd, atom, -EFAULT);
+
+ for (i = startat; i > -1; i--) {
+ uint8_t n = (number >> (i * 8)) & 0xff;
+ ON_TOKEN_ERROR_RETURN(u8, cmd, n, -EFAULT);
+ }
+ }
+
+ return len + 1;
+}
+
+static ssize_t add_token_uid(struct opal_cmd *cmd, enum OPAL_UID uid)
+{
+ ON_TOKEN_ERROR_RETURN(u8, cmd, OPAL_SHORT_BYTESTRING8, -EFAULT);
+
+ if (cmd->pos + 8 > IO_BUFFER_LENGTH)
+ return -EFAULT;
+ memcpy(&cmd->cmd[cmd->pos], &OPALUID[uid][0], 8);
+ cmd->pos += 8;
+
+ return 8;
+}
+
+static ssize_t add_token_method(struct opal_cmd *cmd, enum OPAL_METHOD method)
+{
+ ON_TOKEN_ERROR_RETURN(u8, cmd, OPAL_SHORT_BYTESTRING8, -EFAULT);
+
+ if (cmd->pos + 8 > IO_BUFFER_LENGTH)
+ return -EFAULT;
+ memcpy(&cmd->cmd[cmd->pos], &OPALMETHOD[method][0], 8);
+ cmd->pos += 8;
+
+ return 8;
+}
+
+static ssize_t add_token_range(struct opal_cmd *cmd, uint8_t lr)
+{
+ uint8_t *pos;
+ ON_TOKEN_ERROR_RETURN(u8, cmd, OPAL_SHORT_BYTESTRING8, -EFAULT);
+
+ if (cmd->pos + 8 > IO_BUFFER_LENGTH)
+ return -EFAULT;
+
+ pos = &cmd->cmd[cmd->pos];
+ memcpy(pos, &OPALUID[OPAL_LOCKINGRANGE_GLOBAL][0], 8);
+ if (lr == 0) {
+ cmd->pos += 8;
+ return 8;
+ }
+
+ pos[5] = 0x03;
+ cmd->pos += 7;
+ ON_TOKEN_ERROR_RETURN(u8, cmd, lr, -EFAULT);
+
+ return 8;
+}
+
+static ssize_t add_token_array(struct opal_cmd *cmd, const uint8_t *array,
+ size_t length)
+{
+ if (cmd->pos + length > IO_BUFFER_LENGTH)
+ return -EFAULT;
+ memcpy(&cmd->cmd[cmd->pos], &array[0], length);
+ cmd->pos += length;
+
+ return length;
+}
+
+static ssize_t add_token_bytestring(struct opal_cmd *cmd, const uint8_t *array,
+ size_t max_length)
+{
+ size_t length = strlen(array);
+
+ if (length > max_length)
+ length = max_length;
+
+ if (cmd->pos + length > IO_BUFFER_LENGTH)
+ return -EFAULT;
+
+ cmd->cmd[cmd->pos++] = 0xd0 | (uint8_t) ((length >> 8) & 0x07);
+ cmd->cmd[cmd->pos++] = (uint8_t) (length & 0xff);
+
+ memcpy(&cmd->cmd[cmd->pos], &array[0], length);
+ cmd->pos += length;
+
+ return length;
+}
+
+static void setComID(struct opal_cmd *cmd, uint16_t comID)
+{
+ struct opal_header *hdr = (struct opal_header *)&cmd->cmd[0];
+ hdr->cp.extendedComID[0] = ((comID & 0xff00) >> 8);
+ hdr->cp.extendedComID[1] = comID & 0x00ff;
+ hdr->cp.extendedComID[2] = 0;
+ hdr->cp.extendedComID[3] = 0;
+}
+
+static int cmd_finalize(struct opal_cmd *cmd, uint32_t hsn, uint32_t tsn)
+{
+ struct opal_header *hdr;
+
+ ON_TOKEN_ERROR_RETURN(u8, cmd, OPAL_ENDOFDATA, -EFAULT);
+ ON_TOKEN_ERROR_RETURN(u8, cmd, OPAL_STARTLIST, -EFAULT);
+ ON_TOKEN_ERROR_RETURN(u8, cmd, 0, -EFAULT);
+ ON_TOKEN_ERROR_RETURN(u8, cmd, 0, -EFAULT);
+ ON_TOKEN_ERROR_RETURN(u8, cmd, 0, -EFAULT);
+ ON_TOKEN_ERROR_RETURN(u8, cmd, OPAL_ENDLIST, -EFAULT);
+
+ hdr = (struct opal_header *) cmd->cmd;
+
+ hdr->pkt.TSN = cpu_to_be32(tsn);
+ hdr->pkt.HSN = cpu_to_be32(hsn);
+
+ hdr->subpkt.length = cpu_to_be32(cmd->pos - sizeof(*hdr));
+ while (cmd->pos % 4 != 0)
+ cmd->cmd[cmd->pos++] = 0;
+ hdr->pkt.length = cpu_to_be32(cmd->pos - sizeof(hdr->cp) -
+ sizeof(hdr->pkt));
+ hdr->cp.length = cpu_to_be32(cmd->pos - sizeof(hdr->cp));
+ if (cmd->pos > IO_BUFFER_LENGTH) {
+ pr_err("OPAL: buffer overrun\n");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static enum OPAL_RESPONSE_TOKEN token_type(const struct parsed_resp *resp,
+ int n)
+{
+ const struct opal_resp_tok *tok;
+
+ if (n >= resp->num) {
+ pr_err("OPAL: token number doesn't exist: %d, resp: %d\n",
+ n, resp->num);
+ return OPAL_DTA_TOKENID_INVALID;
+ }
+
+ tok = &resp->toks[n];
+ if (tok->len == 0) {
+ pr_err("OPAL: token length must be non-zero\n");
+ return OPAL_DTA_TOKENID_INVALID;
+ }
+
+ return tok->type;
+}
+
+/*
+ * This function returns 0 in case of invalid token. One should call
+ * token_type() first to find out if the token is valid or not.
+ */
+static enum OPAL_TOKEN response_get_token(const struct parsed_resp *resp,
+ int n)
+{
+ const struct opal_resp_tok *tok;
+
+ if (n >= resp->num) {
+ pr_err("OPAL: token number doesn't exist: %d, resp: %d\n",
+ n, resp->num);
+ return 0;
+ }
+
+ tok = &resp->toks[n];
+ if (tok->len == 0) {
+ pr_err("OPAL: token length must be non-zero\n");
+ return 0;
+ }
+
+ return tok->pos[0];
+}
+
+static size_t response_parse_tiny(struct opal_resp_tok *tok,
+ const uint8_t *pos)
+{
+ tok->pos = pos;
+ tok->len = 1;
+ tok->width = OPAL_WIDTH_TINY;
+
+ if (pos[0] & 0x40) {
+ tok->type = OPAL_DTA_TOKENID_SINT;
+ } else {
+ tok->type = OPAL_DTA_TOKENID_UINT;
+ tok->stored.u = pos[0] & 0x3f;
+ }
+
+ return tok->len;
+}
+
+static size_t response_parse_short(struct opal_resp_tok *tok,
+ const uint8_t *pos)
+{
+ tok->pos = pos;
+ tok->len = (pos[0] & 0x0f) + 1;
+ tok->width = OPAL_WIDTH_SHORT;
+
+ if (pos[0] & 0x20) {
+ tok->type = OPAL_DTA_TOKENID_BYTESTRING;
+ } else if (pos[0] & 0x10) {
+ tok->type = OPAL_DTA_TOKENID_SINT;
+ } else {
+ uint64_t whatever = 0;
+ int i, b = 0;
+ tok->type = OPAL_DTA_TOKENID_UINT;
+ if (tok->len > 9)
+ pr_warn("OPAL: uint64 with more than 8 bytes\n");
+ for (i = tok->len - 1; i > 0; i--) {
+ whatever |= (uint64_t)(pos[i] << (8 * b));
+ b++;
+ }
+ tok->stored.u = whatever;
+ }
+
+ return tok->len;
+}
+
+static size_t response_parse_medium(struct opal_resp_tok *tok,
+ const uint8_t *pos)
+{
+ tok->pos = pos;
+ tok->len = (((pos[0] & 0x07) << 8) | pos[1]) + 2;
+ tok->width = OPAL_WIDTH_MEDIUM;
+
+ if (pos[0] & 0x10)
+ tok->type = OPAL_DTA_TOKENID_BYTESTRING;
+ else if (pos[0] & 0x08)
+ tok->type = OPAL_DTA_TOKENID_SINT;
+ else
+ tok->type = OPAL_DTA_TOKENID_UINT;
+
+ return tok->len;
+}
+
+static size_t response_parse_long(struct opal_resp_tok *tok,
+ const uint8_t *pos)
+{
+ tok->pos = pos;
+ tok->len = ((pos[1] << 16) | (pos[2] << 8) | pos[3]) + 4;
+ tok->width = OPAL_WIDTH_LONG;
+
+ if (pos[0] & 0x02)
+ tok->type = OPAL_DTA_TOKENID_BYTESTRING;
+ else if (pos[0] & 0x01)
+ tok->type = OPAL_DTA_TOKENID_SINT;
+ else
+ tok->type = OPAL_DTA_TOKENID_UINT;
+
+ return tok->len;
+}
+
+static size_t response_parse_token(struct opal_resp_tok *tok,
+ const uint8_t *pos)
+{
+ tok->pos = pos;
+ tok->len = 1;
+ tok->type = OPAL_DTA_TOKENID_TOKEN;
+ tok->width = OPAL_WIDTH_TOKEN;
+
+ return tok->len;
+}
+
+static struct parsed_resp *response_parse(const void *buf, size_t length)
+{
+ const struct opal_header *hdr;
+ const uint8_t *pos;
+ int e, num_entries = 0;
+ uint32_t cpos = 0, total;
+ struct opal_resp_tok *iter;
+ struct parsed_resp *resp;
+ size_t token_length;
+
+ if (!buf)
+ return ERR_PTR(-EFAULT);
+
+ resp = kzalloc(sizeof(*resp), GFP_KERNEL);
+ if (!resp)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(resp->buf, buf, IO_BUFFER_LENGTH);
+
+ hdr = (struct opal_header *)resp->buf;
+ pos = resp->buf;
+ pos += sizeof(*hdr);
+
+ pr_debug("OPAL: response size: cp: %d, pkt: %d, subpkt: %d\n",
+ be32_to_cpu(hdr->cp.length),
+ be32_to_cpu(hdr->pkt.length),
+ be32_to_cpu(hdr->subpkt.length));
+
+ if ((hdr->cp.length == 0)
+ || (hdr->pkt.length == 0)
+ || (hdr->subpkt.length == 0)) {
+ pr_err("OPAL: bad header length. cp: %d, pkt: %d, subpkt: %d\n",
+ hdr->cp.length, hdr->pkt.length, hdr->subpkt.length);
+ e = -EINVAL;
+ goto err;
+ }
+
+ if (pos > resp->buf + length) {
+ e = -EFAULT;
+ goto err;
+ }
+
+ iter = resp->toks;
+ total = be32_to_cpu(hdr->subpkt.length);
+ print_buffer(pos, total);
+ while (cpos < total) {
+ if (!(pos[0] & 0x80)) /* tiny atom */
+ token_length = response_parse_tiny(iter, pos);
+ else if (!(pos[0] & 0x40)) /* short atom */
+ token_length = response_parse_short(iter, pos);
+ else if (!(pos[0] & 0x20)) /* medium atom */
+ token_length = response_parse_medium(iter, pos);
+ else if (!(pos[0] & 0x10)) /* long atom */
+ token_length = response_parse_long(iter, pos);
+ else /* TOKEN */
+ token_length = response_parse_token(iter, pos);
+
+ pos += token_length;
+ cpos += token_length;
+ iter++;
+ num_entries++;
+ }
+
+ if (num_entries == 0) {
+ pr_err("OPAL: couldn't parse response.\n");
+ e = -EINVAL;
+ goto err;
+ }
+ resp->num = num_entries;
+
+ return resp;
+err:
+ kfree(resp);
+ return ERR_PTR(e);
+}
+
+static uint64_t response_get_u64(const struct parsed_resp *resp, int n)
+{
+ if (!resp) {
+ pr_err("OPAL: response is NULL\n");
+ return 0;
+ }
+
+ if (n > resp->num) {
+ pr_err("OPAL: response has %d tokens. Can't access %d\n",
+ resp->num, n);
+ return 0;
+ }
+
+ if (resp->toks[n].type != OPAL_DTA_TOKENID_UINT) {
+ pr_err("OPAL: token is not unsigned it: %d\n",
+ resp->toks[n].type);
+ return 0;
+ }
+ if (!((resp->toks[n].width == OPAL_WIDTH_TINY) ||
+ (resp->toks[n].width == OPAL_WIDTH_SHORT))) {
+ pr_err("OPAL: atom is not short or tiny: %d\n",
+ resp->toks[n].width);
+ return 0;
+ }
+
+ return resp->toks[n].stored.u;
+}
+
+static uint8_t response_status(const struct parsed_resp *resp)
+{
+ if ((token_type(resp, 0) == OPAL_DTA_TOKENID_TOKEN)
+ && (response_get_token(resp, 0) == OPAL_ENDOFSESSION)) {
+ return 0;
+ }
+
+ if (resp->num < 5)
+ return DTAERROR_NO_METHOD_STATUS;
+
+ if ((token_type(resp, resp->num - 1) != OPAL_DTA_TOKENID_TOKEN) ||
+ (token_type(resp, resp->num - 5) != OPAL_DTA_TOKENID_TOKEN) ||
+ (response_get_token(resp, resp->num - 1) != OPAL_ENDLIST) ||
+ (response_get_token(resp, resp->num - 5) != OPAL_STARTLIST))
+ return DTAERROR_NO_METHOD_STATUS;
+
+ return response_get_u64(resp, resp->num - 4);
+}
+
+static int start_opal_session(struct nvme_ns *ns,
+ uint16_t comID,
+ uint8_t locking_range,
+ const uint8_t *key,
+ uint32_t *hsn, uint32_t *tsn)
+{
+ struct opal_cmd *cmd;
+ int ret;
+ uint32_t HSN, TSN;
+ struct parsed_resp *resp;
+
+ cmd = alloc_opal_cmd();
+ if (IS_ERR(cmd))
+ return PTR_ERR(cmd);
+
+ setComID(cmd, comID);
+
+ if (hsn)
+ HSN = *hsn;
+ else
+ HSN = 105;
+
+ /* STARTSESSION cmd buffer */
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_CALL, token_error);
+ ON_TOKEN_ERROR_GOTO(uid, cmd, OPAL_SMUID_UID, token_error);
+ ON_TOKEN_ERROR_GOTO(method, cmd, OPAL_STARTSESSION, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_STARTLIST, token_error);
+ ON_TOKEN_ERROR_GOTO(u64, cmd, HSN, token_error);
+ ON_TOKEN_ERROR_GOTO(uid, cmd, OPAL_LOCKINGSP_UID, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_TINY_UINT_01, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_STARTNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_TINY_UINT_00, token_error);
+ if (add_token_bytestring(cmd, key, KEY_MAX) < 0) {
+ pr_err("OPAL: [%s:%d] error building command buffer\n",
+ __func__, __LINE__);
+ goto token_error;
+ }
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_STARTNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_TINY_UINT_03, token_error);
+
+ /* construct Sign Authority for unlocking */
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_SHORT_BYTESTRING8, token_error);
+ if (add_token_array(cmd, &OPALUID[OPAL_USER1_UID][0], 7) < 0) {
+ pr_err("OPAL: [%s:%d] error building command buffer\n",
+ __func__, __LINE__);
+ goto token_error;
+ }
+ ON_TOKEN_ERROR_GOTO(u8, cmd, locking_range + 1, token_error);
+
+ /* finish cmd buffer */
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDLIST, token_error);
+ ret = cmd_finalize(cmd, 0, 0);
+ if (ret) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: building finalizing command buffer: %d\n", ret);
+ goto free_cmd;
+ }
+
+ print_buffer(&cmd->cmd[0], cmd->pos);
+ ret = nvme_opal_send_cmd(ns, comID,
+ cmd->cmd, IO_BUFFER_LENGTH,
+ cmd->resp, IO_BUFFER_LENGTH);
+ if (ret) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: Error running command: %d\n", ret);
+ goto free_cmd;
+ }
+
+ resp = response_parse(cmd->resp, IO_BUFFER_LENGTH);
+ if (IS_ERR(resp)) {
+ dev_err(ns->ctrl->dev, "OPAL: Couldn't parse response.\n");
+ ret = PTR_ERR(resp);
+ goto free_cmd;
+ }
+
+ if ((ret = response_status(resp)) != 0) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: Start session command status: %d\n", ret);
+ ret = -EINVAL;
+ goto free_resp;
+ }
+
+ HSN = response_get_u64(resp, 4);
+ TSN = response_get_u64(resp, 5);
+
+ if (hsn) *hsn = HSN;
+ if (tsn) *tsn = TSN;
+
+ if (HSN == 0 && TSN == 0) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: Couldn't authenticate session\n");
+ ret = -EFAULT;
+ }
+
+free_resp:
+ kfree(resp);
+free_cmd:
+ kfree(cmd);
+ return ret;
+
+token_error:
+ kfree(cmd);
+ return -EFAULT;
+}
+
+static int unlock_locking_range(struct nvme_ns *ns,
+ uint16_t comID,
+ uint8_t locking_range,
+ uint32_t hsn, uint32_t tsn)
+{
+ struct opal_cmd *cmd;
+ int ret;
+ struct parsed_resp *resp;
+
+ cmd = alloc_opal_cmd();
+ if (IS_ERR(cmd))
+ return PTR_ERR(cmd);
+
+ setComID(cmd, comID);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_CALL, token_error);
+ ON_TOKEN_ERROR_GOTO(range, cmd, locking_range, token_error);
+ ON_TOKEN_ERROR_GOTO(method, cmd, OPAL_SET, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_STARTLIST, token_error);
+
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_STARTNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_VALUES, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_STARTLIST, token_error);
+
+ /* enable locking on the range to enforce locking state */
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_STARTNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_READLOCKENABLED, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_TRUE, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_STARTNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_WRITELOCKENABLED, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_TRUE, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDNAME, token_error);
+
+ /* set read/write */
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_STARTNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_READLOCKED, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_FALSE, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_STARTNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_WRITELOCKED, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_FALSE, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDNAME, token_error);
+
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDLIST, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDNAME, token_error);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDLIST, token_error);
+
+ ret = cmd_finalize(cmd, hsn, tsn);
+ if (ret) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: building finalizing command buffer: %d\n", ret);
+ goto free_cmd;
+ }
+
+ print_buffer(&cmd->cmd[0], cmd->pos);
+
+ ret = nvme_opal_send_cmd(ns, comID,
+ cmd->cmd, IO_BUFFER_LENGTH,
+ cmd->resp, IO_BUFFER_LENGTH);
+ if (ret) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: Error running erase locking command: %d\n",
+ ret);
+ goto free_cmd;
+ }
+
+ resp = response_parse(cmd->resp, IO_BUFFER_LENGTH);
+ if (IS_ERR(resp)) {
+ dev_err(ns->ctrl->dev, "OPAL: Couldn't parse response.\n");
+ ret = PTR_ERR(resp);
+ goto free_cmd;
+ }
+
+ if ((ret = response_status(resp)) != 0) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: unlock command status: %d\n", ret);
+ ret = -EINVAL;
+ goto free_resp;
+ }
+
+free_resp:
+ kfree(resp);
+free_cmd:
+ kfree(cmd);
+ return ret;
+
+token_error:
+ kfree(cmd);
+ return -EFAULT;
+}
+
+static int end_opal_session(struct nvme_ns *ns,
+ uint16_t comID,
+ uint32_t hsn, uint32_t tsn)
+{
+ struct opal_cmd *cmd;
+ int ret;
+ struct parsed_resp *resp;
+
+ cmd = alloc_opal_cmd();
+ if (IS_ERR(cmd))
+ return PTR_ERR(cmd);
+
+ setComID(cmd, comID);
+ ON_TOKEN_ERROR_GOTO(u8, cmd, OPAL_ENDOFSESSION, token_error);
+ ret = cmd_finalize(cmd, hsn, tsn);
+ if (ret) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: building finalizing command buffer: %d\n", ret);
+ goto free_cmd;
+ }
+ print_buffer(&cmd->cmd[0], cmd->pos);
+
+ ret = nvme_opal_send_cmd(ns, comID,
+ cmd->cmd, IO_BUFFER_LENGTH,
+ cmd->resp, IO_BUFFER_LENGTH);
+ if (ret) {
+ dev_err(ns->ctrl->dev, "OPAL: Couldn't end session: %d\n", ret);
+ goto free_cmd;
+ }
+
+ resp = response_parse(cmd->resp, IO_BUFFER_LENGTH);
+ if (IS_ERR(resp)) {
+ dev_err(ns->ctrl->dev, "OPAL: Couldn't parse response\n");
+ ret = PTR_ERR(resp);
+ goto free_cmd;
+ }
+
+ if ((ret = response_status(resp)) != 0) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: end session command status: %d\n", ret);
+ ret = -EINVAL;
+ goto free_resp;
+ }
+
+free_resp:
+ kfree(resp);
+free_cmd:
+ kfree(cmd);
+ return ret;
+
+token_error:
+ kfree(cmd);
+ return -EFAULT;
+}
+
+#undef ON_TOKEN_ERROR_RETURN
+#undef ON_TOKEN_ERROR_GOTO
+
+static void release_opal_dev(struct kref *ref)
+{
+ struct nvme_opal_dev *opal_dev;
+ opal_dev = container_of(ref, struct nvme_opal_dev, refcount);
+ kfree(opal_dev);
+}
+
+static int unlock_opal_range_SUM(struct nvme_ns *ns,
+ struct nvme_opal_dev *opal_dev)
+{
+ int ret;
+ uint32_t hsn, tsn;
+ uint16_t comID = opal_dev->comID;
+ uint8_t lr = opal_dev->locking_range;
+ const uint8_t *key = opal_dev->key;
+
+ hsn = 105;
+
+ if ((ret = start_opal_session(ns, comID, lr, key, &hsn, &tsn)) != 0)
+ return ret;
+
+ if ((ret = unlock_locking_range(ns, comID, lr, hsn, tsn)) != 0)
+ return ret;
+
+ if ((ret = end_opal_session(ns, comID, hsn, tsn)) != 0)
+ return ret;
+
+ dev_info(ns->ctrl->dev,
+ "OPAL: successfully unlocked ns: %d, range: %d\n",
+ ns->ns_id, opal_dev->locking_range);
+
+ return ret;
+}
+
+static struct opal_job *opal_job_add(struct nvme_opal_dev *opal_dev,
+ struct list_head *list)
+{
+ struct opal_job *job;
+
+ job = kzalloc(sizeof(*job), GFP_KERNEL);
+ if (!job)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&job->node);
+ job->dev = opal_dev;
+ list_add_tail(&job->node, list);
+ kref_get(&job->dev->refcount);
+
+ return job;
+}
+
+static struct opal_job *get_next_unlock_job(struct list_head *list)
+{
+ struct opal_job *job;
+
+ job = list_first_entry_or_null(list, struct opal_job, node);
+ if (job)
+ list_del(&job->node);
+
+ return job;
+}
+
+int nvme_opal_unlock(struct nvme_ns *ns)
+{
+ struct nvme_opal_dev *iter;
+ struct opal_job *job;
+ LIST_HEAD(unlock_list);
+
+ mutex_lock(&opal_list_mutex);
+ list_for_each_entry(iter, &opal_list, node) {
+ if (!strncmp(iter->serial, ns->ctrl->serial,
+ sizeof(iter->serial))
+ && !strncmp(iter->model, ns->ctrl->model,
+ sizeof(iter->model))
+ && iter->nsid == ns->ns_id)
+ opal_job_add(iter, &unlock_list);
+ }
+ mutex_unlock(&opal_list_mutex);
+
+ while ((job = get_next_unlock_job(&unlock_list)) != NULL) {
+ unlock_opal_range_SUM(ns, job->dev);
+ kref_put(&job->dev->refcount, release_opal_dev);
+ kfree(job);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(nvme_opal_unlock);
+
+static struct nvme_opal_dev *alloc_opal_dev(struct nvme_ns *ns,
+ uint8_t locking_range,
+ uint16_t comID)
+{
+ struct nvme_opal_dev *opal_dev;
+ struct nvme_ctrl *ctrl = ns->ctrl;
+
+ opal_dev = kzalloc(sizeof(*opal_dev), GFP_KERNEL);
+ if (!opal_dev)
+ return ERR_PTR(-ENOMEM);
+
+ kref_init(&opal_dev->refcount);
+ INIT_LIST_HEAD(&opal_dev->node);
+
+ memcpy(opal_dev->serial, ctrl->serial, sizeof(opal_dev->serial));
+ memcpy(opal_dev->model, ctrl->model, sizeof(opal_dev->model));
+ opal_dev->nsid = ns->ns_id;
+ opal_dev->locking_range = locking_range;
+ opal_dev->comID = comID;
+
+ list_add_tail(&opal_dev->node, &opal_list);
+
+ return opal_dev;
+}
+
+static struct nvme_opal_dev *find_opal_dev(struct nvme_ns *ns,
+ uint8_t locking_range)
+{
+ struct nvme_opal_dev *iter, *opal_dev = NULL;
+ struct nvme_ctrl *ctrl = ns->ctrl;
+
+ list_for_each_entry(iter, &opal_list, node) {
+ if (!strncmp(iter->serial, ctrl->serial, sizeof(iter->serial))
+ && !strncmp(iter->model, ctrl->model, sizeof(iter->model))
+ && (iter->nsid == ns->ns_id)
+ && (iter->locking_range == locking_range)) {
+ opal_dev = iter;
+ break;
+ }
+ }
+
+ return opal_dev;
+}
+
+int nvme_opal_register(struct nvme_ns *ns,
+ struct nvme_opal_key __user *arg)
+{
+ struct nvme_opal_dev *opal_dev = NULL;
+ struct nvme_opal_key cmd;
+ int ret = 0;
+ uint32_t hsn = 105, tsn;
+ uint16_t comID;
+
+ if (copy_from_user(&cmd, arg, sizeof(cmd)))
+ return -EFAULT;
+
+ if ((ret = nvme_opal_discovery0(ns, &comID)) != 0) {
+ dev_err(ns->ctrl->dev, "OPAL: Discovery0 failed.\n");
+ return ret;
+ }
+
+ if ((ret = start_opal_session(ns, comID, cmd.locking_range, cmd.key,
+ &hsn, &tsn)) != 0) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: Could not authenticate key\n");
+ return ret;
+ }
+
+ if ((ret = end_opal_session(ns, comID, hsn, tsn)) != 0) {
+ dev_err(ns->ctrl->dev, "OPAL: Could not end session\n");
+ return ret;
+ }
+
+ mutex_lock(&opal_list_mutex);
+
+ opal_dev = find_opal_dev(ns, cmd.locking_range);
+ if (!opal_dev)
+ opal_dev = alloc_opal_dev(ns, cmd.locking_range, comID);
+
+ if (IS_ERR(opal_dev)) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: Error registering device: allocation\n");
+ ret = PTR_ERR(opal_dev);
+ goto unlock_exit;
+ }
+
+ if (!memcpy(opal_dev->key, cmd.key, sizeof(opal_dev->key))) {
+ dev_err(ns->ctrl->dev,
+ "OPAL: Error registering device: copying key\n");
+ ret = -EFAULT;
+ list_del(&opal_dev->node);
+ kref_put(&opal_dev->refcount, release_opal_dev);
+ }
+
+unlock_exit:
+ mutex_unlock(&opal_list_mutex);
+
+ if (ret == 0)
+ dev_info(ns->ctrl->dev,
+ "OPAL: Registered key for locking range: %d\n",
+ cmd.locking_range);
+
+ return ret;
+}
+EXPORT_SYMBOL(nvme_opal_register);
+
+void nvme_opal_unregister(struct nvme_ns *ns, uint8_t locking_range)
+{
+ struct nvme_opal_dev *opal_dev;
+
+ opal_dev = find_opal_dev(ns, locking_range);
+ if (!opal_dev)
+ return;
+
+ mutex_lock(&opal_list_mutex);
+ list_del(&opal_dev->node);
+ kref_put(&opal_dev->refcount, release_opal_dev);
+ mutex_unlock(&opal_list_mutex);
+}
+EXPORT_SYMBOL(nvme_opal_unregister);
+
+int __init nvme_opal_init(void)
+{
+ return 0;
+}
+EXPORT_SYMBOL(nvme_opal_init);
+
+static struct nvme_opal_dev *get_next_opal_dev(void)
+{
+ return list_first_entry_or_null(&opal_list, struct nvme_opal_dev, node);
+}
+
+void nvme_opal_exit(void)
+{
+ struct nvme_opal_dev *dev;
+
+ mutex_lock(&opal_list_mutex);
+ while ((dev = get_next_opal_dev()) != NULL) {
+ list_del(&dev->node);
+ kref_put(&dev->refcount, release_opal_dev);
+ }
+ mutex_unlock(&opal_list_mutex);
+
+}
+EXPORT_SYMBOL(nvme_opal_exit);
diff --git a/drivers/nvme/host/opal.h b/drivers/nvme/host/opal.h
new file mode 100644
index 0000000..66f0137
--- /dev/null
+++ b/drivers/nvme/host/opal.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2016 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rafael Antognolli <rafael.antognolli@intel.com>
+ */
+
+#ifndef _NVME_OPAL_H
+#define _NVME_OPAL_H
+
+#include <linux/nvme_ioctl.h>
+/* TODO: Put this inside ifdef.
+ * Need to make function parameters more opaque.
+ */
+#include "nvme.h"
+
+#ifdef CONFIG_BLK_DEV_NVME_OPAL
+
+int nvme_opal_init(void);
+void nvme_opal_exit(void);
+
+int nvme_opal_register(struct nvme_ns *ns, struct nvme_opal_key __user *arg);
+void nvme_opal_unregister(struct nvme_ns *ns, uint8_t locking_range);
+
+int nvme_opal_unlock(struct nvme_ns *ns);
+
+#else
+
+static inline int nvme_opal_init(void)
+{
+ return 0;
+}
+
+static inline void nvme_opal_exit(void)
+{
+}
+
+static inline int nvme_opal_register(struct nvme_ns *ns, struct nvme_opal_key __user *arg)
+{
+ return -ENOTTY;
+}
+
+static inline void nvme_opal_unregister(struct nvme_ns *ns, uint8_t locking_range)
+{
+}
+
+static inline int nvme_opal_unlock(struct nvme_ns *ns)
+{
+ return -ENOTTY;
+}
+
+#endif
+
+#endif /* _NVME_OPAL_H */
diff --git a/drivers/nvme/host/opal_internal.h b/drivers/nvme/host/opal_internal.h
new file mode 100644
index 0000000..7258f68
--- /dev/null
+++ b/drivers/nvme/host/opal_internal.h
@@ -0,0 +1,501 @@
+/*
+ * Copyright © 2016 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rafael Antognolli <rafael.antognolli@intel.com>
+ */
+
+#ifndef _NVME_OPAL_INTERNAL_H
+#define _NVME_OPAL_INTERNAL_H
+
+#include "opal.h"
+
+#define DTAERROR_NO_METHOD_STATUS 0x89
+
+/* User IDs used in the TCG storage SSCs */
+static const uint8_t OPALUID[][8] = {
+ /* users */
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}, /* session management */
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }, /* special "thisSP" syntax */
+ { 0x00, 0x00, 0x02, 0x05, 0x00, 0x00, 0x00, 0x01 }, /* Administrative SP */
+ { 0x00, 0x00, 0x02, 0x05, 0x00, 0x00, 0x00, 0x02 }, /* Locking SP */
+ { 0x00, 0x00, 0x02, 0x05, 0x00, 0x01, 0x00, 0x01 }, /* ENTERPRISE Locking SP */
+ { 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01 }, /* anybody */
+ { 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06 }, /* SID */
+ { 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x00, 0x01 }, /* ADMIN1 */
+ { 0x00, 0x00, 0x00, 0x09, 0x00, 0x03, 0x00, 0x01 }, /* USER1 */
+ { 0x00, 0x00, 0x00, 0x09, 0x00, 0x03, 0x00, 0x02 }, /* USER2 */
+ { 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0xff, 0x01 }, /* PSID user */
+ { 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x80, 0x01 }, /* BandMaster 0 */
+ { 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x84, 0x01 }, /* EraseMaster */
+ /* tables */
+ { 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x01 }, /* Locking_GlobalRange */
+ { 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0xE0, 0x01 }, /* ACE_Locking_Range_Set_RdLocked UID */
+ { 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0xE8, 0x01 }, /* ACE_Locking_Range_Set_WrLocked UID */
+ { 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x00, 0x01 }, /* MBR Control */
+ { 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00 }, /* Shadow MBR */
+ { 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00}, /* AUTHORITY_TABLE */
+ { 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00}, /* C_PIN_TABLE */
+ { 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x01 }, /* OPAL Locking Info */
+ { 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00 }, /* Enterprise Locking Info */
+ /* C_PIN_TABLE object ID's */
+ { 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x84, 0x02}, /* C_PIN_MSID */
+ { 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01}, /* C_PIN_SID */
+ { 0x00, 0x00, 0x00, 0x0B, 0x00, 0x01, 0x00, 0x01}, /* C_PIN_ADMIN1 */
+ /* half UID's (only first 4 bytes used) */
+ { 0x00, 0x00, 0x0C, 0x05, 0xff, 0xff, 0xff, 0xff }, /* Half-UID – Authority_object_ref */
+ { 0x00, 0x00, 0x04, 0x0E, 0xff, 0xff, 0xff, 0xff }, /* Half-UID – Boolean ACE */
+ /* special value for omitted optional parameter */
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, /* HEXFF for omitted */
+};
+
+/* Enum to index OPALUID array */
+enum OPAL_UID {
+ /* users */
+ OPAL_SMUID_UID,
+ OPAL_THISSP_UID,
+ OPAL_ADMINSP_UID,
+ OPAL_LOCKINGSP_UID,
+ OPAL_ENTERPRISE_LOCKINGSP_UID,
+ OPAL_ANYBODY_UID,
+ OPAL_SID_UID,
+ OPAL_ADMIN1_UID,
+ OPAL_USER1_UID,
+ OPAL_USER2_UID,
+ OPAL_PSID_UID,
+ OPAL_ENTERPRISE_BANDMASTER0_UID,
+ OPAL_ENTERPRISE_ERASEMASTER_UID,
+ /* tables */
+ OPAL_LOCKINGRANGE_GLOBAL,
+ OPAL_LOCKINGRANGE_ACE_RDLOCKED,
+ OPAL_LOCKINGRANGE_ACE_WRLOCKED,
+ OPAL_MBRCONTROL,
+ OPAL_MBR,
+ OPAL_AUTHORITY_TABLE,
+ OPAL_C_PIN_TABLE,
+ OPAL_LOCKING_INFO_TABLE,
+ OPAL_ENTERPRISE_LOCKING_INFO_TABLE,
+ /* C_PIN_TABLE object ID's */
+ OPAL_C_PIN_MSID,
+ OPAL_C_PIN_SID,
+ OPAL_C_PIN_ADMIN1,
+ /* half UID's (only first 4 bytes used) */
+ OPAL_HALF_UID_AUTHORITY_OBJ_REF,
+ OPAL_HALF_UID_BOOLEAN_ACE,
+ /* omitted optional parameter */
+ OPAL_UID_HEXFF,
+};
+
+/*
+ * TCG Storage SSC Methods.
+ */
+static const uint8_t OPALMETHOD[][8] = {
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01 }, /* Properties */
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x02 }, /* STARTSESSION */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x02, 0x02 }, /* Revert */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x02, 0x03 }, /* Activate */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06 }, /* Enterprise Get */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07 }, /* Enterprise Set */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08 }, /* NEXT */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0c }, /* Enterprise Authenticate */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0d }, /* GetACL */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x10 }, /* GenKey */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11 }, /* revertSP */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x16 }, /* Get */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17 }, /* Set */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1c }, /* Authenticate */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x01 }, /* Random */
+ { 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x08, 0x03 }, /* Erase */
+};
+
+/* Enum for indexing the OPALMETHOD array */
+enum OPAL_METHOD {
+ OPAL_PROPERTIES,
+ OPAL_STARTSESSION,
+ OPAL_REVERT,
+ OPAL_ACTIVATE,
+ OPAL_EGET,
+ OPAL_ESET,
+ OPAL_NEXT,
+ OPAL_EAUTHENTICATE,
+ OPAL_GETACL,
+ OPAL_GENKEY,
+ OPAL_REVERTSP,
+ OPAL_GET,
+ OPAL_SET,
+ OPAL_AUTHENTICATE,
+ OPAL_RANDOM,
+ OPAL_ERASE,
+};
+
+enum OPAL_RESPONSE_TOKEN {
+ OPAL_DTA_TOKENID_BYTESTRING = 0xe0,
+ OPAL_DTA_TOKENID_SINT = 0xe1,
+ OPAL_DTA_TOKENID_UINT = 0xe2,
+ OPAL_DTA_TOKENID_TOKEN = 0xe3, /* actual token is returned */
+ OPAL_DTA_TOKENID_INVALID = 0X0
+};
+
+/*
+ * TODO: Split this enum
+ * Apparently these different types of enums were grouped together so they
+ * could be matched in a single C++ method. Since we don't have polymorphism in
+ * C, and we are going to cast it to uint8_t anyway, there's no need to keep
+ * these enums, and we can split them into separate declarations.
+ */
+enum OPAL_TOKEN {
+ /* Boolean */
+ OPAL_TRUE = 0x01,
+ OPAL_FALSE = 0x00,
+ OPAL_BOOLEAN_EXPR = 0x03,
+ /* cellblocks */
+ OPAL_TABLE = 0x00,
+ OPAL_STARTROW = 0x01,
+ OPAL_ENDROW = 0x02,
+ OPAL_STARTCOLUMN = 0x03,
+ OPAL_ENDCOLUMN = 0x04,
+ OPAL_VALUES = 0x01,
+ /* authority table */
+ OPAL_PIN = 0x03,
+ /* locking tokens */
+ OPAL_RANGESTART = 0x03,
+ OPAL_RANGELENGTH = 0x04,
+ OPAL_READLOCKENABLED = 0x05,
+ OPAL_WRITELOCKENABLED = 0x06,
+ OPAL_READLOCKED = 0x07,
+ OPAL_WRITELOCKED = 0x08,
+ OPAL_ACTIVEKEY = 0x0A,
+ /* locking info table */
+ OPAL_MAXRANGES = 0x04,
+ /* mbr control */
+ OPAL_MBRENABLE = 0x01,
+ OPAL_MBRDONE = 0x02,
+ /* properties */
+ OPAL_HOSTPROPERTIES =0x00,
+ /* atoms */
+ OPAL_STARTLIST = 0xf0,
+ OPAL_ENDLIST = 0xf1,
+ OPAL_STARTNAME = 0xf2,
+ OPAL_ENDNAME = 0xf3,
+ OPAL_CALL = 0xf8,
+ OPAL_ENDOFDATA = 0xf9,
+ OPAL_ENDOFSESSION = 0xfa,
+ OPAL_STARTTRANSACTON = 0xfb,
+ OPAL_ENDTRANSACTON = 0xfC,
+ OPAL_EMPTYATOM = 0xff,
+ OPAL_WHERE = 0x00,
+};
+
+/* Useful tiny atoms.
+ * Useful for table columns etc
+ */
+enum OPAL_TINY_ATOM {
+ OPAL_TINY_UINT_00 = 0x00,
+ OPAL_TINY_UINT_01 = 0x01,
+ OPAL_TINY_UINT_02 = 0x02,
+ OPAL_TINY_UINT_03 = 0x03,
+ OPAL_TINY_UINT_04 = 0x04,
+ OPAL_TINY_UINT_05 = 0x05,
+ OPAL_TINY_UINT_06 = 0x06,
+ OPAL_TINY_UINT_07 = 0x07,
+ OPAL_TINY_UINT_08 = 0x08,
+ OPAL_TINY_UINT_09 = 0x09,
+ OPAL_TINY_UINT_10 = 0x0a,
+ OPAL_TINY_UINT_11 = 0x0b,
+ OPAL_TINY_UINT_12 = 0x0c,
+ OPAL_TINY_UINT_13 = 0x0d,
+ OPAL_TINY_UINT_14 = 0x0e,
+ OPAL_TINY_UINT_15 = 0x0f,
+};
+
+/*
+ * Useful short atoms.
+ */
+enum OPAL_SHORT_ATOM {
+ OPAL_SHORT_UINT_3 = 0x83,
+ OPAL_SHORT_BYTESTRING4 = 0xa4,
+ OPAL_SHORT_BYTESTRING8 = 0xa8,
+};
+
+enum OPAL_ATOM_WIDTH {
+ OPAL_WIDTH_TINY,
+ OPAL_WIDTH_SHORT,
+ OPAL_WIDTH_MEDIUM,
+ OPAL_WIDTH_LONG,
+ OPAL_WIDTH_TOKEN
+};
+
+/* Locking state for a locking range */
+enum OPAL_LOCKINGSTATE {
+ OPAL_LOCKING_READWRITE = 0x01,
+ OPAL_LOCKING_READONLY = 0x02,
+ OPAL_LOCKING_LOCKED = 0x03,
+};
+
+/*
+ * Structures to build and decode the Opal SSC messages
+ * fields that are NOT really numeric are defined as uint8_t[] to
+ * help reduce the endianess issues
+ */
+
+/* Comm Packet (header) for transmissions. */
+struct opal_compacket {
+ uint32_t reserved0;
+ uint8_t extendedComID[4];
+ uint32_t outstandingData;
+ uint32_t minTransfer;
+ uint32_t length;
+};
+
+/* Packet structure. */
+struct opal_packet {
+ uint32_t TSN;
+ uint32_t HSN;
+ uint32_t seq_number;
+ uint16_t reserved0;
+ uint16_t ack_type;
+ uint32_t acknowledgement;
+ uint32_t length;
+};
+
+/* Data sub packet header */
+struct opal_data_subpacket {
+ uint8_t reserved0[6];
+ uint16_t kind;
+ uint32_t length;
+};
+
+/* header of a response */
+struct opal_header {
+ struct opal_compacket cp;
+ struct opal_packet pkt;
+ struct opal_data_subpacket subpkt;
+};
+
+#define FC_TPER 0x0001
+#define FC_LOCKING 0x0002
+#define FC_GEOMETRY 0x0003
+#define FC_ENTERPRISE 0x0100
+#define FC_DATASTORE 0x0202
+#define FC_SINGLEUSER 0x0201
+#define FC_OPALV100 0x0200
+#define FC_OPALV200 0x0203
+
+/*
+ * The Discovery 0 Header. As defined in
+ * Opal SSC Documentation
+ */
+struct d0_header {
+ uint32_t length; /* the length of the header 48 in 2.00.100 */
+ uint32_t revision; /**< revision of the header 1 in 2.00.100 */
+ uint32_t reserved01;
+ uint32_t reserved02;
+ /*
+ * the remainder of the structure is vendor specific and will not be
+ * addressed now
+ */
+};
+
+/*
+ * TPer Feature Descriptor. Contains flags indicating support for the
+ * TPer features described in the OPAL specification. The names match the
+ * OPAL terminology
+ *
+ * code == 0x001 in 2.00.100
+ */
+struct d0_tper_features {
+ /*
+ * supported_features bits:
+ * bit 7: reserved
+ * bit 6: com ID management
+ * bit 5: reserved
+ * bit 4: streaming support
+ * bit 3: buffer management
+ * bit 2: ACK/NACK
+ * bit 1: async
+ * bit 0: sync
+ */
+ uint8_t supported_features;
+ /*
+ * bytes 5 through 15 are reserved, but we represent the first 3 as
+ * uint8_t to keep the other two 32bits integers aligned.
+ */
+ uint8_t reserved01[3];
+ uint32_t reserved02;
+ uint32_t reserved03;
+};
+
+/*
+ * Locking Feature Descriptor. Contains flags indicating support for the
+ * locking features described in the OPAL specification. The names match the
+ * OPAL terminology
+ *
+ * code == 0x0002 in 2.00.100
+ */
+struct d0_locking_features {
+ /*
+ * supported_features bits:
+ * bits 6-7: reserved
+ * bit 5: MBR done
+ * bit 4: MBR enabled
+ * bit 3: media encryption
+ * bit 2: locked
+ * bit 1: locking enabled
+ * bit 0: locking supported
+ */
+ uint8_t supported_features;
+ /*
+ * bytes 5 through 15 are reserved, but we represent the first 3 as
+ * uint8_t to keep the other two 32bits integers aligned.
+ */
+ uint8_t reserved01[3];
+ uint32_t reserved02;
+ uint32_t reserved03;
+};
+
+/*
+ * Geometry Feature Descriptor. Contains flags indicating support for the
+ * geometry features described in the OPAL specification. The names match the
+ * OPAL terminology
+ *
+ * code == 0x0003 in 2.00.100
+ */
+struct d0_geometry_features {
+ /*
+ * reserved01:
+ * bits 1-6: reserved
+ * bit 0: align
+ */
+ uint8_t reserved01;
+ uint8_t reserved02[7];
+ uint32_t logical_block_size;
+ uint64_t alignment_granularity;
+ uint64_t lowest_aligned_lba;
+};
+
+/*
+ * Enterprise SSC Feature
+ *
+ * code == 0x0100
+ */
+struct d0_enterprise_ssc {
+ uint16_t baseComID;
+ uint16_t numComIDs;
+ /* range_crossing:
+ * bits 1-6: reserved
+ * bit 0: range crossing
+ */
+ uint8_t range_crossing;
+ uint8_t reserved01;
+ uint16_t reserved02;
+ uint32_t reserved03;
+ uint32_t reserved04;
+};
+
+/*
+ * Opal V1 feature
+ *
+ * code == 0x0200
+ */
+struct d0_opal_v100 {
+ uint16_t baseComID;
+ uint16_t numComIDs;
+};
+
+/*
+ * Single User Mode feature
+ *
+ * code == 0x0201
+ */
+struct d0_single_user_mode {
+ uint32_t num_locking_objects;
+ /* reserved01:
+ * bit 0: any
+ * bit 1: all
+ * bit 2: policy
+ * bits 3-7: reserved
+ */
+ uint8_t reserved01;
+ uint8_t reserved02;
+ uint16_t reserved03;
+ uint32_t reserved04;
+};
+
+/*
+ * Additonal Datastores feature
+ *
+ * code == 0x0202
+ */
+struct d0_datastore_table {
+ uint16_t reserved01;
+ uint16_t max_tables;
+ uint32_t max_size_tables;
+ uint32_t table_size_alignment;
+};
+
+/*
+ * OPAL 2.0 feature
+ *
+ * code == 0x0203
+ */
+struct d0_opal_v200 {
+ uint16_t baseComID;
+ uint16_t numComIDs;
+ /* range_crossing:
+ * bits 1-6: reserved
+ * bit 0: range crossing
+ */
+ uint8_t range_crossing;
+ /* num_locking_admin_auth:
+ * not aligned to 16 bits, so use two uint8_t.
+ * stored in big endian:
+ * 0: MSB
+ * 1: LSB
+ */
+ uint8_t num_locking_admin_auth[2];
+ /* num_locking_user_auth:
+ * not aligned to 16 bits, so use two uint8_t.
+ * stored in big endian:
+ * 0: MSB
+ * 1: LSB
+ */
+ uint8_t num_locking_user_auth[2];
+ uint8_t initialPIN;
+ uint8_t revertedPIN;
+ uint8_t reserved01;
+ uint32_t reserved02;
+};
+
+/* Union of features used to parse the discovery 0 response */
+struct d0_features {
+ uint16_t code;
+ /*
+ * r_version bits:
+ * bits 4-7: version
+ * bits 0-3: reserved
+ */
+ uint8_t r_version;
+ uint8_t length;
+ uint8_t features[];
+};
+
+#endif /* _NVME_OPAL_INTERNAL_H */
--
1.9.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/2] NVMe: Add ioctls to save and unlock an Opal locking range.
2016-04-22 23:12 [PATCH 0/2] Add Opal unlock support to NVMe Rafael Antognolli
2016-04-22 23:12 ` [PATCH 1/2] Add optane OPAL unlocking code Rafael Antognolli
@ 2016-04-22 23:12 ` Rafael Antognolli
2016-04-25 8:24 ` [PATCH 0/2] Add Opal unlock support to NVMe Christoph Hellwig
2 siblings, 0 replies; 7+ messages in thread
From: Rafael Antognolli @ 2016-04-22 23:12 UTC (permalink / raw)
To: linux-nvme; +Cc: linux-kernel, linux-block, Rafael Antognolli
Two ioctls are added to the NVMe namespace: NVME_IOCTL_SAVE_OPAL_KEY and
NVME_IOCTL_UNLOCK_OPAL. These ioctls map directly to the respective
nvme_opal_register() and nvme_opal_unlock() functions.
Additionally, nvme_opal_unlock() is called upon nvme_revalidate_disk, so it
will try to unlock a locking range (if a password for it is saved) during PM
resume.
Signed-off-by: Rafael Antognolli <rafael.antognolli@intel.com>
---
drivers/nvme/host/core.c | 9 +++++++++
include/uapi/linux/nvme_ioctl.h | 7 +++++++
2 files changed, 16 insertions(+)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 643f457..1f4b78c 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -30,6 +30,7 @@
#include <asm/unaligned.h>
#include "nvme.h"
+#include "opal.h"
#define NVME_MINORS (1U << MINORBITS)
@@ -517,6 +518,10 @@ static int nvme_ioctl(struct block_device *bdev, fmode_t mode,
case SG_IO:
return nvme_sg_io(ns, (void __user *)arg);
#endif
+ case NVME_IOCTL_SAVE_OPAL_KEY:
+ return nvme_opal_register(ns, (void __user *)arg);
+ case NVME_IOCTL_UNLOCK_OPAL:
+ return nvme_opal_unlock(ns);
default:
return -ENOTTY;
}
@@ -675,6 +680,7 @@ static int nvme_revalidate_disk(struct gendisk *disk)
if (ns->ctrl->oncs & NVME_CTRL_ONCS_DSM)
nvme_config_discard(ns);
blk_mq_unfreeze_queue(disk->queue);
+ nvme_opal_unlock(ns);
kfree(id);
return 0;
@@ -1596,6 +1602,8 @@ int __init nvme_core_init(void)
goto unregister_chrdev;
}
+ nvme_opal_init();
+
return 0;
unregister_chrdev:
@@ -1607,6 +1615,7 @@ int __init nvme_core_init(void)
void nvme_core_exit(void)
{
+ nvme_opal_exit();
unregister_blkdev(nvme_major, "nvme");
class_destroy(nvme_class);
__unregister_chrdev(nvme_char_major, 0, NVME_MINORS, "nvme");
diff --git a/include/uapi/linux/nvme_ioctl.h b/include/uapi/linux/nvme_ioctl.h
index c4b2a3f..8f8ab02 100644
--- a/include/uapi/linux/nvme_ioctl.h
+++ b/include/uapi/linux/nvme_ioctl.h
@@ -53,6 +53,11 @@ struct nvme_passthru_cmd {
__u32 result;
};
+struct nvme_opal_key {
+ __u8 locking_range;
+ __u8 key[256];
+};
+
#define nvme_admin_cmd nvme_passthru_cmd
#define NVME_IOCTL_ID _IO('N', 0x40)
@@ -61,5 +66,7 @@ struct nvme_passthru_cmd {
#define NVME_IOCTL_IO_CMD _IOWR('N', 0x43, struct nvme_passthru_cmd)
#define NVME_IOCTL_RESET _IO('N', 0x44)
#define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45)
+#define NVME_IOCTL_SAVE_OPAL_KEY _IOW('N', 0X46, struct nvme_opal_key)
+#define NVME_IOCTL_UNLOCK_OPAL _IO('N', 0X47)
#endif /* _UAPI_LINUX_NVME_IOCTL_H */
--
1.9.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 0/2] Add Opal unlock support to NVMe.
2016-04-22 23:12 [PATCH 0/2] Add Opal unlock support to NVMe Rafael Antognolli
2016-04-22 23:12 ` [PATCH 1/2] Add optane OPAL unlocking code Rafael Antognolli
2016-04-22 23:12 ` [PATCH 2/2] NVMe: Add ioctls to save and unlock an Opal locking range Rafael Antognolli
@ 2016-04-25 8:24 ` Christoph Hellwig
2016-04-26 3:29 ` Elliott, Robert (Persistent Memory)
2 siblings, 1 reply; 7+ messages in thread
From: Christoph Hellwig @ 2016-04-25 8:24 UTC (permalink / raw)
To: Rafael Antognolli; +Cc: linux-nvme, linux-kernel, linux-block
On Fri, Apr 22, 2016 at 04:12:10PM -0700, Rafael Antognolli wrote:
> This patch series implement a small set of the Opal protocol for self
> encrypting devices. It's implemented only what is needed for saving a password
> and unlocking a given "locking range". The password is saved on the driver and
> replayed back to the device on resume from suspend to RAM. It is specifically
> supporting the single user mode.
>
> It is not planned to implement the full Opal protocol (at least not for now).
I think the OPAL code should be a generic library outside the NVMe
code so that we can use it for SATA and SAS as well, just with a little
glue code for the Security Send / Receive commands to wire it up to
NVMe.
^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [PATCH 0/2] Add Opal unlock support to NVMe.
2016-04-25 8:24 ` [PATCH 0/2] Add Opal unlock support to NVMe Christoph Hellwig
@ 2016-04-26 3:29 ` Elliott, Robert (Persistent Memory)
2016-04-26 21:33 ` Rafael Antognolli
0 siblings, 1 reply; 7+ messages in thread
From: Elliott, Robert (Persistent Memory) @ 2016-04-26 3:29 UTC (permalink / raw)
To: Christoph Hellwig, Rafael Antognolli
Cc: linux-nvme@lists.infradead.org, linux-kernel@vger.kernel.org,
linux-block@vger.kernel.org
> -----Original Message-----
> From: linux-block-owner@vger.kernel.org [mailto:linux-block-
> owner@vger.kernel.org] On Behalf Of Christoph Hellwig
> Sent: Monday, April 25, 2016 3:24 AM
> To: Rafael Antognolli <rafael.antognolli@intel.com>
> Cc: linux-nvme@lists.infradead.org; linux-kernel@vger.kernel.org;
> linux-block@vger.kernel.org
> Subject: Re: [PATCH 0/2] Add Opal unlock support to NVMe.
>
> On Fri, Apr 22, 2016 at 04:12:10PM -0700, Rafael Antognolli wrote:
> > This patch series implement a small set of the Opal protocol for
> > self encrypting devices. It's implemented only what is needed for
> > saving a password and unlocking a given "locking range". The
> > password is saved on the driver and replayed back to the device
> > on resume from suspend to RAM. It is specifically supporting
> > the single user mode.
Passwords stored in memory are subject to cold boot attacks.
Could you tie this into the keyring infrastructure, so it would
least be no worse than other kernel modules? This would allow
support for TPM-based keys (if present) to resist more attacks.
If register-based key storage or other techniques prove viable,
they would probably show up there first.
> > It is not planned to implement the full Opal protocol (at least
> > not for now).
>
> I think the OPAL code should be a generic library outside the NVMe
> code so that we can use it for SATA and SAS as well, just with a
> little glue code for the Security Send / Receive commands to wire
> it up to NVMe.
NVDIMMs would benefit from that as well.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 0/2] Add Opal unlock support to NVMe.
2016-04-26 3:29 ` Elliott, Robert (Persistent Memory)
@ 2016-04-26 21:33 ` Rafael Antognolli
2016-05-18 23:54 ` Rafael Antognolli
0 siblings, 1 reply; 7+ messages in thread
From: Rafael Antognolli @ 2016-04-26 21:33 UTC (permalink / raw)
To: Elliott, Robert (Persistent Memory)
Cc: Christoph Hellwig, linux-nvme@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-block@vger.kernel.org
On Mon, Apr 25, 2016 at 08:29:22PM -0700, Elliott, Robert (Persistent Memory) wrote:
>
>
> > -----Original Message-----
> > From: linux-block-owner@vger.kernel.org [mailto:linux-block-
> > owner@vger.kernel.org] On Behalf Of Christoph Hellwig
> > Sent: Monday, April 25, 2016 3:24 AM
> > To: Rafael Antognolli <rafael.antognolli@intel.com>
> > Cc: linux-nvme@lists.infradead.org; linux-kernel@vger.kernel.org;
> > linux-block@vger.kernel.org
> > Subject: Re: [PATCH 0/2] Add Opal unlock support to NVMe.
> >
> > On Fri, Apr 22, 2016 at 04:12:10PM -0700, Rafael Antognolli wrote:
> > > This patch series implement a small set of the Opal protocol for
> > > self encrypting devices. It's implemented only what is needed for
> > > saving a password and unlocking a given "locking range". The
> > > password is saved on the driver and replayed back to the device
> > > on resume from suspend to RAM. It is specifically supporting
> > > the single user mode.
>
> Passwords stored in memory are subject to cold boot attacks.
>
> Could you tie this into the keyring infrastructure, so it would
> least be no worse than other kernel modules? This would allow
> support for TPM-based keys (if present) to resist more attacks.
> If register-based key storage or other techniques prove viable,
> they would probably show up there first.
I'll take a look at it.
> > > It is not planned to implement the full Opal protocol (at least
> > > not for now).
> >
> > I think the OPAL code should be a generic library outside the NVMe
> > code so that we can use it for SATA and SAS as well, just with a
> > little glue code for the Security Send / Receive commands to wire
> > it up to NVMe.
>
> NVDIMMs would benefit from that as well.
Yes, I can definitely change it to be that generic.
Thank you,
Rafael
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 0/2] Add Opal unlock support to NVMe.
2016-04-26 21:33 ` Rafael Antognolli
@ 2016-05-18 23:54 ` Rafael Antognolli
0 siblings, 0 replies; 7+ messages in thread
From: Rafael Antognolli @ 2016-05-18 23:54 UTC (permalink / raw)
To: Elliott, Robert (Persistent Memory)
Cc: Christoph Hellwig, linux-nvme@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-block@vger.kernel.org
On Tue, Apr 26, 2016 at 02:33:49PM -0700, Rafael Antognolli wrote:
> On Mon, Apr 25, 2016 at 08:29:22PM -0700, Elliott, Robert (Persistent Memory) wrote:
> >
> >
> > > -----Original Message-----
> > > From: linux-block-owner@vger.kernel.org [mailto:linux-block-
> > > owner@vger.kernel.org] On Behalf Of Christoph Hellwig
> > > Sent: Monday, April 25, 2016 3:24 AM
> > > To: Rafael Antognolli <rafael.antognolli@intel.com>
> > > Cc: linux-nvme@lists.infradead.org; linux-kernel@vger.kernel.org;
> > > linux-block@vger.kernel.org
> > > Subject: Re: [PATCH 0/2] Add Opal unlock support to NVMe.
> > >
> > > On Fri, Apr 22, 2016 at 04:12:10PM -0700, Rafael Antognolli wrote:
> > > > This patch series implement a small set of the Opal protocol for
> > > > self encrypting devices. It's implemented only what is needed for
> > > > saving a password and unlocking a given "locking range". The
> > > > password is saved on the driver and replayed back to the device
> > > > on resume from suspend to RAM. It is specifically supporting
> > > > the single user mode.
> >
> > Passwords stored in memory are subject to cold boot attacks.
> >
> > Could you tie this into the keyring infrastructure, so it would
> > least be no worse than other kernel modules? This would allow
> > support for TPM-based keys (if present) to resist more attacks.
> > If register-based key storage or other techniques prove viable,
> > they would probably show up there first.
>
> I'll take a look at it.
Hi Robert, I've been looking at possibilities to integrate this into the
keyring infra as you suggested, particularly with the trusted
(TPM-based) and/or encrypted key types. However, these key types do not
support any way of loading a key from userspace; the API for creating
trusted or encrypted keys generate them inside the kernel and it's never
available to userspace. And at the same time, the current utility to set
a password on Opal devices (sedutil [1]) is a userspace program.
I could not find an easy way to connect both things unless, for
instance, I generated a trusted key that the Opal kernel code can
access, and used that key to encrypt the password to the Opal device.
That would work but would mean the user has to request a
trusted/encrypted key to be created, and then point to that when
requesting to save the password. IMHO the API of this module would start
to be a bit tricky.
Another solution would be to add support to at least encrypted or
trusted keys to be loaded from userspace (as unencrypted keys, not
encrypted blobs), even if they never get available again to userspace.
This should be easy to do, I'm just not sure if it would make sense to
have that on the trusted/encrypted keys.
And yet another solution would be to implement a lot more of the Opal
protocol in this module, and make it support setting up the locking
ranges and passwords (trusted/encrypted keys that the userspace never
saw unencrypted). But at least for now, my intention was to implement
just the least needed to unlock devices, not really implement the whole
OPAL spec.
Anyway, do you see other options, or would you mind giving your opinion
about this?
[1]: https://github.com/Drive-Trust-Alliance/sedutil
Thank you,
Rafael
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2016-05-18 23:56 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-04-22 23:12 [PATCH 0/2] Add Opal unlock support to NVMe Rafael Antognolli
2016-04-22 23:12 ` [PATCH 1/2] Add optane OPAL unlocking code Rafael Antognolli
2016-04-22 23:12 ` [PATCH 2/2] NVMe: Add ioctls to save and unlock an Opal locking range Rafael Antognolli
2016-04-25 8:24 ` [PATCH 0/2] Add Opal unlock support to NVMe Christoph Hellwig
2016-04-26 3:29 ` Elliott, Robert (Persistent Memory)
2016-04-26 21:33 ` Rafael Antognolli
2016-05-18 23:54 ` Rafael Antognolli
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).