Commit a7428c3d authored by Paolo Bonzini's avatar Paolo Bonzini

Merge tag 'kvm-s390-next-20140825' of...

Merge tag 'kvm-s390-next-20140825' of git://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux into HEAD

KVM: s390: Fixes and features for 3.18 part 1

1. The usual cleanups: get rid of duplicate code, use defines, factor
   out the sync_reg handling, additional docs for sync_regs, better
   error handling on interrupt injection
2. We use KVM_REQ_TLB_FLUSH instead of open coding tlb flushes
3. Additional registers for kvm_run sync regs. This is usually not
   needed in the fast path due to eventfd/irqfd, but kvm stat claims
   that we reduced the overhead of console output by ~50% on my system
4. A rework of the gmap infrastructure. This is the 2nd step towards
   host large page support (after getting rid of the storage key
   dependency). We introduces two radix trees to store the guest-to-host
   and host-to-guest translations. This gets us rid of most of
   the page-table walks in the gmap code. Only one in __gmap_link is left,
   this one is required to link the shadow page table to the process page
   table. Finally this contains the plumbing to support gmap page tables
   with less than 5 levels.
parents 54ad89b0 f079e952
...@@ -2861,6 +2861,10 @@ kvm_valid_regs for specific bits. These bits are architecture specific ...@@ -2861,6 +2861,10 @@ kvm_valid_regs for specific bits. These bits are architecture specific
and usually define the validity of a groups of registers. (e.g. one bit and usually define the validity of a groups of registers. (e.g. one bit
for general purpose registers) for general purpose registers)
Please note that the kernel is allowed to use the kvm_run structure as the
primary storage for certain register types. Therefore, the kernel may use the
values in kvm_run even if the corresponding bit in kvm_dirty_regs is not set.
}; };
......
...@@ -18,9 +18,9 @@ ...@@ -18,9 +18,9 @@
unsigned long *crst_table_alloc(struct mm_struct *); unsigned long *crst_table_alloc(struct mm_struct *);
void crst_table_free(struct mm_struct *, unsigned long *); void crst_table_free(struct mm_struct *, unsigned long *);
unsigned long *page_table_alloc(struct mm_struct *, unsigned long); unsigned long *page_table_alloc(struct mm_struct *);
void page_table_free(struct mm_struct *, unsigned long *); void page_table_free(struct mm_struct *, unsigned long *);
void page_table_free_rcu(struct mmu_gather *, unsigned long *); void page_table_free_rcu(struct mmu_gather *, unsigned long *, unsigned long);
void page_table_reset_pgste(struct mm_struct *, unsigned long, unsigned long, void page_table_reset_pgste(struct mm_struct *, unsigned long, unsigned long,
bool init_skey); bool init_skey);
...@@ -145,8 +145,8 @@ static inline void pmd_populate(struct mm_struct *mm, ...@@ -145,8 +145,8 @@ static inline void pmd_populate(struct mm_struct *mm,
/* /*
* page table entry allocation/free routines. * page table entry allocation/free routines.
*/ */
#define pte_alloc_one_kernel(mm, vmaddr) ((pte_t *) page_table_alloc(mm, vmaddr)) #define pte_alloc_one_kernel(mm, vmaddr) ((pte_t *) page_table_alloc(mm))
#define pte_alloc_one(mm, vmaddr) ((pte_t *) page_table_alloc(mm, vmaddr)) #define pte_alloc_one(mm, vmaddr) ((pte_t *) page_table_alloc(mm))
#define pte_free_kernel(mm, pte) page_table_free(mm, (unsigned long *) pte) #define pte_free_kernel(mm, pte) page_table_free(mm, (unsigned long *) pte)
#define pte_free(mm, pte) page_table_free(mm, (unsigned long *) pte) #define pte_free(mm, pte) page_table_free(mm, (unsigned long *) pte)
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/mm_types.h> #include <linux/mm_types.h>
#include <linux/page-flags.h> #include <linux/page-flags.h>
#include <linux/radix-tree.h>
#include <asm/bug.h> #include <asm/bug.h>
#include <asm/page.h> #include <asm/page.h>
...@@ -789,82 +790,67 @@ static inline pgste_t pgste_set_pte(pte_t *ptep, pgste_t pgste, pte_t entry) ...@@ -789,82 +790,67 @@ static inline pgste_t pgste_set_pte(pte_t *ptep, pgste_t pgste, pte_t entry)
/** /**
* struct gmap_struct - guest address space * struct gmap_struct - guest address space
* @crst_list: list of all crst tables used in the guest address space
* @mm: pointer to the parent mm_struct * @mm: pointer to the parent mm_struct
* @guest_to_host: radix tree with guest to host address translation
* @host_to_guest: radix tree with pointer to segment table entries
* @guest_table_lock: spinlock to protect all entries in the guest page table
* @table: pointer to the page directory * @table: pointer to the page directory
* @asce: address space control element for gmap page table * @asce: address space control element for gmap page table
* @crst_list: list of all crst tables used in the guest address space
* @pfault_enabled: defines if pfaults are applicable for the guest * @pfault_enabled: defines if pfaults are applicable for the guest
*/ */
struct gmap { struct gmap {
struct list_head list; struct list_head list;
struct list_head crst_list;
struct mm_struct *mm; struct mm_struct *mm;
struct radix_tree_root guest_to_host;
struct radix_tree_root host_to_guest;
spinlock_t guest_table_lock;
unsigned long *table; unsigned long *table;
unsigned long asce; unsigned long asce;
unsigned long asce_end;
void *private; void *private;
struct list_head crst_list;
bool pfault_enabled; bool pfault_enabled;
}; };
/**
* struct gmap_rmap - reverse mapping for segment table entries
* @gmap: pointer to the gmap_struct
* @entry: pointer to a segment table entry
* @vmaddr: virtual address in the guest address space
*/
struct gmap_rmap {
struct list_head list;
struct gmap *gmap;
unsigned long *entry;
unsigned long vmaddr;
};
/**
* struct gmap_pgtable - gmap information attached to a page table
* @vmaddr: address of the 1MB segment in the process virtual memory
* @mapper: list of segment table entries mapping a page table
*/
struct gmap_pgtable {
unsigned long vmaddr;
struct list_head mapper;
};
/** /**
* struct gmap_notifier - notify function block for page invalidation * struct gmap_notifier - notify function block for page invalidation
* @notifier_call: address of callback function * @notifier_call: address of callback function
*/ */
struct gmap_notifier { struct gmap_notifier {
struct list_head list; struct list_head list;
void (*notifier_call)(struct gmap *gmap, unsigned long address); void (*notifier_call)(struct gmap *gmap, unsigned long gaddr);
}; };
struct gmap *gmap_alloc(struct mm_struct *mm); struct gmap *gmap_alloc(struct mm_struct *mm, unsigned long limit);
void gmap_free(struct gmap *gmap); void gmap_free(struct gmap *gmap);
void gmap_enable(struct gmap *gmap); void gmap_enable(struct gmap *gmap);
void gmap_disable(struct gmap *gmap); void gmap_disable(struct gmap *gmap);
int gmap_map_segment(struct gmap *gmap, unsigned long from, int gmap_map_segment(struct gmap *gmap, unsigned long from,
unsigned long to, unsigned long len); unsigned long to, unsigned long len);
int gmap_unmap_segment(struct gmap *gmap, unsigned long to, unsigned long len); int gmap_unmap_segment(struct gmap *gmap, unsigned long to, unsigned long len);
unsigned long __gmap_translate(unsigned long address, struct gmap *); unsigned long __gmap_translate(struct gmap *, unsigned long gaddr);
unsigned long gmap_translate(unsigned long address, struct gmap *); unsigned long gmap_translate(struct gmap *, unsigned long gaddr);
unsigned long __gmap_fault(unsigned long address, struct gmap *); int __gmap_link(struct gmap *gmap, unsigned long gaddr, unsigned long vmaddr);
unsigned long gmap_fault(unsigned long address, struct gmap *); int gmap_fault(struct gmap *, unsigned long gaddr, unsigned int fault_flags);
void gmap_discard(unsigned long from, unsigned long to, struct gmap *); void gmap_discard(struct gmap *, unsigned long from, unsigned long to);
void __gmap_zap(unsigned long address, struct gmap *); void __gmap_zap(struct gmap *, unsigned long gaddr);
bool gmap_test_and_clear_dirty(unsigned long address, struct gmap *); bool gmap_test_and_clear_dirty(unsigned long address, struct gmap *);
void gmap_register_ipte_notifier(struct gmap_notifier *); void gmap_register_ipte_notifier(struct gmap_notifier *);
void gmap_unregister_ipte_notifier(struct gmap_notifier *); void gmap_unregister_ipte_notifier(struct gmap_notifier *);
int gmap_ipte_notify(struct gmap *, unsigned long start, unsigned long len); int gmap_ipte_notify(struct gmap *, unsigned long start, unsigned long len);
void gmap_do_ipte_notify(struct mm_struct *, pte_t *); void gmap_do_ipte_notify(struct mm_struct *, unsigned long addr, pte_t *);
static inline pgste_t pgste_ipte_notify(struct mm_struct *mm, static inline pgste_t pgste_ipte_notify(struct mm_struct *mm,
unsigned long addr,
pte_t *ptep, pgste_t pgste) pte_t *ptep, pgste_t pgste)
{ {
#ifdef CONFIG_PGSTE #ifdef CONFIG_PGSTE
if (pgste_val(pgste) & PGSTE_IN_BIT) { if (pgste_val(pgste) & PGSTE_IN_BIT) {
pgste_val(pgste) &= ~PGSTE_IN_BIT; pgste_val(pgste) &= ~PGSTE_IN_BIT;
gmap_do_ipte_notify(mm, ptep); gmap_do_ipte_notify(mm, addr, ptep);
} }
#endif #endif
return pgste; return pgste;
...@@ -1110,7 +1096,7 @@ static inline int ptep_test_and_clear_user_dirty(struct mm_struct *mm, ...@@ -1110,7 +1096,7 @@ static inline int ptep_test_and_clear_user_dirty(struct mm_struct *mm,
pgste_val(pgste) &= ~PGSTE_UC_BIT; pgste_val(pgste) &= ~PGSTE_UC_BIT;
pte = *ptep; pte = *ptep;
if (dirty && (pte_val(pte) & _PAGE_PRESENT)) { if (dirty && (pte_val(pte) & _PAGE_PRESENT)) {
pgste = pgste_ipte_notify(mm, ptep, pgste); pgste = pgste_ipte_notify(mm, addr, ptep, pgste);
__ptep_ipte(addr, ptep); __ptep_ipte(addr, ptep);
if (MACHINE_HAS_ESOP || !(pte_val(pte) & _PAGE_WRITE)) if (MACHINE_HAS_ESOP || !(pte_val(pte) & _PAGE_WRITE))
pte_val(pte) |= _PAGE_PROTECT; pte_val(pte) |= _PAGE_PROTECT;
...@@ -1132,7 +1118,7 @@ static inline int ptep_test_and_clear_young(struct vm_area_struct *vma, ...@@ -1132,7 +1118,7 @@ static inline int ptep_test_and_clear_young(struct vm_area_struct *vma,
if (mm_has_pgste(vma->vm_mm)) { if (mm_has_pgste(vma->vm_mm)) {
pgste = pgste_get_lock(ptep); pgste = pgste_get_lock(ptep);
pgste = pgste_ipte_notify(vma->vm_mm, ptep, pgste); pgste = pgste_ipte_notify(vma->vm_mm, addr, ptep, pgste);
} }
pte = *ptep; pte = *ptep;
...@@ -1178,7 +1164,7 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm, ...@@ -1178,7 +1164,7 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
if (mm_has_pgste(mm)) { if (mm_has_pgste(mm)) {
pgste = pgste_get_lock(ptep); pgste = pgste_get_lock(ptep);
pgste = pgste_ipte_notify(mm, ptep, pgste); pgste = pgste_ipte_notify(mm, address, ptep, pgste);
} }
pte = *ptep; pte = *ptep;
...@@ -1202,7 +1188,7 @@ static inline pte_t ptep_modify_prot_start(struct mm_struct *mm, ...@@ -1202,7 +1188,7 @@ static inline pte_t ptep_modify_prot_start(struct mm_struct *mm,
if (mm_has_pgste(mm)) { if (mm_has_pgste(mm)) {
pgste = pgste_get_lock(ptep); pgste = pgste_get_lock(ptep);
pgste_ipte_notify(mm, ptep, pgste); pgste_ipte_notify(mm, address, ptep, pgste);
} }
pte = *ptep; pte = *ptep;
...@@ -1239,7 +1225,7 @@ static inline pte_t ptep_clear_flush(struct vm_area_struct *vma, ...@@ -1239,7 +1225,7 @@ static inline pte_t ptep_clear_flush(struct vm_area_struct *vma,
if (mm_has_pgste(vma->vm_mm)) { if (mm_has_pgste(vma->vm_mm)) {
pgste = pgste_get_lock(ptep); pgste = pgste_get_lock(ptep);
pgste = pgste_ipte_notify(vma->vm_mm, ptep, pgste); pgste = pgste_ipte_notify(vma->vm_mm, address, ptep, pgste);
} }
pte = *ptep; pte = *ptep;
...@@ -1273,7 +1259,7 @@ static inline pte_t ptep_get_and_clear_full(struct mm_struct *mm, ...@@ -1273,7 +1259,7 @@ static inline pte_t ptep_get_and_clear_full(struct mm_struct *mm,
if (!full && mm_has_pgste(mm)) { if (!full && mm_has_pgste(mm)) {
pgste = pgste_get_lock(ptep); pgste = pgste_get_lock(ptep);
pgste = pgste_ipte_notify(mm, ptep, pgste); pgste = pgste_ipte_notify(mm, address, ptep, pgste);
} }
pte = *ptep; pte = *ptep;
...@@ -1298,7 +1284,7 @@ static inline pte_t ptep_set_wrprotect(struct mm_struct *mm, ...@@ -1298,7 +1284,7 @@ static inline pte_t ptep_set_wrprotect(struct mm_struct *mm,
if (pte_write(pte)) { if (pte_write(pte)) {
if (mm_has_pgste(mm)) { if (mm_has_pgste(mm)) {
pgste = pgste_get_lock(ptep); pgste = pgste_get_lock(ptep);
pgste = pgste_ipte_notify(mm, ptep, pgste); pgste = pgste_ipte_notify(mm, address, ptep, pgste);
} }
ptep_flush_lazy(mm, address, ptep); ptep_flush_lazy(mm, address, ptep);
...@@ -1324,7 +1310,7 @@ static inline int ptep_set_access_flags(struct vm_area_struct *vma, ...@@ -1324,7 +1310,7 @@ static inline int ptep_set_access_flags(struct vm_area_struct *vma,
return 0; return 0;
if (mm_has_pgste(vma->vm_mm)) { if (mm_has_pgste(vma->vm_mm)) {
pgste = pgste_get_lock(ptep); pgste = pgste_get_lock(ptep);
pgste = pgste_ipte_notify(vma->vm_mm, ptep, pgste); pgste = pgste_ipte_notify(vma->vm_mm, address, ptep, pgste);
} }
ptep_flush_direct(vma->vm_mm, address, ptep); ptep_flush_direct(vma->vm_mm, address, ptep);
......
...@@ -105,7 +105,7 @@ static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page) ...@@ -105,7 +105,7 @@ static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page)
static inline void pte_free_tlb(struct mmu_gather *tlb, pgtable_t pte, static inline void pte_free_tlb(struct mmu_gather *tlb, pgtable_t pte,
unsigned long address) unsigned long address)
{ {
page_table_free_rcu(tlb, (unsigned long *) pte); page_table_free_rcu(tlb, (unsigned long *) pte, address);
} }
/* /*
......
...@@ -111,12 +111,22 @@ struct kvm_guest_debug_arch { ...@@ -111,12 +111,22 @@ struct kvm_guest_debug_arch {
#define KVM_SYNC_GPRS (1UL << 1) #define KVM_SYNC_GPRS (1UL << 1)
#define KVM_SYNC_ACRS (1UL << 2) #define KVM_SYNC_ACRS (1UL << 2)
#define KVM_SYNC_CRS (1UL << 3) #define KVM_SYNC_CRS (1UL << 3)
#define KVM_SYNC_ARCH0 (1UL << 4)
#define KVM_SYNC_PFAULT (1UL << 5)
/* definition of registers in kvm_run */ /* definition of registers in kvm_run */
struct kvm_sync_regs { struct kvm_sync_regs {
__u64 prefix; /* prefix register */ __u64 prefix; /* prefix register */
__u64 gprs[16]; /* general purpose registers */ __u64 gprs[16]; /* general purpose registers */
__u32 acrs[16]; /* access registers */ __u32 acrs[16]; /* access registers */
__u64 crs[16]; /* control registers */ __u64 crs[16]; /* control registers */
__u64 todpr; /* tod programmable register [ARCH0] */
__u64 cputm; /* cpu timer [ARCH0] */
__u64 ckc; /* clock comparator [ARCH0] */
__u64 pp; /* program parameter [ARCH0] */
__u64 gbea; /* guest breaking-event address [ARCH0] */
__u64 pft; /* pfault token [PFAULT] */
__u64 pfs; /* pfault select [PFAULT] */
__u64 pfc; /* pfault compare [PFAULT] */
}; };
#define KVM_REG_S390_TODPR (KVM_REG_S390 | KVM_REG_SIZE_U32 | 0x1) #define KVM_REG_S390_TODPR (KVM_REG_S390 | KVM_REG_SIZE_U32 | 0x1)
......
...@@ -37,13 +37,13 @@ static int diag_release_pages(struct kvm_vcpu *vcpu) ...@@ -37,13 +37,13 @@ static int diag_release_pages(struct kvm_vcpu *vcpu)
/* we checked for start > end above */ /* we checked for start > end above */
if (end < prefix || start >= prefix + 2 * PAGE_SIZE) { if (end < prefix || start >= prefix + 2 * PAGE_SIZE) {
gmap_discard(start, end, vcpu->arch.gmap); gmap_discard(vcpu->arch.gmap, start, end);
} else { } else {
if (start < prefix) if (start < prefix)
gmap_discard(start, prefix, vcpu->arch.gmap); gmap_discard(vcpu->arch.gmap, start, prefix);
if (end >= prefix) if (end >= prefix)
gmap_discard(prefix + 2 * PAGE_SIZE, gmap_discard(vcpu->arch.gmap,
end, vcpu->arch.gmap); prefix + 2 * PAGE_SIZE, end);
} }
return 0; return 0;
} }
......
...@@ -26,8 +26,9 @@ ...@@ -26,8 +26,9 @@
#define IOINT_SSID_MASK 0x00030000 #define IOINT_SSID_MASK 0x00030000
#define IOINT_CSSID_MASK 0x03fc0000 #define IOINT_CSSID_MASK 0x03fc0000
#define IOINT_AI_MASK 0x04000000 #define IOINT_AI_MASK 0x04000000
#define PFAULT_INIT 0x0600
static void deliver_ckc_interrupt(struct kvm_vcpu *vcpu); static int deliver_ckc_interrupt(struct kvm_vcpu *vcpu);
static int is_ioint(u64 type) static int is_ioint(u64 type)
{ {
...@@ -205,11 +206,30 @@ static void __set_intercept_indicator(struct kvm_vcpu *vcpu, ...@@ -205,11 +206,30 @@ static void __set_intercept_indicator(struct kvm_vcpu *vcpu,
} }
} }
static u16 get_ilc(struct kvm_vcpu *vcpu)
{
const unsigned short table[] = { 2, 4, 4, 6 };
switch (vcpu->arch.sie_block->icptcode) {
case ICPT_INST:
case ICPT_INSTPROGI:
case ICPT_OPEREXC:
case ICPT_PARTEXEC:
case ICPT_IOINST:
/* last instruction only stored for these icptcodes */
return table[vcpu->arch.sie_block->ipa >> 14];
case ICPT_PROGI:
return vcpu->arch.sie_block->pgmilc;
default:
return 0;
}
}
static int __deliver_prog_irq(struct kvm_vcpu *vcpu, static int __deliver_prog_irq(struct kvm_vcpu *vcpu,
struct kvm_s390_pgm_info *pgm_info) struct kvm_s390_pgm_info *pgm_info)
{ {
const unsigned short table[] = { 2, 4, 4, 6 };
int rc = 0; int rc = 0;
u16 ilc = get_ilc(vcpu);
switch (pgm_info->code & ~PGM_PER) { switch (pgm_info->code & ~PGM_PER) {
case PGM_AFX_TRANSLATION: case PGM_AFX_TRANSLATION:
...@@ -276,25 +296,7 @@ static int __deliver_prog_irq(struct kvm_vcpu *vcpu, ...@@ -276,25 +296,7 @@ static int __deliver_prog_irq(struct kvm_vcpu *vcpu,
(u8 *) __LC_PER_ACCESS_ID); (u8 *) __LC_PER_ACCESS_ID);
} }
switch (vcpu->arch.sie_block->icptcode) { rc |= put_guest_lc(vcpu, ilc, (u16 *) __LC_PGM_ILC);
case ICPT_INST:
case ICPT_INSTPROGI:
case ICPT_OPEREXC:
case ICPT_PARTEXEC:
case ICPT_IOINST:
/* last instruction only stored for these icptcodes */
rc |= put_guest_lc(vcpu, table[vcpu->arch.sie_block->ipa >> 14],
(u16 *) __LC_PGM_ILC);
break;
case ICPT_PROGI:
rc |= put_guest_lc(vcpu, vcpu->arch.sie_block->pgmilc,
(u16 *) __LC_PGM_ILC);
break;
default:
rc |= put_guest_lc(vcpu, 0,
(u16 *) __LC_PGM_ILC);
}
rc |= put_guest_lc(vcpu, pgm_info->code, rc |= put_guest_lc(vcpu, pgm_info->code,
(u16 *)__LC_PGM_INT_CODE); (u16 *)__LC_PGM_INT_CODE);
rc |= write_guest_lc(vcpu, __LC_PGM_OLD_PSW, rc |= write_guest_lc(vcpu, __LC_PGM_OLD_PSW,
...@@ -305,7 +307,7 @@ static int __deliver_prog_irq(struct kvm_vcpu *vcpu, ...@@ -305,7 +307,7 @@ static int __deliver_prog_irq(struct kvm_vcpu *vcpu,
return rc; return rc;
} }
static void __do_deliver_interrupt(struct kvm_vcpu *vcpu, static int __do_deliver_interrupt(struct kvm_vcpu *vcpu,
struct kvm_s390_interrupt_info *inti) struct kvm_s390_interrupt_info *inti)
{ {
const unsigned short table[] = { 2, 4, 4, 6 }; const unsigned short table[] = { 2, 4, 4, 6 };
...@@ -343,7 +345,7 @@ static void __do_deliver_interrupt(struct kvm_vcpu *vcpu, ...@@ -343,7 +345,7 @@ static void __do_deliver_interrupt(struct kvm_vcpu *vcpu,
case KVM_S390_INT_CLOCK_COMP: case KVM_S390_INT_CLOCK_COMP:
trace_kvm_s390_deliver_interrupt(vcpu->vcpu_id, inti->type, trace_kvm_s390_deliver_interrupt(vcpu->vcpu_id, inti->type,
inti->ext.ext_params, 0); inti->ext.ext_params, 0);
deliver_ckc_interrupt(vcpu); rc = deliver_ckc_interrupt(vcpu);
break; break;
case KVM_S390_INT_CPU_TIMER: case KVM_S390_INT_CPU_TIMER:
trace_kvm_s390_deliver_interrupt(vcpu->vcpu_id, inti->type, trace_kvm_s390_deliver_interrupt(vcpu->vcpu_id, inti->type,
...@@ -376,8 +378,9 @@ static void __do_deliver_interrupt(struct kvm_vcpu *vcpu, ...@@ -376,8 +378,9 @@ static void __do_deliver_interrupt(struct kvm_vcpu *vcpu,
case KVM_S390_INT_PFAULT_INIT: case KVM_S390_INT_PFAULT_INIT:
trace_kvm_s390_deliver_interrupt(vcpu->vcpu_id, inti->type, 0, trace_kvm_s390_deliver_interrupt(vcpu->vcpu_id, inti->type, 0,
inti->ext.ext_params2); inti->ext.ext_params2);
rc = put_guest_lc(vcpu, 0x2603, (u16 *) __LC_EXT_INT_CODE); rc = put_guest_lc(vcpu, EXT_IRQ_CP_SERVICE,
rc |= put_guest_lc(vcpu, 0x0600, (u16 *) __LC_EXT_CPU_ADDR); (u16 *) __LC_EXT_INT_CODE);
rc |= put_guest_lc(vcpu, PFAULT_INIT, (u16 *) __LC_EXT_CPU_ADDR);
rc |= write_guest_lc(vcpu, __LC_EXT_OLD_PSW, rc |= write_guest_lc(vcpu, __LC_EXT_OLD_PSW,
&vcpu->arch.sie_block->gpsw, sizeof(psw_t)); &vcpu->arch.sie_block->gpsw, sizeof(psw_t));
rc |= read_guest_lc(vcpu, __LC_EXT_NEW_PSW, rc |= read_guest_lc(vcpu, __LC_EXT_NEW_PSW,
...@@ -501,14 +504,11 @@ static void __do_deliver_interrupt(struct kvm_vcpu *vcpu, ...@@ -501,14 +504,11 @@ static void __do_deliver_interrupt(struct kvm_vcpu *vcpu,
default: default:
BUG(); BUG();
} }
if (rc) {
printk("kvm: The guest lowcore is not mapped during interrupt " return rc;
"delivery, killing userspace\n");
do_exit(SIGKILL);
}
} }
static void deliver_ckc_interrupt(struct kvm_vcpu *vcpu) static int deliver_ckc_interrupt(struct kvm_vcpu *vcpu)
{ {
int rc; int rc;
...@@ -518,11 +518,7 @@ static void deliver_ckc_interrupt(struct kvm_vcpu *vcpu) ...@@ -518,11 +518,7 @@ static void deliver_ckc_interrupt(struct kvm_vcpu *vcpu)
rc |= read_guest_lc(vcpu, __LC_EXT_NEW_PSW, rc |= read_guest_lc(vcpu, __LC_EXT_NEW_PSW,
&vcpu->arch.sie_block->gpsw, &vcpu->arch.sie_block->gpsw,
sizeof(psw_t)); sizeof(psw_t));
if (rc) { return rc;
printk("kvm: The guest lowcore is not mapped during interrupt "
"delivery, killing userspace\n");
do_exit(SIGKILL);
}
} }
/* Check whether SIGP interpretation facility has an external call pending */ /* Check whether SIGP interpretation facility has an external call pending */
...@@ -661,12 +657,13 @@ void kvm_s390_clear_local_irqs(struct kvm_vcpu *vcpu) ...@@ -661,12 +657,13 @@ void kvm_s390_clear_local_irqs(struct kvm_vcpu *vcpu)
&vcpu->kvm->arch.sca->cpu[vcpu->vcpu_id].ctrl); &vcpu->kvm->arch.sca->cpu[vcpu->vcpu_id].ctrl);
} }
void kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu) int kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
{ {
struct kvm_s390_local_interrupt *li = &vcpu->arch.local_int; struct kvm_s390_local_interrupt *li = &vcpu->arch.local_int;
struct kvm_s390_float_interrupt *fi = vcpu->arch.local_int.float_int; struct kvm_s390_float_interrupt *fi = vcpu->arch.local_int.float_int;
struct kvm_s390_interrupt_info *n, *inti = NULL; struct kvm_s390_interrupt_info *n, *inti = NULL;
int deliver; int deliver;
int rc = 0;
__reset_intercept_indicators(vcpu); __reset_intercept_indicators(vcpu);
if (atomic_read(&li->active)) { if (atomic_read(&li->active)) {
...@@ -685,16 +682,16 @@ void kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu) ...@@ -685,16 +682,16 @@ void kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
atomic_set(&li->active, 0); atomic_set(&li->active, 0);
spin_unlock(&li->lock); spin_unlock(&li->lock);
if (deliver) { if (deliver) {
__do_deliver_interrupt(vcpu, inti); rc = __do_deliver_interrupt(vcpu, inti);
kfree(inti); kfree(inti);
} }
} while (deliver); } while (!rc && deliver);
} }
if (kvm_cpu_has_pending_timer(vcpu)) if (!rc && kvm_cpu_has_pending_timer(vcpu))
deliver_ckc_interrupt(vcpu); rc = deliver_ckc_interrupt(vcpu);
if (atomic_read(&fi->active)) { if (!rc && atomic_read(&fi->active)) {
do { do {
deliver = 0; deliver = 0;
spin_lock(&fi->lock); spin_lock(&fi->lock);
...@@ -711,67 +708,13 @@ void kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu) ...@@ -711,67 +708,13 @@ void kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
atomic_set(&fi->active, 0); atomic_set(&fi->active, 0);
spin_unlock(&fi->lock); spin_unlock(&fi->lock);
if (deliver) { if (deliver) {
__do_deliver_interrupt(vcpu, inti); rc = __do_deliver_interrupt(vcpu, inti);
kfree(inti); kfree(inti);
} }
} while (deliver); } while (!rc && deliver);
} }
}
void kvm_s390_deliver_pending_machine_checks(struct kvm_vcpu *vcpu) return rc;
{
struct kvm_s390_local_interrupt *li = &vcpu->arch.local_int;
struct kvm_s390_float_interrupt *fi = vcpu->arch.local_int.float_int;
struct kvm_s390_interrupt_info *n, *inti = NULL;
int deliver;
__reset_intercept_indicators(vcpu);
if (atomic_read(&li->active)) {
do {
deliver = 0;
spin_lock(&li->lock);
list_for_each_entry_safe(inti, n, &li->list, list) {
if ((inti->type == KVM_S390_MCHK) &&
__interrupt_is_deliverable(vcpu, inti)) {
list_del(&inti->list);
deliver = 1;
break;
}
__set_intercept_indicator(vcpu, inti);
}
if (list_empty(&li->list))
atomic_set(&li->active, 0);
spin_unlock(&li->lock);
if (deliver) {
__do_deliver_interrupt(vcpu, inti);
kfree(inti);
}
} while (deliver);
}
if (atomic_read(&fi->active)) {
do {
deliver = 0;
spin_lock(&fi->lock);
list_for_each_entry_safe(inti, n, &fi->list, list) {
if ((inti->type == KVM_S390_MCHK) &&
__interrupt_is_deliverable(vcpu, inti)) {
list_del(&inti->list);
fi->irq_count--;
deliver = 1;
break;
}
__set_intercept_indicator(vcpu, inti);
}
if (list_empty(&fi->list))
atomic_set(&fi->active, 0);
spin_unlock(&fi->lock);
if (deliver) {
__do_deliver_interrupt(vcpu, inti);
kfree(inti);
}
} while (deliver);
}
} }
int kvm_s390_inject_program_int(struct kvm_vcpu *vcpu, u16 code) int kvm_s390_inject_program_int(struct kvm_vcpu *vcpu, u16 code)
...@@ -1048,7 +991,6 @@ int kvm_s390_inject_vcpu(struct kvm_vcpu *vcpu, ...@@ -1048,7 +991,6 @@ int kvm_s390_inject_vcpu(struct kvm_vcpu *vcpu,
trace_kvm_s390_inject_vcpu(vcpu->vcpu_id, s390int->type, s390int->parm, trace_kvm_s390_inject_vcpu(vcpu->vcpu_id, s390int->type, s390int->parm,
s390int->parm64, 2); s390int->parm64, 2);
mutex_lock(&vcpu->kvm->lock);
li = &vcpu->arch.local_int; li = &vcpu->arch.local_int;
spin_lock(&li->lock); spin_lock(&li->lock);
if (inti->type == KVM_S390_PROGRAM_INT) if (inti->type == KVM_S390_PROGRAM_INT)
...@@ -1060,7 +1002,6 @@ int kvm_s390_inject_vcpu(struct kvm_vcpu *vcpu, ...@@ -1060,7 +1002,6 @@ int kvm_s390_inject_vcpu(struct kvm_vcpu *vcpu,
li->action_bits |= ACTION_STOP_ON_STOP; li->action_bits |= ACTION_STOP_ON_STOP;
atomic_set_mask(CPUSTAT_EXT_INT, li->cpuflags); atomic_set_mask(CPUSTAT_EXT_INT, li->cpuflags);
spin_unlock(&li->lock); spin_unlock(&li->lock);
mutex_unlock(&vcpu->kvm->lock);
kvm_s390_vcpu_wakeup(vcpu); kvm_s390_vcpu_wakeup(vcpu);
return 0; return 0;
} }
...@@ -1300,7 +1241,7 @@ static int kvm_s390_adapter_map(struct kvm *kvm, unsigned int id, __u64 addr) ...@@ -1300,7 +1241,7 @@ static int kvm_s390_adapter_map(struct kvm *kvm, unsigned int id, __u64 addr)
} }
INIT_LIST_HEAD(&map->list); INIT_LIST_HEAD(&map->list);
map->guest_addr = addr; map->guest_addr = addr;
map->addr = gmap_translate(addr, kvm->arch.gmap); map->addr = gmap_translate(kvm->arch.gmap, addr);
if (map->addr == -EFAULT) { if (map->addr == -EFAULT) {
ret = -EFAULT; ret = -EFAULT;
goto out; goto out;
......
...@@ -451,7 +451,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) ...@@ -451,7 +451,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
if (type & KVM_VM_S390_UCONTROL) { if (type & KVM_VM_S390_UCONTROL) {
kvm->arch.gmap = NULL; kvm->arch.gmap = NULL;
} else { } else {
kvm->arch.gmap = gmap_alloc(current->mm); kvm->arch.gmap = gmap_alloc(current->mm, -1UL);
if (!kvm->arch.gmap) if (!kvm->arch.gmap)
goto out_nogmap; goto out_nogmap;
kvm->arch.gmap->private = kvm; kvm->arch.gmap->private = kvm;
...@@ -535,7 +535,7 @@ int kvm_arch_vcpu_init(struct kvm_vcpu *vcpu) ...@@ -535,7 +535,7 @@ int kvm_arch_vcpu_init(struct kvm_vcpu *vcpu)
vcpu->arch.pfault_token = KVM_S390_PFAULT_TOKEN_INVALID; vcpu->arch.pfault_token = KVM_S390_PFAULT_TOKEN_INVALID;
kvm_clear_async_pf_completion_queue(vcpu); kvm_clear_async_pf_completion_queue(vcpu);
if (kvm_is_ucontrol(vcpu->kvm)) { if (kvm_is_ucontrol(vcpu->kvm)) {
vcpu->arch.gmap = gmap_alloc(current->mm); vcpu->arch.gmap = gmap_alloc(current->mm, -1UL);
if (!vcpu->arch.gmap) if (!vcpu->arch.gmap)
return -ENOMEM; return -ENOMEM;
vcpu->arch.gmap->private = vcpu->kvm; vcpu->arch.gmap->private = vcpu->kvm;
...@@ -546,7 +546,9 @@ int kvm_arch_vcpu_init(struct kvm_vcpu *vcpu) ...@@ -546,7 +546,9 @@ int kvm_arch_vcpu_init(struct kvm_vcpu *vcpu)
vcpu->run->kvm_valid_regs = KVM_SYNC_PREFIX | vcpu->run->kvm_valid_regs = KVM_SYNC_PREFIX |
KVM_SYNC_GPRS | KVM_SYNC_GPRS |
KVM_SYNC_ACRS | KVM_SYNC_ACRS |
KVM_SYNC_CRS; KVM_SYNC_CRS |
KVM_SYNC_ARCH0 |
KVM_SYNC_PFAULT;
return 0; return 0;
} }
...@@ -1053,6 +1055,11 @@ static int kvm_s390_handle_requests(struct kvm_vcpu *vcpu) ...@@ -1053,6 +1055,11 @@ static int kvm_s390_handle_requests(struct kvm_vcpu *vcpu)
goto retry; goto retry;
} }
if (kvm_check_request(KVM_REQ_TLB_FLUSH, vcpu)) {
vcpu->arch.sie_block->ihcpu = 0xffff;
goto retry;
}
if (kvm_check_request(KVM_REQ_ENABLE_IBS, vcpu)) { if (kvm_check_request(KVM_REQ_ENABLE_IBS, vcpu)) {
if (!ibs_enabled(vcpu)) { if (!ibs_enabled(vcpu)) {
trace_kvm_s390_enable_disable_ibs(vcpu->vcpu_id, 1); trace_kvm_s390_enable_disable_ibs(vcpu->vcpu_id, 1);
...@@ -1089,18 +1096,8 @@ static int kvm_s390_handle_requests(struct kvm_vcpu *vcpu) ...@@ -1089,18 +1096,8 @@ static int kvm_s390_handle_requests(struct kvm_vcpu *vcpu)
*/ */
long kvm_arch_fault_in_page(struct kvm_vcpu *vcpu, gpa_t gpa, int writable) long kvm_arch_fault_in_page(struct kvm_vcpu *vcpu, gpa_t gpa, int writable)
{ {
struct mm_struct *mm = current->mm; return gmap_fault(vcpu->arch.gmap, gpa,
hva_t hva; writable ? FAULT_FLAG_WRITE : 0);
long rc;
hva = gmap_fault(gpa, vcpu->arch.gmap);
if (IS_ERR_VALUE(hva))
return (long)hva;
down_read(&mm->mmap_sem);
rc = get_user_pages(current, mm, hva, 1, writable, 0, NULL, NULL);
up_read(&mm->mmap_sem);
return rc < 0 ? rc : 0;
} }
static void __kvm_inject_pfault_token(struct kvm_vcpu *vcpu, bool start_token, static void __kvm_inject_pfault_token(struct kvm_vcpu *vcpu, bool start_token,
...@@ -1195,8 +1192,11 @@ static int vcpu_pre_run(struct kvm_vcpu *vcpu) ...@@ -1195,8 +1192,11 @@ static int vcpu_pre_run(struct kvm_vcpu *vcpu)
if (test_cpu_flag(CIF_MCCK_PENDING)) if (test_cpu_flag(CIF_MCCK_PENDING))
s390_handle_mcck(); s390_handle_mcck();
if (!kvm_is_ucontrol(vcpu->kvm)) if (!kvm_is_ucontrol(vcpu->kvm)) {
kvm_s390_deliver_pending_interrupts(vcpu); rc = kvm_s390_deliver_pending_interrupts(vcpu);
if (rc)
return rc;
}
rc = kvm_s390_handle_requests(vcpu); rc = kvm_s390_handle_requests(vcpu);
if (rc) if (rc)
...@@ -1300,6 +1300,48 @@ static int __vcpu_run(struct kvm_vcpu *vcpu) ...@@ -1300,6 +1300,48 @@ static int __vcpu_run(struct kvm_vcpu *vcpu)
return rc; return rc;
} }
static void sync_regs(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run)
{
vcpu->arch.sie_block->gpsw.mask = kvm_run->psw_mask;
vcpu->arch.sie_block->gpsw.addr = kvm_run->psw_addr;
if (kvm_run->kvm_dirty_regs & KVM_SYNC_PREFIX)
kvm_s390_set_prefix(vcpu, kvm_run->s.regs.prefix);
if (kvm_run->kvm_dirty_regs & KVM_SYNC_CRS) {
memcpy(&vcpu->arch.sie_block->gcr, &kvm_run->s.regs.crs, 128);
/* some control register changes require a tlb flush */
kvm_make_request(KVM_REQ_TLB_FLUSH, vcpu);
}
if (kvm_run->kvm_dirty_regs & KVM_SYNC_ARCH0) {
vcpu->arch.sie_block->cputm = kvm_run->s.regs.cputm;
vcpu->arch.sie_block->ckc = kvm_run->s.regs.ckc;
vcpu->arch.sie_block->todpr = kvm_run->s.regs.todpr;
vcpu->arch.sie_block->pp = kvm_run->s.regs.pp;
vcpu->arch.sie_block->gbea = kvm_run->s.regs.gbea;
}
if (kvm_run->kvm_dirty_regs & KVM_SYNC_PFAULT) {
vcpu->arch.pfault_token = kvm_run->s.regs.pft;
vcpu->arch.pfault_select = kvm_run->s.regs.pfs;
vcpu->arch.pfault_compare = kvm_run->s.regs.pfc;
}
kvm_run->kvm_dirty_regs = 0;
}
static void store_regs(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run)
{
kvm_run->psw_mask = vcpu->arch.sie_block->gpsw.mask;
kvm_run->psw_addr = vcpu->arch.sie_block->gpsw.addr;
kvm_run->s.regs.prefix = kvm_s390_get_prefix(vcpu);
memcpy(&kvm_run->s.regs.crs, &vcpu->arch.sie_block->gcr, 128);
kvm_run->s.regs.cputm = vcpu->arch.sie_block->cputm;
kvm_run->s.regs.ckc = vcpu->arch.sie_block->ckc;
kvm_run->s.regs.todpr = vcpu->arch.sie_block->todpr;
kvm_run->s.regs.pp = vcpu->arch.sie_block->pp;
kvm_run->s.regs.gbea = vcpu->arch.sie_block->gbea;
kvm_run->s.regs.pft = vcpu->arch.pfault_token;
kvm_run->s.regs.pfs = vcpu->arch.pfault_select;
kvm_run->s.regs.pfc = vcpu->arch.pfault_compare;
}
int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run) int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run)
{ {
int rc; int rc;
...@@ -1321,17 +1363,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run) ...@@ -1321,17 +1363,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run)
return -EINVAL; return -EINVAL;
} }
vcpu->arch.sie_block->gpsw.mask = kvm_run->psw_mask; sync_regs(vcpu, kvm_run);
vcpu->arch.sie_block->gpsw.addr = kvm_run->psw_addr;
if (kvm_run->kvm_dirty_regs & KVM_SYNC_PREFIX) {
kvm_run->kvm_dirty_regs &= ~KVM_SYNC_PREFIX;
kvm_s390_set_prefix(vcpu, kvm_run->s.regs.prefix);
}
if (kvm_run->kvm_dirty_regs & KVM_SYNC_CRS) {
kvm_run->kvm_dirty_regs &= ~KVM_SYNC_CRS;
memcpy(&vcpu->arch.sie_block->gcr, &kvm_run->s.regs.crs, 128);
kvm_s390_set_prefix(vcpu, kvm_run->s.regs.prefix);
}
might_fault(); might_fault();
rc = __vcpu_run(vcpu); rc = __vcpu_run(vcpu);
...@@ -1361,10 +1393,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run) ...@@ -1361,10 +1393,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run)
rc = 0; rc = 0;
} }
kvm_run->psw_mask = vcpu->arch.sie_block->gpsw.mask; store_regs(vcpu, kvm_run);
kvm_run->psw_addr = vcpu->arch.sie_block->gpsw.addr;
kvm_run->s.regs.prefix = kvm_s390_get_prefix(vcpu);
memcpy(&kvm_run->s.regs.crs, &vcpu->arch.sie_block->gcr, 128);
if (vcpu->sigset_active) if (vcpu->sigset_active)
sigprocmask(SIG_SETMASK, &sigsaved, NULL); sigprocmask(SIG_SETMASK, &sigsaved, NULL);
...@@ -1493,7 +1522,7 @@ void kvm_s390_vcpu_start(struct kvm_vcpu *vcpu) ...@@ -1493,7 +1522,7 @@ void kvm_s390_vcpu_start(struct kvm_vcpu *vcpu)
* Another VCPU might have used IBS while we were offline. * Another VCPU might have used IBS while we were offline.
* Let's play safe and flush the VCPU at startup. * Let's play safe and flush the VCPU at startup.
*/ */
vcpu->arch.sie_block->ihcpu = 0xffff; kvm_make_request(KVM_REQ_TLB_FLUSH, vcpu);
spin_unlock(&vcpu->kvm->arch.start_stop_lock); spin_unlock(&vcpu->kvm->arch.start_stop_lock);
return; return;
} }
...@@ -1648,9 +1677,7 @@ long kvm_arch_vcpu_ioctl(struct file *filp, ...@@ -1648,9 +1677,7 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
} }
#endif #endif
case KVM_S390_VCPU_FAULT: { case KVM_S390_VCPU_FAULT: {
r = gmap_fault(arg, vcpu->arch.gmap); r = gmap_fault(vcpu->arch.gmap, arg, 0);
if (!IS_ERR_VALUE(r))
r = 0;
break; break;
} }
case KVM_ENABLE_CAP: case KVM_ENABLE_CAP:
......
...@@ -70,7 +70,7 @@ static inline u32 kvm_s390_get_prefix(struct kvm_vcpu *vcpu) ...@@ -70,7 +70,7 @@ static inline u32 kvm_s390_get_prefix(struct kvm_vcpu *vcpu)
static inline void kvm_s390_set_prefix(struct kvm_vcpu *vcpu, u32 prefix) static inline void kvm_s390_set_prefix(struct kvm_vcpu *vcpu, u32 prefix)
{ {
vcpu->arch.sie_block->prefix = prefix >> GUEST_PREFIX_SHIFT; vcpu->arch.sie_block->prefix = prefix >> GUEST_PREFIX_SHIFT;
vcpu->arch.sie_block->ihcpu = 0xffff; kvm_make_request(KVM_REQ_TLB_FLUSH, vcpu);
kvm_make_request(KVM_REQ_MMU_RELOAD, vcpu); kvm_make_request(KVM_REQ_MMU_RELOAD, vcpu);
} }
...@@ -138,8 +138,7 @@ static inline int kvm_s390_user_cpu_state_ctrl(struct kvm *kvm) ...@@ -138,8 +138,7 @@ static inline int kvm_s390_user_cpu_state_ctrl(struct kvm *kvm)
int kvm_s390_handle_wait(struct kvm_vcpu *vcpu); int kvm_s390_handle_wait(struct kvm_vcpu *vcpu);
void kvm_s390_vcpu_wakeup(struct kvm_vcpu *vcpu); void kvm_s390_vcpu_wakeup(struct kvm_vcpu *vcpu);
enum hrtimer_restart kvm_s390_idle_wakeup(struct hrtimer *timer); enum hrtimer_restart kvm_s390_idle_wakeup(struct hrtimer *timer);
void kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu); int kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu);
void kvm_s390_deliver_pending_machine_checks(struct kvm_vcpu *vcpu);
void kvm_s390_clear_local_irqs(struct kvm_vcpu *vcpu); void kvm_s390_clear_local_irqs(struct kvm_vcpu *vcpu);
void kvm_s390_clear_float_irqs(struct kvm *kvm); void kvm_s390_clear_float_irqs(struct kvm *kvm);
int __must_check kvm_s390_inject_vm(struct kvm *kvm, int __must_check kvm_s390_inject_vm(struct kvm *kvm,
......
...@@ -352,13 +352,6 @@ static int handle_stfl(struct kvm_vcpu *vcpu) ...@@ -352,13 +352,6 @@ static int handle_stfl(struct kvm_vcpu *vcpu)
return 0; return 0;
} }
static void handle_new_psw(struct kvm_vcpu *vcpu)
{
/* Check whether the new psw is enabled for machine checks. */
if (vcpu->arch.sie_block->gpsw.mask & PSW_MASK_MCHECK)
kvm_s390_deliver_pending_machine_checks(vcpu);
}
#define PSW_MASK_ADDR_MODE (PSW_MASK_EA | PSW_MASK_BA) #define PSW_MASK_ADDR_MODE (PSW_MASK_EA | PSW_MASK_BA)
#define PSW_MASK_UNASSIGNED 0xb80800fe7fffffffUL #define PSW_MASK_UNASSIGNED 0xb80800fe7fffffffUL
#define PSW_ADDR_24 0x0000000000ffffffUL #define PSW_ADDR_24 0x0000000000ffffffUL
...@@ -405,7 +398,6 @@ int kvm_s390_handle_lpsw(struct kvm_vcpu *vcpu) ...@@ -405,7 +398,6 @@ int kvm_s390_handle_lpsw(struct kvm_vcpu *vcpu)
gpsw->addr = new_psw.addr & ~PSW32_ADDR_AMODE; gpsw->addr = new_psw.addr & ~PSW32_ADDR_AMODE;
if (!is_valid_psw(gpsw)) if (!is_valid_psw(gpsw))
return kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION); return kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
handle_new_psw(vcpu);
return 0; return 0;
} }
...@@ -427,7 +419,6 @@ static int handle_lpswe(struct kvm_vcpu *vcpu) ...@@ -427,7 +419,6 @@ static int handle_lpswe(struct kvm_vcpu *vcpu)
vcpu->arch.sie_block->gpsw = new_psw; vcpu->arch.sie_block->gpsw = new_psw;
if (!is_valid_psw(&vcpu->arch.sie_block->gpsw)) if (!is_valid_psw(&vcpu->arch.sie_block->gpsw))
return kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION); return kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
handle_new_psw(vcpu);
return 0; return 0;
} }
...@@ -738,7 +729,7 @@ static int handle_essa(struct kvm_vcpu *vcpu) ...@@ -738,7 +729,7 @@ static int handle_essa(struct kvm_vcpu *vcpu)
/* invalid entry */ /* invalid entry */
break; break;
/* try to free backing */ /* try to free backing */
__gmap_zap(cbrle, gmap); __gmap_zap(gmap, cbrle);
} }
up_read(&gmap->mm->mmap_sem); up_read(&gmap->mm->mmap_sem);
if (i < entries) if (i < entries)
......
...@@ -442,18 +442,15 @@ static inline int do_exception(struct pt_regs *regs, int access) ...@@ -442,18 +442,15 @@ static inline int do_exception(struct pt_regs *regs, int access)
down_read(&mm->mmap_sem); down_read(&mm->mmap_sem);
#ifdef CONFIG_PGSTE #ifdef CONFIG_PGSTE
gmap = (struct gmap *) gmap = (current->flags & PF_VCPU) ?
((current->flags & PF_VCPU) ? S390_lowcore.gmap : 0); (struct gmap *) S390_lowcore.gmap : NULL;
if (gmap) { if (gmap) {
address = __gmap_fault(address, gmap); current->thread.gmap_addr = address;
address = __gmap_translate(gmap, address);
if (address == -EFAULT) { if (address == -EFAULT) {
fault = VM_FAULT_BADMAP; fault = VM_FAULT_BADMAP;
goto out_up; goto out_up;
} }
if (address == -ENOMEM) {
fault = VM_FAULT_OOM;
goto out_up;
}
if (gmap->pfault_enabled) if (gmap->pfault_enabled)
flags |= FAULT_FLAG_RETRY_NOWAIT; flags |= FAULT_FLAG_RETRY_NOWAIT;
} }
...@@ -530,6 +527,20 @@ static inline int do_exception(struct pt_regs *regs, int access) ...@@ -530,6 +527,20 @@ static inline int do_exception(struct pt_regs *regs, int access)
goto retry; goto retry;
} }
} }
#ifdef CONFIG_PGSTE
if (gmap) {
address = __gmap_link(gmap, current->thread.gmap_addr,
address);
if (address == -EFAULT) {
fault = VM_FAULT_BADMAP;
goto out_up;
}
if (address == -ENOMEM) {
fault = VM_FAULT_OOM;
goto out_up;
}
}
#endif
fault = 0; fault = 0;
out_up: out_up:
up_read(&mm->mmap_sem); up_read(&mm->mmap_sem);
......
...@@ -145,30 +145,56 @@ void crst_table_downgrade(struct mm_struct *mm, unsigned long limit) ...@@ -145,30 +145,56 @@ void crst_table_downgrade(struct mm_struct *mm, unsigned long limit)
/** /**
* gmap_alloc - allocate a guest address space * gmap_alloc - allocate a guest address space
* @mm: pointer to the parent mm_struct * @mm: pointer to the parent mm_struct
* @limit: maximum size of the gmap address space
* *
* Returns a guest address space structure. * Returns a guest address space structure.
*/ */
struct gmap *gmap_alloc(struct mm_struct *mm) struct gmap *gmap_alloc(struct mm_struct *mm, unsigned long limit)
{ {
struct gmap *gmap; struct gmap *gmap;
struct page *page; struct page *page;
unsigned long *table; unsigned long *table;
unsigned long etype, atype;
if (limit < (1UL << 31)) {
limit = (1UL << 31) - 1;
atype = _ASCE_TYPE_SEGMENT;
etype = _SEGMENT_ENTRY_EMPTY;
} else if (limit < (1UL << 42)) {
limit = (1UL << 42) - 1;
atype = _ASCE_TYPE_REGION3;
etype = _REGION3_ENTRY_EMPTY;
} else if (limit < (1UL << 53)) {
limit = (1UL << 53) - 1;
atype = _ASCE_TYPE_REGION2;
etype = _REGION2_ENTRY_EMPTY;
} else {
limit = -1UL;
atype = _ASCE_TYPE_REGION1;
etype = _REGION1_ENTRY_EMPTY;
}
gmap = kzalloc(sizeof(struct gmap), GFP_KERNEL); gmap = kzalloc(sizeof(struct gmap), GFP_KERNEL);
if (!gmap) if (!gmap)
goto out; goto out;
INIT_LIST_HEAD(&gmap->crst_list); INIT_LIST_HEAD(&gmap->crst_list);
INIT_RADIX_TREE(&gmap->guest_to_host, GFP_KERNEL);
INIT_RADIX_TREE(&gmap->host_to_guest, GFP_ATOMIC);
spin_lock_init(&gmap->guest_table_lock);
gmap->mm = mm; gmap->mm = mm;
page = alloc_pages(GFP_KERNEL, ALLOC_ORDER); page = alloc_pages(GFP_KERNEL, ALLOC_ORDER);
if (!page) if (!page)
goto out_free; goto out_free;
page->index = 0;
list_add(&page->lru, &gmap->crst_list); list_add(&page->lru, &gmap->crst_list);
table = (unsigned long *) page_to_phys(page); table = (unsigned long *) page_to_phys(page);
crst_table_init(table, _REGION1_ENTRY_EMPTY); crst_table_init(table, etype);
gmap->table = table; gmap->table = table;
gmap->asce = _ASCE_TYPE_REGION1 | _ASCE_TABLE_LENGTH | gmap->asce = atype | _ASCE_TABLE_LENGTH |
_ASCE_USER_BITS | __pa(table); _ASCE_USER_BITS | __pa(table);
gmap->asce_end = limit;
down_write(&mm->mmap_sem);
list_add(&gmap->list, &mm->context.gmap_list); list_add(&gmap->list, &mm->context.gmap_list);
up_write(&mm->mmap_sem);
return gmap; return gmap;
out_free: out_free:
...@@ -178,36 +204,38 @@ struct gmap *gmap_alloc(struct mm_struct *mm) ...@@ -178,36 +204,38 @@ struct gmap *gmap_alloc(struct mm_struct *mm)
} }
EXPORT_SYMBOL_GPL(gmap_alloc); EXPORT_SYMBOL_GPL(gmap_alloc);
static int gmap_unlink_segment(struct gmap *gmap, unsigned long *table)
{
struct gmap_pgtable *mp;
struct gmap_rmap *rmap;
struct page *page;
if (*table & _SEGMENT_ENTRY_INVALID)
return 0;
page = pfn_to_page(*table >> PAGE_SHIFT);
mp = (struct gmap_pgtable *) page->index;
list_for_each_entry(rmap, &mp->mapper, list) {
if (rmap->entry != table)
continue;
list_del(&rmap->list);
kfree(rmap);
break;
}
*table = mp->vmaddr | _SEGMENT_ENTRY_INVALID | _SEGMENT_ENTRY_PROTECT;
return 1;
}
static void gmap_flush_tlb(struct gmap *gmap) static void gmap_flush_tlb(struct gmap *gmap)
{ {
if (MACHINE_HAS_IDTE) if (MACHINE_HAS_IDTE)
__tlb_flush_asce(gmap->mm, (unsigned long) gmap->table | __tlb_flush_asce(gmap->mm, gmap->asce);
_ASCE_TYPE_REGION1);
else else
__tlb_flush_global(); __tlb_flush_global();
} }
static void gmap_radix_tree_free(struct radix_tree_root *root)
{
struct radix_tree_iter iter;
unsigned long indices[16];
unsigned long index;
void **slot;
int i, nr;
/* A radix tree is freed by deleting all of its entries */
index = 0;
do {
nr = 0;
radix_tree_for_each_slot(slot, root, &iter, index) {
indices[nr] = iter.index;
if (++nr == 16)
break;
}
for (i = 0; i < nr; i++) {
index = indices[i];
radix_tree_delete(root, index);
}
} while (nr > 0);
}
/** /**
* gmap_free - free a guest address space * gmap_free - free a guest address space
* @gmap: pointer to the guest address space structure * @gmap: pointer to the guest address space structure
...@@ -215,31 +243,21 @@ static void gmap_flush_tlb(struct gmap *gmap) ...@@ -215,31 +243,21 @@ static void gmap_flush_tlb(struct gmap *gmap)
void gmap_free(struct gmap *gmap) void gmap_free(struct gmap *gmap)
{ {
struct page *page, *next; struct page *page, *next;
unsigned long *table;
int i;
/* Flush tlb. */ /* Flush tlb. */
if (MACHINE_HAS_IDTE) if (MACHINE_HAS_IDTE)
__tlb_flush_asce(gmap->mm, (unsigned long) gmap->table | __tlb_flush_asce(gmap->mm, gmap->asce);
_ASCE_TYPE_REGION1);
else else
__tlb_flush_global(); __tlb_flush_global();
/* Free all segment & region tables. */ /* Free all segment & region tables. */
down_read(&gmap->mm->mmap_sem); list_for_each_entry_safe(page, next, &gmap->crst_list, lru)
spin_lock(&gmap->mm->page_table_lock);
list_for_each_entry_safe(page, next, &gmap->crst_list, lru) {
table = (unsigned long *) page_to_phys(page);
if ((*table & _REGION_ENTRY_TYPE_MASK) == 0)
/* Remove gmap rmap structures for segment table. */
for (i = 0; i < PTRS_PER_PMD; i++, table++)
gmap_unlink_segment(gmap, table);
__free_pages(page, ALLOC_ORDER); __free_pages(page, ALLOC_ORDER);
} gmap_radix_tree_free(&gmap->guest_to_host);
spin_unlock(&gmap->mm->page_table_lock); gmap_radix_tree_free(&gmap->host_to_guest);
up_read(&gmap->mm->mmap_sem); down_write(&gmap->mm->mmap_sem);
list_del(&gmap->list); list_del(&gmap->list);
up_write(&gmap->mm->mmap_sem);
kfree(gmap); kfree(gmap);
} }
EXPORT_SYMBOL_GPL(gmap_free); EXPORT_SYMBOL_GPL(gmap_free);
...@@ -267,42 +285,97 @@ EXPORT_SYMBOL_GPL(gmap_disable); ...@@ -267,42 +285,97 @@ EXPORT_SYMBOL_GPL(gmap_disable);
/* /*
* gmap_alloc_table is assumed to be called with mmap_sem held * gmap_alloc_table is assumed to be called with mmap_sem held
*/ */
static int gmap_alloc_table(struct gmap *gmap, static int gmap_alloc_table(struct gmap *gmap, unsigned long *table,
unsigned long *table, unsigned long init) unsigned long init, unsigned long gaddr)
__releases(&gmap->mm->page_table_lock)
__acquires(&gmap->mm->page_table_lock)
{ {
struct page *page; struct page *page;
unsigned long *new; unsigned long *new;
/* since we dont free the gmap table until gmap_free we can unlock */ /* since we dont free the gmap table until gmap_free we can unlock */
spin_unlock(&gmap->mm->page_table_lock);
page = alloc_pages(GFP_KERNEL, ALLOC_ORDER); page = alloc_pages(GFP_KERNEL, ALLOC_ORDER);
spin_lock(&gmap->mm->page_table_lock);
if (!page) if (!page)
return -ENOMEM; return -ENOMEM;
new = (unsigned long *) page_to_phys(page); new = (unsigned long *) page_to_phys(page);
crst_table_init(new, init); crst_table_init(new, init);
spin_lock(&gmap->mm->page_table_lock);
if (*table & _REGION_ENTRY_INVALID) { if (*table & _REGION_ENTRY_INVALID) {
list_add(&page->lru, &gmap->crst_list); list_add(&page->lru, &gmap->crst_list);
*table = (unsigned long) new | _REGION_ENTRY_LENGTH | *table = (unsigned long) new | _REGION_ENTRY_LENGTH |
(*table & _REGION_ENTRY_TYPE_MASK); (*table & _REGION_ENTRY_TYPE_MASK);
} else page->index = gaddr;
page = NULL;
}
spin_unlock(&gmap->mm->page_table_lock);
if (page)
__free_pages(page, ALLOC_ORDER); __free_pages(page, ALLOC_ORDER);
return 0; return 0;
} }
/**
* __gmap_segment_gaddr - find virtual address from segment pointer
* @entry: pointer to a segment table entry in the guest address space
*
* Returns the virtual address in the guest address space for the segment
*/
static unsigned long __gmap_segment_gaddr(unsigned long *entry)
{
struct page *page;
unsigned long offset;
offset = (unsigned long) entry / sizeof(unsigned long);
offset = (offset & (PTRS_PER_PMD - 1)) * PMD_SIZE;
page = pmd_to_page((pmd_t *) entry);
return page->index + offset;
}
/**
* __gmap_unlink_by_vmaddr - unlink a single segment via a host address
* @gmap: pointer to the guest address space structure
* @vmaddr: address in the host process address space
*
* Returns 1 if a TLB flush is required
*/
static int __gmap_unlink_by_vmaddr(struct gmap *gmap, unsigned long vmaddr)
{
unsigned long *entry;
int flush = 0;
spin_lock(&gmap->guest_table_lock);
entry = radix_tree_delete(&gmap->host_to_guest, vmaddr >> PMD_SHIFT);
if (entry) {
flush = (*entry != _SEGMENT_ENTRY_INVALID);
*entry = _SEGMENT_ENTRY_INVALID;
}
spin_unlock(&gmap->guest_table_lock);
return flush;
}
/**
* __gmap_unmap_by_gaddr - unmap a single segment via a guest address
* @gmap: pointer to the guest address space structure
* @gaddr: address in the guest address space
*
* Returns 1 if a TLB flush is required
*/
static int __gmap_unmap_by_gaddr(struct gmap *gmap, unsigned long gaddr)
{
unsigned long vmaddr;
vmaddr = (unsigned long) radix_tree_delete(&gmap->guest_to_host,
gaddr >> PMD_SHIFT);
return vmaddr ? __gmap_unlink_by_vmaddr(gmap, vmaddr) : 0;
}
/** /**
* gmap_unmap_segment - unmap segment from the guest address space * gmap_unmap_segment - unmap segment from the guest address space
* @gmap: pointer to the guest address space structure * @gmap: pointer to the guest address space structure
* @addr: address in the guest address space * @to: address in the guest address space
* @len: length of the memory area to unmap * @len: length of the memory area to unmap
* *
* Returns 0 if the unmap succeeded, -EINVAL if not. * Returns 0 if the unmap succeeded, -EINVAL if not.
*/ */
int gmap_unmap_segment(struct gmap *gmap, unsigned long to, unsigned long len) int gmap_unmap_segment(struct gmap *gmap, unsigned long to, unsigned long len)
{ {
unsigned long *table;
unsigned long off; unsigned long off;
int flush; int flush;
...@@ -312,31 +385,10 @@ int gmap_unmap_segment(struct gmap *gmap, unsigned long to, unsigned long len) ...@@ -312,31 +385,10 @@ int gmap_unmap_segment(struct gmap *gmap, unsigned long to, unsigned long len)
return -EINVAL; return -EINVAL;
flush = 0; flush = 0;
down_read(&gmap->mm->mmap_sem); down_write(&gmap->mm->mmap_sem);
spin_lock(&gmap->mm->page_table_lock); for (off = 0; off < len; off += PMD_SIZE)
for (off = 0; off < len; off += PMD_SIZE) { flush |= __gmap_unmap_by_gaddr(gmap, to + off);
/* Walk the guest addr space page table */ up_write(&gmap->mm->mmap_sem);
table = gmap->table + (((to + off) >> 53) & 0x7ff);
if (*table & _REGION_ENTRY_INVALID)
goto out;
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
table = table + (((to + off) >> 42) & 0x7ff);
if (*table & _REGION_ENTRY_INVALID)
goto out;
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
table = table + (((to + off) >> 31) & 0x7ff);
if (*table & _REGION_ENTRY_INVALID)
goto out;
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
table = table + (((to + off) >> 20) & 0x7ff);
/* Clear segment table entry in guest address space. */
flush |= gmap_unlink_segment(gmap, table);
*table = _SEGMENT_ENTRY_INVALID;
}
out:
spin_unlock(&gmap->mm->page_table_lock);
up_read(&gmap->mm->mmap_sem);
if (flush) if (flush)
gmap_flush_tlb(gmap); gmap_flush_tlb(gmap);
return 0; return 0;
...@@ -348,87 +400,47 @@ EXPORT_SYMBOL_GPL(gmap_unmap_segment); ...@@ -348,87 +400,47 @@ EXPORT_SYMBOL_GPL(gmap_unmap_segment);
* @gmap: pointer to the guest address space structure * @gmap: pointer to the guest address space structure
* @from: source address in the parent address space * @from: source address in the parent address space
* @to: target address in the guest address space * @to: target address in the guest address space
* @len: length of the memory area to map
* *
* Returns 0 if the mmap succeeded, -EINVAL or -ENOMEM if not. * Returns 0 if the mmap succeeded, -EINVAL or -ENOMEM if not.
*/ */
int gmap_map_segment(struct gmap *gmap, unsigned long from, int gmap_map_segment(struct gmap *gmap, unsigned long from,
unsigned long to, unsigned long len) unsigned long to, unsigned long len)
{ {
unsigned long *table;
unsigned long off; unsigned long off;
int flush; int flush;
if ((from | to | len) & (PMD_SIZE - 1)) if ((from | to | len) & (PMD_SIZE - 1))
return -EINVAL; return -EINVAL;
if (len == 0 || from + len > TASK_MAX_SIZE || if (len == 0 || from + len < from || to + len < to ||
from + len < from || to + len < to) from + len > TASK_MAX_SIZE || to + len > gmap->asce_end)
return -EINVAL; return -EINVAL;
flush = 0; flush = 0;
down_read(&gmap->mm->mmap_sem); down_write(&gmap->mm->mmap_sem);
spin_lock(&gmap->mm->page_table_lock);
for (off = 0; off < len; off += PMD_SIZE) { for (off = 0; off < len; off += PMD_SIZE) {
/* Walk the gmap address space page table */ /* Remove old translation */
table = gmap->table + (((to + off) >> 53) & 0x7ff); flush |= __gmap_unmap_by_gaddr(gmap, to + off);
if ((*table & _REGION_ENTRY_INVALID) && /* Store new translation */
gmap_alloc_table(gmap, table, _REGION2_ENTRY_EMPTY)) if (radix_tree_insert(&gmap->guest_to_host,
goto out_unmap; (to + off) >> PMD_SHIFT,
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); (void *) from + off))
table = table + (((to + off) >> 42) & 0x7ff); break;
if ((*table & _REGION_ENTRY_INVALID) &&
gmap_alloc_table(gmap, table, _REGION3_ENTRY_EMPTY))
goto out_unmap;
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
table = table + (((to + off) >> 31) & 0x7ff);
if ((*table & _REGION_ENTRY_INVALID) &&
gmap_alloc_table(gmap, table, _SEGMENT_ENTRY_EMPTY))
goto out_unmap;
table = (unsigned long *) (*table & _REGION_ENTRY_ORIGIN);
table = table + (((to + off) >> 20) & 0x7ff);
/* Store 'from' address in an invalid segment table entry. */
flush |= gmap_unlink_segment(gmap, table);
*table = (from + off) | (_SEGMENT_ENTRY_INVALID |
_SEGMENT_ENTRY_PROTECT);
} }
spin_unlock(&gmap->mm->page_table_lock); up_write(&gmap->mm->mmap_sem);
up_read(&gmap->mm->mmap_sem);
if (flush) if (flush)
gmap_flush_tlb(gmap); gmap_flush_tlb(gmap);
return 0; if (off >= len)
return 0;
out_unmap:
spin_unlock(&gmap->mm->page_table_lock);
up_read(&gmap->mm->mmap_sem);
gmap_unmap_segment(gmap, to, len); gmap_unmap_segment(gmap, to, len);
return -ENOMEM; return -ENOMEM;
} }
EXPORT_SYMBOL_GPL(gmap_map_segment); EXPORT_SYMBOL_GPL(gmap_map_segment);
static unsigned long *gmap_table_walk(unsigned long address, struct gmap *gmap)
{
unsigned long *table;
table = gmap->table + ((address >> 53) & 0x7ff);
if (unlikely(*table & _REGION_ENTRY_INVALID))
return ERR_PTR(-EFAULT);
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
table = table + ((address >> 42) & 0x7ff);
if (unlikely(*table & _REGION_ENTRY_INVALID))
return ERR_PTR(-EFAULT);
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
table = table + ((address >> 31) & 0x7ff);
if (unlikely(*table & _REGION_ENTRY_INVALID))
return ERR_PTR(-EFAULT);
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
table = table + ((address >> 20) & 0x7ff);
return table;
}
/** /**
* __gmap_translate - translate a guest address to a user space address * __gmap_translate - translate a guest address to a user space address
* @address: guest address
* @gmap: pointer to guest mapping meta data structure * @gmap: pointer to guest mapping meta data structure
* @gaddr: guest address
* *
* Returns user space address which corresponds to the guest address or * Returns user space address which corresponds to the guest address or
* -EFAULT if no such mapping exists. * -EFAULT if no such mapping exists.
...@@ -436,168 +448,161 @@ static unsigned long *gmap_table_walk(unsigned long address, struct gmap *gmap) ...@@ -436,168 +448,161 @@ static unsigned long *gmap_table_walk(unsigned long address, struct gmap *gmap)
* The mmap_sem of the mm that belongs to the address space must be held * The mmap_sem of the mm that belongs to the address space must be held
* when this function gets called. * when this function gets called.
*/ */
unsigned long __gmap_translate(unsigned long address, struct gmap *gmap) unsigned long __gmap_translate(struct gmap *gmap, unsigned long gaddr)
{ {
unsigned long *segment_ptr, vmaddr, segment; unsigned long vmaddr;
struct gmap_pgtable *mp;
struct page *page;
current->thread.gmap_addr = address; vmaddr = (unsigned long)
segment_ptr = gmap_table_walk(address, gmap); radix_tree_lookup(&gmap->guest_to_host, gaddr >> PMD_SHIFT);
if (IS_ERR(segment_ptr)) return vmaddr ? (vmaddr | (gaddr & ~PMD_MASK)) : -EFAULT;
return PTR_ERR(segment_ptr);
/* Convert the gmap address to an mm address. */
segment = *segment_ptr;
if (!(segment & _SEGMENT_ENTRY_INVALID)) {
page = pfn_to_page(segment >> PAGE_SHIFT);
mp = (struct gmap_pgtable *) page->index;
return mp->vmaddr | (address & ~PMD_MASK);
} else if (segment & _SEGMENT_ENTRY_PROTECT) {
vmaddr = segment & _SEGMENT_ENTRY_ORIGIN;
return vmaddr | (address & ~PMD_MASK);
}
return -EFAULT;
} }
EXPORT_SYMBOL_GPL(__gmap_translate); EXPORT_SYMBOL_GPL(__gmap_translate);
/** /**
* gmap_translate - translate a guest address to a user space address * gmap_translate - translate a guest address to a user space address
* @address: guest address
* @gmap: pointer to guest mapping meta data structure * @gmap: pointer to guest mapping meta data structure
* @gaddr: guest address
* *
* Returns user space address which corresponds to the guest address or * Returns user space address which corresponds to the guest address or
* -EFAULT if no such mapping exists. * -EFAULT if no such mapping exists.
* This function does not establish potentially missing page table entries. * This function does not establish potentially missing page table entries.
*/ */
unsigned long gmap_translate(unsigned long address, struct gmap *gmap) unsigned long gmap_translate(struct gmap *gmap, unsigned long gaddr)
{ {
unsigned long rc; unsigned long rc;
down_read(&gmap->mm->mmap_sem); down_read(&gmap->mm->mmap_sem);
rc = __gmap_translate(address, gmap); rc = __gmap_translate(gmap, gaddr);
up_read(&gmap->mm->mmap_sem); up_read(&gmap->mm->mmap_sem);
return rc; return rc;
} }
EXPORT_SYMBOL_GPL(gmap_translate); EXPORT_SYMBOL_GPL(gmap_translate);
static int gmap_connect_pgtable(unsigned long address, unsigned long segment, /**
unsigned long *segment_ptr, struct gmap *gmap) * gmap_unlink - disconnect a page table from the gmap shadow tables
* @gmap: pointer to guest mapping meta data structure
* @table: pointer to the host page table
* @vmaddr: vm address associated with the host page table
*/
static void gmap_unlink(struct mm_struct *mm, unsigned long *table,
unsigned long vmaddr)
{
struct gmap *gmap;
int flush;
list_for_each_entry(gmap, &mm->context.gmap_list, list) {
flush = __gmap_unlink_by_vmaddr(gmap, vmaddr);
if (flush)
gmap_flush_tlb(gmap);
}
}
/**
* gmap_link - set up shadow page tables to connect a host to a guest address
* @gmap: pointer to guest mapping meta data structure
* @gaddr: guest address
* @vmaddr: vm address
*
* Returns 0 on success, -ENOMEM for out of memory conditions, and -EFAULT
* if the vm address is already mapped to a different guest segment.
* The mmap_sem of the mm that belongs to the address space must be held
* when this function gets called.
*/
int __gmap_link(struct gmap *gmap, unsigned long gaddr, unsigned long vmaddr)
{ {
unsigned long vmaddr;
struct vm_area_struct *vma;
struct gmap_pgtable *mp;
struct gmap_rmap *rmap;
struct mm_struct *mm; struct mm_struct *mm;
struct page *page; unsigned long *table;
spinlock_t *ptl;
pgd_t *pgd; pgd_t *pgd;
pud_t *pud; pud_t *pud;
pmd_t *pmd; pmd_t *pmd;
int rc;
mm = gmap->mm; /* Create higher level tables in the gmap page table */
vmaddr = segment & _SEGMENT_ENTRY_ORIGIN; table = gmap->table;
vma = find_vma(mm, vmaddr); if ((gmap->asce & _ASCE_TYPE_MASK) >= _ASCE_TYPE_REGION1) {
if (!vma || vma->vm_start > vmaddr) table += (gaddr >> 53) & 0x7ff;
return -EFAULT; if ((*table & _REGION_ENTRY_INVALID) &&
gmap_alloc_table(gmap, table, _REGION2_ENTRY_EMPTY,
gaddr & 0xffe0000000000000))
return -ENOMEM;
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
}
if ((gmap->asce & _ASCE_TYPE_MASK) >= _ASCE_TYPE_REGION2) {
table += (gaddr >> 42) & 0x7ff;
if ((*table & _REGION_ENTRY_INVALID) &&
gmap_alloc_table(gmap, table, _REGION3_ENTRY_EMPTY,
gaddr & 0xfffffc0000000000))
return -ENOMEM;
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
}
if ((gmap->asce & _ASCE_TYPE_MASK) >= _ASCE_TYPE_REGION3) {
table += (gaddr >> 31) & 0x7ff;
if ((*table & _REGION_ENTRY_INVALID) &&
gmap_alloc_table(gmap, table, _SEGMENT_ENTRY_EMPTY,
gaddr & 0xffffffff80000000))
return -ENOMEM;
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
}
table += (gaddr >> 20) & 0x7ff;
/* Walk the parent mm page table */ /* Walk the parent mm page table */
mm = gmap->mm;
pgd = pgd_offset(mm, vmaddr); pgd = pgd_offset(mm, vmaddr);
pud = pud_alloc(mm, pgd, vmaddr); VM_BUG_ON(pgd_none(*pgd));
if (!pud) pud = pud_offset(pgd, vmaddr);
return -ENOMEM; VM_BUG_ON(pud_none(*pud));
pmd = pmd_alloc(mm, pud, vmaddr); pmd = pmd_offset(pud, vmaddr);
if (!pmd) VM_BUG_ON(pmd_none(*pmd));
return -ENOMEM;
if (!pmd_present(*pmd) &&
__pte_alloc(mm, vma, pmd, vmaddr))
return -ENOMEM;
/* large pmds cannot yet be handled */ /* large pmds cannot yet be handled */
if (pmd_large(*pmd)) if (pmd_large(*pmd))
return -EFAULT; return -EFAULT;
/* pmd now points to a valid segment table entry. */
rmap = kmalloc(sizeof(*rmap), GFP_KERNEL|__GFP_REPEAT);
if (!rmap)
return -ENOMEM;
/* Link gmap segment table entry location to page table. */ /* Link gmap segment table entry location to page table. */
page = pmd_page(*pmd); rc = radix_tree_preload(GFP_KERNEL);
mp = (struct gmap_pgtable *) page->index; if (rc)
rmap->gmap = gmap; return rc;
rmap->entry = segment_ptr; ptl = pmd_lock(mm, pmd);
rmap->vmaddr = address & PMD_MASK; spin_lock(&gmap->guest_table_lock);
spin_lock(&mm->page_table_lock); if (*table == _SEGMENT_ENTRY_INVALID) {
if (*segment_ptr == segment) { rc = radix_tree_insert(&gmap->host_to_guest,
list_add(&rmap->list, &mp->mapper); vmaddr >> PMD_SHIFT, table);
/* Set gmap segment table entry to page table. */ if (!rc)
*segment_ptr = pmd_val(*pmd) & PAGE_MASK; *table = pmd_val(*pmd);
rmap = NULL; } else
} rc = 0;
spin_unlock(&mm->page_table_lock); spin_unlock(&gmap->guest_table_lock);
kfree(rmap); spin_unlock(ptl);
return 0; radix_tree_preload_end();
} return rc;
static void gmap_disconnect_pgtable(struct mm_struct *mm, unsigned long *table)
{
struct gmap_rmap *rmap, *next;
struct gmap_pgtable *mp;
struct page *page;
int flush;
flush = 0;
spin_lock(&mm->page_table_lock);
page = pfn_to_page(__pa(table) >> PAGE_SHIFT);
mp = (struct gmap_pgtable *) page->index;
list_for_each_entry_safe(rmap, next, &mp->mapper, list) {
*rmap->entry = mp->vmaddr | (_SEGMENT_ENTRY_INVALID |
_SEGMENT_ENTRY_PROTECT);
list_del(&rmap->list);
kfree(rmap);
flush = 1;
}
spin_unlock(&mm->page_table_lock);
if (flush)
__tlb_flush_global();
} }
/* /**
* this function is assumed to be called with mmap_sem held * gmap_fault - resolve a fault on a guest address
* @gmap: pointer to guest mapping meta data structure
* @gaddr: guest address
* @fault_flags: flags to pass down to handle_mm_fault()
*
* Returns 0 on success, -ENOMEM for out of memory conditions, and -EFAULT
* if the vm address is already mapped to a different guest segment.
*/ */
unsigned long __gmap_fault(unsigned long address, struct gmap *gmap) int gmap_fault(struct gmap *gmap, unsigned long gaddr,
unsigned int fault_flags)
{ {
unsigned long *segment_ptr, segment; unsigned long vmaddr;
struct gmap_pgtable *mp;
struct page *page;
int rc; int rc;
current->thread.gmap_addr = address;
segment_ptr = gmap_table_walk(address, gmap);
if (IS_ERR(segment_ptr))
return -EFAULT;
/* Convert the gmap address to an mm address. */
while (1) {
segment = *segment_ptr;
if (!(segment & _SEGMENT_ENTRY_INVALID)) {
/* Page table is present */
page = pfn_to_page(segment >> PAGE_SHIFT);
mp = (struct gmap_pgtable *) page->index;
return mp->vmaddr | (address & ~PMD_MASK);
}
if (!(segment & _SEGMENT_ENTRY_PROTECT))
/* Nothing mapped in the gmap address space. */
break;
rc = gmap_connect_pgtable(address, segment, segment_ptr, gmap);
if (rc)
return rc;
}
return -EFAULT;
}
unsigned long gmap_fault(unsigned long address, struct gmap *gmap)
{
unsigned long rc;
down_read(&gmap->mm->mmap_sem); down_read(&gmap->mm->mmap_sem);
rc = __gmap_fault(address, gmap); vmaddr = __gmap_translate(gmap, gaddr);
if (IS_ERR_VALUE(vmaddr)) {
rc = vmaddr;
goto out_up;
}
if (fixup_user_fault(current, gmap->mm, vmaddr, fault_flags)) {
rc = -EFAULT;
goto out_up;
}
rc = __gmap_link(gmap, gaddr, vmaddr);
out_up:
up_read(&gmap->mm->mmap_sem); up_read(&gmap->mm->mmap_sem);
return rc; return rc;
} }
EXPORT_SYMBOL_GPL(gmap_fault); EXPORT_SYMBOL_GPL(gmap_fault);
...@@ -617,17 +622,24 @@ static void gmap_zap_swap_entry(swp_entry_t entry, struct mm_struct *mm) ...@@ -617,17 +622,24 @@ static void gmap_zap_swap_entry(swp_entry_t entry, struct mm_struct *mm)
free_swap_and_cache(entry); free_swap_and_cache(entry);
} }
/** /*
* The mm->mmap_sem lock must be held * this function is assumed to be called with mmap_sem held
*/ */
static void gmap_zap_unused(struct mm_struct *mm, unsigned long address) void __gmap_zap(struct gmap *gmap, unsigned long gaddr)
{ {
unsigned long ptev, pgstev; unsigned long vmaddr, ptev, pgstev;
pte_t *ptep, pte;
spinlock_t *ptl; spinlock_t *ptl;
pgste_t pgste; pgste_t pgste;
pte_t *ptep, pte;
ptep = get_locked_pte(mm, address, &ptl); /* Find the vm address for the guest address */
vmaddr = (unsigned long) radix_tree_lookup(&gmap->guest_to_host,
gaddr >> PMD_SHIFT);
if (!vmaddr)
return;
vmaddr |= gaddr & ~PMD_MASK;
/* Get pointer to the page table entry */
ptep = get_locked_pte(gmap->mm, vmaddr, &ptl);
if (unlikely(!ptep)) if (unlikely(!ptep))
return; return;
pte = *ptep; pte = *ptep;
...@@ -639,87 +651,34 @@ static void gmap_zap_unused(struct mm_struct *mm, unsigned long address) ...@@ -639,87 +651,34 @@ static void gmap_zap_unused(struct mm_struct *mm, unsigned long address)
ptev = pte_val(pte); ptev = pte_val(pte);
if (((pgstev & _PGSTE_GPS_USAGE_MASK) == _PGSTE_GPS_USAGE_UNUSED) || if (((pgstev & _PGSTE_GPS_USAGE_MASK) == _PGSTE_GPS_USAGE_UNUSED) ||
((pgstev & _PGSTE_GPS_ZERO) && (ptev & _PAGE_INVALID))) { ((pgstev & _PGSTE_GPS_ZERO) && (ptev & _PAGE_INVALID))) {
gmap_zap_swap_entry(pte_to_swp_entry(pte), mm); gmap_zap_swap_entry(pte_to_swp_entry(pte), gmap->mm);
pte_clear(mm, address, ptep); pte_clear(gmap->mm, vmaddr, ptep);
} }
pgste_set_unlock(ptep, pgste); pgste_set_unlock(ptep, pgste);
out_pte: out_pte:
pte_unmap_unlock(*ptep, ptl); pte_unmap_unlock(*ptep, ptl);
} }
/*
* this function is assumed to be called with mmap_sem held
*/
void __gmap_zap(unsigned long address, struct gmap *gmap)
{
unsigned long *table, *segment_ptr;
unsigned long segment, pgstev, ptev;
struct gmap_pgtable *mp;
struct page *page;
segment_ptr = gmap_table_walk(address, gmap);
if (IS_ERR(segment_ptr))
return;
segment = *segment_ptr;
if (segment & _SEGMENT_ENTRY_INVALID)
return;
page = pfn_to_page(segment >> PAGE_SHIFT);
mp = (struct gmap_pgtable *) page->index;
address = mp->vmaddr | (address & ~PMD_MASK);
/* Page table is present */
table = (unsigned long *)(segment & _SEGMENT_ENTRY_ORIGIN);
table = table + ((address >> 12) & 0xff);
pgstev = table[PTRS_PER_PTE];
ptev = table[0];
/* quick check, checked again with locks held */
if (((pgstev & _PGSTE_GPS_USAGE_MASK) == _PGSTE_GPS_USAGE_UNUSED) ||
((pgstev & _PGSTE_GPS_ZERO) && (ptev & _PAGE_INVALID)))
gmap_zap_unused(gmap->mm, address);
}
EXPORT_SYMBOL_GPL(__gmap_zap); EXPORT_SYMBOL_GPL(__gmap_zap);
void gmap_discard(unsigned long from, unsigned long to, struct gmap *gmap) void gmap_discard(struct gmap *gmap, unsigned long from, unsigned long to)
{ {
unsigned long gaddr, vmaddr, size;
unsigned long *table, address, size;
struct vm_area_struct *vma; struct vm_area_struct *vma;
struct gmap_pgtable *mp;
struct page *page;
down_read(&gmap->mm->mmap_sem); down_read(&gmap->mm->mmap_sem);
address = from; for (gaddr = from; gaddr < to;
while (address < to) { gaddr = (gaddr + PMD_SIZE) & PMD_MASK) {
/* Walk the gmap address space page table */ /* Find the vm address for the guest address */
table = gmap->table + ((address >> 53) & 0x7ff); vmaddr = (unsigned long)
if (unlikely(*table & _REGION_ENTRY_INVALID)) { radix_tree_lookup(&gmap->guest_to_host,
address = (address + PMD_SIZE) & PMD_MASK; gaddr >> PMD_SHIFT);
continue; if (!vmaddr)
}
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
table = table + ((address >> 42) & 0x7ff);
if (unlikely(*table & _REGION_ENTRY_INVALID)) {
address = (address + PMD_SIZE) & PMD_MASK;
continue; continue;
} vmaddr |= gaddr & ~PMD_MASK;
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); /* Find vma in the parent mm */
table = table + ((address >> 31) & 0x7ff); vma = find_vma(gmap->mm, vmaddr);
if (unlikely(*table & _REGION_ENTRY_INVALID)) { size = min(to - gaddr, PMD_SIZE - (gaddr & ~PMD_MASK));
address = (address + PMD_SIZE) & PMD_MASK; zap_page_range(vma, vmaddr, size, NULL);
continue;
}
table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
table = table + ((address >> 20) & 0x7ff);
if (unlikely(*table & _SEGMENT_ENTRY_INVALID)) {
address = (address + PMD_SIZE) & PMD_MASK;
continue;
}
page = pfn_to_page(*table >> PAGE_SHIFT);
mp = (struct gmap_pgtable *) page->index;
vma = find_vma(gmap->mm, mp->vmaddr);
size = min(to - address, PMD_SIZE - (address & ~PMD_MASK));
zap_page_range(vma, mp->vmaddr | (address & ~PMD_MASK),
size, NULL);
address = (address + PMD_SIZE) & PMD_MASK;
} }
up_read(&gmap->mm->mmap_sem); up_read(&gmap->mm->mmap_sem);
} }
...@@ -755,7 +714,7 @@ EXPORT_SYMBOL_GPL(gmap_unregister_ipte_notifier); ...@@ -755,7 +714,7 @@ EXPORT_SYMBOL_GPL(gmap_unregister_ipte_notifier);
/** /**
* gmap_ipte_notify - mark a range of ptes for invalidation notification * gmap_ipte_notify - mark a range of ptes for invalidation notification
* @gmap: pointer to guest mapping meta data structure * @gmap: pointer to guest mapping meta data structure
* @start: virtual address in the guest address space * @gaddr: virtual address in the guest address space
* @len: size of area * @len: size of area
* *
* Returns 0 if for each page in the given range a gmap mapping exists and * Returns 0 if for each page in the given range a gmap mapping exists and
...@@ -763,7 +722,7 @@ EXPORT_SYMBOL_GPL(gmap_unregister_ipte_notifier); ...@@ -763,7 +722,7 @@ EXPORT_SYMBOL_GPL(gmap_unregister_ipte_notifier);
* for one or more pages -EFAULT is returned. If no memory could be allocated * for one or more pages -EFAULT is returned. If no memory could be allocated
* -ENOMEM is returned. This function establishes missing page table entries. * -ENOMEM is returned. This function establishes missing page table entries.
*/ */
int gmap_ipte_notify(struct gmap *gmap, unsigned long start, unsigned long len) int gmap_ipte_notify(struct gmap *gmap, unsigned long gaddr, unsigned long len)
{ {
unsigned long addr; unsigned long addr;
spinlock_t *ptl; spinlock_t *ptl;
...@@ -771,12 +730,12 @@ int gmap_ipte_notify(struct gmap *gmap, unsigned long start, unsigned long len) ...@@ -771,12 +730,12 @@ int gmap_ipte_notify(struct gmap *gmap, unsigned long start, unsigned long len)
pgste_t pgste; pgste_t pgste;
int rc = 0; int rc = 0;
if ((start & ~PAGE_MASK) || (len & ~PAGE_MASK)) if ((gaddr & ~PAGE_MASK) || (len & ~PAGE_MASK))
return -EINVAL; return -EINVAL;
down_read(&gmap->mm->mmap_sem); down_read(&gmap->mm->mmap_sem);
while (len) { while (len) {
/* Convert gmap address and connect the page tables */ /* Convert gmap address and connect the page tables */
addr = __gmap_fault(start, gmap); addr = __gmap_translate(gmap, gaddr);
if (IS_ERR_VALUE(addr)) { if (IS_ERR_VALUE(addr)) {
rc = addr; rc = addr;
break; break;
...@@ -786,6 +745,9 @@ int gmap_ipte_notify(struct gmap *gmap, unsigned long start, unsigned long len) ...@@ -786,6 +745,9 @@ int gmap_ipte_notify(struct gmap *gmap, unsigned long start, unsigned long len)
rc = -EFAULT; rc = -EFAULT;
break; break;
} }
rc = __gmap_link(gmap, gaddr, addr);
if (rc)
break;
/* Walk the process page table, lock and get pte pointer */ /* Walk the process page table, lock and get pte pointer */
ptep = get_locked_pte(gmap->mm, addr, &ptl); ptep = get_locked_pte(gmap->mm, addr, &ptl);
if (unlikely(!ptep)) if (unlikely(!ptep))
...@@ -796,7 +758,7 @@ int gmap_ipte_notify(struct gmap *gmap, unsigned long start, unsigned long len) ...@@ -796,7 +758,7 @@ int gmap_ipte_notify(struct gmap *gmap, unsigned long start, unsigned long len)
pgste = pgste_get_lock(ptep); pgste = pgste_get_lock(ptep);
pgste_val(pgste) |= PGSTE_IN_BIT; pgste_val(pgste) |= PGSTE_IN_BIT;
pgste_set_unlock(ptep, pgste); pgste_set_unlock(ptep, pgste);
start += PAGE_SIZE; gaddr += PAGE_SIZE;
len -= PAGE_SIZE; len -= PAGE_SIZE;
} }
spin_unlock(ptl); spin_unlock(ptl);
...@@ -809,28 +771,30 @@ EXPORT_SYMBOL_GPL(gmap_ipte_notify); ...@@ -809,28 +771,30 @@ EXPORT_SYMBOL_GPL(gmap_ipte_notify);
/** /**
* gmap_do_ipte_notify - call all invalidation callbacks for a specific pte. * gmap_do_ipte_notify - call all invalidation callbacks for a specific pte.
* @mm: pointer to the process mm_struct * @mm: pointer to the process mm_struct
* @addr: virtual address in the process address space
* @pte: pointer to the page table entry * @pte: pointer to the page table entry
* *
* This function is assumed to be called with the page table lock held * This function is assumed to be called with the page table lock held
* for the pte to notify. * for the pte to notify.
*/ */
void gmap_do_ipte_notify(struct mm_struct *mm, pte_t *pte) void gmap_do_ipte_notify(struct mm_struct *mm, unsigned long vmaddr, pte_t *pte)
{ {
unsigned long segment_offset; unsigned long offset, gaddr;
unsigned long *table;
struct gmap_notifier *nb; struct gmap_notifier *nb;
struct gmap_pgtable *mp; struct gmap *gmap;
struct gmap_rmap *rmap;
struct page *page;
segment_offset = ((unsigned long) pte) & (255 * sizeof(pte_t)); offset = ((unsigned long) pte) & (255 * sizeof(pte_t));
segment_offset = segment_offset * (4096 / sizeof(pte_t)); offset = offset * (4096 / sizeof(pte_t));
page = pfn_to_page(__pa(pte) >> PAGE_SHIFT);
mp = (struct gmap_pgtable *) page->index;
spin_lock(&gmap_notifier_lock); spin_lock(&gmap_notifier_lock);
list_for_each_entry(rmap, &mp->mapper, list) { list_for_each_entry(gmap, &mm->context.gmap_list, list) {
table = radix_tree_lookup(&gmap->host_to_guest,
vmaddr >> PMD_SHIFT);
if (!table)
continue;
gaddr = __gmap_segment_gaddr(table) + offset;
list_for_each_entry(nb, &gmap_notifier_list, list) list_for_each_entry(nb, &gmap_notifier_list, list)
nb->notifier_call(rmap->gmap, nb->notifier_call(gmap, gaddr);
rmap->vmaddr + segment_offset);
} }
spin_unlock(&gmap_notifier_lock); spin_unlock(&gmap_notifier_lock);
} }
...@@ -841,29 +805,18 @@ static inline int page_table_with_pgste(struct page *page) ...@@ -841,29 +805,18 @@ static inline int page_table_with_pgste(struct page *page)
return atomic_read(&page->_mapcount) == 0; return atomic_read(&page->_mapcount) == 0;
} }
static inline unsigned long *page_table_alloc_pgste(struct mm_struct *mm, static inline unsigned long *page_table_alloc_pgste(struct mm_struct *mm)
unsigned long vmaddr)
{ {
struct page *page; struct page *page;
unsigned long *table; unsigned long *table;
struct gmap_pgtable *mp;
page = alloc_page(GFP_KERNEL|__GFP_REPEAT); page = alloc_page(GFP_KERNEL|__GFP_REPEAT);
if (!page) if (!page)
return NULL; return NULL;
mp = kmalloc(sizeof(*mp), GFP_KERNEL|__GFP_REPEAT);
if (!mp) {
__free_page(page);
return NULL;
}
if (!pgtable_page_ctor(page)) { if (!pgtable_page_ctor(page)) {
kfree(mp);
__free_page(page); __free_page(page);
return NULL; return NULL;
} }
mp->vmaddr = vmaddr & PMD_MASK;
INIT_LIST_HEAD(&mp->mapper);
page->index = (unsigned long) mp;
atomic_set(&page->_mapcount, 0); atomic_set(&page->_mapcount, 0);
table = (unsigned long *) page_to_phys(page); table = (unsigned long *) page_to_phys(page);
clear_table(table, _PAGE_INVALID, PAGE_SIZE/2); clear_table(table, _PAGE_INVALID, PAGE_SIZE/2);
...@@ -874,14 +827,10 @@ static inline unsigned long *page_table_alloc_pgste(struct mm_struct *mm, ...@@ -874,14 +827,10 @@ static inline unsigned long *page_table_alloc_pgste(struct mm_struct *mm,
static inline void page_table_free_pgste(unsigned long *table) static inline void page_table_free_pgste(unsigned long *table)
{ {
struct page *page; struct page *page;
struct gmap_pgtable *mp;
page = pfn_to_page(__pa(table) >> PAGE_SHIFT); page = pfn_to_page(__pa(table) >> PAGE_SHIFT);
mp = (struct gmap_pgtable *) page->index;
BUG_ON(!list_empty(&mp->mapper));
pgtable_page_dtor(page); pgtable_page_dtor(page);
atomic_set(&page->_mapcount, -1); atomic_set(&page->_mapcount, -1);
kfree(mp);
__free_page(page); __free_page(page);
} }
...@@ -1038,8 +987,7 @@ static inline int page_table_with_pgste(struct page *page) ...@@ -1038,8 +987,7 @@ static inline int page_table_with_pgste(struct page *page)
return 0; return 0;
} }
static inline unsigned long *page_table_alloc_pgste(struct mm_struct *mm, static inline unsigned long *page_table_alloc_pgste(struct mm_struct *mm)
unsigned long vmaddr)
{ {
return NULL; return NULL;
} }
...@@ -1053,8 +1001,8 @@ static inline void page_table_free_pgste(unsigned long *table) ...@@ -1053,8 +1001,8 @@ static inline void page_table_free_pgste(unsigned long *table)
{ {
} }
static inline void gmap_disconnect_pgtable(struct mm_struct *mm, static inline void gmap_unlink(struct mm_struct *mm, unsigned long *table,
unsigned long *table) unsigned long vmaddr)
{ {
} }
...@@ -1074,14 +1022,14 @@ static inline unsigned int atomic_xor_bits(atomic_t *v, unsigned int bits) ...@@ -1074,14 +1022,14 @@ static inline unsigned int atomic_xor_bits(atomic_t *v, unsigned int bits)
/* /*
* page table entry allocation/free routines. * page table entry allocation/free routines.
*/ */
unsigned long *page_table_alloc(struct mm_struct *mm, unsigned long vmaddr) unsigned long *page_table_alloc(struct mm_struct *mm)
{ {
unsigned long *uninitialized_var(table); unsigned long *uninitialized_var(table);
struct page *uninitialized_var(page); struct page *uninitialized_var(page);
unsigned int mask, bit; unsigned int mask, bit;
if (mm_has_pgste(mm)) if (mm_has_pgste(mm))
return page_table_alloc_pgste(mm, vmaddr); return page_table_alloc_pgste(mm);
/* Allocate fragments of a 4K page as 1K/2K page table */ /* Allocate fragments of a 4K page as 1K/2K page table */
spin_lock_bh(&mm->context.list_lock); spin_lock_bh(&mm->context.list_lock);
mask = FRAG_MASK; mask = FRAG_MASK;
...@@ -1123,10 +1071,8 @@ void page_table_free(struct mm_struct *mm, unsigned long *table) ...@@ -1123,10 +1071,8 @@ void page_table_free(struct mm_struct *mm, unsigned long *table)
unsigned int bit, mask; unsigned int bit, mask;
page = pfn_to_page(__pa(table) >> PAGE_SHIFT); page = pfn_to_page(__pa(table) >> PAGE_SHIFT);
if (page_table_with_pgste(page)) { if (page_table_with_pgste(page))
gmap_disconnect_pgtable(mm, table);
return page_table_free_pgste(table); return page_table_free_pgste(table);
}
/* Free 1K/2K page table fragment of a 4K page */ /* Free 1K/2K page table fragment of a 4K page */
bit = 1 << ((__pa(table) & ~PAGE_MASK)/(PTRS_PER_PTE*sizeof(pte_t))); bit = 1 << ((__pa(table) & ~PAGE_MASK)/(PTRS_PER_PTE*sizeof(pte_t)));
spin_lock_bh(&mm->context.list_lock); spin_lock_bh(&mm->context.list_lock);
...@@ -1158,7 +1104,8 @@ static void __page_table_free_rcu(void *table, unsigned bit) ...@@ -1158,7 +1104,8 @@ static void __page_table_free_rcu(void *table, unsigned bit)
} }
} }
void page_table_free_rcu(struct mmu_gather *tlb, unsigned long *table) void page_table_free_rcu(struct mmu_gather *tlb, unsigned long *table,
unsigned long vmaddr)
{ {
struct mm_struct *mm; struct mm_struct *mm;
struct page *page; struct page *page;
...@@ -1167,7 +1114,7 @@ void page_table_free_rcu(struct mmu_gather *tlb, unsigned long *table) ...@@ -1167,7 +1114,7 @@ void page_table_free_rcu(struct mmu_gather *tlb, unsigned long *table)
mm = tlb->mm; mm = tlb->mm;
page = pfn_to_page(__pa(table) >> PAGE_SHIFT); page = pfn_to_page(__pa(table) >> PAGE_SHIFT);
if (page_table_with_pgste(page)) { if (page_table_with_pgste(page)) {
gmap_disconnect_pgtable(mm, table); gmap_unlink(mm, table, vmaddr);
table = (unsigned long *) (__pa(table) | FRAG_MASK); table = (unsigned long *) (__pa(table) | FRAG_MASK);
tlb_remove_table(tlb, table); tlb_remove_table(tlb, table);
return; return;
...@@ -1303,7 +1250,7 @@ static unsigned long page_table_realloc_pmd(struct mmu_gather *tlb, ...@@ -1303,7 +1250,7 @@ static unsigned long page_table_realloc_pmd(struct mmu_gather *tlb,
if (page_table_with_pgste(page)) if (page_table_with_pgste(page))
continue; continue;
/* Allocate new page table with pgstes */ /* Allocate new page table with pgstes */
new = page_table_alloc_pgste(mm, addr); new = page_table_alloc_pgste(mm);
if (!new) if (!new)
return -ENOMEM; return -ENOMEM;
...@@ -1318,7 +1265,7 @@ static unsigned long page_table_realloc_pmd(struct mmu_gather *tlb, ...@@ -1318,7 +1265,7 @@ static unsigned long page_table_realloc_pmd(struct mmu_gather *tlb,
/* Establish new table */ /* Establish new table */
pmd_populate(mm, pmd, (pte_t *) new); pmd_populate(mm, pmd, (pte_t *) new);
/* Free old table with rcu, there might be a walker! */ /* Free old table with rcu, there might be a walker! */
page_table_free_rcu(tlb, table); page_table_free_rcu(tlb, table, addr);
new = NULL; new = NULL;
} }
spin_unlock(ptl); spin_unlock(ptl);
......
...@@ -65,7 +65,7 @@ static pte_t __ref *vmem_pte_alloc(unsigned long address) ...@@ -65,7 +65,7 @@ static pte_t __ref *vmem_pte_alloc(unsigned long address)
pte_t *pte; pte_t *pte;
if (slab_is_available()) if (slab_is_available())
pte = (pte_t *) page_table_alloc(&init_mm, address); pte = (pte_t *) page_table_alloc(&init_mm);
else else
pte = alloc_bootmem_align(PTRS_PER_PTE * sizeof(pte_t), pte = alloc_bootmem_align(PTRS_PER_PTE * sizeof(pte_t),
PTRS_PER_PTE * sizeof(pte_t)); PTRS_PER_PTE * sizeof(pte_t));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment