@@ -9,6 +9,8 @@
#include <asm/msr.h>
#include <xen/errno.h>
#include <xen/mm.h>
+#include <xen/sched.h>
+#include <asm/p2m.h>
#include <asm/hvm/vmx/sgx.h>
#include <asm/hvm/vmx/vmcs.h>
@@ -90,6 +92,319 @@ void unmap_epc_page(void *addr)
/* Nothing */
}
+/* ENCLS opcode */
+#define ENCLS .byte 0x0f, 0x01, 0xcf
+
+/*
+ * ENCLS leaf functions
+ *
+ * However currently we only needs EREMOVE..
+ */
+enum {
+ ECREATE = 0x0,
+ EADD = 0x1,
+ EINIT = 0x2,
+ EREMOVE = 0x3,
+ EDGBRD = 0x4,
+ EDGBWR = 0x5,
+ EEXTEND = 0x6,
+ ELDU = 0x8,
+ EBLOCK = 0x9,
+ EPA = 0xA,
+ EWB = 0xB,
+ ETRACK = 0xC,
+ EAUG = 0xD,
+ EMODPR = 0xE,
+ EMODT = 0xF,
+};
+
+/*
+ * ENCLS error code
+ *
+ * Currently we only need SGX_CHILD_PRESENT
+ */
+#define SGX_CHILD_PRESENT 13
+
+static inline int __encls(unsigned long rax, unsigned long rbx,
+ unsigned long rcx, unsigned long rdx)
+{
+ int ret;
+
+ asm volatile ( "ENCLS;\n\t"
+ : "=a" (ret)
+ : "a" (rax), "b" (rbx), "c" (rcx), "d" (rdx)
+ : "memory", "cc");
+
+ return ret;
+}
+
+static inline int __eremove(void *epc)
+{
+ unsigned long rbx = 0, rdx = 0;
+
+ return __encls(EREMOVE, rbx, (unsigned long)epc, rdx);
+}
+
+static int sgx_eremove(struct epc_page *epg)
+{
+ void *addr = map_epc_page_to_xen(epg);
+ int ret;
+
+ BUG_ON(!addr);
+
+ ret = __eremove(addr);
+
+ unmap_epc_page(addr);
+
+ return ret;
+}
+
+/*
+ * Reset domain's EPC with EREMOVE. free_epc indicates whether to free EPC
+ * pages during reset. This will be called when domain goes into S3-S5 state
+ * (with free_epc being false), and when domain is destroyed (with free_epc
+ * being true).
+ *
+ * It is possible that EREMOVE will be called for SECS when it still has
+ * children present, in which case SGX_CHILD_PRESENT will be returned. In this
+ * case, SECS page is kept to a tmp list and after all EPC pages have been
+ * called with EREMOVE, we call EREMOVE for all the SECS pages again, and this
+ * time SGX_CHILD_PRESENT should never occur as all children should have been
+ * removed.
+ *
+ * If unexpected error returned by EREMOVE, it means the EPC page becomes
+ * abnormal, so it will not be freed even free_epc is true, as further use of
+ * this EPC can cause unexpected error, potentially damaging other domains.
+ */
+static int __hvm_reset_epc(struct domain *d, unsigned long epc_base_pfn,
+ unsigned long epc_npages, bool_t free_epc)
+{
+ struct list_head secs_list;
+ struct list_head *p, *tmp;
+ unsigned long i;
+ int ret = 0;
+
+ INIT_LIST_HEAD(&secs_list);
+
+ for ( i = 0; i < epc_npages; i++ )
+ {
+ struct epc_page *epg;
+ unsigned long gfn;
+ mfn_t mfn;
+ p2m_type_t t;
+ int r;
+
+ gfn = i + epc_base_pfn;
+ mfn = get_gfn_query(d, gfn, &t);
+ if ( unlikely(mfn_eq(mfn, INVALID_MFN)) )
+ {
+ printk("Domain %d: Reset EPC error: invalid MFN for gfn 0x%lx\n",
+ d->domain_id, gfn);
+ put_gfn(d, gfn);
+ ret = -EFAULT;
+ continue;
+ }
+
+ if ( unlikely(!p2m_is_epc(t)) )
+ {
+ printk("Domain %d: Reset EPC error: (gfn 0x%lx, mfn 0x%lx): "
+ "is not p2m_epc.\n", d->domain_id, gfn, mfn_x(mfn));
+ put_gfn(d, gfn);
+ ret = -EFAULT;
+ continue;
+ }
+
+ put_gfn(d, gfn);
+
+ epg = epc_mfn_to_page(mfn_x(mfn));
+
+ /* EREMOVE the EPC page to make it invalid */
+ r = sgx_eremove(epg);
+ if ( r == SGX_CHILD_PRESENT )
+ {
+ list_add_tail(&epg->list, &secs_list);
+ continue;
+ }
+
+ if ( r )
+ {
+ printk("Domain %d: Reset EPC error: (gfn 0x%lx, mfn 0x%lx): "
+ "EREMOVE returns %d\n", d->domain_id, gfn, mfn_x(mfn), r);
+ ret = r;
+ if ( free_epc )
+ printk("WARNING: EPC (mfn 0x%lx) becomes abnormal. "
+ "Remove it from useable EPC.", mfn_x(mfn));
+ continue;
+ }
+
+ if ( free_epc )
+ {
+ /* If EPC page is going to be freed, then also remove the mapping */
+ if ( clear_epc_p2m_entry(d, gfn, mfn) )
+ {
+ printk("Domain %d: Reset EPC error: (gfn 0x%lx, mfn 0x%lx): "
+ "clear p2m entry failed.\n", d->domain_id, gfn,
+ mfn_x(mfn));
+ ret = -EFAULT;
+ }
+ free_epc_page(epg);
+ }
+ }
+
+ list_for_each_safe(p, tmp, &secs_list)
+ {
+ struct epc_page *epg = list_entry(p, struct epc_page, list);
+ int r;
+
+ r = sgx_eremove(epg);
+ if ( r )
+ {
+ printk("Domain %d: Reset EPC error: mfn 0x%lx: "
+ "EREMOVE returns %d for SECS page\n",
+ d->domain_id, epc_page_to_mfn(epg), r);
+ ret = r;
+ list_del(p);
+
+ if ( free_epc )
+ printk("WARNING: EPC (mfn 0x%lx) becomes abnormal. "
+ "Remove it from useable EPC.",
+ epc_page_to_mfn(epg));
+ continue;
+ }
+
+ if ( free_epc )
+ free_epc_page(epg);
+ }
+
+ return ret;
+}
+
+static void __hvm_unpopulate_epc(struct domain *d, unsigned long epc_base_pfn,
+ unsigned long populated_npages)
+{
+ unsigned long i;
+
+ for ( i = 0; i < populated_npages; i++ )
+ {
+ struct epc_page *epg;
+ unsigned long gfn;
+ mfn_t mfn;
+ p2m_type_t t;
+
+ gfn = i + epc_base_pfn;
+ mfn = get_gfn_query(d, gfn, &t);
+ if ( unlikely(mfn_eq(mfn, INVALID_MFN)) )
+ {
+ /*
+ * __hvm_unpopulate_epc only called when creating the domain on
+ * failure, therefore we can just ignore this error.
+ */
+ printk("%s: Domain %u gfn 0x%lx returns invalid mfn\n", __func__,
+ d->domain_id, gfn);
+ put_gfn(d, gfn);
+ continue;
+ }
+
+ if ( unlikely(!p2m_is_epc(t)) )
+ {
+ printk("%s: Domain %u gfn 0x%lx returns non-EPC p2m type: %d\n",
+ __func__, d->domain_id, gfn, (int)t);
+ put_gfn(d, gfn);
+ continue;
+ }
+
+ put_gfn(d, gfn);
+
+ if ( clear_epc_p2m_entry(d, gfn, mfn) )
+ {
+ printk("clear_epc_p2m_entry failed: gfn 0x%lx, mfn 0x%lx\n",
+ gfn, mfn_x(mfn));
+ continue;
+ }
+
+ epg = epc_mfn_to_page(mfn_x(mfn));
+ free_epc_page(epg);
+ }
+}
+
+static int __hvm_populate_epc(struct domain *d, unsigned long epc_base_pfn,
+ unsigned long epc_npages)
+{
+ unsigned long i;
+ int ret;
+
+ for ( i = 0; i < epc_npages; i++ )
+ {
+ struct epc_page *epg = alloc_epc_page();
+ unsigned long mfn;
+
+ if ( !epg )
+ {
+ printk("%s: Out of EPC\n", __func__);
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ mfn = epc_page_to_mfn(epg);
+ ret = set_epc_p2m_entry(d, i + epc_base_pfn, _mfn(mfn));
+ if ( ret )
+ {
+ printk("%s: set_epc_p2m_entry failed with %d: gfn 0x%lx, "
+ "mfn 0x%lx\n", __func__, ret, i + epc_base_pfn, mfn);
+ free_epc_page(epg);
+ goto err;
+ }
+ }
+
+ return 0;
+
+err:
+ __hvm_unpopulate_epc(d, epc_base_pfn, i);
+ return ret;
+}
+
+int hvm_populate_epc(struct domain *d, unsigned long epc_base_pfn,
+ unsigned long epc_npages)
+{
+ struct sgx_domain *sgx = to_sgx(d);
+ int ret;
+
+ if ( hvm_epc_populated(d) )
+ return -EBUSY;
+
+ if ( !epc_base_pfn || !epc_npages )
+ return -EINVAL;
+
+ if ( (ret = __hvm_populate_epc(d, epc_base_pfn, epc_npages)) )
+ return ret;
+
+ sgx->epc_base_pfn = epc_base_pfn;
+ sgx->epc_npages = epc_npages;
+
+ return 0;
+}
+
+/*
+ *
+*
+ * This function returns error immediately if there's any unexpected error
+ * during this process.
+ */
+int hvm_reset_epc(struct domain *d, bool_t free_epc)
+{
+ struct sgx_domain *sgx = to_sgx(d);
+
+ if ( !hvm_epc_populated(d) )
+ return 0;
+
+ return __hvm_reset_epc(d, sgx->epc_base_pfn, sgx->epc_npages, free_epc);
+}
+
+void hvm_destroy_epc(struct domain *d)
+{
+ hvm_reset_epc(d, true);
+}
+
static bool_t sgx_enabled_in_bios(void)
{
uint64_t val, sgx_enabled = IA32_FEATURE_CONTROL_SGX_ENABLE |
@@ -416,6 +416,9 @@ static int vmx_domain_initialise(struct domain *d)
static void vmx_domain_destroy(struct domain *d)
{
+ if ( hvm_epc_populated(d) )
+ hvm_destroy_epc(d);
+
if ( !has_vlapic(d) )
return;
@@ -13,6 +13,7 @@
#include <xen/init.h>
#include <asm/processor.h>
#include <xen/list.h>
+#include <public/hvm/params.h> /* HVM_PARAM_SGX */
#define SGX_CPUID 0x12
@@ -61,4 +62,17 @@ struct epc_page *epc_mfn_to_page(unsigned long mfn);
void *map_epc_page_to_xen(struct epc_page *epg);
void unmap_epc_page(void *addr);
+struct sgx_domain {
+ unsigned long epc_base_pfn;
+ unsigned long epc_npages;
+};
+
+#define to_sgx(d) (&((d)->arch.hvm_domain.vmx.sgx))
+#define hvm_epc_populated(d) (!!((d)->arch.hvm_domain.vmx.sgx.epc_base_pfn))
+
+int hvm_populate_epc(struct domain *d, unsigned long epc_base_pfn,
+ unsigned long epc_npages);
+int hvm_reset_epc(struct domain *d, bool_t free_epc);
+void hvm_destroy_epc(struct domain *d);
+
#endif /* __ASM_X86_HVM_VMX_SGX_H__ */
@@ -20,6 +20,7 @@
#include <asm/hvm/io.h>
#include <irq_vectors.h>
+#include <asm/hvm/vmx/sgx.h>
extern void vmcs_dump_vcpu(struct vcpu *v);
extern void setup_vmcs_dump(void);
@@ -62,6 +63,7 @@ struct vmx_domain {
unsigned long apic_access_mfn;
/* VMX_DOMAIN_* */
unsigned int status;
+ struct sgx_domain sgx;
};
struct pi_desc {
Add per-domain structure to store SGX per-domain info. Currently only domain's EPC base and size are stored. Also add new functions for further use: - hvm_populate_epc # populate EPC when EPC base & size are notified. - hvm_reset_epc # Reset domain's EPC to be invalid. Used when domain goes to S3-S5, or being destroyed. - hvm_destroy_epc # destroy and free domain's EPC. Signed-off-by: Kai Huang <kai.huang@linux.intel.com> --- xen/arch/x86/hvm/vmx/sgx.c | 315 +++++++++++++++++++++++++++++++++++++ xen/arch/x86/hvm/vmx/vmx.c | 3 + xen/include/asm-x86/hvm/vmx/sgx.h | 14 ++ xen/include/asm-x86/hvm/vmx/vmcs.h | 2 + 4 files changed, 334 insertions(+)