@@ -7,12 +7,17 @@
#include <linux/cpufeature.h>
#include <linux/export.h>
#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
#include <asm/coco.h>
#include <asm/tdx.h>
#include <asm/vmx.h>
#include <asm/insn.h>
#include <asm/insn-eval.h>
#include <asm/pgtable.h>
+#include <asm/irqdomain.h>
/* TDX module Call Leaf IDs */
#define TDX_GET_INFO 1
@@ -27,6 +32,7 @@
/* TDX hypercall Leaf IDs */
#define TDVMCALL_MAP_GPA 0x10001
#define TDVMCALL_REPORT_FATAL_ERROR 0x10003
+#define TDVMCALL_SETUP_NOTIFY_INTR 0x10004
/* MMIO direction */
#define EPT_READ 0
@@ -51,6 +57,16 @@
#define TDREPORT_SUBTYPE_0 0
+struct event_irq_entry {
+ tdx_event_irq_cb_t handler;
+ void *data;
+ struct list_head head;
+};
+
+static int tdx_event_irq __ro_after_init;
+static LIST_HEAD(event_irq_cb_list);
+static DEFINE_SPINLOCK(event_irq_cb_lock);
+
/*
* Wrapper for standard use of __tdx_hypercall with no output aside from
* return code.
@@ -873,3 +889,146 @@ void __init tdx_early_init(void)
pr_info("Guest detected\n");
}
+
+static irqreturn_t tdx_event_irq_handler(int irq, void *dev_id)
+{
+ struct event_irq_entry *entry;
+
+ spin_lock(&event_irq_cb_lock);
+ list_for_each_entry(entry, &event_irq_cb_list, head) {
+ if (entry->handler)
+ entry->handler(entry->data);
+ }
+ spin_unlock(&event_irq_cb_lock);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * tdx_event_irq_init() - Register IRQ for event notification from the VMM to
+ * the TDX Guest.
+ *
+ * Use SetupEventNotifyInterrupt TDVMCALL to register the event notification
+ * IRQ with the VMM, which is used by the VMM to notify the TDX guest when
+ * needed, for instance, when VMM finishes the GetQuote request from the TDX
+ * guest. The VMM always notifies the TDX guest via the same CPU that calls
+ * the SetupEventNotifyInterrupt TDVMCALL. Allocate an IRQ/vector from the
+ * x86_vector_domain and pin it on the same CPU on which TDVMCALL is called.
+ * For simplicity, use early_initcall() to allow both IRQ allocation and
+ * TDVMCALL to use BSP.
+ */
+static int __init tdx_event_irq_init(void)
+{
+ struct irq_affinity_desc desc;
+ struct irq_alloc_info info;
+ struct irq_cfg *cfg;
+ int irq;
+
+ if (!cpu_feature_enabled(X86_FEATURE_TDX_GUEST))
+ return 0;
+
+ init_irq_alloc_info(&info, NULL);
+
+ cpumask_set_cpu(smp_processor_id(), &desc.mask);
+
+ irq = __irq_domain_alloc_irqs(x86_vector_domain, -1, 1,
+ cpu_to_node(smp_processor_id()), &info,
+ false, &desc);
+ if (irq <= 0) {
+ pr_err("Event notification IRQ allocation failed %d\n", irq);
+ return -EIO;
+ }
+
+ irq_set_handler(irq, handle_edge_irq);
+
+ /*
+ * The IRQ cannot be migrated because VMM always notifies the TDX
+ * guest on the same CPU on which the SetupEventNotifyInterrupt
+ * TDVMCALL is called. Set the IRQ with IRQF_NOBALANCING to prevent
+ * its affinity from being changed.
+ */
+ if (request_irq(irq, tdx_event_irq_handler, IRQF_NOBALANCING,
+ "tdx_event_irq", NULL)) {
+ pr_err("Event notification IRQ request failed\n");
+ goto err_free_domain_irqs;
+ }
+
+ cfg = irq_cfg(irq);
+
+ if (_tdx_hypercall(TDVMCALL_SETUP_NOTIFY_INTR, cfg->vector, 0, 0, 0)) {
+ pr_err("Event notification hypercall failed\n");
+ goto err_free_irqs;
+ }
+
+ tdx_event_irq = irq;
+
+ return 0;
+
+err_free_irqs:
+ free_irq(irq, NULL);
+err_free_domain_irqs:
+ irq_domain_free_irqs(irq, 1);
+
+ return -EIO;
+}
+early_initcall(tdx_event_irq_init)
+
+/**
+ * tdx_register_event_irq_cb() - Register TDX event IRQ callback handler.
+ * @handler: Address of driver specific event IRQ callback handler. Handler
+ * will be called in IRQ context and hence cannot sleep.
+ * @data: Context data to be passed to the callback handler.
+ *
+ * Return: 0 on success or standard error code on other failures.
+ */
+int tdx_register_event_irq_cb(tdx_event_irq_cb_t handler, void *data)
+{
+ struct event_irq_entry *entry;
+ unsigned long flags;
+
+ if (tdx_event_irq <= 0)
+ return -EIO;
+
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ entry->data = data;
+ entry->handler = handler;
+
+ spin_lock_irqsave(&event_irq_cb_lock, flags);
+ list_add_tail(&entry->head, &event_irq_cb_list);
+ spin_unlock_irqrestore(&event_irq_cb_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tdx_register_event_irq_cb);
+
+/**
+ * tdx_unregister_event_irq_cb() - Unregister TDX event IRQ callback handler.
+ * @handler: Address of driver specific event IRQ callback handler.
+ * @data: Context data to be passed to the callback handler.
+ *
+ * Return: 0 on success or -EIO if event IRQ is not allocated.
+ */
+int tdx_unregister_event_irq_cb(tdx_event_irq_cb_t handler, void *data)
+{
+ struct event_irq_entry *entry;
+ unsigned long flags;
+
+ if (tdx_event_irq <= 0)
+ return -EIO;
+
+ spin_lock_irqsave(&event_irq_cb_lock, flags);
+ list_for_each_entry(entry, &event_irq_cb_list, head) {
+ if (entry->handler == handler && entry->data == data) {
+ list_del(&entry->head);
+ kfree(entry);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&event_irq_cb_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tdx_unregister_event_irq_cb);
@@ -53,6 +53,8 @@ struct ve_info {
#ifdef CONFIG_INTEL_TDX_GUEST
+typedef int (*tdx_event_irq_cb_t)(void *);
+
void __init tdx_early_init(void);
/* Used to communicate with the TDX module */
@@ -69,6 +71,10 @@ bool tdx_early_handle_ve(struct pt_regs *regs);
int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport);
+int tdx_register_event_irq_cb(tdx_event_irq_cb_t handler, void *data);
+
+int tdx_unregister_event_irq_cb(tdx_event_irq_cb_t handler, void *data);
+
#else
static inline void tdx_early_init(void) { };