@@ -29,5 +29,8 @@ int sev_inject_launch_secret(const char *hdr, const char *secret,
int sev_es_save_reset_vector(void *flash_ptr, uint64_t flash_size);
void sev_es_set_reset_vector(CPUState *cpu);
+int sev_remove_shared_regions_list(unsigned long gfn_start,
+ unsigned long gfn_end);
+int sev_add_shared_regions_list(unsigned long gfn_start, unsigned long gfn_end);
#endif
@@ -343,6 +343,7 @@ struct kvm_run {
} mmio;
/* KVM_EXIT_HYPERCALL */
struct {
+#define KVM_HC_MAP_GPA_RANGE 12
__u64 nr;
__u64 args[6];
__u64 ret;
@@ -1113,6 +1114,8 @@ struct kvm_ppc_resize_hpt {
#define KVM_CAP_EXIT_ON_EMULATION_FAILURE 204
#define KVM_CAP_ARM_MTE 205
+#define KVM_EXIT_HYPERCALL_VALID_MASK (1 << KVM_HC_MAP_GPA_RANGE)
+
#ifdef KVM_CAP_IRQ_ROUTING
struct kvm_irq_routing_irqchip {
@@ -125,6 +125,7 @@ static int has_xsave;
static int has_xcrs;
static int has_pit_state2;
static int has_exception_payload;
+static int has_map_gpa_range;
static bool has_msr_mcg_ext_ctl;
@@ -1916,6 +1917,15 @@ int kvm_arch_init_vcpu(CPUState *cs)
c->eax = MAX(c->eax, KVM_CPUID_SIGNATURE | 0x10);
}
+ if (sev_enabled()) {
+ c = cpuid_find_entry(&cpuid_data.cpuid,
+ KVM_CPUID_FEATURES | kvm_base, 0);
+ c->eax |= (1 << KVM_FEATURE_MIGRATION_CONTROL);
+ if (has_map_gpa_range) {
+ c->eax |= (1 << KVM_FEATURE_HC_MAP_GPA_RANGE);
+ }
+ }
+
cpuid_data.cpuid.nent = cpuid_i;
cpuid_data.cpuid.padding = 0;
@@ -2277,6 +2287,17 @@ int kvm_arch_init(MachineState *ms, KVMState *s)
}
}
+ has_map_gpa_range = kvm_check_extension(s, KVM_CAP_EXIT_HYPERCALL);
+ if (has_map_gpa_range) {
+ ret = kvm_vm_enable_cap(s, KVM_CAP_EXIT_HYPERCALL, 0,
+ KVM_EXIT_HYPERCALL_VALID_MASK);
+ if (ret < 0) {
+ error_report("kvm: Failed to enable MAP_GPA_RANGE cap: %s",
+ strerror(-ret));
+ return ret;
+ }
+ }
+
ret = kvm_get_supported_msrs(s);
if (ret < 0) {
return ret;
@@ -4429,6 +4450,28 @@ static int kvm_handle_tpr_access(X86CPU *cpu)
return 1;
}
+static int kvm_handle_exit_hypercall(X86CPU *cpu, struct kvm_run *run)
+{
+ /*
+ * Currently this exit is only used by SEV guests for
+ * guest page encryption status tracking.
+ */
+ if (run->hypercall.nr == KVM_HC_MAP_GPA_RANGE) {
+ unsigned long enc = run->hypercall.args[2];
+ unsigned long gpa = run->hypercall.args[0];
+ unsigned long npages = run->hypercall.args[1];
+ unsigned long gfn_start = gpa >> TARGET_PAGE_BITS;
+ unsigned long gfn_end = gfn_start + npages;
+
+ if (enc) {
+ sev_remove_shared_regions_list(gfn_start, gfn_end);
+ } else {
+ sev_add_shared_regions_list(gfn_start, gfn_end);
+ }
+ }
+ return 0;
+}
+
int kvm_arch_insert_sw_breakpoint(CPUState *cs, struct kvm_sw_breakpoint *bp)
{
static const uint8_t int3 = 0xcc;
@@ -4690,6 +4733,9 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run)
/* already handled in kvm_arch_post_run */
ret = 0;
break;
+ case KVM_EXIT_HYPERCALL:
+ ret = kvm_handle_exit_hypercall(cpu, run);
+ break;
default:
fprintf(stderr, "KVM: unknown exit reason %d\n", run->exit_reason);
ret = -1;
@@ -40,6 +40,10 @@
#define TYPE_SEV_GUEST "sev-guest"
OBJECT_DECLARE_SIMPLE_TYPE(SevGuestState, SEV_GUEST)
+struct shared_region {
+ unsigned long gfn_start, gfn_end;
+ QTAILQ_ENTRY(shared_region) list;
+};
/**
* SevGuestState:
@@ -83,6 +87,8 @@ struct SevGuestState {
uint32_t reset_cs;
uint32_t reset_ip;
bool reset_data_valid;
+
+ QTAILQ_HEAD(, shared_region) shared_regions_list;
};
#define DEFAULT_GUEST_POLICY 0x1 /* disable debug */
@@ -996,6 +1002,7 @@ int sev_kvm_init(ConfidentialGuestSupport *cgs, Error **errp)
add_migration_state_change_notifier(&sev_migration_state_notify);
cgs_class->memory_encryption_ops = &sev_memory_encryption_ops;
+ QTAILQ_INIT(&sev->shared_regions_list);
cgs->ready = true;
@@ -1499,6 +1506,104 @@ int sev_load_incoming_page(QEMUFile *f, uint8_t *ptr)
return sev_receive_update_data(f, ptr);
}
+int sev_remove_shared_regions_list(unsigned long start, unsigned long end)
+{
+ SevGuestState *s = sev_guest;
+ struct shared_region *pos;
+
+ QTAILQ_FOREACH(pos, &s->shared_regions_list, list) {
+ unsigned long l, r;
+ unsigned long curr_gfn_end = pos->gfn_end;
+
+ /*
+ * Find if any intersection exists ?
+ * left bound for intersecting segment
+ */
+ l = MAX(start, pos->gfn_start);
+ /* right bound for intersecting segment */
+ r = MIN(end, pos->gfn_end);
+ if (l <= r) {
+ if (pos->gfn_start == l && pos->gfn_end == r) {
+ QTAILQ_REMOVE(&s->shared_regions_list, pos, list);
+ } else if (l == pos->gfn_start) {
+ pos->gfn_start = r;
+ } else if (r == pos->gfn_end) {
+ pos->gfn_end = l;
+ } else {
+ /* Do a de-merge -- split linked list nodes */
+ struct shared_region *shrd_region;
+
+ pos->gfn_end = l;
+ shrd_region = g_malloc0(sizeof(*shrd_region));
+ if (!shrd_region) {
+ return 0;
+ }
+ shrd_region->gfn_start = r;
+ shrd_region->gfn_end = curr_gfn_end;
+ QTAILQ_INSERT_AFTER(&s->shared_regions_list, pos,
+ shrd_region, list);
+ }
+ }
+ if (end <= curr_gfn_end) {
+ break;
+ }
+ }
+ return 0;
+}
+
+int sev_add_shared_regions_list(unsigned long start, unsigned long end)
+{
+ struct shared_region *shrd_region;
+ struct shared_region *pos;
+ SevGuestState *s = sev_guest;
+
+ if (QTAILQ_EMPTY(&s->shared_regions_list)) {
+ shrd_region = g_malloc0(sizeof(*shrd_region));
+ if (!shrd_region) {
+ return -1;
+ }
+ shrd_region->gfn_start = start;
+ shrd_region->gfn_end = end;
+ QTAILQ_INSERT_TAIL(&s->shared_regions_list, shrd_region, list);
+ return 0;
+ }
+
+ /*
+ * shared regions list is a sorted list in ascending order
+ * of guest PA's and also merges consecutive range of guest PA's
+ */
+ QTAILQ_FOREACH(pos, &s->shared_regions_list, list) {
+ /* handle duplicate overlapping regions */
+ if (start >= pos->gfn_start && end <= pos->gfn_end) {
+ return 0;
+ }
+ if (pos->gfn_end < start) {
+ continue;
+ }
+ /* merge consecutive guest PA(s) -- forward merge */
+ if (pos->gfn_start <= start && pos->gfn_end >= start) {
+ pos->gfn_end = end;
+ return 0;
+ }
+ break;
+ }
+ /*
+ * Add a new node
+ */
+ shrd_region = g_malloc0(sizeof(*shrd_region));
+ if (!shrd_region) {
+ return -1;
+ }
+ shrd_region->gfn_start = start;
+ shrd_region->gfn_end = end;
+ if (pos) {
+ QTAILQ_INSERT_BEFORE(pos, shrd_region, list);
+ } else {
+ QTAILQ_INSERT_TAIL(&s->shared_regions_list, shrd_region, list);
+ }
+ return 1;
+}
+
static void
sev_register_types(void)
{