@@ -42,6 +42,17 @@ ABI. However, in the future, if the TDX Module supports more than one subtype,
a new IOCTL CMD will be created to handle it. To keep the IOCTL naming
consistent, a subtype index is added as part of the IOCTL CMD.
+2.2 TDX_CMD_GET_QUOTE
+----------------------
+
+:Input parameters: struct tdx_quote_req
+:Output: Return 0 on success, -EINTR for interrupted request, -EIO on TDCALL
+ failure or standard error number on common failures. Upon successful
+ execution, QUOTE data is copied to tdx_quote_req.buf.
+
+The TDX_CMD_GET_QUOTE IOCTL can be used by attestation software to generate
+QUOTE for the given TDREPORT using TDG.VP.VMCALL<GetQuote> hypercall.
+
Reference
---------
@@ -34,6 +34,7 @@
#define TDVMCALL_MAP_GPA 0x10001
#define TDVMCALL_REPORT_FATAL_ERROR 0x10003
#define TDVMCALL_SETUP_NOTIFY_INTR 0x10004
+#define TDVMCALL_GET_QUOTE 0x10002
/* MMIO direction */
#define EPT_READ 0
@@ -199,6 +200,45 @@ static void __noreturn tdx_panic(const char *msg)
__tdx_hypercall(&args, 0);
}
+/**
+ * tdx_hcall_get_quote() - Wrapper to request TD Quote using GetQuote
+ * hypercall.
+ * @tdquote: Address of the direct mapped kernel buffer which contains
+ * TDREPORT data. The same buffer will be used by VMM to store
+ * the generated TD Quote output.
+ * @size: size of the tdquote buffer.
+ *
+ * Refer to section titled "TDG.VP.VMCALL<GetQuote>" in the TDX GHCI
+ * v1.0 specification for more information on GetQuote hypercall.
+ * It is used in the TDX guest driver module to get the TD Quote.
+ *
+ * Return 0 on success or error code on failure.
+ */
+int tdx_hcall_get_quote(u8 *tdquote, size_t size)
+{
+ struct tdx_hypercall_args args = {0};
+
+ /*
+ * TDX guest driver is the only user of this function and it uses
+ * the kernel mapped memory. So use virt_to_phys() to get the
+ * physical address of the TDQuote buffer without any additional
+ * checks for memory type.
+ */
+ args.r10 = TDX_HYPERCALL_STANDARD;
+ args.r11 = TDVMCALL_GET_QUOTE;
+ args.r12 = cc_mkdec(virt_to_phys(tdquote));
+ args.r13 = size;
+
+ /*
+ * Pass the physical address of TDREPORT to the VMM and
+ * trigger the Quote generation. It is not a blocking
+ * call, hence completion of this request will be notified to
+ * the TD guest via a callback interrupt.
+ */
+ return __tdx_hypercall(&args, 0);
+}
+EXPORT_SYMBOL_GPL(tdx_hcall_get_quote);
+
static void tdx_parse_tdinfo(u64 *cc_mask)
{
struct tdx_module_output out;
@@ -75,6 +75,8 @@ 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);
+int tdx_hcall_get_quote(u8 *tdquote, size_t size);
+
#else
static inline void tdx_early_init(void) { };
@@ -12,12 +12,151 @@
#include <linux/mod_devicetable.h>
#include <linux/string.h>
#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/dma-mapping.h>
#include <uapi/linux/tdx-guest.h>
#include <asm/cpu_device_id.h>
#include <asm/tdx.h>
+/**
+ * struct quote_entry - Quote request struct
+ * @valid: Flag to check validity of the GetQuote request.
+ * @buf: Kernel buffer to share data with VMM (size is page aligned).
+ * @buf_len: Size of the buf in bytes.
+ * @dma_address: DMA address of the buffer.
+ * @dev: Reference to device used in DMA allocation.
+ * @compl: Completion object to track completion of GetQuote request.
+ * @list: List object for reference in quote_list.
+ */
+struct quote_entry {
+ bool valid;
+ void *buf;
+ size_t buf_len;
+ dma_addr_t dma_address;
+ struct device *dev;
+ struct completion compl;
+ struct list_head list;
+};
+
+/*
+ * To support parallel GetQuote requests, use the list
+ * to track active GetQuote requests.
+ */
+static LIST_HEAD(quote_list);
+
+/* Lock to protect quote_list */
+static DEFINE_MUTEX(quote_lock);
+
+/* Quote generation work */
+static void quote_callback_handler(struct work_struct *work);
+
+static DECLARE_WORK(quote_work, quote_callback_handler);
+
+static struct platform_device *tdx_dev;
+
+static struct quote_entry *alloc_quote_entry(struct device *dev, size_t buf_len)
+{
+ struct quote_entry *entry = NULL;
+ size_t new_len = PAGE_ALIGN(buf_len);
+
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return NULL;
+
+ entry->buf = dma_alloc_coherent(dev, new_len, &entry->dma_address,
+ GFP_KERNEL);
+ if (!entry->buf) {
+ kfree(entry);
+ return NULL;
+ }
+
+ entry->buf_len = new_len;
+ init_completion(&entry->compl);
+ entry->valid = true;
+ entry->dev = dev;
+
+ return entry;
+}
+
+static void free_quote_entry(struct quote_entry *entry)
+{
+ dma_free_coherent(entry->dev, entry->buf_len, entry->buf,
+ entry->dma_address);
+ kfree(entry);
+}
+
+/* Must be called with quote_lock held */
+static void _del_quote_entry(struct quote_entry *entry)
+{
+ list_del(&entry->list);
+ free_quote_entry(entry);
+}
+
+static void del_quote_entry(struct quote_entry *entry)
+{
+ mutex_lock("e_lock);
+ _del_quote_entry(entry);
+ mutex_unlock("e_lock);
+}
+
+/* Handles early termination of GetQuote requests */
+static void terminate_quote_request(struct quote_entry *entry)
+{
+ struct tdx_quote_hdr *quote_hdr;
+
+ /*
+ * For early termination, if the request is not yet
+ * processed by VMM (GET_QUOTE_IN_FLIGHT), the VMM
+ * still owns the shared buffer, so mark the request
+ * invalid to let quote_callback_handler() handle the
+ * memory cleanup function. If the request is already
+ * processed, then do the cleanup and return.
+ */
+ mutex_lock("e_lock);
+ quote_hdr = entry->buf;
+ if (quote_hdr->status == GET_QUOTE_IN_FLIGHT) {
+ entry->valid = false;
+ mutex_unlock("e_lock);
+ return;
+ }
+ _del_quote_entry(entry);
+ mutex_unlock("e_lock);
+}
+
+static int attestation_callback_handler(void *dev_id)
+{
+ schedule_work("e_work);
+ return 0;
+}
+
+static void quote_callback_handler(struct work_struct *work)
+{
+ struct tdx_quote_hdr *quote_hdr;
+ struct quote_entry *entry, *next;
+
+ /* Find processed quote request and mark it complete */
+ mutex_lock("e_lock);
+ list_for_each_entry_safe(entry, next, "e_list, list) {
+ quote_hdr = entry->buf;
+ if (quote_hdr->status == GET_QUOTE_IN_FLIGHT)
+ continue;
+ /*
+ * If user invalidated the current request, remove the
+ * entry from the quote list and free it. If the request
+ * is still valid, mark it complete.
+ */
+ if (entry->valid)
+ complete(&entry->compl);
+ else
+ _del_quote_entry(entry);
+ }
+ mutex_unlock("e_lock);
+}
+
static long tdx_get_report0(struct tdx_report_req __user *req)
{
u8 *reportdata, *tdreport;
@@ -53,12 +192,78 @@ static long tdx_get_report0(struct tdx_report_req __user *req)
return ret;
}
+static long tdx_get_quote(struct tdx_quote_req __user *ureq)
+{
+ struct device *dev = &tdx_dev->dev;
+ struct quote_entry *entry;
+ struct tdx_quote_req req;
+ long ret;
+
+ if (copy_from_user(&req, ureq, sizeof(req)))
+ return -EFAULT;
+
+ /*
+ * TDX GHCI specification does not specify any upper limit
+ * on Quote buffer size. So no upper limit check is added.
+ * If a user passes a large value, it is expected that it
+ * will fail during memory allocation in alloc_quote_entry().
+ */
+ if (!req.len)
+ return -EINVAL;
+
+ entry = alloc_quote_entry(dev, req.len);
+ if (!entry)
+ return -ENOMEM;
+
+ if (copy_from_user(entry->buf, (void __user *)req.buf, req.len)) {
+ free_quote_entry(entry);
+ return -EFAULT;
+ }
+
+ mutex_lock("e_lock);
+
+ /* Submit GetQuote Request using GetQuote hypercall */
+ ret = tdx_hcall_get_quote(entry->buf, entry->buf_len);
+ if (ret) {
+ mutex_unlock("e_lock);
+ dev_err(dev, "GetQuote hypercall failed, status:%lx\n", ret);
+ free_quote_entry(entry);
+ return -EIO;
+ }
+
+ /* Add current quote entry to quote_list to track active requests */
+ list_add_tail(&entry->list, "e_list);
+
+ mutex_unlock("e_lock);
+
+ /*
+ * Wait till GetQuote completion or request is cancelled by the
+ * user signal. If the request is cancelled by the user, don't
+ * cleanup the quote buffer as it is still owned by the VMM.
+ */
+ ret = wait_for_completion_interruptible(&entry->compl);
+ if (ret < 0) {
+ dev_err(dev, "GetQuote request terminated\n");
+ terminate_quote_request(entry);
+ return -EINTR;
+ }
+
+ if (copy_to_user((void __user *)req.buf, entry->buf, req.len))
+ ret = -EFAULT;
+
+ del_quote_entry(entry);
+
+ return ret;
+}
+
static long tdx_guest_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case TDX_CMD_GET_REPORT0:
return tdx_get_report0((struct tdx_report_req __user *)arg);
+ case TDX_CMD_GET_QUOTE:
+ return tdx_get_quote((struct tdx_quote_req *)arg);
default:
return -ENOTTY;
}
@@ -76,6 +281,27 @@ static struct miscdevice tdx_misc_dev = {
.fops = &tdx_guest_fops,
};
+static int tdx_guest_probe(struct platform_device *pdev)
+{
+ if (tdx_register_event_irq_cb(attestation_callback_handler, pdev))
+ return -EIO;
+
+ return misc_register(&tdx_misc_dev);
+}
+
+static int tdx_guest_remove(struct platform_device *pdev)
+{
+ tdx_unregister_event_irq_cb(attestation_callback_handler, pdev);
+ misc_deregister(&tdx_misc_dev);
+ return 0;
+}
+
+static struct platform_driver tdx_guest_driver = {
+ .probe = tdx_guest_probe,
+ .remove = tdx_guest_remove,
+ .driver.name = KBUILD_MODNAME,
+};
+
static const struct x86_cpu_id tdx_guest_ids[] = {
X86_MATCH_FEATURE(X86_FEATURE_TDX_GUEST, NULL),
{}
@@ -84,16 +310,35 @@ MODULE_DEVICE_TABLE(x86cpu, tdx_guest_ids);
static int __init tdx_guest_init(void)
{
+ int ret;
+
if (!x86_match_cpu(tdx_guest_ids))
return -ENODEV;
- return misc_register(&tdx_misc_dev);
+ ret = platform_driver_register(&tdx_guest_driver);
+ if (ret) {
+ pr_err("failed to register driver, err=%d\n", ret);
+ return ret;
+ }
+
+ tdx_dev = platform_device_register_simple(KBUILD_MODNAME,
+ PLATFORM_DEVID_NONE,
+ NULL, 0);
+ if (IS_ERR(tdx_dev)) {
+ ret = PTR_ERR(tdx_dev);
+ pr_err("failed to allocate device, err=%d\n", ret);
+ platform_driver_unregister(&tdx_guest_driver);
+ return ret;
+ }
+
+ return 0;
}
module_init(tdx_guest_init);
static void __exit tdx_guest_exit(void)
{
- misc_deregister(&tdx_misc_dev);
+ platform_device_unregister(tdx_dev);
+ platform_driver_unregister(&tdx_guest_driver);
}
module_exit(tdx_guest_exit);
@@ -17,6 +17,12 @@
/* Length of TDREPORT used in TDG.MR.REPORT TDCALL */
#define TDX_REPORT_LEN 1024
+/* TD Quote status codes */
+#define GET_QUOTE_SUCCESS 0
+#define GET_QUOTE_IN_FLIGHT 0xffffffffffffffff
+#define GET_QUOTE_ERROR 0x8000000000000000
+#define GET_QUOTE_SERVICE_UNAVAILABLE 0x8000000000000001
+
/**
* struct tdx_report_req - Request struct for TDX_CMD_GET_REPORT0 IOCTL.
*
@@ -30,6 +36,35 @@ struct tdx_report_req {
__u8 tdreport[TDX_REPORT_LEN];
};
+/* struct tdx_quote_hdr: Format of Quote request buffer header.
+ * @version: Quote format version, filled by TD.
+ * @status: Status code of Quote request, filled by VMM.
+ * @in_len: Length of TDREPORT, filled by TD.
+ * @out_len: Length of Quote data, filled by VMM.
+ * @data: Quote data on output or TDREPORT on input.
+ *
+ * More details of Quote data header can be found in TDX
+ * Guest-Host Communication Interface (GHCI) for Intel TDX 1.0,
+ * section titled "TDG.VP.VMCALL<GetQuote>"
+ */
+struct tdx_quote_hdr {
+ __u64 version;
+ __u64 status;
+ __u32 in_len;
+ __u32 out_len;
+ __u64 data[];
+};
+
+/* struct tdx_quote_req: Request struct for TDX_CMD_GET_QUOTE IOCTL.
+ * @buf: Address of user buffer that includes TDREPORT. Upon successful
+ * completion of IOCTL, output is copied back to the same buffer.
+ * @len: Length of the Quote buffer.
+ */
+struct tdx_quote_req {
+ __u64 buf;
+ __u64 len;
+};
+
/*
* TDX_CMD_GET_REPORT0 - Get TDREPORT0 (a.k.a. TDREPORT subtype 0) using
* TDCALL[TDG.MR.REPORT]
@@ -39,4 +74,13 @@ struct tdx_report_req {
*/
#define TDX_CMD_GET_REPORT0 _IOWR('T', 1, struct tdx_report_req)
+/*
+ * TDX_CMD_GET_QUOTE - Get TD Guest Quote from QE/QGS using GetQuote
+ * TDVMCALL.
+ *
+ * Returns 0 on success, -EINTR for interrupted request, and
+ * standard errno on other failures.
+ */
+#define TDX_CMD_GET_QUOTE _IOR('T', 2, struct tdx_quote_req)
+
#endif /* _UAPI_LINUX_TDX_GUEST_H_ */