diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 50bd78e03d9f..51338d1901e0 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -77,6 +77,7 @@ TEST_GEN_PROGS_x86 += x86/pmu_counters_test
 TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test
 TEST_GEN_PROGS_x86 += x86/private_mem_conversions_test
 TEST_GEN_PROGS_x86 += x86/private_mem_kvm_exits_test
+TEST_GEN_PROGS_x86 += x86/savic_test
 TEST_GEN_PROGS_x86 += x86/set_boot_cpu_id
 TEST_GEN_PROGS_x86 += x86/set_sregs_test
 TEST_GEN_PROGS_x86 += x86/smaller_maxphyaddr_emulation_test
diff --git a/tools/testing/selftests/kvm/include/x86/savic.h b/tools/testing/selftests/kvm/include/x86/savic.h
index 238d7450ab6e..cb432eb527b3 100644
--- a/tools/testing/selftests/kvm/include/x86/savic.h
+++ b/tools/testing/selftests/kvm/include/x86/savic.h
@@ -17,4 +17,5 @@ uint64_t savic_hv_read_reg(uint32_t reg);
 void savic_enable(void);
 int savic_nr_pages_required(uint64_t page_size);
 void savic_vc_handler(struct ex_regs *regs);
+struct guest_apic_page *get_guest_apic_page(void);
 #endif
diff --git a/tools/testing/selftests/kvm/lib/x86/savic.c b/tools/testing/selftests/kvm/lib/x86/savic.c
index ae48978479bf..d4c9fcf835ad 100644
--- a/tools/testing/selftests/kvm/lib/x86/savic.c
+++ b/tools/testing/selftests/kvm/lib/x86/savic.c
@@ -105,6 +105,11 @@ void set_savic_control_msr(struct guest_apic_page *apic_page, bool enable, bool
 	wrmsr(MSR_AMD64_SECURE_AVIC_CONTROL, val);
 }
 
+struct guest_apic_page *get_guest_apic_page(void)
+{
+	return &apic_page_pool->guest_apic_page[x2apic_read_reg(APIC_ID)];
+}
+
 /*
  * Write APIC reg offset in the guest APIC backing page.
  *
@@ -176,11 +181,17 @@ static void savic_init_backing_page(struct guest_apic_page *apic_page, uint32_t
 	regval = savic_hv_read_reg(APIC_LDR);
 	savic_write_reg(apic_page, APIC_LDR, regval);
 
-	for (i = LVT_THERMAL_MONITOR; i < APIC_MAX_NR_LVT_ENTRIES; i++) {
+	for (i = LVT_TIMER; i < APIC_MAX_NR_LVT_ENTRIES; i++) {
 		regval = savic_hv_read_reg(APIC_LVTx(i));
 		savic_write_reg(apic_page, APIC_LVTx(i), regval);
 	}
 
+	regval = savic_hv_read_reg(APIC_TMICT);
+	savic_write_reg(apic_page, APIC_TMICT, regval);
+
+	regval = savic_hv_read_reg(APIC_TDCR);
+	savic_write_reg(apic_page, APIC_TDCR, regval);
+
 	regval = savic_hv_read_reg(APIC_LVT0);
 	savic_write_reg(apic_page, APIC_LVT0, regval);
 
diff --git a/tools/testing/selftests/kvm/x86/savic_test.c b/tools/testing/selftests/kvm/x86/savic_test.c
new file mode 100644
index 000000000000..ca1d7352bc3e
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/savic_test.c
@@ -0,0 +1,462 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ *  Copyright (C) 2024 Advanced Micro Devices, Inc.
+ *
+ */
+#include <pthread.h>
+
+#include "processor.h"
+#include "apic.h"
+#include "kvm_util.h"
+#include "sev.h"
+#include "test_util.h"
+#include "savic.h"
+
+#define NR_SAVIC_VCPUS	1
+
+static struct kvm_vcpu *vcpus[NR_SAVIC_VCPUS];
+static pthread_t threads[NR_SAVIC_VCPUS];
+
+#define SAVIC_TEST_STATE(STATE) \
+	STATE ## _START, \
+	STATE ## _END
+
+enum savic_test_state {
+	/* Reset state of APIC regs */
+	SAVIC_TEST_STATE(RESET),
+	/* APIC regs state on X2apic enablement */
+	SAVIC_TEST_STATE(X2APIC_ENABLE),
+	/* APIC regs state on Secure AVIC enablement */
+	SAVIC_TEST_STATE(SAVIC_EN),
+};
+
+/* APIC reg values written by host. */
+enum savic_test_host_vals {
+	H_APIC_TASKPRI_VAL = 0x30,
+	H_APIC_LVTT_VAL = APIC_DM_FIXED | 0x30,
+	H_APIC_LVTTHMR_VAL = APIC_DM_FIXED | 0x31,
+	H_APIC_LVTPC_VAL = APIC_DM_FIXED | 0x32,
+	H_APIC_LVT0_VAL = APIC_DM_FIXED | 0x33,
+	H_APIC_LVT1_VAL = APIC_DM_FIXED | 0x34,
+	H_APIC_TMICT_VAL = 0x555555,
+	H_APIC_TDCR_VAL = 0x3,
+};
+
+/* APIC reg values written by guest. */
+enum savic_test_guest_vals {
+	G_APIC_TASKPRI_VAL = 0x40,
+	G_APIC_LVTT_VAL = APIC_DM_FIXED | 0x40,
+	G_APIC_LVTTHMR_VAL = APIC_DM_FIXED | 0x41,
+	G_APIC_LVTPC_VAL = APIC_DM_FIXED | 0x42,
+	G_APIC_LVT0_VAL = APIC_DM_FIXED | 0x43,
+	G_APIC_LVT1_VAL = APIC_DM_FIXED | 0x34,
+	G_APIC_TMICT_VAL = 0xaaaaaa,
+	G_APIC_TDCR_VAL = 0x1,
+};
+
+struct reg_ent {
+	char *regname;
+	uint32_t reg;
+	uint64_t val;
+};
+
+#define CREATE_H_REG_ENTRY(_reg) {\
+	.regname = #_reg, \
+	.reg = _reg, \
+	.val = H_ ## _reg ## _VAL \
+}
+
+#define CREATE_G_REG_ENTRY(_reg) {\
+	.regname = #_reg, \
+	.reg = _reg, \
+	.val = G_ ## _reg ## _VAL \
+}
+
+static struct reg_ent host_apic_regs[] = {
+	CREATE_H_REG_ENTRY(APIC_TASKPRI),
+	CREATE_H_REG_ENTRY(APIC_LVTT),
+	CREATE_H_REG_ENTRY(APIC_LVTTHMR),
+	CREATE_H_REG_ENTRY(APIC_LVTPC),
+	CREATE_H_REG_ENTRY(APIC_LVT0),
+	CREATE_H_REG_ENTRY(APIC_LVT1),
+	CREATE_H_REG_ENTRY(APIC_TMICT),
+	CREATE_H_REG_ENTRY(APIC_TDCR),
+};
+
+static struct reg_ent guest_apic_regs[] = {
+	CREATE_G_REG_ENTRY(APIC_TASKPRI),
+	CREATE_G_REG_ENTRY(APIC_LVTT),
+	CREATE_G_REG_ENTRY(APIC_LVTTHMR),
+	CREATE_G_REG_ENTRY(APIC_LVTPC),
+	CREATE_G_REG_ENTRY(APIC_LVT0),
+	CREATE_G_REG_ENTRY(APIC_LVT1),
+	CREATE_G_REG_ENTRY(APIC_TMICT),
+	CREATE_G_REG_ENTRY(APIC_TDCR),
+};
+
+/* Context which is doing testing of APIC regs values. */
+enum context {
+	CTXT_HOST,
+	CTXT_GUEST,
+};
+
+static struct kvm_lapic_state apic_state[NR_SAVIC_VCPUS];
+
+/*
+ * Write APIC reg from host or guest context.
+ *
+ * @id : vCPU id (required only for host context).
+ * @reg : APIC reg offset.
+ * @ctxt : Context which is doing write operation.
+ * @val : New value to write to the reg.
+ * x2apic : x2apic is enabled.
+ */
+static void write_apic_reg(int id, uint32_t reg, enum context ctxt,
+		uint32_t val, bool x2apic)
+{
+	if (ctxt == CTXT_HOST) {
+		*(uint32_t *)&apic_state[id].regs[reg] = val;
+	} else {
+		if (x2apic)
+			x2apic_write_reg(reg, val);
+		else
+			xapic_write_reg(reg, val);
+	}
+}
+
+/*
+ * Read APIC reg from host or guest context.
+ *
+ * @id : vCPU id (required only for host context).
+ * @reg : APIC reg offset.
+ * @ctxt : Context which is doing read operation.
+ *
+ *
+ * Returns APIC register value.
+ */
+static uint32_t read_apic_reg(int id, uint32_t reg, enum context ctxt, bool x2apic)
+{
+	if (ctxt == CTXT_HOST)
+		return *(uint32_t *)&apic_state[id].regs[reg];
+
+	if (x2apic)
+		return x2apic_read_reg(reg);
+	else
+		return xapic_read_reg(reg);
+}
+
+/*
+ * Set test APIC regs values from host/guest context.
+ *
+ * @id : vCPU ID for which APIC state is set.
+ * @host : Test APIC regs data source values.
+ * @ctxt : Context from which this function is called.
+ * @x2apic : X2apic is enabled.
+ */
+static void set_apic_vals(int id, bool host, enum context ctxt, bool x2apic)
+{
+	struct reg_ent *reg_entries;
+	struct reg_ent ent;
+	int i;
+
+	if (host)
+		reg_entries = host_apic_regs;
+	else
+		reg_entries = guest_apic_regs;
+
+	if (ctxt == CTXT_HOST)
+		memset(&apic_state[id], 0, sizeof(apic_state[i]));
+
+	for (i = 0; i < ARRAY_SIZE(host_apic_regs); i++) {
+		ent = reg_entries[i];
+		write_apic_reg(id, ent.reg, ctxt, ent.val, x2apic);
+	}
+
+	if (ctxt == CTXT_HOST)
+		vcpu_ioctl(vcpus[id], KVM_SET_LAPIC, &apic_state[id]);
+}
+
+#define CTXT_ASSERT(ctxt, reg, exp, val) \
+	do { \
+		if (ctxt == CTXT_HOST) \
+			TEST_ASSERT(exp == val, \
+				"Unexpected %s %s val: 0x%x expected 0x%x\n", \
+				"host", reg, val, exp); \
+		else \
+			__GUEST_ASSERT(exp == val, \
+				"Unexpected %s %s val: 0x%x expected 0x%x\n", \
+				"guest", reg, val, exp); \
+	} while (0)
+
+/*
+ * Test APIC regs values from host/guest context.
+ *
+ * @id : vCPU ID for which values are tested.
+ * @host : Test APIC regs expected values data source.
+ * @ctxt : Context from which this function is called.
+ * @x2apic : X2apic is enabled.
+ * @savic : Secure AVIC is enabled.
+ */
+static void test_apic_vals(int id, bool host, enum context ctxt, bool x2apic,
+		bool savic)
+{
+	uint32_t exp_regval, regval;
+	struct reg_ent *reg_entries;
+	struct reg_ent ent;
+	char *regname;
+	uint32_t reg;
+	int i;
+
+	if (host)
+		reg_entries = host_apic_regs;
+	else
+		reg_entries = guest_apic_regs;
+
+
+	if (ctxt == CTXT_HOST)
+		vcpu_ioctl(vcpus[id], KVM_GET_LAPIC, &apic_state[id]);
+
+	for (i = 0; i < ARRAY_SIZE(host_apic_regs); i++) {
+		ent = reg_entries[i];
+		reg = ent.reg;
+		/* APIC_TASKPRI is accelerated in guest and not synced in host APIC state. */
+		if (savic && reg == APIC_TASKPRI) {
+			if (ctxt == CTXT_GUEST)
+				continue;
+			else
+				ent = host_apic_regs[i];
+		}
+		regname = ent.regname;
+		exp_regval = ent.val;
+		regval = read_apic_reg(id, reg, ctxt, x2apic);
+		CTXT_ASSERT(ctxt, regname, exp_regval, regval);
+	}
+}
+
+#define SAVIC_GUEST_SYNC(sync, func) ({\
+	GUEST_SYNC(sync ## _START); \
+	func(id); \
+	GUEST_SYNC(sync ## _END); \
+})
+
+static void savic_write_apic_regs(struct guest_apic_page *apage)
+{
+	struct reg_ent ent;
+	uint32_t regval;
+	uint32_t reg;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(guest_apic_regs); i++) {
+		ent = guest_apic_regs[i];
+		reg = ent.reg;
+		regval = ent.val;
+		savic_write_reg(apage, reg, regval);
+		if (reg != APIC_TASKPRI)
+			savic_hv_write_reg(reg, regval);
+	}
+}
+
+static void guest_test_and_set_apic_regs(int id, bool x2apic)
+{
+	/*
+	 * On guest entry, APIC reg values read from guest context matches
+	 * the previous values set by host.
+	 */
+	test_apic_vals(id, true, CTXT_GUEST, x2apic, false);
+	set_apic_vals(id, false, CTXT_GUEST, x2apic);
+	test_apic_vals(id, false, CTXT_GUEST, x2apic, false);
+}
+
+static void guest_savic_start(int id)
+{
+	guest_test_and_set_apic_regs(id, false);
+}
+
+static void guest_x2apic_enabled(int id)
+{
+	guest_test_and_set_apic_regs(id, true);
+}
+
+static void guest_savic_enabled(int id)
+{
+	struct guest_apic_page *apage = get_guest_apic_page();
+
+	/* Host values are written to guest backing page */
+	test_apic_vals(id, true, CTXT_GUEST, true, true);
+	/* Update host APIC regs with guest values */
+	savic_write_apic_regs(apage);
+}
+
+static void guest_code(int id)
+{
+	GUEST_ASSERT(rdmsr(MSR_AMD64_SEV) & MSR_AMD64_SNP_SECURE_AVIC);
+
+	SAVIC_GUEST_SYNC(RESET, guest_savic_start);
+
+	x2apic_enable();
+
+	SAVIC_GUEST_SYNC(X2APIC_ENABLE, guest_x2apic_enabled);
+
+	savic_enable();
+
+	SAVIC_GUEST_SYNC(SAVIC_EN, guest_savic_enabled);
+
+	GUEST_DONE();
+}
+
+static void host_reset_s_test_apic_regs(struct kvm_vcpu *vcpu)
+{
+	/* Set and test APIC regs before guest entry. */
+	set_apic_vals(vcpu->id, true, CTXT_HOST, false);
+	test_apic_vals(vcpu->id, true, CTXT_HOST, false, false);
+}
+
+static void host_reset_s(int id)
+{
+	struct kvm_vcpu *vcpu = vcpus[id];
+	uint64_t apic_base;
+
+	apic_base = vcpu_get_msr(vcpu, MSR_IA32_APICBASE);
+	TEST_ASSERT(apic_base & MSR_IA32_APICBASE_ENABLE,
+			"APIC not in ENABLED state at vCPU RESET");
+	TEST_ASSERT(!(apic_base & X2APIC_ENABLE),
+			"APIC not in xAPIC mode at vCPU RESET");
+	host_reset_s_test_apic_regs(vcpu);
+}
+
+static void host_reset_e(int id)
+{
+	/* On guest exit, APIC regs state must match guest values. */
+	test_apic_vals(id, false, CTXT_HOST, false, false);
+	/* Reset the APIC regs to host values. */
+	set_apic_vals(id, true, CTXT_HOST, false);
+}
+
+static void host_x2apic_enabled_s(int id)
+{
+	struct kvm_vcpu *vcpu = vcpus[id];
+	uint64_t apic_base;
+
+	apic_base = vcpu_get_msr(vcpu, MSR_IA32_APICBASE);
+	TEST_ASSERT(apic_base & X2APIC_ENABLE, "APIC not in x2APIC mode");
+
+	/*
+	 * On guest x2apic enablement, previous APIC regs values
+	 * which were set by host are preserved.
+	 */
+	test_apic_vals(id, true, CTXT_HOST, true, false);
+	set_apic_vals(id, true, CTXT_HOST, true);
+	test_apic_vals(id, true, CTXT_HOST, true, false);
+}
+
+static void host_x2apic_enabled_e(int id)
+{
+	/*
+	 * In guest x2apic enabled mode, guest written states are propagated
+	 * to host APIC state.
+	 */
+	test_apic_vals(id, false, CTXT_HOST, true, false);
+	set_apic_vals(id, true, CTXT_HOST, true);
+}
+
+static void host_post_savic_en_start(int id)
+{
+	/* Host APIC regs before SAVIC enablement are not lost. */
+	test_apic_vals(id, true, CTXT_HOST, true, true);
+	set_apic_vals(id, true, CTXT_HOST, true);
+}
+
+static void host_post_savic_en_end(int id)
+{
+	/*
+	 * LVTT and other emulated APIC regs are propagated to host APIC
+	 * state by SAVIC guest.
+	 */
+	test_apic_vals(id, false, CTXT_HOST, true, true);
+}
+
+static void host_test_savic(struct kvm_vm *vm, int id, enum savic_test_state test_state)
+{
+	switch (test_state) {
+	case RESET_START:
+		host_reset_s(id);
+		break;
+	case RESET_END:
+		host_reset_e(id);
+		break;
+	case X2APIC_ENABLE_START:
+		host_x2apic_enabled_s(id);
+		break;
+	case X2APIC_ENABLE_END:
+		host_x2apic_enabled_e(id);
+		break;
+	case SAVIC_EN_START:
+		host_post_savic_en_start(id);
+		break;
+	case SAVIC_EN_END:
+		host_post_savic_en_end(id);
+		break;
+	default:
+		break;
+	}
+}
+
+static void *vcpu_thread(void *arg)
+{
+	struct kvm_vcpu *vcpu = (struct kvm_vcpu *)arg;
+	struct ucall uc;
+
+	fprintf(stderr, "vCPU thread running vCPU %u\n", vcpu->id);
+
+	while (true) {
+		vcpu_run(vcpu);
+		switch (get_ucall(vcpu, &uc)) {
+		case UCALL_SYNC:
+			host_test_savic(vcpu->vm, vcpu->id, uc.args[1]);
+			break;
+		case UCALL_DONE:
+			return NULL;
+		case UCALL_ABORT:
+			REPORT_GUEST_ASSERT(uc);
+			break;
+		case UCALL_NONE:
+			continue;
+		default:
+			TEST_FAIL("Unknown ucall 0x%lx.", uc.cmd);
+		}
+
+	}
+
+	return NULL;
+}
+
+int main(int argc, char *argv[])
+{
+	struct kvm_sev_init args = { .vmsa_features = BIT_ULL(SVM_FEAT_SECURE_AVIC) |
+					BIT_ULL(SVM_FEAT_ALLOWED_SEV_FEATURES_VALID)
+				   };
+	struct kvm_vm *vm;
+	int r;
+
+	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SNP));
+	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SECURE_AVIC));
+
+	vm = _vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_code, &vcpus[0], &args);
+
+	virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
+
+	vcpu_args_set(vcpus[0], 1, vcpus[0]->id);
+
+	vm_install_exception_handler(vm, 29, sev_es_vc_handler);
+	vm_sev_launch(vm, snp_default_policy(), NULL);
+
+	r = pthread_create(&threads[0], NULL, vcpu_thread, vcpus[0]);
+	TEST_ASSERT(r == 0, "pthread_create failed errno=%d", errno);
+
+	pthread_join(threads[0], NULL);
+
+	kvm_vm_free(vm);
+
+	return 0;
+}
