* [PATCH v5 1/2] x86/tdx: Move port I/O definitions to a shared header
2026-11-02 22:51 [PATCH v5 0/2] x86/tdx: Use TDVMCALLs directly for earlyprintk Vishal Verma
@ 2026-11-02 22:51 ` Vishal Verma
2026-11-02 22:51 ` [PATCH v5 2/2] x86/early_printk: Add earlyprintk=tdx to drive the UART with TDVMCALLs Vishal Verma
1 sibling, 0 replies; 4+ messages in thread
From: Vishal Verma @ 2026-11-02 22:51 UTC (permalink / raw)
To: x86, Dave Hansen, Kiryl Shutsemau, Rick Edgecombe,
Jonathan Corbet, Shuah Khan, Randy Dunlap
Cc: linux-kernel, linux-coco, kvm, linux-doc, Vishal Verma,
Nikolay Borisov
In preparation for adding a 'tdx' option for early_printk which uses
direct TDVMCALLs for I/O, factor out the PORT_READ and PORT_WRITE
definitions from arch/x86/coco/tdx/tdx.c into
arch/x86/include/asm/shared/tdx.h, and namespace them with 'TDVMCALL_'.
The decompressor also used 0/1 literals in its tdx_io_{in,out} helpers -
switch those to use the new definitions.
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Reviewed-by: Nikolay Borisov <nik.borisov@suse.com>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
---
arch/x86/include/asm/shared/tdx.h | 6 ++++++
arch/x86/boot/compressed/tdx.c | 4 ++--
arch/x86/coco/tdx/tdx.c | 8 ++------
3 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
index 56d741065783b..3d1656a44f75e 100644
--- a/arch/x86/include/asm/shared/tdx.h
+++ b/arch/x86/include/asm/shared/tdx.h
@@ -85,6 +85,12 @@
#define TDVMCALL_STATUS_ALIGN_ERROR 0x8000000000000002ULL
#define TDVMCALL_STATUS_SUBFUNC_UNSUPPORTED 0x8000000000000003ULL
+/*
+ * TDG.VP.VMCALL<Instruction.IO> direction (passed in R13)
+ */
+#define TDVMCALL_PORT_READ 0
+#define TDVMCALL_PORT_WRITE 1
+
/*
* Bitmasks of exposed registers (with VMM).
*/
diff --git a/arch/x86/boot/compressed/tdx.c b/arch/x86/boot/compressed/tdx.c
index 8451d6a1030c6..ed278bac3c939 100644
--- a/arch/x86/boot/compressed/tdx.c
+++ b/arch/x86/boot/compressed/tdx.c
@@ -22,7 +22,7 @@ static inline unsigned int tdx_io_in(int size, u16 port)
.r10 = TDX_HYPERCALL_STANDARD,
.r11 = hcall_func(EXIT_REASON_IO_INSTRUCTION),
.r12 = size,
- .r13 = 0,
+ .r13 = TDVMCALL_PORT_READ,
.r14 = port,
};
@@ -38,7 +38,7 @@ static inline void tdx_io_out(int size, u16 port, u32 value)
.r10 = TDX_HYPERCALL_STANDARD,
.r11 = hcall_func(EXIT_REASON_IO_INSTRUCTION),
.r12 = size,
- .r13 = 1,
+ .r13 = TDVMCALL_PORT_WRITE,
.r14 = port,
.r15 = value,
};
diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c
index 8bacbef62891f..9531490c9c246 100644
--- a/arch/x86/coco/tdx/tdx.c
+++ b/arch/x86/coco/tdx/tdx.c
@@ -24,10 +24,6 @@
#define EPT_READ 0
#define EPT_WRITE 1
-/* Port I/O direction */
-#define PORT_READ 0
-#define PORT_WRITE 1
-
/* See Exit Qualification for I/O Instructions in VMX documentation */
#define VE_IS_IO_IN(e) ((e) & BIT(3))
#define VE_GET_IO_SIZE(e) (((e) & GENMASK(2, 0)) + 1)
@@ -673,7 +669,7 @@ static bool handle_in(struct pt_regs *regs, int size, int port)
.r10 = TDX_HYPERCALL_STANDARD,
.r11 = hcall_func(EXIT_REASON_IO_INSTRUCTION),
.r12 = size,
- .r13 = PORT_READ,
+ .r13 = TDVMCALL_PORT_READ,
.r14 = port,
};
bool success;
@@ -702,7 +698,7 @@ static bool handle_out(struct pt_regs *regs, int size, int port)
* "TDG.VP.VMCALL<Instruction.IO>".
*/
return !_tdx_hypercall(hcall_func(EXIT_REASON_IO_INSTRUCTION), size,
- PORT_WRITE, port, regs->ax & mask);
+ TDVMCALL_PORT_WRITE, port, regs->ax & mask);
}
/*
--
2.56.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH v5 2/2] x86/early_printk: Add earlyprintk=tdx to drive the UART with TDVMCALLs
2026-11-02 22:51 [PATCH v5 0/2] x86/tdx: Use TDVMCALLs directly for earlyprintk Vishal Verma
2026-11-02 22:51 ` [PATCH v5 1/2] x86/tdx: Move port I/O definitions to a shared header Vishal Verma
@ 2026-11-02 22:51 ` Vishal Verma
2026-11-02 22:59 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: Vishal Verma @ 2026-11-02 22:51 UTC (permalink / raw)
To: x86, Dave Hansen, Kiryl Shutsemau, Rick Edgecombe,
Jonathan Corbet, Shuah Khan, Randy Dunlap
Cc: linux-kernel, linux-coco, kvm, linux-doc, Vishal Verma,
Nikolay Borisov
A TDX guest cannot execute port I/O instructions directly, but
earlyprintk's serial console still issues plain inb()/outb() and lets
each one fault into the #VE handler to be emulated as a TDVMCALL.
While that works, it is a roundabout way to get a character out.
early_serial_putc() polls the LSR, and then writes a byte, but since the
TDX guest can't directly do port I/O, a #VE exception is raised. The #VE
handler must call TDG.VP.VEINFO.GET to find out what faulted, and then
it can issue the TDVMCALL that does the actual work.
This makes #VE a functional mechanism for doing I/O, which is not
desirable, is unnecessarily complicated and fragile, and results in
twice the number of calls into the TDX module.
Instead, add an earlyprintk=tdx option, which can issue the TDVMCALLs
directly. Add a pair of tdx_serial_in() and tdx_serial_out() accessors
and set them up in this case.
Note that the output does not appear any earlier - "earlyprintk=" is an
early_param(), so the console is still registered from
parse_early_param(). This only changes how the bytes leave the guest
once it is up.
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Reviewed-by: Nikolay Borisov <nik.borisov@suse.com>
---
Documentation/admin-guide/kernel-parameters.txt | 8 +++++++
arch/x86/include/asm/tdx.h | 4 ++++
arch/x86/coco/tdx/tdx.c | 29 +++++++++++++++++++++++++
arch/x86/kernel/early_printk.c | 20 +++++++++++++++++
4 files changed, 61 insertions(+)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 0b3c140babd94..da5aa688294cc 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1547,6 +1547,7 @@ Kernel parameters
earlyprintk=serial[,ttySn[,baudrate]]
earlyprintk=serial[,0x...[,baudrate]]
earlyprintk=ttySn[,baudrate]
+ earlyprintk=tdx
earlyprintk=dbgp[debugController#]
earlyprintk=mmio32,membase[,{nocfg|baudrate}]
earlyprintk=pciserial[,force],bus:device.function[,{nocfg|baudrate}]
@@ -1560,6 +1561,13 @@ Kernel parameters
Use "nocfg" to skip UART configuration, assume
BIOS/firmware has configured UART correctly.
+ On x86, "tdx" is a fixed COM1 console, and takes no
+ options. In a TDX guest the port is driven with
+ TDVMCALLs rather than port I/O instructions, avoiding
+ the #VE exception that would otherwise be taken to
+ emulate every access. Outside a TDX guest it falls back
+ to port I/O, behaving like "earlyprintk=serial".
+
Append ",keep" to not disable it when the real console
takes over.
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 1fcfaed515fec..c6f6a60fbbac4 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -98,6 +98,10 @@ static inline bool tdx_early_handle_ve(struct pt_regs *regs) { return false; }
#endif /* CONFIG_INTEL_TDX_GUEST */
+/* Only referenced when X86_FEATURE_TDX_GUEST is enabled, no stubs needed */
+__noendbr unsigned int tdx_serial_in(unsigned long addr, int offset);
+__noendbr void tdx_serial_out(unsigned long addr, int offset, int value);
+
#if defined(CONFIG_KVM_GUEST) && defined(CONFIG_INTEL_TDX_GUEST)
long tdx_kvm_hypercall(unsigned int nr, unsigned long p1, unsigned long p2,
unsigned long p3, unsigned long p4);
diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c
index 9531490c9c246..74d4e479964dd 100644
--- a/arch/x86/coco/tdx/tdx.c
+++ b/arch/x86/coco/tdx/tdx.c
@@ -211,6 +211,35 @@ int tdx_get_max_quote_size(u64 *max_quote_size)
}
EXPORT_SYMBOL_FOR_MODULES(tdx_get_max_quote_size, "tdx-guest");
+/*
+ * early_printk accessors for a TDX guest. Ask the VMM to do the port I/O with
+ * TDG.VP.VMCALL<Instruction.IO> instead of executing IN/OUT and having the
+ * resulting #VE emulate it.
+ */
+__noendbr unsigned int tdx_serial_in(unsigned long addr, int offset)
+{
+ struct tdx_module_args args = {
+ .r10 = TDX_HYPERCALL_STANDARD,
+ .r11 = hcall_func(EXIT_REASON_IO_INSTRUCTION),
+ .r12 = 1,
+ .r13 = TDVMCALL_PORT_READ,
+ .r14 = addr + offset,
+ };
+
+ if (__tdx_hypercall(&args))
+ return 0xFF;
+
+ return args.r11;
+}
+ANNOTATE_NOENDBR_SYM(tdx_serial_in);
+
+__noendbr void tdx_serial_out(unsigned long addr, int offset, int value)
+{
+ _tdx_hypercall(hcall_func(EXIT_REASON_IO_INSTRUCTION), 1,
+ TDVMCALL_PORT_WRITE, addr + offset, value);
+}
+ANNOTATE_NOENDBR_SYM(tdx_serial_out);
+
/*
* The kernel cannot handle #VEs when accessing normal kernel memory. Ensure
* that no #VE will be delivered for accesses to TD-private memory.
diff --git a/arch/x86/kernel/early_printk.c b/arch/x86/kernel/early_printk.c
index cba75306e5b62..04dadcd73b857 100644
--- a/arch/x86/kernel/early_printk.c
+++ b/arch/x86/kernel/early_printk.c
@@ -21,6 +21,7 @@
#include <linux/usb/xhci-dbgp.h>
#include <asm/pci_x86.h>
#include <linux/static_call.h>
+#include <asm/tdx.h>
/* Simple VGA output */
#define VGABASE (__ISA_IO_base + 0xb8000)
@@ -196,6 +197,20 @@ static __init void early_serial_init(char *s)
early_serial_hw_init(divisor);
}
+/*
+ * Use early_serial_base (COM1) and same default baud rate as
+ * "earlyprintk=serial"
+ */
+static __init void early_serial_tdx_init(void)
+{
+ if (cpu_feature_enabled(X86_FEATURE_TDX_GUEST)) {
+ static_call_update(serial_in, tdx_serial_in);
+ static_call_update(serial_out, tdx_serial_out);
+ }
+
+ early_serial_hw_init(115200 / DEFAULT_BAUD);
+}
+
static __noendbr void mem32_serial_out(unsigned long addr, int offset, int value)
{
u32 __iomem *vaddr = (u32 __iomem *)addr;
@@ -414,6 +429,11 @@ static int __init setup_early_printk(char *buf)
early_serial_init(buf + 4);
early_console_register(&early_serial_console, keep);
}
+ if (!strncmp(buf, "tdx", 3)) {
+ early_serial_tdx_init();
+ early_console_register(&early_serial_console, keep);
+ break;
+ }
#ifdef CONFIG_PCI
if (!strncmp(buf, "pciserial", 9)) {
buf += 9; /* Keep from match the above "pciserial" */
--
2.56.0
^ permalink raw reply related [flat|nested] 4+ messages in thread