|
@@ -8,6 +8,7 @@
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/err.h>
|
|
#include <linux/err.h>
|
|
#include <asm/pgtable.h>
|
|
#include <asm/pgtable.h>
|
|
|
|
+#include <asm/gmap.h>
|
|
#include "kvm-s390.h"
|
|
#include "kvm-s390.h"
|
|
#include "gaccess.h"
|
|
#include "gaccess.h"
|
|
#include <asm/switch_to.h>
|
|
#include <asm/switch_to.h>
|
|
@@ -946,3 +947,170 @@ int kvm_s390_check_low_addr_prot_real(struct kvm_vcpu *vcpu, unsigned long gra)
|
|
return 0;
|
|
return 0;
|
|
return trans_exc(vcpu, PGM_PROTECTION, gra, 0, GACC_STORE, PROT_TYPE_LA);
|
|
return trans_exc(vcpu, PGM_PROTECTION, gra, 0, GACC_STORE, PROT_TYPE_LA);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * kvm_s390_shadow_tables - walk the guest page table and create shadow tables
|
|
|
|
+ * @sg: pointer to the shadow guest address space structure
|
|
|
|
+ * @saddr: faulting address in the shadow gmap
|
|
|
|
+ * @pgt: pointer to the page table address result
|
|
|
|
+ */
|
|
|
|
+static int kvm_s390_shadow_tables(struct gmap *sg, unsigned long saddr,
|
|
|
|
+ unsigned long *pgt, int *dat_protection)
|
|
|
|
+{
|
|
|
|
+ struct gmap *parent;
|
|
|
|
+ union asce asce;
|
|
|
|
+ union vaddress vaddr;
|
|
|
|
+ unsigned long ptr;
|
|
|
|
+ int rc;
|
|
|
|
+
|
|
|
|
+ parent = sg->parent;
|
|
|
|
+ vaddr.addr = saddr;
|
|
|
|
+ asce.val = sg->orig_asce;
|
|
|
|
+ ptr = asce.origin * 4096;
|
|
|
|
+ switch (asce.dt) {
|
|
|
|
+ case ASCE_TYPE_REGION1:
|
|
|
|
+ if (vaddr.rfx01 > asce.tl)
|
|
|
|
+ return PGM_REGION_FIRST_TRANS;
|
|
|
|
+ break;
|
|
|
|
+ case ASCE_TYPE_REGION2:
|
|
|
|
+ if (vaddr.rfx)
|
|
|
|
+ return PGM_ASCE_TYPE;
|
|
|
|
+ if (vaddr.rsx01 > asce.tl)
|
|
|
|
+ return PGM_REGION_SECOND_TRANS;
|
|
|
|
+ break;
|
|
|
|
+ case ASCE_TYPE_REGION3:
|
|
|
|
+ if (vaddr.rfx || vaddr.rsx)
|
|
|
|
+ return PGM_ASCE_TYPE;
|
|
|
|
+ if (vaddr.rtx01 > asce.tl)
|
|
|
|
+ return PGM_REGION_THIRD_TRANS;
|
|
|
|
+ break;
|
|
|
|
+ case ASCE_TYPE_SEGMENT:
|
|
|
|
+ if (vaddr.rfx || vaddr.rsx || vaddr.rtx)
|
|
|
|
+ return PGM_ASCE_TYPE;
|
|
|
|
+ if (vaddr.sx01 > asce.tl)
|
|
|
|
+ return PGM_SEGMENT_TRANSLATION;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switch (asce.dt) {
|
|
|
|
+ case ASCE_TYPE_REGION1: {
|
|
|
|
+ union region1_table_entry rfte;
|
|
|
|
+
|
|
|
|
+ rc = gmap_read_table(parent, ptr + vaddr.rfx * 8, &rfte.val);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ if (rfte.i)
|
|
|
|
+ return PGM_REGION_FIRST_TRANS;
|
|
|
|
+ if (rfte.tt != TABLE_TYPE_REGION1)
|
|
|
|
+ return PGM_TRANSLATION_SPEC;
|
|
|
|
+ if (vaddr.rsx01 < rfte.tf || vaddr.rsx01 > rfte.tl)
|
|
|
|
+ return PGM_REGION_SECOND_TRANS;
|
|
|
|
+ rc = gmap_shadow_r2t(sg, saddr, rfte.val);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ ptr = rfte.rto * 4096;
|
|
|
|
+ /* fallthrough */
|
|
|
|
+ }
|
|
|
|
+ case ASCE_TYPE_REGION2: {
|
|
|
|
+ union region2_table_entry rste;
|
|
|
|
+
|
|
|
|
+ rc = gmap_read_table(parent, ptr + vaddr.rsx * 8, &rste.val);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ if (rste.i)
|
|
|
|
+ return PGM_REGION_SECOND_TRANS;
|
|
|
|
+ if (rste.tt != TABLE_TYPE_REGION2)
|
|
|
|
+ return PGM_TRANSLATION_SPEC;
|
|
|
|
+ if (vaddr.rtx01 < rste.tf || vaddr.rtx01 > rste.tl)
|
|
|
|
+ return PGM_REGION_THIRD_TRANS;
|
|
|
|
+ rc = gmap_shadow_r3t(sg, saddr, rste.val);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ ptr = rste.rto * 4096;
|
|
|
|
+ /* fallthrough */
|
|
|
|
+ }
|
|
|
|
+ case ASCE_TYPE_REGION3: {
|
|
|
|
+ union region3_table_entry rtte;
|
|
|
|
+
|
|
|
|
+ rc = gmap_read_table(parent, ptr + vaddr.rtx * 8, &rtte.val);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ if (rtte.i)
|
|
|
|
+ return PGM_REGION_THIRD_TRANS;
|
|
|
|
+ if (rtte.tt != TABLE_TYPE_REGION3)
|
|
|
|
+ return PGM_TRANSLATION_SPEC;
|
|
|
|
+ if (vaddr.sx01 < rtte.fc0.tf || vaddr.sx01 > rtte.fc0.tl)
|
|
|
|
+ return PGM_SEGMENT_TRANSLATION;
|
|
|
|
+ rc = gmap_shadow_sgt(sg, saddr, rtte.val);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ ptr = rtte.fc0.sto * 4096;
|
|
|
|
+ /* fallthrough */
|
|
|
|
+ }
|
|
|
|
+ case ASCE_TYPE_SEGMENT: {
|
|
|
|
+ union segment_table_entry ste;
|
|
|
|
+
|
|
|
|
+ rc = gmap_read_table(parent, ptr + vaddr.sx * 8, &ste.val);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ if (ste.i)
|
|
|
|
+ return PGM_SEGMENT_TRANSLATION;
|
|
|
|
+ if (ste.tt != TABLE_TYPE_SEGMENT)
|
|
|
|
+ return PGM_TRANSLATION_SPEC;
|
|
|
|
+ if (ste.cs && asce.p)
|
|
|
|
+ return PGM_TRANSLATION_SPEC;
|
|
|
|
+ *dat_protection = ste.fc0.p;
|
|
|
|
+ rc = gmap_shadow_pgt(sg, saddr, ste.val);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ ptr = ste.fc0.pto * 2048;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ /* Return the parent address of the page table */
|
|
|
|
+ *pgt = ptr;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * kvm_s390_shadow_fault - handle fault on a shadow page table
|
|
|
|
+ * @sg: pointer to the shadow guest address space structure
|
|
|
|
+ * @saddr: faulting address in the shadow gmap
|
|
|
|
+ * @write: =1 map r/w, =0 map r/o
|
|
|
|
+ *
|
|
|
|
+ * Returns: - 0 if the shadow fault was successfully resolved
|
|
|
|
+ * - > 0 (pgm exception code) on exceptions while faulting
|
|
|
|
+ * - -EAGAIN if the caller can retry immediately
|
|
|
|
+ * - -EFAULT when accessing invalid guest addresses
|
|
|
|
+ * - -ENOMEM if out of memory
|
|
|
|
+ */
|
|
|
|
+int kvm_s390_shadow_fault(struct gmap *sg, unsigned long saddr, int write)
|
|
|
|
+{
|
|
|
|
+ union vaddress vaddr;
|
|
|
|
+ union page_table_entry pte;
|
|
|
|
+ unsigned long pgt;
|
|
|
|
+ int dat_protection;
|
|
|
|
+ int rc;
|
|
|
|
+
|
|
|
|
+ rc = gmap_shadow_pgt_lookup(sg, saddr, &pgt, &dat_protection);
|
|
|
|
+ if (rc) {
|
|
|
|
+ rc = kvm_s390_shadow_tables(sg, saddr, &pgt, &dat_protection);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ vaddr.addr = saddr;
|
|
|
|
+ rc = gmap_read_table(sg->parent, pgt + vaddr.px * 8, &pte.val);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ if (pte.i)
|
|
|
|
+ return PGM_PAGE_TRANSLATION;
|
|
|
|
+ if (pte.z || pte.co)
|
|
|
|
+ return PGM_TRANSLATION_SPEC;
|
|
|
|
+ dat_protection |= pte.p;
|
|
|
|
+ if (write && dat_protection)
|
|
|
|
+ return PGM_PROTECTION;
|
|
|
|
+ rc = gmap_shadow_page(sg, saddr, pte.pfra * 4096, write);
|
|
|
|
+ if (rc)
|
|
|
|
+ return rc;
|
|
|
|
+ return 0;
|
|
|
|
+}
|