KVM: s390: Fix and cleanup

- fix sigp sense/start/stop/inconsistency
 - cleanups
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+SKTgaM0CPnbq/vKEXu8gLWmHHwFAmHAaOoACgkQEXu8gLWm
 HHzRrQ/6A+Q2KDk5GJ5ISE3fhNn6Cur33muo+YqeGvTIiJXqJcM8Blk/iFZKBslm
 D61C8XMZnR3Svbfct7k80bx2WYMjji+gTBksgb9EbEtzFaQLfF9F/aYYcvIKpFoA
 0D9KpE6oeKLpoMgWsRBJb7uq8AKO4sBZR0juLuHAIzIzAZPC0cALuUP8R1MH3qmG
 7kR8rke8+KRH4NQYSX16IB+9pZNZzyt+HqNUY23plv06bMkX0lp+zaJCQO8wn6Bb
 n4iWp7uJTQWEOPoKVk6FLIMC5xQFNWR0LDxMR4ucNTRxc4do6R/AS9MtyC9UDtDx
 weAu4z37vfPaElHO1+51QJ1hoYa0u8kcIeiug+GkkYK3TdrkMyJMF4JERnoV/WqR
 6XxtEdkPl/HDVU+azjK64jGORj8WQkYhpuW/dvxeu7GLw0m9OvnCcbk9pSUAgiyz
 b3U1uEKRBlwlejmFv6+d470l2BPjdi3OKQFCsOMD7XXwnm4NrDYqTAXVeHP/KN4B
 0+oAoDc1EQN8lUhRu+G9YrpUklnwx9bsmhfNAWbX6wy8rShwXn6hOK9CreqpkEc1
 YaRJ1b/UbKV64faMGzZU2AyJ7T4z21g0tK1ZOUNlKqd5WTjrGitN2ogVebuk1I4V
 2L61tZeLs7Tn7iHM7UG5c+xYNP9Go3ikK2IAgGtFdsKwiFf3EuQ=
 =+F7q
 -----END PGP SIGNATURE-----

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

KVM: s390: Fix and cleanup

- fix sigp sense/start/stop/inconsistency
- cleanups
This commit is contained in:
Paolo Bonzini 2021-12-21 12:59:53 -05:00
commit 5e4e84f112
7 changed files with 155 additions and 88 deletions

View file

@ -3701,7 +3701,7 @@ KVM with the currently defined set of flags.
:Architectures: s390 :Architectures: s390
:Type: vm ioctl :Type: vm ioctl
:Parameters: struct kvm_s390_skeys :Parameters: struct kvm_s390_skeys
:Returns: 0 on success, KVM_S390_GET_KEYS_NONE if guest is not using storage :Returns: 0 on success, KVM_S390_GET_SKEYS_NONE if guest is not using storage
keys, negative value on error keys, negative value on error
This ioctl is used to get guest storage key values on the s390 This ioctl is used to get guest storage key values on the s390
@ -3720,7 +3720,7 @@ you want to get.
The count field is the number of consecutive frames (starting from start_gfn) The count field is the number of consecutive frames (starting from start_gfn)
whose storage keys to get. The count field must be at least 1 and the maximum whose storage keys to get. The count field must be at least 1 and the maximum
allowed value is defined as KVM_S390_SKEYS_ALLOC_MAX. Values outside this range allowed value is defined as KVM_S390_SKEYS_MAX. Values outside this range
will cause the ioctl to return -EINVAL. will cause the ioctl to return -EINVAL.
The skeydata_addr field is the address to a buffer large enough to hold count The skeydata_addr field is the address to a buffer large enough to hold count
@ -3744,7 +3744,7 @@ you want to set.
The count field is the number of consecutive frames (starting from start_gfn) The count field is the number of consecutive frames (starting from start_gfn)
whose storage keys to get. The count field must be at least 1 and the maximum whose storage keys to get. The count field must be at least 1 and the maximum
allowed value is defined as KVM_S390_SKEYS_ALLOC_MAX. Values outside this range allowed value is defined as KVM_S390_SKEYS_MAX. Values outside this range
will cause the ioctl to return -EINVAL. will cause the ioctl to return -EINVAL.
The skeydata_addr field is the address to a buffer containing count bytes of The skeydata_addr field is the address to a buffer containing count bytes of

View file

@ -91,23 +91,23 @@ struct uv_cb_header {
/* Query Ultravisor Information */ /* Query Ultravisor Information */
struct uv_cb_qui { struct uv_cb_qui {
struct uv_cb_header header; struct uv_cb_header header; /* 0x0000 */
u64 reserved08; u64 reserved08; /* 0x0008 */
u64 inst_calls_list[4]; u64 inst_calls_list[4]; /* 0x0010 */
u64 reserved30[2]; u64 reserved30[2]; /* 0x0030 */
u64 uv_base_stor_len; u64 uv_base_stor_len; /* 0x0040 */
u64 reserved48; u64 reserved48; /* 0x0048 */
u64 conf_base_phys_stor_len; u64 conf_base_phys_stor_len; /* 0x0050 */
u64 conf_base_virt_stor_len; u64 conf_base_virt_stor_len; /* 0x0058 */
u64 conf_virt_var_stor_len; u64 conf_virt_var_stor_len; /* 0x0060 */
u64 cpu_stor_len; u64 cpu_stor_len; /* 0x0068 */
u32 reserved70[3]; u32 reserved70[3]; /* 0x0070 */
u32 max_num_sec_conf; u32 max_num_sec_conf; /* 0x007c */
u64 max_guest_stor_addr; u64 max_guest_stor_addr; /* 0x0080 */
u8 reserved88[158 - 136]; u8 reserved88[158 - 136]; /* 0x0088 */
u16 max_guest_cpu_id; u16 max_guest_cpu_id; /* 0x009e */
u64 uv_feature_indications; u64 uv_feature_indications; /* 0x00a0 */
u8 reserveda0[200 - 168]; u8 reserveda8[200 - 168]; /* 0x00a8 */
} __packed __aligned(8); } __packed __aligned(8);
/* Initialize Ultravisor */ /* Initialize Ultravisor */

View file

@ -794,46 +794,100 @@ static int low_address_protection_enabled(struct kvm_vcpu *vcpu,
return 1; return 1;
} }
static int guest_page_range(struct kvm_vcpu *vcpu, unsigned long ga, u8 ar, /**
unsigned long *pages, unsigned long nr_pages, * guest_range_to_gpas() - Calculate guest physical addresses of page fragments
const union asce asce, enum gacc_mode mode) * covering a logical range
* @vcpu: virtual cpu
* @ga: guest address, start of range
* @ar: access register
* @gpas: output argument, may be NULL
* @len: length of range in bytes
* @asce: address-space-control element to use for translation
* @mode: access mode
*
* Translate a logical range to a series of guest absolute addresses,
* such that the concatenation of page fragments starting at each gpa make up
* the whole range.
* The translation is performed as if done by the cpu for the given @asce, @ar,
* @mode and state of the @vcpu.
* If the translation causes an exception, its program interruption code is
* returned and the &struct kvm_s390_pgm_info pgm member of @vcpu is modified
* such that a subsequent call to kvm_s390_inject_prog_vcpu() will inject
* a correct exception into the guest.
* The resulting gpas are stored into @gpas, unless it is NULL.
*
* Note: All fragments except the first one start at the beginning of a page.
* When deriving the boundaries of a fragment from a gpa, all but the last
* fragment end at the end of the page.
*
* Return:
* * 0 - success
* * <0 - translation could not be performed, for example if guest
* memory could not be accessed
* * >0 - an access exception occurred. In this case the returned value
* is the program interruption code and the contents of pgm may
* be used to inject an exception into the guest.
*/
static int guest_range_to_gpas(struct kvm_vcpu *vcpu, unsigned long ga, u8 ar,
unsigned long *gpas, unsigned long len,
const union asce asce, enum gacc_mode mode)
{ {
psw_t *psw = &vcpu->arch.sie_block->gpsw; psw_t *psw = &vcpu->arch.sie_block->gpsw;
unsigned int offset = offset_in_page(ga);
unsigned int fragment_len;
int lap_enabled, rc = 0; int lap_enabled, rc = 0;
enum prot_type prot; enum prot_type prot;
unsigned long gpa;
lap_enabled = low_address_protection_enabled(vcpu, asce); lap_enabled = low_address_protection_enabled(vcpu, asce);
while (nr_pages) { while (min(PAGE_SIZE - offset, len) > 0) {
fragment_len = min(PAGE_SIZE - offset, len);
ga = kvm_s390_logical_to_effective(vcpu, ga); ga = kvm_s390_logical_to_effective(vcpu, ga);
if (mode == GACC_STORE && lap_enabled && is_low_address(ga)) if (mode == GACC_STORE && lap_enabled && is_low_address(ga))
return trans_exc(vcpu, PGM_PROTECTION, ga, ar, mode, return trans_exc(vcpu, PGM_PROTECTION, ga, ar, mode,
PROT_TYPE_LA); PROT_TYPE_LA);
ga &= PAGE_MASK;
if (psw_bits(*psw).dat) { if (psw_bits(*psw).dat) {
rc = guest_translate(vcpu, ga, pages, asce, mode, &prot); rc = guest_translate(vcpu, ga, &gpa, asce, mode, &prot);
if (rc < 0) if (rc < 0)
return rc; return rc;
} else { } else {
*pages = kvm_s390_real_to_abs(vcpu, ga); gpa = kvm_s390_real_to_abs(vcpu, ga);
if (kvm_is_error_gpa(vcpu->kvm, *pages)) if (kvm_is_error_gpa(vcpu->kvm, gpa))
rc = PGM_ADDRESSING; rc = PGM_ADDRESSING;
} }
if (rc) if (rc)
return trans_exc(vcpu, rc, ga, ar, mode, prot); return trans_exc(vcpu, rc, ga, ar, mode, prot);
ga += PAGE_SIZE; if (gpas)
pages++; *gpas++ = gpa;
nr_pages--; offset = 0;
ga += fragment_len;
len -= fragment_len;
} }
return 0; return 0;
} }
static int access_guest_page(struct kvm *kvm, enum gacc_mode mode, gpa_t gpa,
void *data, unsigned int len)
{
const unsigned int offset = offset_in_page(gpa);
const gfn_t gfn = gpa_to_gfn(gpa);
int rc;
if (mode == GACC_STORE)
rc = kvm_write_guest_page(kvm, gfn, data, offset, len);
else
rc = kvm_read_guest_page(kvm, gfn, data, offset, len);
return rc;
}
int access_guest(struct kvm_vcpu *vcpu, unsigned long ga, u8 ar, void *data, int access_guest(struct kvm_vcpu *vcpu, unsigned long ga, u8 ar, void *data,
unsigned long len, enum gacc_mode mode) unsigned long len, enum gacc_mode mode)
{ {
psw_t *psw = &vcpu->arch.sie_block->gpsw; psw_t *psw = &vcpu->arch.sie_block->gpsw;
unsigned long _len, nr_pages, gpa, idx; unsigned long nr_pages, idx;
unsigned long pages_array[2]; unsigned long gpa_array[2];
unsigned long *pages; unsigned int fragment_len;
unsigned long *gpas;
int need_ipte_lock; int need_ipte_lock;
union asce asce; union asce asce;
int rc; int rc;
@ -845,49 +899,42 @@ int access_guest(struct kvm_vcpu *vcpu, unsigned long ga, u8 ar, void *data,
if (rc) if (rc)
return rc; return rc;
nr_pages = (((ga & ~PAGE_MASK) + len - 1) >> PAGE_SHIFT) + 1; nr_pages = (((ga & ~PAGE_MASK) + len - 1) >> PAGE_SHIFT) + 1;
pages = pages_array; gpas = gpa_array;
if (nr_pages > ARRAY_SIZE(pages_array)) if (nr_pages > ARRAY_SIZE(gpa_array))
pages = vmalloc(array_size(nr_pages, sizeof(unsigned long))); gpas = vmalloc(array_size(nr_pages, sizeof(unsigned long)));
if (!pages) if (!gpas)
return -ENOMEM; return -ENOMEM;
need_ipte_lock = psw_bits(*psw).dat && !asce.r; need_ipte_lock = psw_bits(*psw).dat && !asce.r;
if (need_ipte_lock) if (need_ipte_lock)
ipte_lock(vcpu); ipte_lock(vcpu);
rc = guest_page_range(vcpu, ga, ar, pages, nr_pages, asce, mode); rc = guest_range_to_gpas(vcpu, ga, ar, gpas, len, asce, mode);
for (idx = 0; idx < nr_pages && !rc; idx++) { for (idx = 0; idx < nr_pages && !rc; idx++) {
gpa = *(pages + idx) + (ga & ~PAGE_MASK); fragment_len = min(PAGE_SIZE - offset_in_page(gpas[idx]), len);
_len = min(PAGE_SIZE - (gpa & ~PAGE_MASK), len); rc = access_guest_page(vcpu->kvm, mode, gpas[idx], data, fragment_len);
if (mode == GACC_STORE) len -= fragment_len;
rc = kvm_write_guest(vcpu->kvm, gpa, data, _len); data += fragment_len;
else
rc = kvm_read_guest(vcpu->kvm, gpa, data, _len);
len -= _len;
ga += _len;
data += _len;
} }
if (need_ipte_lock) if (need_ipte_lock)
ipte_unlock(vcpu); ipte_unlock(vcpu);
if (nr_pages > ARRAY_SIZE(pages_array)) if (nr_pages > ARRAY_SIZE(gpa_array))
vfree(pages); vfree(gpas);
return rc; return rc;
} }
int access_guest_real(struct kvm_vcpu *vcpu, unsigned long gra, int access_guest_real(struct kvm_vcpu *vcpu, unsigned long gra,
void *data, unsigned long len, enum gacc_mode mode) void *data, unsigned long len, enum gacc_mode mode)
{ {
unsigned long _len, gpa; unsigned int fragment_len;
unsigned long gpa;
int rc = 0; int rc = 0;
while (len && !rc) { while (len && !rc) {
gpa = kvm_s390_real_to_abs(vcpu, gra); gpa = kvm_s390_real_to_abs(vcpu, gra);
_len = min(PAGE_SIZE - (gpa & ~PAGE_MASK), len); fragment_len = min(PAGE_SIZE - offset_in_page(gpa), len);
if (mode) rc = access_guest_page(vcpu->kvm, mode, gpa, data, fragment_len);
rc = write_guest_abs(vcpu, gpa, data, _len); len -= fragment_len;
else gra += fragment_len;
rc = read_guest_abs(vcpu, gpa, data, _len); data += fragment_len;
len -= _len;
gra += _len;
data += _len;
} }
return rc; return rc;
} }
@ -909,8 +956,6 @@ int access_guest_real(struct kvm_vcpu *vcpu, unsigned long gra,
int guest_translate_address(struct kvm_vcpu *vcpu, unsigned long gva, u8 ar, int guest_translate_address(struct kvm_vcpu *vcpu, unsigned long gva, u8 ar,
unsigned long *gpa, enum gacc_mode mode) unsigned long *gpa, enum gacc_mode mode)
{ {
psw_t *psw = &vcpu->arch.sie_block->gpsw;
enum prot_type prot;
union asce asce; union asce asce;
int rc; int rc;
@ -918,23 +963,7 @@ int guest_translate_address(struct kvm_vcpu *vcpu, unsigned long gva, u8 ar,
rc = get_vcpu_asce(vcpu, &asce, gva, ar, mode); rc = get_vcpu_asce(vcpu, &asce, gva, ar, mode);
if (rc) if (rc)
return rc; return rc;
if (is_low_address(gva) && low_address_protection_enabled(vcpu, asce)) { return guest_range_to_gpas(vcpu, gva, ar, gpa, 1, asce, mode);
if (mode == GACC_STORE)
return trans_exc(vcpu, PGM_PROTECTION, gva, 0,
mode, PROT_TYPE_LA);
}
if (psw_bits(*psw).dat && !asce.r) { /* Use DAT? */
rc = guest_translate(vcpu, gva, gpa, asce, mode, &prot);
if (rc > 0)
return trans_exc(vcpu, rc, gva, 0, mode, prot);
} else {
*gpa = kvm_s390_real_to_abs(vcpu, gva);
if (kvm_is_error_gpa(vcpu->kvm, *gpa))
return trans_exc(vcpu, rc, gva, PGM_ADDRESSING, mode, 0);
}
return rc;
} }
/** /**
@ -948,17 +977,14 @@ int guest_translate_address(struct kvm_vcpu *vcpu, unsigned long gva, u8 ar,
int check_gva_range(struct kvm_vcpu *vcpu, unsigned long gva, u8 ar, int check_gva_range(struct kvm_vcpu *vcpu, unsigned long gva, u8 ar,
unsigned long length, enum gacc_mode mode) unsigned long length, enum gacc_mode mode)
{ {
unsigned long gpa; union asce asce;
unsigned long currlen;
int rc = 0; int rc = 0;
rc = get_vcpu_asce(vcpu, &asce, gva, ar, mode);
if (rc)
return rc;
ipte_lock(vcpu); ipte_lock(vcpu);
while (length > 0 && !rc) { rc = guest_range_to_gpas(vcpu, gva, ar, NULL, length, asce, mode);
currlen = min(length, PAGE_SIZE - (gva % PAGE_SIZE));
rc = guest_translate_address(vcpu, gva, ar, &gpa, mode);
gva += currlen;
length -= currlen;
}
ipte_unlock(vcpu); ipte_unlock(vcpu);
return rc; return rc;

View file

@ -2116,6 +2116,13 @@ int kvm_s390_is_stop_irq_pending(struct kvm_vcpu *vcpu)
return test_bit(IRQ_PEND_SIGP_STOP, &li->pending_irqs); return test_bit(IRQ_PEND_SIGP_STOP, &li->pending_irqs);
} }
int kvm_s390_is_restart_irq_pending(struct kvm_vcpu *vcpu)
{
struct kvm_s390_local_interrupt *li = &vcpu->arch.local_int;
return test_bit(IRQ_PEND_RESTART, &li->pending_irqs);
}
void kvm_s390_clear_stop_irq(struct kvm_vcpu *vcpu) void kvm_s390_clear_stop_irq(struct kvm_vcpu *vcpu)
{ {
struct kvm_s390_local_interrupt *li = &vcpu->arch.local_int; struct kvm_s390_local_interrupt *li = &vcpu->arch.local_int;

View file

@ -4599,10 +4599,15 @@ int kvm_s390_vcpu_stop(struct kvm_vcpu *vcpu)
} }
} }
/* SIGP STOP and SIGP STOP AND STORE STATUS has been fully processed */ /*
* Set the VCPU to STOPPED and THEN clear the interrupt flag,
* now that the SIGP STOP and SIGP STOP AND STORE STATUS orders
* have been fully processed. This will ensure that the VCPU
* is kept BUSY if another VCPU is inquiring with SIGP SENSE.
*/
kvm_s390_set_cpuflags(vcpu, CPUSTAT_STOPPED);
kvm_s390_clear_stop_irq(vcpu); kvm_s390_clear_stop_irq(vcpu);
kvm_s390_set_cpuflags(vcpu, CPUSTAT_STOPPED);
__disable_ibs_on_vcpu(vcpu); __disable_ibs_on_vcpu(vcpu);
for (i = 0; i < online_vcpus; i++) { for (i = 0; i < online_vcpus; i++) {

View file

@ -441,6 +441,7 @@ void kvm_s390_destroy_adapters(struct kvm *kvm);
int kvm_s390_ext_call_pending(struct kvm_vcpu *vcpu); int kvm_s390_ext_call_pending(struct kvm_vcpu *vcpu);
extern struct kvm_device_ops kvm_flic_ops; extern struct kvm_device_ops kvm_flic_ops;
int kvm_s390_is_stop_irq_pending(struct kvm_vcpu *vcpu); int kvm_s390_is_stop_irq_pending(struct kvm_vcpu *vcpu);
int kvm_s390_is_restart_irq_pending(struct kvm_vcpu *vcpu);
void kvm_s390_clear_stop_irq(struct kvm_vcpu *vcpu); void kvm_s390_clear_stop_irq(struct kvm_vcpu *vcpu);
int kvm_s390_set_irq_state(struct kvm_vcpu *vcpu, int kvm_s390_set_irq_state(struct kvm_vcpu *vcpu,
void __user *buf, int len); void __user *buf, int len);

View file

@ -276,6 +276,34 @@ static int handle_sigp_dst(struct kvm_vcpu *vcpu, u8 order_code,
if (!dst_vcpu) if (!dst_vcpu)
return SIGP_CC_NOT_OPERATIONAL; return SIGP_CC_NOT_OPERATIONAL;
/*
* SIGP RESTART, SIGP STOP, and SIGP STOP AND STORE STATUS orders
* are processed asynchronously. Until the affected VCPU finishes
* its work and calls back into KVM to clear the (RESTART or STOP)
* interrupt, we need to return any new non-reset orders "busy".
*
* This is important because a single VCPU could issue:
* 1) SIGP STOP $DESTINATION
* 2) SIGP SENSE $DESTINATION
*
* If the SIGP SENSE would not be rejected as "busy", it could
* return an incorrect answer as to whether the VCPU is STOPPED
* or OPERATING.
*/
if (order_code != SIGP_INITIAL_CPU_RESET &&
order_code != SIGP_CPU_RESET) {
/*
* Lockless check. Both SIGP STOP and SIGP (RE)START
* properly synchronize everything while processing
* their orders, while the guest cannot observe a
* difference when issuing other orders from two
* different VCPUs.
*/
if (kvm_s390_is_stop_irq_pending(dst_vcpu) ||
kvm_s390_is_restart_irq_pending(dst_vcpu))
return SIGP_CC_BUSY;
}
switch (order_code) { switch (order_code) {
case SIGP_SENSE: case SIGP_SENSE:
vcpu->stat.instruction_sigp_sense++; vcpu->stat.instruction_sigp_sense++;