arch/riscv: Keystone
Driver for the Keystone trusted execution environment.
Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@tuni.fi>
diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index fcbb81f..824057e 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -4,6 +4,17 @@
# see Documentation/kbuild/kconfig-language.rst.
#
+config RISCV_KEYSTONE
+ bool "Keystone"
+ default n
+ depends on CMA
+ help
+ Keystone is a trusted execution environment hosted by Keystone security
+ monitor, which runs inside SBI. Keystone driver provides ioctl interface
+ for creating and running Keystone enclaves.
+
+ If you don't know what to do here, say N.
+
config 64BIT
bool
diff --git a/arch/riscv/include/uapi/asm/keystone.h b/arch/riscv/include/uapi/asm/keystone.h
new file mode 100644
index 0000000..a35b9c8
--- /dev/null
+++ b/arch/riscv/include/uapi/asm/keystone.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2018-2022 The Regents of the University of California (Regents).
+ * Copyright (c) 2023 University of Tampere.
+ *
+ * Authors:
+ * Dayeol Lee <dayeol@berkeley.edu>
+ * Evgeny Pobachienko <evgenyp@berkeley.edu>
+ * Jarkko Sakkinen <jarkko.sakkinen@tuni.fi>
+ */
+
+#ifndef UAPI_ASM_RISCV_KEYSTONE_H
+#define UAPI_ASM_RISCV_KEYSTONE_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+/* Same as TEE_IOC_MAGIC */
+#define KEYSTONE_IOC 0xA4
+
+#define KEYSTONE_IOC_CREATE_ENCLAVE _IOR(KEYSTONE_IOC, 0x00, struct keystone_ioctl_enclave)
+#define KEYSTONE_IOC_DESTROY_ENCLAVE _IOW(KEYSTONE_IOC, 0x01, struct keystone_ioctl_enclave)
+#define KEYSTONE_IOC_RUN_ENCLAVE _IOR(KEYSTONE_IOC, 0x04, struct keystone_ioctl_run)
+#define KEYSTONE_IOC_RESUME_ENCLAVE _IOR(KEYSTONE_IOC, 0x05, struct keystone_ioctl_run)
+#define KEYSTONE_IOC_FINALIZE_ENCLAVE _IOR(KEYSTONE_IOC, 0x06, struct keystone_ioctl_enclave)
+#define KEYSTONE_IOC_UTM_INIT _IOR(KEYSTONE_IOC, 0x07, struct keystone_ioctl_enclave)
+
+struct keystone_ioctl_enclave {
+ __u64 eid; /* out */
+ __u64 min_pages; /* in */
+ __u64 runtime_vaddr; /* not used by the driver */
+ __u64 user_vaddr; /* not used by the driver */
+ __u64 pt_ptr;
+ __u64 utm_free_ptr;
+ __u64 epm_paddr;
+ __u64 utm_paddr;
+ __u64 runtime_paddr;
+ __u64 user_paddr;
+ __u64 free_paddr;
+ __u64 epm_size;
+ __u64 utm_size;
+ __u64 runtime_entry; /* in */
+ __u64 user_entry; /* in */
+ __u64 untrusted_ptr; /* in */
+ __u64 untrusted_size; /* in */
+};
+
+struct keystone_ioctl_run {
+ __u64 eid;
+ __u64 error;
+ __u64 value;
+};
+
+#endif /* UAPI_ASM_RISCV_KEYSTONE_H */
diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
index 33bb60a..c597218 100644
--- a/arch/riscv/kernel/Makefile
+++ b/arch/riscv/kernel/Makefile
@@ -3,6 +3,8 @@
# Makefile for the RISC-V Linux kernel
#
+obj-$(CONFIG_RISCV_KEYSTONE) += keystone.o
+
ifdef CONFIG_FTRACE
CFLAGS_REMOVE_ftrace.o = $(CC_FLAGS_FTRACE)
CFLAGS_REMOVE_patch.o = $(CC_FLAGS_FTRACE)
diff --git a/arch/riscv/kernel/keystone.c b/arch/riscv/kernel/keystone.c
new file mode 100644
index 0000000..5bc4db0
--- /dev/null
+++ b/arch/riscv/kernel/keystone.c
@@ -0,0 +1,419 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2022 The Regents of the University of California (Regents).
+ * Copyright (c) 2023 Univrsity of Tampere.
+ *
+ * Authors:
+ * Dayeol Lee <dayeol@berkeley.edu>
+ * Evgeny Pobachienko <evgenyp@berkeley.edu>
+ * Jarkko Sakkinen <jarkko.sakkinen@tuni.fi>
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/mm.h>
+#include <linux/file.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/anon_inodes.h>
+#include <linux/genalloc.h>
+#include <linux/slab.h>
+#include <asm/sbi.h>
+#include <uapi/asm/keystone.h>
+
+#undef pr_fmt
+#define pr_fmt(fmt) "keystone: " fmt
+
+#define KEYSTONE_SBI_EXT_ID 0x08424b45
+#define SBI_SM_CREATE_ENCLAVE 2001
+#define SBI_SM_DESTROY_ENCLAVE 2002
+#define SBI_SM_RUN_ENCLAVE 2003
+#define SBI_SM_RESUME_ENCLAVE 2005
+
+struct keystone_sbi_create {
+ u64 epm_pa;
+ u64 epm_size;
+ u64 utm_pa;
+ u64 utm_size;
+ u64 runtime_paddr;
+ u64 user_paddr;
+ u64 free_paddr;
+ u64 runtime_entry;
+ u64 user_entry;
+ u64 untrusted_ptr;
+ u64 untrusted_size;
+};
+
+struct keystone_mem {
+ u64 va;
+ u64 pa;
+ struct gen_pool *pool;
+};
+
+struct keystone_region {
+ u64 va;
+ u64 pa;
+ u64 size;
+};
+
+struct keystone_enclave {
+ long eid;
+ unsigned long busy;
+ struct keystone_region utm;
+ struct keystone_region epm;
+};
+
+#define sbi_sm_call(id, value) sbi_ecall(KEYSTONE_SBI_EXT_ID, (id), (value), 0, 0, 0, 0, 0)
+
+static const unsigned long KEYSTONE_POOL_SIZE = 256UL << 20;
+static const long KEYSTONE_EID_NULL = -1;
+static const long KEYSTONE_EID_LAST = 15;
+
+static struct keystone_mem keystone_mem;
+
+static inline bool keystone_region_empty(struct keystone_region *region)
+{
+ return !memchr_inv(region, 0, sizeof(*region));
+}
+
+static inline bool keystone_is_valid_eid(long eid)
+{
+ if (eid >= 0 && eid <= KEYSTONE_EID_LAST)
+ return true;
+
+ if (eid != KEYSTONE_EID_NULL)
+ pr_err("%s: Unknown EID %ld\n", __func__, eid);
+
+ return false;
+}
+
+static int keystone_alloc_region(struct keystone_region *region, unsigned long requested_pages)
+{
+ unsigned long order = ilog2(requested_pages - 1) + 1;
+ unsigned long aligned_pages = 1UL << order;
+ unsigned long size = aligned_pages << PAGE_SHIFT;
+ dma_addr_t pa;
+ u64 va;
+
+ if (!keystone_region_empty(region))
+ return -EPERM;
+
+ va = (u64)gen_pool_dma_zalloc_align(keystone_mem.pool, size, &pa, size);
+ if (!va)
+ return -ENOMEM;
+
+ region->va = va;
+ region->pa = pa;
+ region->size = size;
+ return 0;
+}
+
+static void keystone_free_region(struct keystone_region *region)
+{
+ if (keystone_region_empty(region))
+ return;
+
+ gen_pool_free(keystone_mem.pool, region->va, region->size);
+ memset(region, 0, sizeof(*region));
+}
+
+static int keystone_ioc_create_enclave(struct file *filep, unsigned long arg)
+{
+ struct keystone_ioctl_enclave *enclp = (struct keystone_ioctl_enclave *)arg;
+ struct keystone_enclave *enclave = filep->private_data;
+
+ if (keystone_is_valid_eid(enclave->eid))
+ return -EPERM;
+
+ if (keystone_alloc_region(&enclave->epm, enclp->min_pages))
+ return -ENOMEM;
+
+ enclp->pt_ptr = enclave->epm.pa;
+ enclp->epm_size = enclave->epm.size;
+ return 0;
+}
+
+static int keystone_ioc_finalize_enclave(struct file *filep, unsigned long arg)
+{
+ struct keystone_ioctl_enclave *enclp = (struct keystone_ioctl_enclave *)arg;
+ struct keystone_enclave *enclave = filep->private_data;
+ struct keystone_sbi_create args;
+ struct sbiret ret;
+
+ /* At minimum EPM needs to be created: */
+ if (keystone_is_valid_eid(enclave->eid) || keystone_region_empty(&enclave->epm))
+ return -EPERM;
+
+ args.epm_pa = enclave->epm.pa;
+ args.epm_size = enclave->epm.size;
+ args.utm_pa = enclave->utm.pa;
+ args.utm_size = enclave->utm.size;
+ args.runtime_paddr = enclp->runtime_paddr;
+ args.user_paddr = enclp->user_paddr;
+ args.free_paddr = enclp->free_paddr;
+ args.runtime_entry = enclp->runtime_entry;
+ args.user_entry = enclp->user_entry;
+ args.untrusted_ptr = enclp->untrusted_ptr;
+ args.untrusted_size = enclp->untrusted_size;
+
+ ret = sbi_sm_call(SBI_SM_CREATE_ENCLAVE, (unsigned long)&args);
+ if (ret.error) {
+ pr_err("SBI_SM_CREATE_ENCLAVE error %ld\n", ret.error);
+ return -EIO;
+ }
+
+ if (!keystone_is_valid_eid(ret.value))
+ return -EIO;
+
+ enclave->eid = ret.value;
+ return 0;
+}
+
+static int keystone_ioc_run_enclave(struct file *filep, unsigned long data)
+{
+ struct keystone_ioctl_run *arg = (struct keystone_ioctl_run *)data;
+ struct keystone_enclave *enclave = filep->private_data;
+ struct sbiret ret;
+
+ if (!keystone_is_valid_eid(enclave->eid))
+ return -EPERM;
+
+ ret = sbi_sm_call(SBI_SM_RUN_ENCLAVE, enclave->eid);
+ arg->error = ret.error;
+ arg->value = ret.value;
+ return 0;
+}
+
+static int __keystone_ioc_destroy_enclave(struct keystone_enclave *enclave)
+{
+ struct sbiret ret;
+
+ if (keystone_is_valid_eid(enclave->eid)) {
+ ret = sbi_sm_call(SBI_SM_DESTROY_ENCLAVE, enclave->eid);
+ if (ret.error) {
+ pr_warn("SBI_SM_DESTROY_ENCLAVE error %ld\n", ret.error);
+ return -EIO;
+ }
+ }
+
+ keystone_free_region(&enclave->epm);
+ keystone_free_region(&enclave->utm);
+ enclave->eid = KEYSTONE_EID_NULL;
+ return 0;
+}
+
+static int keystone_ioc_destroy_enclave(struct file *filep, unsigned long arg)
+{
+ struct keystone_enclave *enclave = filep->private_data;
+
+ return __keystone_ioc_destroy_enclave(enclave);
+}
+
+static int keystone_ioc_resume_enclave(struct file *filep, unsigned long data)
+{
+ struct keystone_ioctl_run *arg = (struct keystone_ioctl_run *)data;
+ struct keystone_enclave *enclave = filep->private_data;
+ struct sbiret ret;
+
+ if (!keystone_is_valid_eid(enclave->eid))
+ return -EPERM;
+
+ ret = sbi_sm_call(SBI_SM_RESUME_ENCLAVE, enclave->eid);
+ arg->error = ret.error;
+ arg->value = ret.value;
+ return 0;
+}
+
+static int keystone_ioc_utm_init(struct file *filep, unsigned long arg)
+{
+ struct keystone_ioctl_enclave *enclp = (struct keystone_ioctl_enclave *)arg;
+ unsigned long pages = PFN_DOWN(ALIGN(enclp->untrusted_size, PAGE_SIZE));
+ struct keystone_enclave *enclave = filep->private_data;
+ int ret;
+
+ if (keystone_is_valid_eid(enclave->eid))
+ return -EPERM;
+
+ ret = keystone_alloc_region(&enclave->utm, pages);
+ if (!ret)
+ enclp->utm_free_ptr = enclave->utm.pa;
+
+ return ret;
+}
+
+static long keystone_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ struct keystone_enclave *enclave = filep->private_data;
+ size_t ioc_size;
+ char data[512];
+ long ret;
+
+ if (!enclave)
+ return -EIO;
+
+ if (test_and_set_bit(true, &enclave->busy))
+ return -EBUSY;
+
+ ioc_size = _IOC_SIZE(cmd);
+ ioc_size = ioc_size > sizeof(data) ? sizeof(data) : ioc_size;
+
+ if (copy_from_user(data, (void __user *)arg, ioc_size)) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ switch (cmd) {
+ case KEYSTONE_IOC_CREATE_ENCLAVE:
+ ret = keystone_ioc_create_enclave(filep, (unsigned long)data);
+ break;
+ case KEYSTONE_IOC_FINALIZE_ENCLAVE:
+ ret = keystone_ioc_finalize_enclave(filep, (unsigned long)data);
+ break;
+ case KEYSTONE_IOC_DESTROY_ENCLAVE:
+ ret = keystone_ioc_destroy_enclave(filep, (unsigned long)data);
+ break;
+ case KEYSTONE_IOC_RUN_ENCLAVE:
+ ret = keystone_ioc_run_enclave(filep, (unsigned long)data);
+ break;
+ case KEYSTONE_IOC_RESUME_ENCLAVE:
+ ret = keystone_ioc_resume_enclave(filep, (unsigned long)data);
+ break;
+ case KEYSTONE_IOC_UTM_INIT:
+ ret = keystone_ioc_utm_init(filep, (unsigned long)data);
+ break;
+ default:
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+
+ if (!ret && copy_to_user((void __user *)arg, data, ioc_size))
+ ret = -EFAULT;
+
+err:
+ clear_bit(true, &enclave->busy);
+ return ret;
+}
+
+static int keystone_open(struct inode *inode, struct file *file)
+{
+ struct keystone_enclave *enclave;
+
+ enclave = kzalloc(sizeof(*enclave), GFP_KERNEL);
+ if (!enclave)
+ return -ENOMEM;
+
+ enclave->eid = KEYSTONE_EID_NULL;
+ file->private_data = enclave;
+ return 0;
+}
+
+static int keystone_release(struct inode *inode, struct file *file)
+{
+ struct keystone_enclave *enclave = file->private_data;
+ int ret = 0;
+
+ WARN_ON(!enclave);
+
+ if (enclave) {
+ ret = __keystone_ioc_destroy_enclave(enclave);
+ kfree(enclave);
+ }
+
+ return ret;
+}
+
+static int keystone_mmap(struct file *filep, struct vm_area_struct *vma)
+{
+ struct keystone_enclave *enclave = filep->private_data;
+ unsigned long vma_size = vma->vm_end - vma->vm_start;
+ unsigned long pfn;
+
+ if (!enclave)
+ return -EIO;
+
+ if (!keystone_is_valid_eid(enclave->eid)) {
+ if (vma_size > PAGE_SIZE)
+ return -EINVAL;
+
+ pfn = PFN_DOWN(enclave->epm.pa + (vma->vm_pgoff << PAGE_SHIFT));
+ } else {
+ if (vma_size > enclave->utm.size)
+ return -EINVAL;
+
+ pfn = PFN_DOWN(enclave->utm.pa);
+ }
+
+ return remap_pfn_range(vma, vma->vm_start, pfn, vma_size, vma->vm_page_prot);
+}
+
+static const struct file_operations keystone_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = keystone_ioctl,
+ .open = keystone_open,
+ .release = keystone_release,
+ .mmap = keystone_mmap,
+};
+
+struct miscdevice keystone_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "keystone_enclave",
+ .fops = &keystone_fops,
+ .mode = 0666,
+};
+
+static int __init keystone_mem_init(void)
+{
+ struct device *dev = keystone_dev.this_device;
+ struct gen_pool *pool;
+ dma_addr_t pa;
+ void *va_ptr;
+ int ret;
+
+ dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ pool = gen_pool_create(__ffs(PAGE_SIZE), -1);
+ if (!pool)
+ return -ENOMEM;
+
+ va_ptr = dma_alloc_coherent(dev, KEYSTONE_POOL_SIZE, &pa, GFP_KERNEL | __GFP_ZERO);
+ if (!va_ptr) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ ret = gen_pool_add_virt(pool, (unsigned long)va_ptr, pa, KEYSTONE_POOL_SIZE, -1);
+ if (ret < 0)
+ goto err_attach;
+
+ keystone_mem.pool = pool;
+ keystone_mem.va = (unsigned long)va_ptr;
+ keystone_mem.pa = pa;
+ pr_info("%s: %lu MiB\n", __func__, KEYSTONE_POOL_SIZE >> 20);
+ return 0;
+
+err_attach:
+ dma_free_coherent(dev, KEYSTONE_POOL_SIZE, (void *)va_ptr, pa);
+
+err_alloc:
+ gen_pool_destroy(pool);
+ return ret;
+}
+
+static int __init keystone_init(void)
+{
+ int ret;
+
+ ret = misc_register(&keystone_dev);
+ if (ret < 0)
+ return ret;
+
+ ret = keystone_mem_init();
+ if (ret < 0) {
+ misc_deregister(&keystone_dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+device_initcall(keystone_init);