/* txt.c
 * Checking Trusted Execution Technology chipset 
 * By Nguyen Anh Quynh <aquynh@gmail.com>
 * Based on TXT patch for Xen by Cihula Joseph et al from Intel.
 * */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>

MODULE_LICENSE("GPL");

#define IA32_FEATURE_CONTROL_MSR	0x3a
#define IA32_FEATURE_CONTROL_MSR_ENABLE_VMX_IN_SMX	0x2
#define IA32_FEATURE_CONTROL_MSR_ENABLE_VMX_OUT_SMX 0x4
#define IA32_FEATURE_CONTROL_MSR_ENABLE_SENTER	0x8000
#define IA32_FEATURE_CONTROL_MSR_SENTER_PARAM_CTL	0x7f00
#define IA32_FEATURE_CONTROL_MSR_LOCK	0x1

#define X86_CR4_SMXE	0x4000  /* enable SMX */

typedef union {
	uint32_t _raw;
	struct {
		uint32_t chipset_present  : 1;
		uint32_t undefined1	      : 1;
		uint32_t enteraccs	      : 1;
		uint32_t exitac	          : 1;
		uint32_t senter	          : 1;
		uint32_t sexit	          : 1;
		uint32_t parameters	      : 1;
		uint32_t smctrl	          : 1;
		uint32_t wakeup	          : 1;
		uint32_t undefined9	      : 22;
		uint32_t extended_leafs   : 1;
	};
} capabilities_t;

static unsigned long g_cpuid_ext_feat_info;
static unsigned long g_feat_ctrl_msr;

static void read_processor_info(void)
{
	unsigned long f1, f2;

	/* is CPUID supported? */
	/* (it's supported if ID flag in EFLAGS can be set and cleared) */
	asm("pushf\n\t"
			"pushf\n\t"
			"pop %0\n\t"
			"mov %0,%1\n\t"
			"xor %2,%0\n\t"
			"push %0\n\t"
			"popf\n\t"
			"pushf\n\t"
			"pop %0\n\t"
			"popf\n\t"
			: "=&r" (f1), "=&r" (f2)
			: "ir" (X86_EFLAGS_ID));

	if ( ((f1^f2) & X86_EFLAGS_ID) == 0 ) {
		g_cpuid_ext_feat_info = 0;
		return;
	}

	g_cpuid_ext_feat_info = cpuid_ecx(1);

	rdmsrl(IA32_FEATURE_CONTROL_MSR, g_feat_ctrl_msr);
}

static int supports_vmx(void)
{
	/* check that processor supports VMX instructions */
	if ( !(test_bit(5, &g_cpuid_ext_feat_info)) ) {
		printk(KERN_INFO "ERR: CPU does not support VMX\n");
		return 0;
	}
	printk(KERN_INFO "CPU is VMX-capable\n");

	/* and that VMX is enabled in the feature control MSR */
	if ( !(g_feat_ctrl_msr & IA32_FEATURE_CONTROL_MSR_ENABLE_VMX_IN_SMX) ) {
		printk(KERN_INFO "ERR: VMXON disabled by feature control MSR (%lx)\n",
				g_feat_ctrl_msr);
		return 0;
	}

	return 1;
}

static int supports_smx(void)
{
	/* check that processor supports SMX instructions */
	if ( !(test_bit(6, &g_cpuid_ext_feat_info)) ) {
		printk(KERN_INFO "ERR: CPU does not support SMX\n");
		return 0;
	}
	printk(KERN_INFO "CPU is SMX-capable\n");

	/*
	 * and that SMX is enabled in the feature control MSR
	 */

	/* check that the MSR is locked -- BIOS should always lock it */
	if ( !(g_feat_ctrl_msr & IA32_FEATURE_CONTROL_MSR_LOCK) ) {
		printk(KERN_INFO "ERR: IA32_FEATURE_CONTROL_MSR_LOCK is not locked\n");
		return 0;
	}

	/* check that SENTER (w/ full params) is enabled */
	if ( !(g_feat_ctrl_msr & (IA32_FEATURE_CONTROL_MSR_ENABLE_SENTER |
					IA32_FEATURE_CONTROL_MSR_SENTER_PARAM_CTL)) ) {
		printk(KERN_INFO "ERR: SENTER disabled by feature control MSR (%lx)\n",
				g_feat_ctrl_msr);
		return 0;
	}

	return 1;
}

static inline capabilities_t getsec_capabilities(uint32_t index)
{
#define IA32_GETSEC_OPCODE	".byte 0x0f,0x37"
#define IA32_GETSEC_CAPABILITIES	0
	uint32_t cap;

	__asm__ __volatile__ (IA32_GETSEC_OPCODE "\n"
			: "=a"(cap)
			: "a"(IA32_GETSEC_CAPABILITIES), "b"(index));

	return (capabilities_t)cap;
}

static void check_txt(void)
{
    capabilities_t cap;

	read_processor_info();

    /* processor must support SMX */
    if ( !supports_smx() || !supports_vmx() )
        return;

    /* testing for chipset support requires enabling SMX on the processor */
    write_cr4(read_cr4() | X86_CR4_SMXE);
    
    /*
     * verify that an TXT-capable chipset is present and 
     * check that all needed SMX capabilities are supported
     */
    cap = getsec_capabilities(0);
    if ( cap.chipset_present ) {
        if ( cap.senter && cap.sexit && cap.parameters && cap.smctrl &&
             cap.wakeup ) {
            printk(KERN_INFO "TXT chipset and all needed capabilities present\n");
			/* clear SMX flag now */
			write_cr4(read_cr4() & ~X86_CR4_SMXE);
            return;
        }
        else
            printk(KERN_INFO "ERR: insufficient SMX capabilities (%x)\n", cap._raw);
    }
    else
	    printk(KERN_INFO "ERR: TXT-capable chipset not present\n");

    /* clear SMX flag now */
    write_cr4(read_cr4() & ~X86_CR4_SMXE);
}

static int txt_init(void)
{
	check_txt();
	/* we dont want to load this module */
	return -1;
}


module_init(txt_init);
