return !__tdx_hypercall(&args, 0);
 }
 
+static bool handle_cpuid(struct pt_regs *regs)
+{
+       struct tdx_hypercall_args args = {
+               .r10 = TDX_HYPERCALL_STANDARD,
+               .r11 = hcall_func(EXIT_REASON_CPUID),
+               .r12 = regs->ax,
+               .r13 = regs->cx,
+       };
+
+       /*
+        * Only allow VMM to control range reserved for hypervisor
+        * communication.
+        *
+        * Return all-zeros for any CPUID outside the range. It matches CPU
+        * behaviour for non-supported leaf.
+        */
+       if (regs->ax < 0x40000000 || regs->ax > 0x4FFFFFFF) {
+               regs->ax = regs->bx = regs->cx = regs->dx = 0;
+               return true;
+       }
+
+       /*
+        * Emulate the CPUID instruction via a hypercall. More info about
+        * ABI can be found in TDX Guest-Host-Communication Interface
+        * (GHCI), section titled "VP.VMCALL<Instruction.CPUID>".
+        */
+       if (__tdx_hypercall(&args, TDX_HCALL_HAS_OUTPUT))
+               return false;
+
+       /*
+        * As per TDX GHCI CPUID ABI, r12-r15 registers contain contents of
+        * EAX, EBX, ECX, EDX registers after the CPUID instruction execution.
+        * So copy the register contents back to pt_regs.
+        */
+       regs->ax = args.r12;
+       regs->bx = args.r13;
+       regs->cx = args.r14;
+       regs->dx = args.r15;
+
+       return true;
+}
+
 void tdx_get_ve_info(struct ve_info *ve)
 {
        struct tdx_module_output out;
        ve->instr_info  = upper_32_bits(out.r10);
 }
 
+/* Handle the user initiated #VE */
+static bool virt_exception_user(struct pt_regs *regs, struct ve_info *ve)
+{
+       switch (ve->exit_reason) {
+       case EXIT_REASON_CPUID:
+               return handle_cpuid(regs);
+       default:
+               pr_warn("Unexpected #VE: %lld\n", ve->exit_reason);
+               return false;
+       }
+}
+
 /* Handle the kernel #VE */
 static bool virt_exception_kernel(struct pt_regs *regs, struct ve_info *ve)
 {
                return read_msr(regs);
        case EXIT_REASON_MSR_WRITE:
                return write_msr(regs);
+       case EXIT_REASON_CPUID:
+               return handle_cpuid(regs);
        default:
                pr_warn("Unexpected #VE: %lld\n", ve->exit_reason);
                return false;
        bool ret;
 
        if (user_mode(regs))
-               ret = false;
+               ret = virt_exception_user(regs, ve);
        else
                ret = virt_exception_kernel(regs, ve);