diff mbox series

[v4,2/2] misc: Add Nitro Secure Module driver

Message ID 20231009212053.2007-3-graf@amazon.com
State New
Headers show
Series Add Nitro Secure Module support | expand

Commit Message

Alexander Graf Oct. 9, 2023, 9:20 p.m. UTC
When running Linux inside a Nitro Enclave, the hypervisor provides a
special virtio device called "NSM". This device has 3 main functions:

  1) Provide attestation reports
  2) Modify PCR state
  3) Provide entropy

This patch adds a driver for NSM that exposes a /dev/nsm device node which
user space can issue ioctls on to request attestation documents, influence
PCR states, read entropy and enumerate status of the device. In addition,
the driver implements a hwrng backend and exposes some of the NSM device
metadata as sysfs entries.

Originally-by: Petre Eftime <petre.eftime@gmail.com>
Signed-off-by: Alexander Graf <graf@amazon.com>

---

v1 -> v2:

  - Remove boilerplate
  - Add uapi header

v2 -> v3:

  - Move globals to device struct
  - Add compat handling
  - Simplify some naming
  - Remove debug prints
  - Use module_virtio_driver
  - Drop use of uio.h

v3 -> v4:

  - Merge hwrng into the misc driver
  - Add dependency on CBOR library
  - Add internal and ioctl logic for all current NSM actions
  - Use in-struct arrays instead of kvecs
  - Add sysfs entries for NSM metadata
  - Use dev_ print and devm_ allocation helpers
---
 MAINTAINERS              |    9 +
 drivers/misc/Kconfig     |   13 +
 drivers/misc/Makefile    |    1 +
 drivers/misc/nsm.c       | 1466 ++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/nsm.h |  188 +++++
 5 files changed, 1677 insertions(+)
 create mode 100644 drivers/misc/nsm.c
 create mode 100644 include/uapi/linux/nsm.h

Comments

kernel test robot Oct. 13, 2023, 3:43 p.m. UTC | #1
Hi Alexander,

kernel test robot noticed the following build errors:

[auto build test ERROR on char-misc/char-misc-testing]
[also build test ERROR on char-misc/char-misc-next char-misc/char-misc-linus soc/for-next linus/master v6.6-rc5 next-20231012]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Alexander-Graf/Import-CBOR-library/20231010-052529
base:   char-misc/char-misc-testing
patch link:    https://lore.kernel.org/r/20231009212053.2007-3-graf%40amazon.com
patch subject: [PATCH v4 2/2] misc: Add Nitro Secure Module driver
config: powerpc-allmodconfig (https://download.01.org/0day-ci/archive/20231013/202310132329.UIMS3J8a-lkp@intel.com/config)
compiler: powerpc64-linux-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231013/202310132329.UIMS3J8a-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202310132329.UIMS3J8a-lkp@intel.com/

All errors (new ones prefixed by >>):

   In file included from include/asm-generic/ioctl.h:5,
                    from arch/powerpc/include/uapi/asm/ioctl.h:12,
                    from include/uapi/linux/ioctl.h:5,
                    from include/uapi/linux/random.h:12,
                    from include/linux/random.h:10,
                    from include/linux/nodemask.h:97,
                    from include/linux/list_lru.h:12,
                    from include/linux/fs.h:13,
                    from drivers/misc/nsm.c:23:
>> include/uapi/asm-generic/ioctl.h:70:9: error: initializer element is not constant
      70 |         (((dir)  << _IOC_DIRSHIFT) | \
         |         ^
   include/uapi/asm-generic/ioctl.h:88:33: note: in expansion of macro '_IOC'
      88 | #define _IOWR(type,nr,size)     _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
         |                                 ^~~~
   include/uapi/linux/nsm.h:172:33: note: in expansion of macro '_IOWR'
     172 | #define NSM_IOCTL_ATTESTATION   _IOWR(NSM_MAGIC, 0x6, union nsm_attestation)
         |                                 ^~~~~
   drivers/misc/nsm.c:1209:11: note: in expansion of macro 'NSM_IOCTL_ATTESTATION'
    1209 |         { NSM_IOCTL_ATTESTATION, _fill_req_attestation, _parse_resp_attestation },
         |           ^~~~~~~~~~~~~~~~~~~~~
   include/uapi/asm-generic/ioctl.h:70:9: note: (near initialization for 'nsm_ioctls[6].cmd')
      70 |         (((dir)  << _IOC_DIRSHIFT) | \
         |         ^
   include/uapi/asm-generic/ioctl.h:88:33: note: in expansion of macro '_IOC'
      88 | #define _IOWR(type,nr,size)     _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
         |                                 ^~~~
   include/uapi/linux/nsm.h:172:33: note: in expansion of macro '_IOWR'
     172 | #define NSM_IOCTL_ATTESTATION   _IOWR(NSM_MAGIC, 0x6, union nsm_attestation)
         |                                 ^~~~~
   drivers/misc/nsm.c:1209:11: note: in expansion of macro 'NSM_IOCTL_ATTESTATION'
    1209 |         { NSM_IOCTL_ATTESTATION, _fill_req_attestation, _parse_resp_attestation },
         |           ^~~~~~~~~~~~~~~~~~~~~
>> include/uapi/asm-generic/ioctl.h:70:9: error: initializer element is not constant
      70 |         (((dir)  << _IOC_DIRSHIFT) | \
         |         ^
   include/uapi/asm-generic/ioctl.h:88:33: note: in expansion of macro '_IOC'
      88 | #define _IOWR(type,nr,size)     _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
         |                                 ^~~~
   include/uapi/linux/nsm.h:186:33: note: in expansion of macro '_IOWR'
     186 | #define NSM_IOCTL_RANDOM        _IOWR(NSM_MAGIC, 0x7, union nsm_get_random)
         |                                 ^~~~~
   drivers/misc/nsm.c:1210:11: note: in expansion of macro 'NSM_IOCTL_RANDOM'
    1210 |         { NSM_IOCTL_RANDOM, _fill_req_get_random, _parse_resp_get_random },
         |           ^~~~~~~~~~~~~~~~
   include/uapi/asm-generic/ioctl.h:70:9: note: (near initialization for 'nsm_ioctls[7].cmd')
      70 |         (((dir)  << _IOC_DIRSHIFT) | \
         |         ^
   include/uapi/asm-generic/ioctl.h:88:33: note: in expansion of macro '_IOC'
      88 | #define _IOWR(type,nr,size)     _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
         |                                 ^~~~
   include/uapi/linux/nsm.h:186:33: note: in expansion of macro '_IOWR'
     186 | #define NSM_IOCTL_RANDOM        _IOWR(NSM_MAGIC, 0x7, union nsm_get_random)
         |                                 ^~~~~
   drivers/misc/nsm.c:1210:11: note: in expansion of macro 'NSM_IOCTL_RANDOM'
    1210 |         { NSM_IOCTL_RANDOM, _fill_req_get_random, _parse_resp_get_random },
         |           ^~~~~~~~~~~~~~~~


vim +70 include/uapi/asm-generic/ioctl.h

8a1ab3155c2ac7 David Howells 2012-10-04  68  
8a1ab3155c2ac7 David Howells 2012-10-04  69  #define _IOC(dir,type,nr,size) \
8a1ab3155c2ac7 David Howells 2012-10-04 @70  	(((dir)  << _IOC_DIRSHIFT) | \
8a1ab3155c2ac7 David Howells 2012-10-04  71  	 ((type) << _IOC_TYPESHIFT) | \
8a1ab3155c2ac7 David Howells 2012-10-04  72  	 ((nr)   << _IOC_NRSHIFT) | \
8a1ab3155c2ac7 David Howells 2012-10-04  73  	 ((size) << _IOC_SIZESHIFT))
8a1ab3155c2ac7 David Howells 2012-10-04  74
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 5c23081ede95..28f861eea8e5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15104,6 +15104,15 @@  F:	include/linux/nitro_enclaves.h
 F:	include/uapi/linux/nitro_enclaves.h
 F:	samples/nitro_enclaves/
 
+NITRO SECURE MODULE (NSM)
+M:	Alexander Graf <graf@amazon.com>
+L:	linux-kernel@vger.kernel.org
+L:	The AWS Nitro Enclaves Team <aws-nitro-enclaves-devel@amazon.com>
+S:	Supported
+W:	https://aws.amazon.com/ec2/nitro/nitro-enclaves/
+F:	drivers/misc/nsm.c
+F:	include/uapi/linux/nsm.h
+
 NOHZ, DYNTICKS SUPPORT
 M:	Frederic Weisbecker <frederic@kernel.org>
 M:	Thomas Gleixner <tglx@linutronix.de>
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index cadd4a820c03..236f36a8e8d4 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -562,6 +562,19 @@  config TPS6594_PFSM
 	  This driver can also be built as a module.  If so, the module
 	  will be called tps6594-pfsm.
 
+config NSM
+	tristate "Nitro (Enclaves) Security Module support"
+	depends on VIRTIO
+	select HW_RANDOM
+	select CBOR
+	help
+	  This driver provides support for the Nitro Security Module
+	  in AWS EC2 Nitro based Enclaves. The driver exposes a /dev/nsm
+	  device user space can use to communicate with the hypervisor.
+
+	  To compile this driver as a module, choose M here.
+	  The module will be called nsm.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index f2a4d1ff65d4..ea6ea5bbbc9c 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -67,3 +67,4 @@  obj-$(CONFIG_TMR_MANAGER)      += xilinx_tmr_manager.o
 obj-$(CONFIG_TMR_INJECT)	+= xilinx_tmr_inject.o
 obj-$(CONFIG_TPS6594_ESM)	+= tps6594-esm.o
 obj-$(CONFIG_TPS6594_PFSM)	+= tps6594-pfsm.o
+obj-$(CONFIG_NSM)		+= nsm.o
diff --git a/drivers/misc/nsm.c b/drivers/misc/nsm.c
new file mode 100644
index 000000000000..8baa91233cab
--- /dev/null
+++ b/drivers/misc/nsm.c
@@ -0,0 +1,1466 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Amazon Nitro Secure Module driver.
+ *
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * The Nitro Secure Module implements the following commands via CBOR over virtio:
+ *
+ *   DescribePCR(u16 index) -> { bool lock, u8[] data }
+ *   ExtendPCR(u16 index, u8[] data) -> { u8[] data }
+ *   LockPCR(u16 index) -> {}
+ *   LockPCRs(u16 range_from_zero) -> {}
+ *   DescribeNSM() -> { u16 version_major, u16 version_minor,
+ *                      u16 version_patch, String module_id,
+ *                      u16 max_pcrs, u16[] locked_pcrs, String digest }
+ *   Attestation(u8[] user_data, u8[] nonce, u8[] public_key) -> { u8[] document }
+ *   GetRandom() -> { u8[] random }
+ *
+ * This driver exposes them directly to user space as ioctls on /dev/nsm.
+ */
+
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/hw_random.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/uio.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_ids.h>
+#include <linux/virtio.h>
+#include <linux/wait.h>
+#include <linux/cbor/decoder.h>
+#include <linux/cbor/encoder.h>
+#include <linux/cbor/parser.h>
+#include <uapi/linux/nsm.h>
+
+/* Timeout for NSM virtqueue respose in milliseconds. */
+#define NSM_DEFAULT_TIMEOUT_MSECS (120000) /* 2 minutes */
+
+struct nsm {
+	struct list_head      node;
+	struct virtio_device *vdev;
+	struct virtqueue     *vq;
+	struct mutex          lock;
+	wait_queue_head_t     wq;
+	bool                  device_notified;
+	struct miscdevice     misc;
+	struct hwrng          hwrng;
+	struct work_struct    misc_init;
+	u16                   major;
+	u16                   minor;
+	u16                   patch;
+	u16                   max_pcrs;
+	char                  module_id[256];
+	char                  digest[16];
+};
+
+/* NSM device ID */
+static const struct virtio_device_id id_table[] = {
+	{ VIRTIO_ID_NITRO_SEC_MOD, VIRTIO_DEV_ANY_ID },
+	{ 0 },
+};
+
+/* Full NSM request/response message */
+struct nsm_msg {
+	struct nsm_data_req req;
+	struct nsm_data_resp resp;
+};
+
+static inline struct nsm *file_to_nsm(struct file *file)
+{
+	return container_of(file->private_data, struct nsm, misc);
+}
+
+static inline struct nsm *hwrng_to_nsm(struct hwrng *rng)
+{
+	return container_of(rng, struct nsm, hwrng);
+}
+
+static inline struct nsm *misc_dev_to_nsm(struct miscdevice *misc)
+{
+	return container_of(misc, struct nsm, misc);
+}
+
+static bool cbor_is_str(cbor_reader_t *reader, cbor_item_t *item,
+			const char *str)
+{
+	int len = strlen(str);
+
+	if (item->size != len)
+		return false;
+	return !memcmp(&reader->msg[item->offset], str, len);
+}
+
+/* Copy the request of a raw message to kernel space */
+static int fill_req_raw(struct nsm *nsm, struct nsm_data_req *req,
+			struct nsm_raw *raw)
+{
+	/* Only root has access to raw requests */
+	if (!capable(CAP_SYS_ADMIN))
+		return -EACCES;
+
+	/* Verify the user input size. */
+	if (raw->request.len > sizeof(req->data))
+		return -EMSGSIZE;
+
+	/* Copy the request payload */
+	if (copy_from_user(req->data, u64_to_user_ptr(raw->request.addr),
+			   raw->request.len))
+		return -EFAULT;
+
+	req->len = raw->request.len;
+
+	return 0;
+}
+
+static int _fill_req_raw(struct nsm *nsm, struct nsm_data_req *req,
+			 void *buffer)
+{
+	return fill_req_raw(nsm, req, buffer);
+}
+
+/* Copy the response of a raw message back to user-space */
+static int parse_resp_raw(struct nsm *nsm, struct nsm_data_resp *resp,
+			  struct nsm_raw *raw)
+{
+	/* Truncate any message that does not fit. */
+	raw->response.len = min_t(u64, raw->response.len, resp->len);
+
+	/* Copy the response content to user space */
+	if (copy_to_user(u64_to_user_ptr(raw->response.addr),
+			 resp->data, raw->response.len))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int _parse_resp_raw(struct nsm *nsm, struct nsm_data_resp *resp,
+			   void *buffer)
+{
+	return parse_resp_raw(nsm, resp, buffer);
+}
+
+/* Virtqueue interrupt handler */
+static void nsm_vq_callback(struct virtqueue *vq)
+{
+	struct nsm *nsm = vq->vdev->priv;
+
+	nsm->device_notified = true;
+	wake_up(&nsm->wq);
+}
+
+/* Forward a message to the NSM device and wait for the response from it */
+static int nsm_sendrecv_msg(struct nsm *nsm, struct nsm_msg *msg)
+{
+	struct device *dev = &nsm->vdev->dev;
+	struct scatterlist sg_in, sg_out;
+	struct virtqueue *vq = nsm->vq;
+	unsigned int len;
+	void *queue_buf;
+	bool kicked;
+	int rc;
+
+	/* Initialize scatter-gather lists with request and response buffers. */
+	sg_init_one(&sg_out, msg->req.data, msg->req.len);
+	sg_init_one(&sg_in, msg->resp.data, sizeof(msg->resp.data));
+
+	mutex_lock(&nsm->lock);
+
+	/* Add the request buffer (read by the device). */
+	rc = virtqueue_add_outbuf(vq, &sg_out, 1, msg->req.data, GFP_KERNEL);
+	if (rc) {
+		mutex_unlock(&nsm->lock);
+		return rc;
+	}
+
+	/* Add the response buffer (written by the device). */
+	rc = virtqueue_add_inbuf(vq, &sg_in, 1, msg->resp.data, GFP_KERNEL);
+	if (rc)
+		goto cleanup;
+
+	nsm->device_notified = false;
+	kicked = virtqueue_kick(vq);
+	if (!kicked) {
+		/* Cannot kick the virtqueue. */
+		rc = -EIO;
+		goto cleanup;
+	}
+
+	/* If the kick succeeded, wait for the device's response. */
+	rc = wait_event_timeout(nsm->wq,
+		nsm->device_notified == true,
+		msecs_to_jiffies(NSM_DEFAULT_TIMEOUT_MSECS));
+	if (!rc) {
+		rc = -ETIMEDOUT;
+		goto cleanup;
+	}
+
+	queue_buf = virtqueue_get_buf(vq, &len);
+	if (!queue_buf || (queue_buf != msg->req.data)) {
+		dev_err(dev, "wrong request buffer.");
+		rc = -ENODATA;
+		goto cleanup;
+	}
+
+	queue_buf = virtqueue_get_buf(vq, &len);
+	if (!queue_buf || (queue_buf != msg->resp.data)) {
+		dev_err(dev, "wrong response buffer.");
+		rc = -ENODATA;
+		goto cleanup;
+	}
+
+	msg->resp.len = len;
+
+	rc = 0;
+
+cleanup:
+	if (rc) {
+		/* Clean the virtqueue. */
+		while (virtqueue_get_buf(vq, &len) != NULL)
+			;
+	}
+
+	mutex_unlock(&nsm->lock);
+	return rc;
+}
+
+/*
+ * DescribePCR request structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("DescribePCR"),
+ *     value = Map(1) {
+ *       key = String("index"),
+ *       value = Int(pcr)
+ *     }
+ *   }
+ * }
+ */
+static int fill_req_describe_pcr(struct nsm *nsm, struct nsm_data_req *req,
+				 union nsm_describe_pcr *desc_union)
+{
+	struct nsm_describe_pcr_req *desc = &desc_union->req;
+	cbor_writer_t writer;
+	cbor_error_t err;
+
+	cbor_writer_init(&writer, req->data, sizeof(req->data));
+	err = cbor_encode_map(&writer, 1);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "DescribePCR");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_map(&writer, 1);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "index");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_unsigned_integer(&writer, desc->index);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	req->len = cbor_writer_len(&writer);
+
+	return 0;
+}
+
+static int _fill_req_describe_pcr(struct nsm *nsm, struct nsm_data_req *req,
+				  void *buffer)
+{
+	return fill_req_describe_pcr(nsm, req, buffer);
+}
+
+/*
+ * DescribePCR response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("DescribePCR"),
+ *     value = Map(2) {
+ *       key = String("data"),
+ *       value = Array(),
+ *       key = String("lock"),
+ *       value = Bool()
+ *     }
+ *   }
+ * }
+ */
+static int parse_resp_describe_pcr(struct nsm *nsm, struct nsm_data_resp *resp,
+				   union nsm_describe_pcr *desc_union)
+{
+	struct nsm_describe_pcr_resp *desc = &desc_union->resp;
+	struct device *dev = &nsm->vdev->dev;
+	cbor_item_t items[7];
+	cbor_reader_t reader;
+	cbor_error_t err;
+	size_t n;
+
+	cbor_reader_init(&reader, items, ARRAY_SIZE(items));
+	err = cbor_parse(&reader, resp->data, resp->len, &n);
+	if (err != CBOR_SUCCESS) {
+		dev_dbg(dev, "Invalid DescribePCR response");
+		return -EINVAL;
+	}
+
+	if (n != 7) {
+		dev_dbg(dev, "Invalid DescribePCR response elements (%zd)", n);
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[1], "DescribePCR")) {
+		dev_dbg(dev, "Invalid DescribePCR response items[1]");
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[3], "data")) {
+		dev_dbg(dev, "Invalid DescribePCR response items[3]");
+		return -EINVAL;
+	}
+
+	if (items[4].type != CBOR_ITEM_STRING) {
+		dev_dbg(dev, "Invalid DescribePCR response items[4]");
+		return -EINVAL;
+	}
+
+	desc->data.len = items[4].size;
+	memcpy(desc->data.data, &resp->data[items[4].offset],
+	       desc->data.len);
+
+	if (!cbor_is_str(&reader, &items[5], "lock")) {
+		dev_dbg(dev, "Invalid DescribePCR response items[5]");
+		return -EINVAL;
+	}
+
+	if (items[6].type != CBOR_ITEM_SIMPLE_VALUE) {
+		dev_dbg(dev, "Invalid DescribePCR response items[6]");
+		return -EINVAL;
+	}
+
+	desc->lock = 0;
+	err = cbor_decode(&reader, &items[6], &desc->lock, sizeof(u16));
+	if (err != CBOR_SUCCESS) {
+		dev_dbg(dev, "Invalid DescribePCR respons items[6]");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int _parse_resp_describe_pcr(struct nsm *nsm, struct nsm_data_resp *resp,
+				    void *buffer)
+{
+	return parse_resp_describe_pcr(nsm, resp, buffer);
+}
+
+/*
+ * ExtendPCR request structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("ExtendPCR"),
+ *     value = Map(2) {
+ *       key = String("index"),
+ *       value = Int(pcr),
+ *       key = String("data"),
+ *       value = Array(data),
+ *     }
+ *   }
+ * }
+ */
+static int fill_req_extend_pcr(struct nsm *nsm, struct nsm_data_req *req,
+			       union nsm_extend_pcr *extend_union)
+{
+	struct nsm_extend_pcr_req *extend = &extend_union->req;
+	cbor_writer_t writer;
+	cbor_error_t err;
+
+	cbor_writer_init(&writer, req->data, sizeof(req->data));
+	err = cbor_encode_map(&writer, 1);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "ExtendPCR");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_map(&writer, 2);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "index");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_unsigned_integer(&writer, extend->index);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "data");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	if (extend->data.len > sizeof(extend->data.data))
+		return -EINVAL;
+
+	err = cbor_encode_byte_string(&writer, extend->data.data,
+				      extend->data.len);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	req->len = cbor_writer_len(&writer);
+
+	return 0;
+}
+
+static int _fill_req_extend_pcr(struct nsm *nsm, struct nsm_data_req *req,
+				void *buffer)
+{
+	return fill_req_extend_pcr(nsm, req, buffer);
+}
+
+/*
+ * ExtendPCR response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("ExtendPCR"),
+ *     value = Map(1) {
+ *       key = String("data"),
+ *       value = Array()
+ *     }
+ *   }
+ * }
+ */
+static int parse_resp_extend_pcr(struct nsm *nsm, struct nsm_data_resp *resp,
+				 union nsm_extend_pcr *extend_union)
+{
+	struct nsm_extend_pcr_resp *extend = &extend_union->resp;
+	struct device *dev = &nsm->vdev->dev;
+	cbor_item_t items[7];
+	cbor_reader_t reader;
+	cbor_error_t err;
+	size_t n;
+
+	cbor_reader_init(&reader, items, ARRAY_SIZE(items));
+	err = cbor_parse(&reader, resp->data, resp->len, &n);
+	if (err != CBOR_SUCCESS) {
+		dev_dbg(dev, "Invalid ExtendPCR response");
+		return -EINVAL;
+	}
+
+	if (n != 5) {
+		dev_dbg(dev, "Invalid ExtendPCR response elements (%zd)", n);
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[1], "ExtendPCR")) {
+		dev_dbg(dev, "Invalid ExtendPCR response items[1]");
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[3], "data")) {
+		dev_dbg(dev, "Invalid ExtendPCR response items[3]");
+		return -EINVAL;
+	}
+
+	if (items[4].type != CBOR_ITEM_STRING) {
+		dev_dbg(dev, "Invalid ExtendPCR response items[4]");
+		return -EINVAL;
+	}
+
+	extend->data.len = items[4].size;
+	memcpy(extend->data.data, &resp->data[items[4].offset],
+	       extend->data.len);
+
+	return 0;
+}
+
+static int _parse_resp_extend_pcr(struct nsm *nsm, struct nsm_data_resp *resp,
+				  void *buffer)
+{
+	return parse_resp_extend_pcr(nsm, resp, buffer);
+}
+
+/*
+ * LockPCR request structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("LockPCR"),
+ *     value = Map(1) {
+ *       key = String("index"),
+ *       value = Int(pcr)
+ *     }
+ *   }
+ * }
+ */
+static int fill_req_lock_pcr(struct nsm *nsm, struct nsm_data_req *req,
+			     union nsm_lock_pcr *lock_union)
+{
+	struct nsm_lock_pcr_req *lock = &lock_union->req;
+	cbor_writer_t writer;
+	cbor_error_t err;
+
+	cbor_writer_init(&writer, req->data, sizeof(req->data));
+	err = cbor_encode_map(&writer, 1);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "LockPCR");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_map(&writer, 1);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "index");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_unsigned_integer(&writer, lock->index);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	req->len = cbor_writer_len(&writer);
+
+	return 0;
+}
+
+static int _fill_req_lock_pcr(struct nsm *nsm, struct nsm_data_req *req,
+			      void *buffer)
+{
+	return fill_req_lock_pcr(nsm, req, buffer);
+}
+
+/*
+ * LockPCR error response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("Error"),
+ *     value = String("ReadOnlyIndex"),
+ *   }
+ * }
+ *
+ * Any other response means success.
+ */
+static int parse_resp_lock_pcr(struct nsm *nsm, struct nsm_data_resp *resp)
+{
+	struct device *dev = &nsm->vdev->dev;
+	cbor_item_t items[7];
+	cbor_reader_t reader;
+	cbor_error_t err;
+	size_t n;
+
+	cbor_reader_init(&reader, items, ARRAY_SIZE(items));
+	err = cbor_parse(&reader, resp->data, resp->len, &n);
+	if (err != CBOR_SUCCESS) {
+		dev_dbg(dev, "Invalid LockPCR response");
+		return -EINVAL;
+	}
+
+	if (n == 3 &&
+	    cbor_is_str(&reader, &items[1], "Error"))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int _parse_resp_lock_pcr(struct nsm *nsm, struct nsm_data_resp *resp,
+				void *buffer)
+{
+	return parse_resp_lock_pcr(nsm, resp);
+}
+
+/*
+ * LockPCRs request structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("LockPCRs"),
+ *     value = Map(1) {
+ *       key = String("range"),
+ *       value = Int(pcr)
+ *     }
+ *   }
+ * }
+ */
+static int fill_req_lock_pcrs(struct nsm *nsm, struct nsm_data_req *req,
+			      union nsm_lock_pcrs *lock_union)
+{
+	struct nsm_lock_pcrs_req *lock = &lock_union->req;
+	cbor_writer_t writer;
+	cbor_error_t err;
+
+	cbor_writer_init(&writer, req->data, sizeof(req->data));
+	err = cbor_encode_map(&writer, 1);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "LockPCRs");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_map(&writer, 1);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "range");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_unsigned_integer(&writer, lock->range_from_zero);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	req->len = cbor_writer_len(&writer);
+
+	return 0;
+}
+
+static int _fill_req_lock_pcrs(struct nsm *nsm, struct nsm_data_req *req,
+			       void *buffer)
+{
+	return fill_req_lock_pcrs(nsm, req, buffer);
+}
+
+/*
+ * LockPCRs error response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("Error"),
+ *     value = String("ReadOnlyIndex"),
+ *   }
+ * }
+ *
+ * Any other response means success.
+ */
+static int parse_resp_lock_pcrs(struct nsm *nsm, struct nsm_data_resp *resp)
+{
+	struct device *dev = &nsm->vdev->dev;
+	cbor_item_t items[7];
+	cbor_reader_t reader;
+	cbor_error_t err;
+	size_t n;
+
+	cbor_reader_init(&reader, items, ARRAY_SIZE(items));
+	err = cbor_parse(&reader, resp->data, resp->len, &n);
+	if (err != CBOR_SUCCESS) {
+		dev_dbg(dev, "Invalid LockPCRs response");
+		return -EINVAL;
+	}
+
+	if (n == 3 &&
+	    cbor_is_str(&reader, &items[1], "Error"))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int _parse_resp_lock_pcrs(struct nsm *nsm, struct nsm_data_resp *resp,
+				 void *buffer)
+{
+	return parse_resp_lock_pcrs(nsm, resp);
+}
+
+/*
+ * DescribeNSM request structure:
+ *
+ * {
+ *   String("DescribeNSM"),
+ * }
+ */
+static int fill_req_describe_nsm(struct nsm *nsm, struct nsm_data_req *req)
+{
+	cbor_writer_t writer;
+	cbor_error_t err;
+
+	cbor_writer_init(&writer, req->data, sizeof(req->data));
+
+	err = cbor_encode_null_terminated_text_string(&writer, "DescribeNSM");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	req->len = cbor_writer_len(&writer);
+
+	return 0;
+}
+
+static int _fill_req_describe_nsm(struct nsm *nsm, struct nsm_data_req *req,
+				  void *buffer)
+{
+	return fill_req_describe_nsm(nsm, req);
+}
+
+/*
+ * DescribeNSM response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("DescribeNSM"),
+ *     value = Map(7) {
+ *       key = String("digest"),
+ *       value = String("SHA384"),
+ *       key = String("max_pcrs"),
+ *       value = Int(32),
+ *       key = String("module_id"),
+ *       value = String("i-1234-enc5678"),
+ *       key = String("locked_pcrs"),
+ *       value = Array<u16>() (libcbor lists every element as item)
+ *       key = String("version_major"),
+ *       value = Int(1),
+ *       key = String("version_minor"),
+ *       value = Int(0),
+ *       key = String("version_patch"),
+ *       value = Int(0)
+ *     }
+ *   }
+ * }
+ */
+static int parse_resp_describe_nsm(struct nsm *nsm, struct nsm_data_resp *resp,
+				   union nsm_describe_nsm *desc_union)
+{
+	struct nsm_describe_nsm_resp *desc = &desc_union->resp;
+	struct device *dev = &nsm->vdev->dev;
+	cbor_item_t items[64];
+	cbor_reader_t reader;
+	cbor_error_t err;
+	size_t n;
+	int i;
+
+	cbor_reader_init(&reader, items, ARRAY_SIZE(items));
+	err = cbor_parse(&reader, resp->data, resp->len, &n);
+	if (err != CBOR_SUCCESS) {
+		dev_dbg(dev, "Invalid DescribeNSM response");
+		return -EINVAL;
+	}
+
+	if (n < 11) {
+		dev_dbg(dev, "Invalid DescribeNSM response elements (%zd)", n);
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[1], "DescribeNSM")) {
+		dev_dbg(dev, "Invalid DescribeNSM response items[1]");
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[3], "digest")) {
+		dev_dbg(dev, "Invalid DescribeNSM response items[3]");
+		return -EINVAL;
+	}
+
+	if (items[4].type != CBOR_ITEM_STRING) {
+		dev_dbg(dev, "Invalid DescribeNSM response items[4]");
+		return -EINVAL;
+	}
+
+	memset(desc->digest, 0, sizeof(desc->digest));
+	memcpy(desc->digest, &resp->data[items[4].offset],
+	       min_t(size_t, items[4].size, sizeof(desc->digest)));
+
+	if (!cbor_is_str(&reader, &items[5], "max_pcrs")) {
+		dev_dbg(dev, "Invalid DescribeNSM response items[5]");
+		return -EINVAL;
+	}
+
+	desc->max_pcrs = 0;
+	err = cbor_decode(&reader, &items[6], &desc->max_pcrs,
+			  sizeof(desc->max_pcrs));
+	if (err != CBOR_SUCCESS) {
+		dev_dbg(dev, "Invalid DescribeNSM respons items[6]");
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[7], "module_id")) {
+		dev_dbg(dev, "Invalid DescribeNSM response items[7]");
+		return -EINVAL;
+	}
+
+	memset(desc->module_id, 0, sizeof(desc->module_id));
+	memcpy(desc->module_id, &resp->data[items[8].offset],
+	       min_t(size_t, items[8].size, sizeof(desc->module_id)));
+
+	if (!cbor_is_str(&reader, &items[9], "locked_pcrs")) {
+		dev_dbg(dev, "Invalid DescribeNSM response items[9]");
+		return -EINVAL;
+	}
+
+	desc->locked_pcrs.u16s = min_t(size_t, ARRAY_SIZE(desc->locked_pcrs.u16),
+				       items[10].size);
+	memcpy(desc->locked_pcrs.u16, &resp->data[items[10].offset],
+	       desc->locked_pcrs.u16s * sizeof(u16));
+
+	/* libcbor interprets each array element as item, use dynamic index */
+	i = 11 + desc->locked_pcrs.u16s;
+
+	if (i > n)
+		return -EINVAL;
+
+	if (!cbor_is_str(&reader, &items[i], "version_major")) {
+		dev_dbg(dev, "Invalid DescribeNSM response items[%d]", i);
+		return -EINVAL;
+	}
+
+	if (++i > n)
+		return -EINVAL;
+
+	desc->major = 0;
+	err = cbor_decode(&reader, &items[i], &desc->major,
+			  sizeof(desc->major));
+
+	if (++i > n)
+		return -EINVAL;
+
+	if (!cbor_is_str(&reader, &items[i], "version_minor")) {
+		dev_dbg(dev, "Invalid DescribeNSM response items[%d]", i);
+		return -EINVAL;
+	}
+
+	if (++i > n)
+		return -EINVAL;
+
+	desc->minor = 0;
+	err = cbor_decode(&reader, &items[i], &desc->minor,
+			  sizeof(desc->minor));
+
+	if (++i > n)
+		return -EINVAL;
+
+	if (!cbor_is_str(&reader, &items[i], "version_patch")) {
+		dev_dbg(dev, "Invalid DescribeNSM response items[%d]", i);
+		return -EINVAL;
+	}
+
+	if (++i > n)
+		return -EINVAL;
+
+	desc->patch = 0;
+	err = cbor_decode(&reader, &items[i], &desc->patch,
+			  sizeof(desc->patch));
+
+	return 0;
+}
+
+static int _parse_resp_describe_nsm(struct nsm *nsm, struct nsm_data_resp *resp,
+				    void *buffer)
+{
+	return parse_resp_describe_nsm(nsm, resp, buffer);
+}
+
+/*
+ * Attestation request structure:
+ *
+ *   Map(1) {
+ *     key = String("Attestation"),
+ *     value = Map(3) {
+ *       key = String("user_data"),
+ *       value = Array(),
+ *       key = String("nonce"),
+ *       value = Array(),
+ *       key = String("public_key"),
+ *       value = Array(),
+ *     }
+ *   }
+ * }
+ */
+static int fill_req_attestation(struct nsm *nsm, struct nsm_data_req *req,
+				union nsm_attestation *att_union)
+{
+	struct nsm_attestation_req *att = &att_union->req;
+	cbor_writer_t writer;
+	cbor_error_t err;
+
+	cbor_writer_init(&writer, req->data, sizeof(req->data));
+
+	err = cbor_encode_map(&writer, 1);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "Attestation");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_map(&writer, 3);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "user_data");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	att->user_data.len = min_t(u32, att->user_data.len,
+				   sizeof(att->user_data.data));
+	err = cbor_encode_byte_string(&writer, att->user_data.data,
+				      att->user_data.len);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "nonce");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	att->nonce.len = min_t(u32, att->nonce.len, sizeof(att->nonce.data));
+	err = cbor_encode_byte_string(&writer, att->nonce.data,
+				      att->nonce.len);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	err = cbor_encode_null_terminated_text_string(&writer, "public_key");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	att->public_key.len = min_t(u32, att->public_key.len,
+				    sizeof(att->public_key.data));
+	err = cbor_encode_byte_string(&writer, att->public_key.data,
+				      att->public_key.len);
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+
+	req->len = cbor_writer_len(&writer);
+
+	return 0;
+}
+
+static int _fill_req_attestation(struct nsm *nsm, struct nsm_data_req *req,
+				 void *buffer)
+{
+	return fill_req_attestation(nsm, req, buffer);
+}
+
+/*
+ * Attestation response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("Attestation"),
+ *     value = Map(1) {
+ *       key = String("document"),
+ *       value = Array()
+ *     }
+ *   }
+ * }
+ */
+static int parse_resp_attestation(struct nsm *nsm, struct nsm_data_resp *resp,
+				  union nsm_attestation *att_union)
+{
+	struct nsm_attestation_resp *att = &att_union->resp;
+	struct device *dev = &nsm->vdev->dev;
+	cbor_item_t items[6];
+	cbor_reader_t reader;
+	cbor_error_t err;
+	size_t n;
+
+	cbor_reader_init(&reader, items, ARRAY_SIZE(items));
+	err = cbor_parse(&reader, resp->data, resp->len, &n);
+	if (err != CBOR_SUCCESS) {
+		dev_dbg(dev, "Invalid Attestation response");
+		return -EINVAL;
+	}
+
+	if (n != 5) {
+		dev_dbg(dev, "Invalid Attestation response elements (%zd)", n);
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[1], "Attestation")) {
+		dev_dbg(dev, "Invalid Attestation response items[1]");
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[3], "document")) {
+		dev_dbg(dev, "Invalid Attestation response items[3]");
+		return -EINVAL;
+	}
+
+	if (items[4].type != CBOR_ITEM_STRING) {
+		dev_dbg(dev, "Invalid Attestation response items[4]");
+		return -EINVAL;
+	}
+
+	att->document.len = items[4].size;
+	memcpy(att->document.data, &resp->data[items[4].offset],
+	       att->document.len);
+
+	return 0;
+}
+
+static int _parse_resp_attestation(struct nsm *nsm, struct nsm_data_resp *resp,
+				   void *buffer)
+{
+	return parse_resp_attestation(nsm, resp, buffer);
+}
+
+/*
+ * GetRandom request structure:
+ *
+ * {
+ *   String("GetRandom")
+ * }
+ */
+static int fill_req_get_random(struct nsm *nsm, struct nsm_data_req *req)
+{
+	cbor_writer_t writer;
+	cbor_error_t err;
+
+	cbor_writer_init(&writer, req->data, sizeof(req->data));
+	err = cbor_encode_null_terminated_text_string(&writer, "GetRandom");
+	if (err != CBOR_SUCCESS)
+		return -ENOMEM;
+	req->len = cbor_writer_len(&writer);
+
+	return 0;
+}
+
+static int _fill_req_get_random(struct nsm *nsm, struct nsm_data_req *req,
+				void *buffer)
+{
+	return fill_req_get_random(nsm, req);
+}
+
+/*
+ * GetRandom response structure:
+ *
+ * {
+ *   Map(1) {
+ *     key = String("GetRandom"),
+ *     value = Map(1) {
+ *       key = String("random"),
+ *       value = Array()
+ *     }
+ *   }
+ * }
+ */
+static int parse_resp_get_random(struct nsm *nsm, struct nsm_data_resp *resp,
+				 union nsm_get_random *random_union)
+{
+	struct nsm_get_random_resp *random = &random_union->resp;
+	struct device *dev = &nsm->vdev->dev;
+	cbor_item_t items[6];
+	cbor_reader_t reader;
+	cbor_error_t err;
+	size_t n;
+
+	cbor_reader_init(&reader, items, ARRAY_SIZE(items));
+	err = cbor_parse(&reader, resp->data, resp->len, &n);
+	if (err != CBOR_SUCCESS) {
+		dev_dbg(dev, "Invalid GetRandom response");
+		return -EINVAL;
+	}
+
+	if (n != 5) {
+		dev_dbg(dev, "Invalid GetRandom response elements (%zd)", n);
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[1], "GetRandom")) {
+		dev_dbg(dev, "Invalid GetRandom response items[1]");
+		return -EINVAL;
+	}
+
+	if (!cbor_is_str(&reader, &items[3], "random")) {
+		dev_dbg(dev, "Invalid GetRandom response items[3]");
+		return -EINVAL;
+	}
+
+	if (items[4].type != CBOR_ITEM_STRING) {
+		dev_dbg(dev, "Invalid GetRandom response items[4]");
+		return -EINVAL;
+	}
+
+	random->random.len = items[4].size;
+	memcpy(random->random.data, &resp->data[items[4].offset],
+	       random->random.len);
+
+	return 0;
+}
+
+static int _parse_resp_get_random(struct nsm *nsm, struct nsm_data_resp *resp,
+				  void *buffer)
+{
+	return parse_resp_get_random(nsm, resp, buffer);
+}
+
+/*
+ * HwRNG implementation
+ */
+static int nsm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
+{
+	struct nsm *nsm = hwrng_to_nsm(rng);
+	struct device *dev = &nsm->vdev->dev;
+	union nsm_get_random *random = NULL;
+	struct nsm_msg *msg;
+	int rc = 0;
+
+	/* NSM always needs to wait for a response */
+	if (!wait)
+		return 0;
+
+	msg = devm_kzalloc(dev, sizeof(*msg), GFP_KERNEL);
+	if (!msg)
+		return -ENOMEM;
+
+	rc = -ENOMEM;
+	random = devm_kzalloc(dev, sizeof(*random), GFP_KERNEL);
+	if (!random)
+		goto out;
+
+	rc = fill_req_get_random(nsm, &msg->req);
+	if (rc != 0)
+		goto out;
+
+	rc = nsm_sendrecv_msg(nsm, msg);
+	if (rc != 0)
+		goto out;
+
+	rc = parse_resp_get_random(nsm, &msg->resp, random);
+	if (rc != 0)
+		goto out;
+
+	rc = min_t(size_t, random->resp.random.len, max);
+	memcpy(data, random->resp.random.data, rc);
+
+	dev_dbg(dev, "RNG: returning rand bytes = %d", rc);
+out:
+	devm_kfree(dev, random);
+	devm_kfree(dev, msg);
+	return rc;
+}
+
+static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct miscdevice *misc_dev = dev_get_drvdata(dev);
+	struct nsm *nsm = misc_dev_to_nsm(misc_dev);
+
+	return sysfs_emit(buf, "%d.%d.%d\n", nsm->major, nsm->minor, nsm->patch);
+}
+static DEVICE_ATTR_RO(version);
+
+static ssize_t max_pcrs_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct miscdevice *misc_dev = dev_get_drvdata(dev);
+	struct nsm *nsm = misc_dev_to_nsm(misc_dev);
+
+	return sysfs_emit(buf, "%d\n", nsm->max_pcrs);
+}
+static DEVICE_ATTR_RO(max_pcrs);
+
+static ssize_t digest_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct miscdevice *misc_dev = dev_get_drvdata(dev);
+	struct nsm *nsm = misc_dev_to_nsm(misc_dev);
+
+	return sysfs_emit(buf, "%s\n", nsm->digest);
+}
+static DEVICE_ATTR_RO(digest);
+
+static ssize_t module_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct miscdevice *misc_dev = dev_get_drvdata(dev);
+	struct nsm *nsm = misc_dev_to_nsm(misc_dev);
+
+	return sysfs_emit(buf, "%s\n", nsm->module_id);
+}
+static DEVICE_ATTR_RO(module_id);
+
+/* Sysfs files to access NSM state in addition to ioctls */
+static struct attribute *nsm_dev_attrs[] = {
+	&dev_attr_version.attr,
+	&dev_attr_max_pcrs.attr,
+	&dev_attr_digest.attr,
+	&dev_attr_module_id.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(nsm_dev);
+
+/*
+ * IOCTL handlers. We always follow the same pattern for each of these: We copy
+ * the full user struct to kernel space, then we generate a device message from
+ * it, send it to the device and parse the response back into the in-kernel
+ * struct. Then we copy the full result back to user space.
+ */
+struct nsm_ioctl_handler {
+	int cmd;
+	int (*fill_req)(struct nsm *nsm, struct nsm_data_req *req, void *buffer);
+	int (*parse_resp)(struct nsm *nsm, struct nsm_data_resp *resp, void *buffer);
+};
+
+static const struct nsm_ioctl_handler nsm_ioctls[] = {
+	{ NSM_IOCTL_RAW, _fill_req_raw, _parse_resp_raw },
+	{ NSM_IOCTL_DESCRIBE_PCR, _fill_req_describe_pcr, _parse_resp_describe_pcr },
+	{ NSM_IOCTL_EXTEND_PCR, _fill_req_extend_pcr, _parse_resp_extend_pcr },
+	{ NSM_IOCTL_LOCK_PCR, _fill_req_lock_pcr, _parse_resp_lock_pcr },
+	{ NSM_IOCTL_LOCK_PCRS, _fill_req_lock_pcrs, _parse_resp_lock_pcrs },
+	{ NSM_IOCTL_DESCRIBE_NSM, _fill_req_describe_nsm, _parse_resp_describe_nsm },
+	{ NSM_IOCTL_ATTESTATION, _fill_req_attestation, _parse_resp_attestation },
+	{ NSM_IOCTL_RANDOM, _fill_req_get_random, _parse_resp_get_random },
+};
+
+static const struct nsm_ioctl_handler *nsm_dev_ioctl_handler(int cmd)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(nsm_ioctls); i++)
+		if (nsm_ioctls[i].cmd == cmd)
+			return &nsm_ioctls[i];
+
+	return NULL;
+}
+
+static long nsm_dev_ioctl(struct file *file, unsigned int cmd,
+	unsigned long arg)
+{
+	void __user *argp = u64_to_user_ptr((u64)arg);
+	const struct nsm_ioctl_handler *handler;
+	struct nsm *nsm = file_to_nsm(file);
+	struct device *dev = &nsm->vdev->dev;
+	struct nsm_msg *msg = NULL;
+	void *buffer = NULL;
+	int r = 0;
+
+	/* Find handler for this ioctl */
+	r = -EINVAL;
+	handler = nsm_dev_ioctl_handler(cmd);
+	if (!handler)
+		goto out;
+
+	/* Allocate message buffers to device */
+	r = -ENOMEM;
+	msg = devm_kzalloc(dev, sizeof(*msg), GFP_KERNEL);
+	if (!msg)
+		goto out;
+
+	/* Allocate kernel copy of ioctl argument */
+	r = -ENOMEM;
+	buffer = devm_kmalloc(dev, _IOC_SIZE(cmd), GFP_KERNEL);
+	if (!buffer)
+		goto out;
+
+	/* Copy user argument struct to kernel argument struct */
+	r = -EFAULT;
+	if (copy_from_user(buffer, argp, _IOC_SIZE(cmd)))
+		goto out;
+
+	/* Convert kernel argument struct to device request */
+	r = handler->fill_req(nsm, &msg->req, buffer);
+	if (r)
+		goto out;
+
+	/* Send message to NSM and read reply */
+	r = nsm_sendrecv_msg(nsm, msg);
+	if (r)
+		goto out;
+
+	/* Parse device response into kernel argument struct */
+	r = handler->parse_resp(nsm, &msg->resp, buffer);
+	if (r)
+		goto out;
+
+	/* Copy kernel argument struct  back to user argument struct */
+	r = -EFAULT;
+	if (copy_to_user(argp, buffer, _IOC_SIZE(cmd)))
+		goto out;
+
+	r = 0;
+
+out:
+	devm_kfree(dev, msg);
+	devm_kfree(dev, buffer);
+	return r;
+}
+
+static int nsm_dev_file_open(struct inode *node, struct file *file)
+{
+	return 0;
+}
+
+static int nsm_dev_file_close(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static int nsm_device_init_vq(struct virtio_device *vdev)
+{
+	struct virtqueue *vq = virtio_find_single_vq(vdev,
+		nsm_vq_callback, "nsm.vq.0");
+	struct nsm *nsm = vdev->priv;
+
+	if (IS_ERR(vq))
+		return PTR_ERR(vq);
+
+	nsm->vq = vq;
+
+	return 0;
+}
+
+static const struct file_operations nsm_dev_fops = {
+	.open = nsm_dev_file_open,
+	.release = nsm_dev_file_close,
+	.unlocked_ioctl = nsm_dev_ioctl,
+	.compat_ioctl = compat_ptr_ioctl,
+};
+
+static int nsm_init_sysfs(struct nsm *nsm)
+{
+	struct device *dev = &nsm->vdev->dev;
+	union nsm_describe_nsm *desc = NULL;
+	struct nsm_msg *msg = NULL;
+	int rc = 0;
+
+	rc = -ENOMEM;
+	msg = devm_kzalloc(dev, sizeof(*msg), GFP_KERNEL);
+	if (!msg)
+		goto out;
+
+	rc = -ENOMEM;
+	desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+	if (!desc)
+		goto out;
+
+	rc = fill_req_describe_nsm(nsm, &msg->req);
+	if (rc != 0)
+		goto out;
+
+	rc = nsm_sendrecv_msg(nsm, msg);
+	if (rc != 0)
+		goto out;
+
+	rc = parse_resp_describe_nsm(nsm, &msg->resp, desc);
+	if (rc != 0)
+		goto out;
+
+	nsm->major = desc->resp.major;
+	nsm->minor = desc->resp.minor;
+	nsm->patch = desc->resp.patch;
+	memcpy(nsm->module_id, desc->resp.module_id, sizeof(nsm->module_id));
+	memcpy(nsm->digest, desc->resp.digest, sizeof(nsm->digest));
+
+out:
+	kfree(desc);
+	kfree(msg);
+	return rc;
+}
+
+static void nsm_misc_init(struct work_struct *work)
+{
+	struct nsm *nsm = container_of(work, struct nsm, misc_init);
+	struct device *dev = &nsm->vdev->dev;
+	int rc;
+
+	/* Initialize sysfs data */
+	rc = nsm_init_sysfs(nsm);
+	if (rc) {
+		dev_err(dev, "Initializing sysfs failed: %d\n", rc);
+		return;
+	}
+
+	/* Register /dev/nsm device node */
+	nsm->misc = (struct miscdevice) {
+		.minor	= MISC_DYNAMIC_MINOR,
+		.name	= "nsm",
+		.fops	= &nsm_dev_fops,
+		.mode	= 0666,
+		.groups = nsm_dev_groups,
+	};
+
+	rc = misc_register(&nsm->misc);
+	if (rc)
+		dev_err(dev, "misc device registration error: %d.\n", rc);
+}
+
+/* Handler for probing the NSM device */
+static int nsm_device_probe(struct virtio_device *vdev)
+{
+	struct device *dev = &vdev->dev;
+	struct nsm *nsm;
+	int rc;
+
+	nsm = devm_kzalloc(&vdev->dev, sizeof(*nsm), GFP_KERNEL);
+	if (!nsm)
+		return -ENOMEM;
+
+	vdev->priv = nsm;
+	nsm->vdev = vdev;
+
+	rc = nsm_device_init_vq(vdev);
+	if (rc) {
+		dev_err(dev, "queue failed to initialize: %d.\n", rc);
+		goto err_init_vq;
+	}
+
+	mutex_init(&nsm->lock);
+	init_waitqueue_head(&nsm->wq);
+
+	/* Register as hwrng provider */
+	nsm->hwrng = (struct hwrng) {
+		.read = nsm_rng_read,
+		.name = "nsm-hwrng",
+		.quality = 1000,
+	};
+
+	rc = devm_hwrng_register(&vdev->dev, &nsm->hwrng);
+	if (rc) {
+		dev_err(dev, "RNG initialization error: %d.\n", rc);
+		goto err_hwrng;
+	}
+
+	/*
+	 * To initialize the misc device's sysfs properties we need to
+	 * communicate with NSM which only works after probe is finished.
+	 * Trigger work so we can delay the misc device registration.
+	 */
+	INIT_WORK(&nsm->misc_init, nsm_misc_init);
+	schedule_work(&nsm->misc_init);
+
+	return 0;
+
+err_hwrng:
+	vdev->config->del_vqs(vdev);
+err_init_vq:
+	kfree(nsm);
+	return rc;
+}
+
+/* Handler for removing the NSM device */
+static void nsm_device_remove(struct virtio_device *vdev)
+{
+	struct nsm *nsm = vdev->priv;
+
+	hwrng_unregister(&nsm->hwrng);
+
+	vdev->config->del_vqs(vdev);
+	misc_deregister(&nsm->misc);
+	list_del(&nsm->node);
+}
+
+/* NSM device configuration structure */
+static struct virtio_driver virtio_nsm_driver = {
+	.feature_table             = 0,
+	.feature_table_size        = 0,
+	.feature_table_legacy      = 0,
+	.feature_table_size_legacy = 0,
+	.driver.name               = KBUILD_MODNAME,
+	.driver.owner              = THIS_MODULE,
+	.id_table                  = id_table,
+	.probe                     = nsm_device_probe,
+	.remove                    = nsm_device_remove,
+};
+
+module_virtio_driver(virtio_nsm_driver);
+MODULE_DEVICE_TABLE(virtio, id_table);
+MODULE_DESCRIPTION("Virtio NSM driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/nsm.h b/include/uapi/linux/nsm.h
new file mode 100644
index 000000000000..4b5c02f0fbf6
--- /dev/null
+++ b/include/uapi/linux/nsm.h
@@ -0,0 +1,188 @@ 
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ */
+
+#ifndef __UAPI_LINUX_NSM_H
+#define __UAPI_LINUX_NSM_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define NSM_MAGIC		0x0A
+
+#define NSM_REQUEST_MAX_SIZE	0x1000
+#define NSM_RESPONSE_MAX_SIZE	0x3000
+#define NSM_PCR_MAX_SIZE        0x200
+#define NSM_MAX_PCRS            0x100
+
+struct nsm_iovec {
+	__u64 addr; /* Virtual address of target buffer */
+	__u64 len;  /* Length of target buffer */
+};
+
+/* Raw NSM message. Only available with CAP_SYS_ADMIN. */
+struct nsm_raw {
+	/* Request from user */
+	struct nsm_iovec request;
+	/* Response to user */
+	struct nsm_iovec response;
+};
+#define NSM_IOCTL_RAW		_IOWR(NSM_MAGIC, 0x0, struct nsm_raw)
+
+/* Maximum length input data */
+struct nsm_data_req {
+	__u32 len;
+	__u8  data[NSM_REQUEST_MAX_SIZE];
+};
+
+/* Maximum length output data */
+struct nsm_data_resp {
+	__u32 len;
+	__u8  data[NSM_RESPONSE_MAX_SIZE];
+};
+
+/* PCR hash. Currently at most 512 bits, but let's leave room for up to 4096. */
+struct nsm_pcr_data {
+	__u32 len;
+	__u8  data[NSM_PCR_MAX_SIZE];
+};
+
+/*
+ * DescribePCR
+ *
+ * Queries the PCR contents of a single PCR. Returns whether the PCR is
+ * currently in locked state and the PCR hash value.
+ */
+struct nsm_describe_pcr_req {
+	__u16 index;
+};
+
+struct nsm_describe_pcr_resp {
+	__u16 lock;
+	struct nsm_pcr_data data;
+};
+
+union nsm_describe_pcr {
+	struct nsm_describe_pcr_req req;
+	struct nsm_describe_pcr_resp resp;
+};
+#define NSM_IOCTL_DESCRIBE_PCR	_IOWR(NSM_MAGIC, 0x1, union nsm_describe_pcr)
+
+/*
+ * ExtendPCR
+ *
+ * Extends the PCR hash with additional binary data. Returns the new PCR
+ * hash value after extension.
+ */
+struct nsm_extend_pcr_req {
+	__u16 index;
+	__u16 pad;
+	struct nsm_data_req data;
+};
+
+struct nsm_extend_pcr_resp {
+	struct nsm_pcr_data data;
+};
+
+union nsm_extend_pcr {
+	struct nsm_extend_pcr_req req;
+	struct nsm_extend_pcr_resp resp;
+};
+#define NSM_IOCTL_EXTEND_PCR	_IOWR(NSM_MAGIC, 0x2, union nsm_extend_pcr)
+
+/*
+ * LockPCR
+ *
+ * Enables lock state for a single PCR. After this operation, the PCR becomes
+ * unmodifyable until Enclave destruction.
+ */
+struct nsm_lock_pcr_req {
+	__u16 index;
+};
+
+union nsm_lock_pcr {
+	struct nsm_lock_pcr_req req;
+};
+#define NSM_IOCTL_LOCK_PCR	_IOWR(NSM_MAGIC, 0x3, union nsm_lock_pcr)
+
+/*
+ * LockPCRs
+ *
+ * Enables lock state for all PCR from 0 up to the given range_from_zero
+ * parameter. After this operation, all PCR in range become unmodifyable
+ * until Enclave destruction.
+ */
+struct nsm_lock_pcrs_req {
+	__u16 range_from_zero;
+};
+
+union nsm_lock_pcrs {
+	struct nsm_lock_pcrs_req req;
+};
+#define NSM_IOCTL_LOCK_PCRS	_IOWR(NSM_MAGIC, 0x4, union nsm_lock_pcrs)
+
+/*
+ * DescribeNSM
+ *
+ * Provides metadata information about the NSM backend implementation,
+ * such as version and maximum number of PCRs.
+ */
+struct nsm_u16_resp {
+	__u32 u16s;
+	__u16 u16[NSM_MAX_PCRS];
+};
+
+struct nsm_describe_nsm_resp {
+	__u16 major;
+	__u16 minor;
+	__u16 patch;
+	char module_id[256];			/* null-terminated C string */
+	__u16 max_pcrs;
+	struct nsm_u16_resp locked_pcrs;
+	char digest[16];			/* null-terminated C string */
+};
+
+union nsm_describe_nsm {
+	struct nsm_describe_nsm_resp resp;
+};
+#define NSM_IOCTL_DESCRIBE_NSM	_IOWR(NSM_MAGIC, 0x5, union nsm_describe_nsm)
+
+/*
+ * Attestation
+ *
+ * Provides an attestation document that you can use to attest the Enclave
+ * against external services. Takes 3 binary input parameters that get
+ * reflected 1:1 inside the attestation document.
+ */
+struct nsm_attestation_req {
+	struct nsm_data_req user_data;
+	struct nsm_data_req nonce;
+	struct nsm_data_req public_key;
+};
+
+struct nsm_attestation_resp {
+	struct nsm_data_resp document;
+};
+
+union nsm_attestation {
+	struct nsm_attestation_req req;
+	struct nsm_attestation_resp resp;
+};
+#define NSM_IOCTL_ATTESTATION	_IOWR(NSM_MAGIC, 0x6, union nsm_attestation)
+
+/*
+ * GetRandom
+ *
+ * Returns random bytes.
+ */
+struct nsm_get_random_resp {
+	struct nsm_data_resp random;
+};
+
+union nsm_get_random {
+	struct nsm_get_random_resp resp;
+};
+#define NSM_IOCTL_RANDOM	_IOWR(NSM_MAGIC, 0x7, union nsm_get_random)
+
+#endif /* __UAPI_LINUX_NSM_H */