summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/s390/include/asm/kvm_host.h5
-rw-r--r--arch/s390/kvm/interrupt.c227
-rw-r--r--arch/s390/kvm/kvm-s390.c3
-rw-r--r--arch/s390/kvm/kvm-s390.h2
4 files changed, 194 insertions, 43 deletions
diff --git a/arch/s390/include/asm/kvm_host.h b/arch/s390/include/asm/kvm_host.h
index aa4c4685f95c..4ff1ede90873 100644
--- a/arch/s390/include/asm/kvm_host.h
+++ b/arch/s390/include/asm/kvm_host.h
@@ -448,6 +448,8 @@ struct kvm_vcpu_arch {
struct kvm_vm_stat {
struct kvm_vm_stat_generic generic;
u64 inject_io;
+ u64 io_390_adapter_map;
+ u64 io_390_adapter_unmap;
u64 inject_float_mchk;
u64 inject_pfault_done;
u64 inject_service_signal;
@@ -479,6 +481,9 @@ struct s390_io_adapter {
bool masked;
bool swap;
bool suppressible;
+ spinlock_t maps_lock;
+ struct list_head maps;
+ unsigned int nr_maps;
};
#define MAX_S390_IO_ADAPTERS ((MAX_ISC + 1) * 8)
diff --git a/arch/s390/kvm/interrupt.c b/arch/s390/kvm/interrupt.c
index 07f59c3b9a7b..d11dea4cb0d3 100644
--- a/arch/s390/kvm/interrupt.c
+++ b/arch/s390/kvm/interrupt.c
@@ -2411,34 +2411,46 @@ static int register_io_adapter(struct kvm_device *dev,
{
struct s390_io_adapter *adapter;
struct kvm_s390_io_adapter adapter_info;
+ int rc = 0;
+ mutex_lock(&dev->kvm->lock);
if (copy_from_user(&adapter_info,
- (void __user *)attr->addr, sizeof(adapter_info)))
- return -EFAULT;
-
- if (adapter_info.id >= MAX_S390_IO_ADAPTERS)
- return -EINVAL;
-
+ (void __user *)attr->addr, sizeof(adapter_info))) {
+ rc = -EFAULT;
+ goto out;
+ }
+ if (adapter_info.id >= MAX_S390_IO_ADAPTERS) {
+ rc = -EINVAL;
+ goto out;
+ }
adapter_info.id = array_index_nospec(adapter_info.id,
MAX_S390_IO_ADAPTERS);
- if (dev->kvm->arch.adapters[adapter_info.id] != NULL)
- return -EINVAL;
-
+ if (dev->kvm->arch.adapters[adapter_info.id] != NULL) {
+ rc = -EINVAL;
+ goto out;
+ }
adapter = kzalloc_obj(*adapter, GFP_KERNEL_ACCOUNT);
- if (!adapter)
- return -ENOMEM;
+ if (!adapter) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ INIT_LIST_HEAD(&adapter->maps);
+ spin_lock_init(&adapter->maps_lock);
+ adapter->nr_maps = 0;
adapter->id = adapter_info.id;
adapter->isc = adapter_info.isc;
adapter->maskable = adapter_info.maskable;
adapter->masked = false;
adapter->swap = adapter_info.swap;
- adapter->suppressible = (adapter_info.flags) &
+ adapter->suppressible = adapter_info.flags &
KVM_S390_ADAPTER_SUPPRESSIBLE;
dev->kvm->arch.adapters[adapter->id] = adapter;
- return 0;
+out:
+ mutex_unlock(&dev->kvm->lock);
+ return rc;
}
int kvm_s390_mask_adapter(struct kvm *kvm, unsigned int id, bool masked)
@@ -2453,12 +2465,151 @@ int kvm_s390_mask_adapter(struct kvm *kvm, unsigned int id, bool masked)
return ret;
}
+static struct page *pin_map_page(struct kvm *kvm, u64 uaddr,
+ unsigned int gup_flags)
+{
+ struct mm_struct *mm = kvm->mm;
+ struct page *page = NULL;
+ int locked = 1;
+
+ if (mmget_not_zero(mm)) {
+ mmap_read_lock(mm);
+ pin_user_pages_remote(mm, uaddr, 1, FOLL_WRITE | gup_flags,
+ &page, &locked);
+ if (locked)
+ mmap_read_unlock(mm);
+ mmput(mm);
+ }
+
+ return page;
+}
+
+static int kvm_s390_adapter_map(struct kvm *kvm, unsigned int id, __u64 addr)
+{
+ struct s390_io_adapter *adapter = get_io_adapter(kvm, id);
+ struct s390_map_info *map;
+ unsigned long flags;
+ __u64 host_addr;
+ int ret, idx;
+
+ if (!adapter || !addr)
+ return -EINVAL;
+
+ map = kzalloc_obj(*map, GFP_KERNEL_ACCOUNT);
+ if (!map)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&map->list);
+ idx = srcu_read_lock(&kvm->srcu);
+ host_addr = gpa_to_hva(kvm, addr);
+ if (kvm_is_error_hva(host_addr)) {
+ srcu_read_unlock(&kvm->srcu, idx);
+ ret = -EFAULT;
+ goto out;
+ }
+ srcu_read_unlock(&kvm->srcu, idx);
+ map->guest_addr = addr;
+ map->addr = host_addr;
+ map->page = pin_map_page(kvm, host_addr, FOLL_LONGTERM);
+ if (!map->page) {
+ ret = -EINVAL;
+ goto out;
+ }
+ spin_lock_irqsave(&adapter->maps_lock, flags);
+ if (adapter->nr_maps < MAX_S390_ADAPTER_MAPS) {
+ list_add_tail(&map->list, &adapter->maps);
+ adapter->nr_maps++;
+ ret = 0;
+ } else {
+ ret = -EINVAL;
+ }
+ spin_unlock_irqrestore(&adapter->maps_lock, flags);
+ if (ret)
+ unpin_user_page(map->page);
+out:
+ if (ret)
+ kfree(map);
+ return ret;
+}
+
+static int kvm_s390_adapter_unmap(struct kvm *kvm, unsigned int id, __u64 addr)
+{
+ struct s390_io_adapter *adapter = get_io_adapter(kvm, id);
+ struct s390_map_info *map, *tmp, *map_to_free;
+ struct page *map_page_to_put = NULL;
+ u64 map_addr_to_mark = 0;
+ unsigned long flags;
+ int found = 0, idx;
+
+ if (!adapter || !addr)
+ return -EINVAL;
+
+ spin_lock_irqsave(&adapter->maps_lock, flags);
+ list_for_each_entry_safe(map, tmp, &adapter->maps, list) {
+ if (map->guest_addr == addr) {
+ found = 1;
+ adapter->nr_maps--;
+ list_del(&map->list);
+ map_page_to_put = map->page;
+ map_addr_to_mark = map->guest_addr;
+ map_to_free = map;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&adapter->maps_lock, flags);
+
+ if (found) {
+ kfree(map_to_free);
+ idx = srcu_read_lock(&kvm->srcu);
+ mark_page_dirty(kvm, map_addr_to_mark >> PAGE_SHIFT);
+ set_page_dirty_lock(map_page_to_put);
+ srcu_read_unlock(&kvm->srcu, idx);
+ unpin_user_page(map_page_to_put);
+ }
+
+ return found ? 0 : -ENOENT;
+}
+
+void kvm_s390_unmap_all_adapters(struct kvm *kvm)
+{
+ struct s390_map_info *map, *tmp;
+ unsigned long flags;
+ int i, idx;
+
+ for (i = 0; i < MAX_S390_IO_ADAPTERS; i++) {
+ struct s390_io_adapter *adapter = kvm->arch.adapters[i];
+ LIST_HEAD(local_list);
+
+ if (!adapter)
+ continue;
+
+ spin_lock_irqsave(&adapter->maps_lock, flags);
+ list_splice_init(&adapter->maps, &local_list);
+ adapter->nr_maps = 0;
+ spin_unlock_irqrestore(&adapter->maps_lock, flags);
+
+ list_for_each_entry_safe(map, tmp, &local_list, list) {
+ list_del(&map->list);
+ idx = srcu_read_lock(&kvm->srcu);
+ mark_page_dirty(kvm, map->guest_addr >> PAGE_SHIFT);
+ set_page_dirty_lock(map->page);
+ srcu_read_unlock(&kvm->srcu, idx);
+ unpin_user_page(map->page);
+ kfree(map);
+ }
+ }
+}
+
void kvm_s390_destroy_adapters(struct kvm *kvm)
{
int i;
- for (i = 0; i < MAX_S390_IO_ADAPTERS; i++)
+ kvm_s390_unmap_all_adapters(kvm);
+
+ for (i = 0; i < MAX_S390_IO_ADAPTERS; i++) {
kfree(kvm->arch.adapters[i]);
+ kvm->arch.adapters[i] = NULL;
+ }
}
static int modify_io_adapter(struct kvm_device *dev,
@@ -2480,14 +2631,22 @@ static int modify_io_adapter(struct kvm_device *dev,
if (ret > 0)
ret = 0;
break;
- /*
- * The following operations are no longer needed and therefore no-ops.
- * The gpa to hva translation is done when an IRQ route is set up. The
- * set_irq code uses get_user_pages_remote() to do the actual write.
- */
case KVM_S390_IO_ADAPTER_MAP:
case KVM_S390_IO_ADAPTER_UNMAP:
- ret = 0;
+ /* If in Secure Execution mode do not long term pin. */
+ mutex_lock(&dev->kvm->lock);
+ if (kvm_s390_pv_is_protected(dev->kvm)) {
+ mutex_unlock(&dev->kvm->lock);
+ return 0;
+ }
+ if (req.type == KVM_S390_IO_ADAPTER_MAP) {
+ dev->kvm->stat.io_390_adapter_map++;
+ ret = kvm_s390_adapter_map(dev->kvm, req.id, req.addr);
+ } else {
+ dev->kvm->stat.io_390_adapter_unmap++;
+ ret = kvm_s390_adapter_unmap(dev->kvm, req.id, req.addr);
+ }
+ mutex_unlock(&dev->kvm->lock);
break;
default:
ret = -EINVAL;
@@ -2733,24 +2892,6 @@ static unsigned long get_ind_bit(__u64 addr, unsigned long bit_nr, bool swap)
return swap ? (bit ^ (BITS_PER_LONG - 1)) : bit;
}
-static struct page *get_map_page(struct kvm *kvm, u64 uaddr)
-{
- struct mm_struct *mm = kvm->mm;
- struct page *page = NULL;
- int locked = 1;
-
- if (mmget_not_zero(mm)) {
- mmap_read_lock(mm);
- get_user_pages_remote(mm, uaddr, 1, FOLL_WRITE,
- &page, &locked);
- if (locked)
- mmap_read_unlock(mm);
- mmput(mm);
- }
-
- return page;
-}
-
static int adapter_indicators_set(struct kvm *kvm,
struct s390_io_adapter *adapter,
struct kvm_s390_adapter_int *adapter_int)
@@ -2760,12 +2901,12 @@ static int adapter_indicators_set(struct kvm *kvm,
struct page *ind_page, *summary_page;
void *map;
- ind_page = get_map_page(kvm, adapter_int->ind_addr);
+ ind_page = pin_map_page(kvm, adapter_int->ind_addr, 0);
if (!ind_page)
return -1;
- summary_page = get_map_page(kvm, adapter_int->summary_addr);
+ summary_page = pin_map_page(kvm, adapter_int->summary_addr, 0);
if (!summary_page) {
- put_page(ind_page);
+ unpin_user_page(ind_page);
return -1;
}
@@ -2784,8 +2925,8 @@ static int adapter_indicators_set(struct kvm *kvm,
set_page_dirty_lock(summary_page);
srcu_read_unlock(&kvm->srcu, idx);
- put_page(ind_page);
- put_page(summary_page);
+ unpin_user_page(ind_page);
+ unpin_user_page(summary_page);
return summary_set ? 0 : 1;
}
diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
index de28ee1f7882..9de6edeb5ae0 100644
--- a/arch/s390/kvm/kvm-s390.c
+++ b/arch/s390/kvm/kvm-s390.c
@@ -68,6 +68,8 @@
const struct kvm_stats_desc kvm_vm_stats_desc[] = {
KVM_GENERIC_VM_STATS(),
STATS_DESC_COUNTER(VM, inject_io),
+ STATS_DESC_COUNTER(VM, io_390_adapter_map),
+ STATS_DESC_COUNTER(VM, io_390_adapter_unmap),
STATS_DESC_COUNTER(VM, inject_float_mchk),
STATS_DESC_COUNTER(VM, inject_pfault_done),
STATS_DESC_COUNTER(VM, inject_service_signal),
@@ -2539,6 +2541,7 @@ static int kvm_s390_handle_pv(struct kvm *kvm, struct kvm_pv_cmd *cmd)
if (kvm_s390_pv_is_protected(kvm))
break;
+ kvm_s390_unmap_all_adapters(kvm);
mmap_write_lock(kvm->mm);
/*
* Disable creation of new THPs. Existing THPs can stay, they
diff --git a/arch/s390/kvm/kvm-s390.h b/arch/s390/kvm/kvm-s390.h
index dc0573b7aa4b..7ba885cb6bd1 100644
--- a/arch/s390/kvm/kvm-s390.h
+++ b/arch/s390/kvm/kvm-s390.h
@@ -560,6 +560,8 @@ void kvm_s390_gisa_disable(struct kvm *kvm);
void kvm_s390_gisa_enable(struct kvm *kvm);
int __init kvm_s390_gib_init(u8 nisc);
void kvm_s390_gib_destroy(void);
+void kvm_s390_unmap_all_adapters(struct kvm *kvm);
+
/* implemented in guestdbg.c */
void kvm_s390_backup_guest_per_regs(struct kvm_vcpu *vcpu);