KVM: s390: Fix and feature for 5.19
- ultravisor communication device driver - fix TEID on terminating storage key ops -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEE+SKTgaM0CPnbq/vKEXu8gLWmHHwFAmKLWW0ACgkQEXu8gLWm HHyhmBAApRObtkLtQjctGs4lzGPvE019EFFdBlK5ayYrgFE0gcaX0adstnLWyzJ+ J7L6UbxUzKKfev0BCDyPCTH+FUW5LHanpS0pBASLrl4VMcloWa7GZh5Ahbiq797x 9QnMC72qUggg4FYj4X4WxYJhxqgqi2lmYrcz7QjCbW6X0RWilryPuzZcL326ghzz gH11gup0cy9HSpe6zr7efNT8UVahUr06ky1VnUBnDRR3ecuMQOUBET/McOXLYUQP Q0eFtdRXvrnDKbCXinORCCp6dbreibBpLAF5PWh5WxlTNluZQtfzYjBUHyMpoeEB akEc/gb/MY7bbR5V7aTG3Joi1soFFmZQ93P9Bn8c39wWfouOkm8gqioCd6erjczW 5seFUNR72uWwUfNxBFvPbDFq7eS6qEVoIx14jLjGhUcTwE9xQhNYCgc5qmmNSqB/ OMUKyKpaqkvPs+mx+/efFhVSScWs+AMimUYzYb2fdTJ7MXxnCRIF0BUkIlFjJVpc 3y1tOi0mD+CEKDyVfTPmigagFgW79FK6rnScSorSRWXqE3xcSpJjv3Afo6II20mQ YJZKviciknzxZ8/uZbJl98DpHvW17oh08SBs9kLweHjLo3SPZtHWa2qzilGTBZMY 75jPiwNMLdZdf/SRYdrIn6nlSNrvUt+16YcN8vqUwcqW+9Of0IQ= =93zj -----END PGP SIGNATURE----- Merge tag 'kvm-s390-next-5.19-1' of git://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux into HEAD KVM: s390: Fix and feature for 5.19 - ultravisor communication device driver - fix TEID on terminating storage key ops
This commit is contained in:
commit
1644e27059
|
@ -3814,12 +3814,18 @@ in case of KVM_S390_MEMOP_F_CHECK_ONLY), the ioctl returns a positive
|
|||
error number indicating the type of exception. This exception is also
|
||||
raised directly at the corresponding VCPU if the flag
|
||||
KVM_S390_MEMOP_F_INJECT_EXCEPTION is set.
|
||||
On protection exceptions, unless specified otherwise, the injected
|
||||
translation-exception identifier (TEID) indicates suppression.
|
||||
|
||||
If the KVM_S390_MEMOP_F_SKEY_PROTECTION flag is set, storage key
|
||||
protection is also in effect and may cause exceptions if accesses are
|
||||
prohibited given the access key designated by "key"; the valid range is 0..15.
|
||||
KVM_S390_MEMOP_F_SKEY_PROTECTION is available if KVM_CAP_S390_MEM_OP_EXTENSION
|
||||
is > 0.
|
||||
Since the accessed memory may span multiple pages and those pages might have
|
||||
different storage keys, it is possible that a protection exception occurs
|
||||
after memory has been modified. In this case, if the exception is injected,
|
||||
the TEID does not indicate suppression.
|
||||
|
||||
Absolute read/write:
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -10785,9 +10785,12 @@ F: Documentation/virt/kvm/s390*
|
|||
F: arch/s390/include/asm/gmap.h
|
||||
F: arch/s390/include/asm/kvm*
|
||||
F: arch/s390/include/uapi/asm/kvm*
|
||||
F: arch/s390/include/uapi/asm/uvdevice.h
|
||||
F: arch/s390/kernel/uv.c
|
||||
F: arch/s390/kvm/
|
||||
F: arch/s390/mm/gmap.c
|
||||
F: drivers/s390/char/uvdevice.c
|
||||
F: tools/testing/selftests/drivers/s390x/uvdevice/
|
||||
F: tools/testing/selftests/kvm/*/s390x/
|
||||
F: tools/testing/selftests/kvm/s390x/
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* Ultravisor Interfaces
|
||||
*
|
||||
* Copyright IBM Corp. 2019
|
||||
* Copyright IBM Corp. 2019, 2022
|
||||
*
|
||||
* Author(s):
|
||||
* Vasily Gorbik <gor@linux.ibm.com>
|
||||
|
@ -52,6 +52,7 @@
|
|||
#define UVC_CMD_UNPIN_PAGE_SHARED 0x0342
|
||||
#define UVC_CMD_SET_SHARED_ACCESS 0x1000
|
||||
#define UVC_CMD_REMOVE_SHARED_ACCESS 0x1001
|
||||
#define UVC_CMD_RETR_ATTEST 0x1020
|
||||
|
||||
/* Bits in installed uv calls */
|
||||
enum uv_cmds_inst {
|
||||
|
@ -76,6 +77,7 @@ enum uv_cmds_inst {
|
|||
BIT_UVC_CMD_UNSHARE_ALL = 20,
|
||||
BIT_UVC_CMD_PIN_PAGE_SHARED = 21,
|
||||
BIT_UVC_CMD_UNPIN_PAGE_SHARED = 22,
|
||||
BIT_UVC_CMD_RETR_ATTEST = 28,
|
||||
};
|
||||
|
||||
enum uv_feat_ind {
|
||||
|
@ -219,6 +221,25 @@ struct uv_cb_share {
|
|||
u64 reserved28;
|
||||
} __packed __aligned(8);
|
||||
|
||||
/* Retrieve Attestation Measurement */
|
||||
struct uv_cb_attest {
|
||||
struct uv_cb_header header; /* 0x0000 */
|
||||
u64 reserved08[2]; /* 0x0008 */
|
||||
u64 arcb_addr; /* 0x0018 */
|
||||
u64 cont_token; /* 0x0020 */
|
||||
u8 reserved28[6]; /* 0x0028 */
|
||||
u16 user_data_len; /* 0x002e */
|
||||
u8 user_data[256]; /* 0x0030 */
|
||||
u32 reserved130[3]; /* 0x0130 */
|
||||
u32 meas_len; /* 0x013c */
|
||||
u64 meas_addr; /* 0x0140 */
|
||||
u8 config_uid[16]; /* 0x0148 */
|
||||
u32 reserved158; /* 0x0158 */
|
||||
u32 add_data_len; /* 0x015c */
|
||||
u64 add_data_addr; /* 0x0160 */
|
||||
u64 reserved168[4]; /* 0x0168 */
|
||||
} __packed __aligned(8);
|
||||
|
||||
static inline int __uv_call(unsigned long r1, unsigned long r2)
|
||||
{
|
||||
int cc;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
/*
|
||||
* Copyright IBM Corp. 2022
|
||||
* Author(s): Steffen Eiden <seiden@linux.ibm.com>
|
||||
*/
|
||||
#ifndef __S390_ASM_UVDEVICE_H
|
||||
#define __S390_ASM_UVDEVICE_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct uvio_ioctl_cb {
|
||||
__u32 flags;
|
||||
__u16 uv_rc; /* UV header rc value */
|
||||
__u16 uv_rrc; /* UV header rrc value */
|
||||
__u64 argument_addr; /* Userspace address of uvio argument */
|
||||
__u32 argument_len;
|
||||
__u8 reserved14[0x40 - 0x14]; /* must be zero */
|
||||
};
|
||||
|
||||
#define UVIO_ATT_USER_DATA_LEN 0x100
|
||||
#define UVIO_ATT_UID_LEN 0x10
|
||||
struct uvio_attest {
|
||||
__u64 arcb_addr; /* 0x0000 */
|
||||
__u64 meas_addr; /* 0x0008 */
|
||||
__u64 add_data_addr; /* 0x0010 */
|
||||
__u8 user_data[UVIO_ATT_USER_DATA_LEN]; /* 0x0018 */
|
||||
__u8 config_uid[UVIO_ATT_UID_LEN]; /* 0x0118 */
|
||||
__u32 arcb_len; /* 0x0128 */
|
||||
__u32 meas_len; /* 0x012c */
|
||||
__u32 add_data_len; /* 0x0130 */
|
||||
__u16 user_data_len; /* 0x0134 */
|
||||
__u16 reserved136; /* 0x0136 */
|
||||
};
|
||||
|
||||
/*
|
||||
* The following max values define an upper length for the IOCTL in/out buffers.
|
||||
* However, they do not represent the maximum the Ultravisor allows which is
|
||||
* often way smaller. By allowing larger buffer sizes we hopefully do not need
|
||||
* to update the code with every machine update. It is therefore possible for
|
||||
* userspace to request more memory than actually used by kernel/UV.
|
||||
*/
|
||||
#define UVIO_ATT_ARCB_MAX_LEN 0x100000
|
||||
#define UVIO_ATT_MEASUREMENT_MAX_LEN 0x8000
|
||||
#define UVIO_ATT_ADDITIONAL_MAX_LEN 0x8000
|
||||
|
||||
#define UVIO_DEVICE_NAME "uv"
|
||||
#define UVIO_TYPE_UVC 'u'
|
||||
|
||||
#define UVIO_IOCTL_ATT _IOWR(UVIO_TYPE_UVC, 0x01, struct uvio_ioctl_cb)
|
||||
|
||||
#endif /* __S390_ASM_UVDEVICE_H */
|
|
@ -491,8 +491,8 @@ enum prot_type {
|
|||
PROT_TYPE_IEP = 4,
|
||||
};
|
||||
|
||||
static int trans_exc(struct kvm_vcpu *vcpu, int code, unsigned long gva,
|
||||
u8 ar, enum gacc_mode mode, enum prot_type prot)
|
||||
static int trans_exc_ending(struct kvm_vcpu *vcpu, int code, unsigned long gva, u8 ar,
|
||||
enum gacc_mode mode, enum prot_type prot, bool terminate)
|
||||
{
|
||||
struct kvm_s390_pgm_info *pgm = &vcpu->arch.pgm;
|
||||
struct trans_exc_code_bits *tec;
|
||||
|
@ -520,6 +520,11 @@ static int trans_exc(struct kvm_vcpu *vcpu, int code, unsigned long gva,
|
|||
tec->b61 = 1;
|
||||
break;
|
||||
}
|
||||
if (terminate) {
|
||||
tec->b56 = 0;
|
||||
tec->b60 = 0;
|
||||
tec->b61 = 0;
|
||||
}
|
||||
fallthrough;
|
||||
case PGM_ASCE_TYPE:
|
||||
case PGM_PAGE_TRANSLATION:
|
||||
|
@ -552,6 +557,12 @@ static int trans_exc(struct kvm_vcpu *vcpu, int code, unsigned long gva,
|
|||
return code;
|
||||
}
|
||||
|
||||
static int trans_exc(struct kvm_vcpu *vcpu, int code, unsigned long gva, u8 ar,
|
||||
enum gacc_mode mode, enum prot_type prot)
|
||||
{
|
||||
return trans_exc_ending(vcpu, code, gva, ar, mode, prot, false);
|
||||
}
|
||||
|
||||
static int get_vcpu_asce(struct kvm_vcpu *vcpu, union asce *asce,
|
||||
unsigned long ga, u8 ar, enum gacc_mode mode)
|
||||
{
|
||||
|
@ -1109,8 +1120,11 @@ int access_guest_with_key(struct kvm_vcpu *vcpu, unsigned long ga, u8 ar,
|
|||
data += fragment_len;
|
||||
ga = kvm_s390_logical_to_effective(vcpu, ga + fragment_len);
|
||||
}
|
||||
if (rc > 0)
|
||||
rc = trans_exc(vcpu, rc, ga, ar, mode, prot);
|
||||
if (rc > 0) {
|
||||
bool terminate = (mode == GACC_STORE) && (idx > 0);
|
||||
|
||||
rc = trans_exc_ending(vcpu, rc, ga, ar, mode, prot, terminate);
|
||||
}
|
||||
out_unlock:
|
||||
if (need_ipte_lock)
|
||||
ipte_unlock(vcpu);
|
||||
|
|
|
@ -100,6 +100,16 @@ config SCLP_OFB
|
|||
This option enables the Open-for-Business interface to the s390
|
||||
Service Element.
|
||||
|
||||
config S390_UV_UAPI
|
||||
def_tristate m
|
||||
prompt "Ultravisor userspace API"
|
||||
help
|
||||
Selecting exposes parts of the UV interface to userspace
|
||||
by providing a misc character device at /dev/uv.
|
||||
Using IOCTLs one can interact with the UV.
|
||||
The device is only available if the Ultravisor
|
||||
Facility (158) is present.
|
||||
|
||||
config S390_TAPE
|
||||
def_tristate m
|
||||
prompt "S/390 tape device support"
|
||||
|
|
|
@ -48,6 +48,7 @@ obj-$(CONFIG_MONREADER) += monreader.o
|
|||
obj-$(CONFIG_MONWRITER) += monwriter.o
|
||||
obj-$(CONFIG_S390_VMUR) += vmur.o
|
||||
obj-$(CONFIG_CRASH_DUMP) += sclp_sdias.o zcore.o
|
||||
obj-$(CONFIG_S390_UV_UAPI) += uvdevice.o
|
||||
|
||||
hmcdrv-objs := hmcdrv_mod.o hmcdrv_dev.o hmcdrv_ftp.o hmcdrv_cache.o diag_ftp.o sclp_ftp.o
|
||||
obj-$(CONFIG_HMC_DRV) += hmcdrv.o
|
||||
|
|
|
@ -0,0 +1,257 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright IBM Corp. 2022
|
||||
* Author(s): Steffen Eiden <seiden@linux.ibm.com>
|
||||
*
|
||||
* This file provides a Linux misc device to give userspace access to some
|
||||
* Ultravisor (UV) functions. The device only accepts IOCTLs and will only
|
||||
* be present if the Ultravisor facility (158) is present.
|
||||
*
|
||||
* When userspace sends a valid IOCTL uvdevice will copy the input data to
|
||||
* kernel space, do some basic validity checks to avoid kernel/system
|
||||
* corruption. Any other check that the Ultravisor does will not be done by
|
||||
* the uvdevice to keep changes minimal when adding new functionalities
|
||||
* to existing UV-calls.
|
||||
* After the checks uvdevice builds a corresponding
|
||||
* Ultravisor Call Control Block, and sends the request to the Ultravisor.
|
||||
* Then, it copies the response, including the return codes, back to userspace.
|
||||
* It is the responsibility of the userspace to check for any error issued
|
||||
* by UV and to interpret the UV response. The uvdevice acts as a communication
|
||||
* channel for userspace to the Ultravisor.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/uvdevice.h>
|
||||
#include <asm/uv.h>
|
||||
|
||||
static int uvio_build_uvcb_attest(struct uv_cb_attest *uvcb_attest, u8 *arcb,
|
||||
u8 *meas, u8 *add_data, struct uvio_attest *uvio_attest)
|
||||
{
|
||||
void __user *user_buf_arcb = (void __user *)uvio_attest->arcb_addr;
|
||||
|
||||
if (copy_from_user(arcb, user_buf_arcb, uvio_attest->arcb_len))
|
||||
return -EFAULT;
|
||||
|
||||
uvcb_attest->header.len = sizeof(*uvcb_attest);
|
||||
uvcb_attest->header.cmd = UVC_CMD_RETR_ATTEST;
|
||||
uvcb_attest->arcb_addr = (u64)arcb;
|
||||
uvcb_attest->cont_token = 0;
|
||||
uvcb_attest->user_data_len = uvio_attest->user_data_len;
|
||||
memcpy(uvcb_attest->user_data, uvio_attest->user_data, sizeof(uvcb_attest->user_data));
|
||||
uvcb_attest->meas_len = uvio_attest->meas_len;
|
||||
uvcb_attest->meas_addr = (u64)meas;
|
||||
uvcb_attest->add_data_len = uvio_attest->add_data_len;
|
||||
uvcb_attest->add_data_addr = (u64)add_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uvio_copy_attest_result_to_user(struct uv_cb_attest *uvcb_attest,
|
||||
struct uvio_ioctl_cb *uv_ioctl,
|
||||
u8 *measurement, u8 *add_data,
|
||||
struct uvio_attest *uvio_attest)
|
||||
{
|
||||
struct uvio_attest __user *user_uvio_attest = (void __user *)uv_ioctl->argument_addr;
|
||||
void __user *user_buf_add = (void __user *)uvio_attest->add_data_addr;
|
||||
void __user *user_buf_meas = (void __user *)uvio_attest->meas_addr;
|
||||
void __user *user_buf_uid = &user_uvio_attest->config_uid;
|
||||
|
||||
if (copy_to_user(user_buf_meas, measurement, uvio_attest->meas_len))
|
||||
return -EFAULT;
|
||||
if (add_data && copy_to_user(user_buf_add, add_data, uvio_attest->add_data_len))
|
||||
return -EFAULT;
|
||||
if (copy_to_user(user_buf_uid, uvcb_attest->config_uid, sizeof(uvcb_attest->config_uid)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_uvio_attest(struct uvio_ioctl_cb *uv_ioctl, struct uvio_attest *uvio_attest)
|
||||
{
|
||||
u8 __user *user_arg_buf = (u8 __user *)uv_ioctl->argument_addr;
|
||||
|
||||
if (copy_from_user(uvio_attest, user_arg_buf, sizeof(*uvio_attest)))
|
||||
return -EFAULT;
|
||||
|
||||
if (uvio_attest->arcb_len > UVIO_ATT_ARCB_MAX_LEN)
|
||||
return -EINVAL;
|
||||
if (uvio_attest->arcb_len == 0)
|
||||
return -EINVAL;
|
||||
if (uvio_attest->meas_len > UVIO_ATT_MEASUREMENT_MAX_LEN)
|
||||
return -EINVAL;
|
||||
if (uvio_attest->meas_len == 0)
|
||||
return -EINVAL;
|
||||
if (uvio_attest->add_data_len > UVIO_ATT_ADDITIONAL_MAX_LEN)
|
||||
return -EINVAL;
|
||||
if (uvio_attest->reserved136)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* uvio_attestation() - Perform a Retrieve Attestation Measurement UVC.
|
||||
*
|
||||
* @uv_ioctl: ioctl control block
|
||||
*
|
||||
* uvio_attestation() does a Retrieve Attestation Measurement Ultravisor Call.
|
||||
* It verifies that the given userspace addresses are valid and request sizes
|
||||
* are sane. Every other check is made by the Ultravisor (UV) and won't result
|
||||
* in a negative return value. It copies the input to kernelspace, builds the
|
||||
* request, sends the UV-call, and copies the result to userspace.
|
||||
*
|
||||
* The Attestation Request has two input and two outputs.
|
||||
* ARCB and User Data are inputs for the UV generated by userspace.
|
||||
* Measurement and Additional Data are outputs for userspace generated by UV.
|
||||
*
|
||||
* The Attestation Request Control Block (ARCB) is a cryptographically verified
|
||||
* and secured request to UV and User Data is some plaintext data which is
|
||||
* going to be included in the Attestation Measurement calculation.
|
||||
*
|
||||
* Measurement is a cryptographic measurement of the callers properties,
|
||||
* optional data configured by the ARCB and the user data. If specified by the
|
||||
* ARCB, UV will add some Additional Data to the measurement calculation.
|
||||
* This Additional Data is then returned as well.
|
||||
*
|
||||
* If the Retrieve Attestation Measurement UV facility is not present,
|
||||
* UV will return invalid command rc. This won't be fenced in the driver
|
||||
* and does not result in a negative return value.
|
||||
*
|
||||
* Context: might sleep
|
||||
*
|
||||
* Return: 0 on success or a negative error code on error.
|
||||
*/
|
||||
static int uvio_attestation(struct uvio_ioctl_cb *uv_ioctl)
|
||||
{
|
||||
struct uv_cb_attest *uvcb_attest = NULL;
|
||||
struct uvio_attest *uvio_attest = NULL;
|
||||
u8 *measurement = NULL;
|
||||
u8 *add_data = NULL;
|
||||
u8 *arcb = NULL;
|
||||
int ret;
|
||||
|
||||
ret = -EINVAL;
|
||||
if (uv_ioctl->argument_len != sizeof(*uvio_attest))
|
||||
goto out;
|
||||
|
||||
ret = -ENOMEM;
|
||||
uvio_attest = kzalloc(sizeof(*uvio_attest), GFP_KERNEL);
|
||||
if (!uvio_attest)
|
||||
goto out;
|
||||
|
||||
ret = get_uvio_attest(uv_ioctl, uvio_attest);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = -ENOMEM;
|
||||
arcb = kvzalloc(uvio_attest->arcb_len, GFP_KERNEL);
|
||||
measurement = kvzalloc(uvio_attest->meas_len, GFP_KERNEL);
|
||||
if (!arcb || !measurement)
|
||||
goto out;
|
||||
|
||||
if (uvio_attest->add_data_len) {
|
||||
add_data = kvzalloc(uvio_attest->add_data_len, GFP_KERNEL);
|
||||
if (!add_data)
|
||||
goto out;
|
||||
}
|
||||
|
||||
uvcb_attest = kzalloc(sizeof(*uvcb_attest), GFP_KERNEL);
|
||||
if (!uvcb_attest)
|
||||
goto out;
|
||||
|
||||
ret = uvio_build_uvcb_attest(uvcb_attest, arcb, measurement, add_data, uvio_attest);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
uv_call_sched(0, (u64)uvcb_attest);
|
||||
|
||||
uv_ioctl->uv_rc = uvcb_attest->header.rc;
|
||||
uv_ioctl->uv_rrc = uvcb_attest->header.rrc;
|
||||
|
||||
ret = uvio_copy_attest_result_to_user(uvcb_attest, uv_ioctl, measurement, add_data,
|
||||
uvio_attest);
|
||||
out:
|
||||
kvfree(arcb);
|
||||
kvfree(measurement);
|
||||
kvfree(add_data);
|
||||
kfree(uvio_attest);
|
||||
kfree(uvcb_attest);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uvio_copy_and_check_ioctl(struct uvio_ioctl_cb *ioctl, void __user *argp)
|
||||
{
|
||||
if (copy_from_user(ioctl, argp, sizeof(*ioctl)))
|
||||
return -EFAULT;
|
||||
if (ioctl->flags != 0)
|
||||
return -EINVAL;
|
||||
if (memchr_inv(ioctl->reserved14, 0, sizeof(ioctl->reserved14)))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* IOCTL entry point for the Ultravisor device.
|
||||
*/
|
||||
static long uvio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
void __user *argp = (void __user *)arg;
|
||||
struct uvio_ioctl_cb uv_ioctl = { };
|
||||
long ret;
|
||||
|
||||
switch (cmd) {
|
||||
case UVIO_IOCTL_ATT:
|
||||
ret = uvio_copy_and_check_ioctl(&uv_ioctl, argp);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = uvio_attestation(&uv_ioctl);
|
||||
break;
|
||||
default:
|
||||
ret = -ENOIOCTLCMD;
|
||||
break;
|
||||
}
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (copy_to_user(argp, &uv_ioctl, sizeof(uv_ioctl)))
|
||||
ret = -EFAULT;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations uvio_dev_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = uvio_ioctl,
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
static struct miscdevice uvio_dev_miscdev = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = UVIO_DEVICE_NAME,
|
||||
.fops = &uvio_dev_fops,
|
||||
};
|
||||
|
||||
static void __exit uvio_dev_exit(void)
|
||||
{
|
||||
misc_deregister(&uvio_dev_miscdev);
|
||||
}
|
||||
|
||||
static int __init uvio_dev_init(void)
|
||||
{
|
||||
if (!test_facility(158))
|
||||
return -ENXIO;
|
||||
return misc_register(&uvio_dev_miscdev);
|
||||
}
|
||||
|
||||
module_init(uvio_dev_init);
|
||||
module_exit(uvio_dev_exit);
|
||||
|
||||
MODULE_AUTHOR("IBM Corporation");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Ultravisor UAPI driver");
|
|
@ -10,6 +10,7 @@ TARGETS += core
|
|||
TARGETS += cpufreq
|
||||
TARGETS += cpu-hotplug
|
||||
TARGETS += drivers/dma-buf
|
||||
TARGETS += drivers/s390x/uvdevice
|
||||
TARGETS += efivarfs
|
||||
TARGETS += exec
|
||||
TARGETS += filesystems
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
/dma-buf/udmabuf
|
||||
/s390x/uvdevice/test_uvdevice
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
include ../../../../../build/Build.include
|
||||
|
||||
UNAME_M := $(shell uname -m)
|
||||
|
||||
ifneq ($(UNAME_M),s390x)
|
||||
nothing:
|
||||
.PHONY: all clean run_tests install
|
||||
.SILENT:
|
||||
else
|
||||
|
||||
TEST_GEN_PROGS := test_uvdevice
|
||||
|
||||
top_srcdir ?= ../../../../../..
|
||||
KSFT_KHDR_INSTALL := 1
|
||||
khdr_dir = $(top_srcdir)/usr/include
|
||||
LINUX_TOOL_ARCH_INCLUDE = $(top_srcdir)/tools/arch/$(ARCH)/include
|
||||
|
||||
CFLAGS += -Wall -Werror -static -I$(khdr_dir) -I$(LINUX_TOOL_ARCH_INCLUDE)
|
||||
|
||||
include ../../../lib.mk
|
||||
|
||||
endif
|
|
@ -0,0 +1 @@
|
|||
CONFIG_S390_UV_UAPI=y
|
|
@ -0,0 +1,276 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* selftest for the Ultravisor UAPI device
|
||||
*
|
||||
* Copyright IBM Corp. 2022
|
||||
* Author(s): Steffen Eiden <seiden@linux.ibm.com>
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <asm/uvdevice.h>
|
||||
|
||||
#include "../../../kselftest_harness.h"
|
||||
|
||||
#define UV_PATH "/dev/uv"
|
||||
#define BUFFER_SIZE 0x200
|
||||
FIXTURE(uvio_fixture) {
|
||||
int uv_fd;
|
||||
struct uvio_ioctl_cb uvio_ioctl;
|
||||
uint8_t buffer[BUFFER_SIZE];
|
||||
__u64 fault_page;
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT(uvio_fixture) {
|
||||
unsigned long ioctl_cmd;
|
||||
uint32_t arg_size;
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(uvio_fixture, att) {
|
||||
.ioctl_cmd = UVIO_IOCTL_ATT,
|
||||
.arg_size = sizeof(struct uvio_attest),
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(uvio_fixture)
|
||||
{
|
||||
self->uv_fd = open(UV_PATH, O_ACCMODE);
|
||||
|
||||
self->uvio_ioctl.argument_addr = (__u64)self->buffer;
|
||||
self->uvio_ioctl.argument_len = variant->arg_size;
|
||||
self->fault_page =
|
||||
(__u64)mmap(NULL, (size_t)getpagesize(), PROT_NONE, MAP_ANONYMOUS, -1, 0);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(uvio_fixture)
|
||||
{
|
||||
if (self->uv_fd)
|
||||
close(self->uv_fd);
|
||||
munmap((void *)self->fault_page, (size_t)getpagesize());
|
||||
}
|
||||
|
||||
TEST_F(uvio_fixture, fault_ioctl_arg)
|
||||
{
|
||||
int rc, errno_cache;
|
||||
|
||||
rc = ioctl(self->uv_fd, variant->ioctl_cmd, NULL);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EFAULT);
|
||||
|
||||
rc = ioctl(self->uv_fd, variant->ioctl_cmd, self->fault_page);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EFAULT);
|
||||
}
|
||||
|
||||
TEST_F(uvio_fixture, fault_uvio_arg)
|
||||
{
|
||||
int rc, errno_cache;
|
||||
|
||||
self->uvio_ioctl.argument_addr = 0;
|
||||
rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EFAULT);
|
||||
|
||||
self->uvio_ioctl.argument_addr = self->fault_page;
|
||||
rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EFAULT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to verify that IOCTLs with invalid values in the ioctl_control block
|
||||
* are rejected.
|
||||
*/
|
||||
TEST_F(uvio_fixture, inval_ioctl_cb)
|
||||
{
|
||||
int rc, errno_cache;
|
||||
|
||||
self->uvio_ioctl.argument_len = 0;
|
||||
rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EINVAL);
|
||||
|
||||
self->uvio_ioctl.argument_len = (uint32_t)-1;
|
||||
rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EINVAL);
|
||||
self->uvio_ioctl.argument_len = variant->arg_size;
|
||||
|
||||
self->uvio_ioctl.flags = (uint32_t)-1;
|
||||
rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EINVAL);
|
||||
self->uvio_ioctl.flags = 0;
|
||||
|
||||
memset(self->uvio_ioctl.reserved14, 0xff, sizeof(self->uvio_ioctl.reserved14));
|
||||
rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EINVAL);
|
||||
|
||||
memset(&self->uvio_ioctl, 0x11, sizeof(self->uvio_ioctl));
|
||||
rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl);
|
||||
ASSERT_EQ(rc, -1);
|
||||
}
|
||||
|
||||
TEST_F(uvio_fixture, inval_ioctl_cmd)
|
||||
{
|
||||
int rc, errno_cache;
|
||||
uint8_t nr = _IOC_NR(variant->ioctl_cmd);
|
||||
unsigned long cmds[] = {
|
||||
_IOWR('a', nr, struct uvio_ioctl_cb),
|
||||
_IOWR(UVIO_TYPE_UVC, nr, int),
|
||||
_IO(UVIO_TYPE_UVC, nr),
|
||||
_IOR(UVIO_TYPE_UVC, nr, struct uvio_ioctl_cb),
|
||||
_IOW(UVIO_TYPE_UVC, nr, struct uvio_ioctl_cb),
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(cmds); i++) {
|
||||
rc = ioctl(self->uv_fd, cmds[i], &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, ENOTTY);
|
||||
}
|
||||
}
|
||||
|
||||
struct test_attest_buffer {
|
||||
uint8_t arcb[0x180];
|
||||
uint8_t meas[64];
|
||||
uint8_t add[32];
|
||||
};
|
||||
|
||||
FIXTURE(attest_fixture) {
|
||||
int uv_fd;
|
||||
struct uvio_ioctl_cb uvio_ioctl;
|
||||
struct uvio_attest uvio_attest;
|
||||
struct test_attest_buffer attest_buffer;
|
||||
__u64 fault_page;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(attest_fixture)
|
||||
{
|
||||
self->uv_fd = open(UV_PATH, O_ACCMODE);
|
||||
|
||||
self->uvio_ioctl.argument_addr = (__u64)&self->uvio_attest;
|
||||
self->uvio_ioctl.argument_len = sizeof(self->uvio_attest);
|
||||
|
||||
self->uvio_attest.arcb_addr = (__u64)&self->attest_buffer.arcb;
|
||||
self->uvio_attest.arcb_len = sizeof(self->attest_buffer.arcb);
|
||||
|
||||
self->uvio_attest.meas_addr = (__u64)&self->attest_buffer.meas;
|
||||
self->uvio_attest.meas_len = sizeof(self->attest_buffer.meas);
|
||||
|
||||
self->uvio_attest.add_data_addr = (__u64)&self->attest_buffer.add;
|
||||
self->uvio_attest.add_data_len = sizeof(self->attest_buffer.add);
|
||||
self->fault_page =
|
||||
(__u64)mmap(NULL, (size_t)getpagesize(), PROT_NONE, MAP_ANONYMOUS, -1, 0);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(attest_fixture)
|
||||
{
|
||||
if (self->uv_fd)
|
||||
close(self->uv_fd);
|
||||
munmap((void *)self->fault_page, (size_t)getpagesize());
|
||||
}
|
||||
|
||||
static void att_inval_sizes_test(uint32_t *size, uint32_t max_size, bool test_zero,
|
||||
struct __test_metadata *_metadata,
|
||||
FIXTURE_DATA(attest_fixture) *self)
|
||||
{
|
||||
int rc, errno_cache;
|
||||
uint32_t tmp = *size;
|
||||
|
||||
if (test_zero) {
|
||||
*size = 0;
|
||||
rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EINVAL);
|
||||
}
|
||||
*size = max_size + 1;
|
||||
rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EINVAL);
|
||||
*size = tmp;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to verify that attestation IOCTLs with invalid values in the UVIO
|
||||
* attestation control block are rejected.
|
||||
*/
|
||||
TEST_F(attest_fixture, att_inval_request)
|
||||
{
|
||||
int rc, errno_cache;
|
||||
|
||||
att_inval_sizes_test(&self->uvio_attest.add_data_len, UVIO_ATT_ADDITIONAL_MAX_LEN,
|
||||
false, _metadata, self);
|
||||
att_inval_sizes_test(&self->uvio_attest.meas_len, UVIO_ATT_MEASUREMENT_MAX_LEN,
|
||||
true, _metadata, self);
|
||||
att_inval_sizes_test(&self->uvio_attest.arcb_len, UVIO_ATT_ARCB_MAX_LEN,
|
||||
true, _metadata, self);
|
||||
|
||||
self->uvio_attest.reserved136 = (uint16_t)-1;
|
||||
rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EINVAL);
|
||||
|
||||
memset(&self->uvio_attest, 0x11, sizeof(self->uvio_attest));
|
||||
rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
|
||||
ASSERT_EQ(rc, -1);
|
||||
}
|
||||
|
||||
static void att_inval_addr_test(__u64 *addr, struct __test_metadata *_metadata,
|
||||
FIXTURE_DATA(attest_fixture) *self)
|
||||
{
|
||||
int rc, errno_cache;
|
||||
__u64 tmp = *addr;
|
||||
|
||||
*addr = 0;
|
||||
rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EFAULT);
|
||||
*addr = self->fault_page;
|
||||
rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl);
|
||||
errno_cache = errno;
|
||||
ASSERT_EQ(rc, -1);
|
||||
ASSERT_EQ(errno_cache, EFAULT);
|
||||
*addr = tmp;
|
||||
}
|
||||
|
||||
TEST_F(attest_fixture, att_inval_addr)
|
||||
{
|
||||
att_inval_addr_test(&self->uvio_attest.arcb_addr, _metadata, self);
|
||||
att_inval_addr_test(&self->uvio_attest.add_data_addr, _metadata, self);
|
||||
att_inval_addr_test(&self->uvio_attest.meas_addr, _metadata, self);
|
||||
}
|
||||
|
||||
static void __attribute__((constructor)) __constructor_order_last(void)
|
||||
{
|
||||
if (!__constructor_order)
|
||||
__constructor_order = _CONSTRUCTOR_ORDER_BACKWARD;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int fd = open(UV_PATH, O_ACCMODE);
|
||||
|
||||
if (fd < 0)
|
||||
ksft_exit_skip("No uv-device or cannot access " UV_PATH "\n"
|
||||
"Enable CONFIG_S390_UV_UAPI and check the access rights on "
|
||||
UV_PATH ".\n");
|
||||
close(fd);
|
||||
return test_harness_run(argc, argv);
|
||||
}
|
|
@ -10,6 +10,8 @@
|
|||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include <linux/bits.h>
|
||||
|
||||
#include "test_util.h"
|
||||
#include "kvm_util.h"
|
||||
|
||||
|
@ -194,6 +196,7 @@ static int err_memop_ioctl(struct test_vcpu vcpu, struct kvm_s390_mem_op *ksmo)
|
|||
#define SIDA_OFFSET(o) ._sida_offset = 1, .sida_offset = (o)
|
||||
#define AR(a) ._ar = 1, .ar = (a)
|
||||
#define KEY(a) .f_key = 1, .key = (a)
|
||||
#define INJECT .f_inject = 1
|
||||
|
||||
#define CHECK_N_DO(f, ...) ({ f(__VA_ARGS__, CHECK_ONLY); f(__VA_ARGS__); })
|
||||
|
||||
|
@ -430,9 +433,18 @@ static void test_copy_key_fetch_prot(void)
|
|||
TEST_ASSERT(rv == 4, "Should result in protection exception"); \
|
||||
})
|
||||
|
||||
static void guest_error_key(void)
|
||||
{
|
||||
GUEST_SYNC(STAGE_INITED);
|
||||
set_storage_key_range(mem1, PAGE_SIZE, 0x18);
|
||||
set_storage_key_range(mem1 + PAGE_SIZE, sizeof(mem1) - PAGE_SIZE, 0x98);
|
||||
GUEST_SYNC(STAGE_SKEYS_SET);
|
||||
GUEST_SYNC(STAGE_IDLED);
|
||||
}
|
||||
|
||||
static void test_errors_key(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy_key_fetch_prot);
|
||||
struct test_default t = test_default_init(guest_error_key);
|
||||
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
|
||||
|
@ -446,6 +458,37 @@ static void test_errors_key(void)
|
|||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
static void test_termination(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_error_key);
|
||||
uint64_t prefix;
|
||||
uint64_t teid;
|
||||
uint64_t teid_mask = BIT(63 - 56) | BIT(63 - 60) | BIT(63 - 61);
|
||||
uint64_t psw[2];
|
||||
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
|
||||
|
||||
/* vcpu, mismatching keys after first page */
|
||||
ERR_PROT_MOP(t.vcpu, LOGICAL, WRITE, mem1, t.size, GADDR_V(mem1), KEY(1), INJECT);
|
||||
/*
|
||||
* The memop injected a program exception and the test needs to check the
|
||||
* Translation-Exception Identification (TEID). It is necessary to run
|
||||
* the guest in order to be able to read the TEID from guest memory.
|
||||
* Set the guest program new PSW, so the guest state is not clobbered.
|
||||
*/
|
||||
prefix = t.run->s.regs.prefix;
|
||||
psw[0] = t.run->psw_mask;
|
||||
psw[1] = t.run->psw_addr;
|
||||
MOP(t.vm, ABSOLUTE, WRITE, psw, sizeof(psw), GADDR(prefix + 464));
|
||||
HOST_SYNC(t.vcpu, STAGE_IDLED);
|
||||
MOP(t.vm, ABSOLUTE, READ, &teid, sizeof(teid), GADDR(prefix + 168));
|
||||
/* Bits 56, 60, 61 form a code, 0 being the only one allowing for termination */
|
||||
ASSERT_EQ(teid & teid_mask, 0);
|
||||
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
static void test_errors_key_storage_prot_override(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy_key_fetch_prot);
|
||||
|
@ -668,6 +711,7 @@ int main(int argc, char *argv[])
|
|||
test_copy_key_fetch_prot();
|
||||
test_copy_key_fetch_prot_override();
|
||||
test_errors_key();
|
||||
test_termination();
|
||||
test_errors_key_storage_prot_override();
|
||||
test_errors_key_fetch_prot_override_not_enabled();
|
||||
test_errors_key_fetch_prot_override_enabled();
|
||||
|
|
Loading…
Reference in New Issue