#include "qemu/osdep.h"
#include "qemu-common.h"
#include "cpu.h"
+#include "kvm_s390x.h"
#include <sys/ioctl.h>
#include "qemu/error-report.h"
+#include "qapi/error.h"
#include "hw/sysbus.h"
#include "sysemu/kvm.h"
-#include "migration/qemu-file.h"
#include "hw/s390x/s390_flic.h"
#include "hw/s390x/adapter.h"
+#include "hw/s390x/css.h"
#include "trace.h"
#define FLIC_SAVE_INITIAL_SIZE getpagesize()
bool clear_io_supported;
} KVMS390FLICState;
-DeviceState *s390_flic_kvm_create(void)
+static KVMS390FLICState *s390_get_kvm_flic(S390FLICState *fs)
{
- DeviceState *dev = NULL;
+ static KVMS390FLICState *flic;
- if (kvm_enabled()) {
- dev = qdev_create(NULL, TYPE_KVM_S390_FLIC);
- object_property_add_child(qdev_get_machine(), TYPE_KVM_S390_FLIC,
- OBJECT(dev), NULL);
+ if (!flic) {
+ /* we only have one flic device, so this is fine to cache */
+ flic = KVM_S390_FLIC(fs);
}
- return dev;
+ return flic;
}
/**
return rc ? -errno : 0;
}
-int kvm_s390_inject_flic(struct kvm_s390_irq *irq)
+static void kvm_s390_inject_flic(S390FLICState *fs, struct kvm_s390_irq *irq)
{
- static KVMS390FLICState *flic;
+ static bool use_flic = true;
+ int r;
+
+ if (use_flic) {
+ r = flic_enqueue_irqs(irq, sizeof(*irq), s390_get_kvm_flic(fs));
+ if (r == -ENOSYS) {
+ use_flic = false;
+ }
+ if (!r) {
+ return;
+ }
+ }
+ /* fallback to legacy KVM IOCTL in case FLIC fails */
+ kvm_s390_floating_interrupt_legacy(irq);
+}
+
+static void kvm_s390_inject_service(S390FLICState *fs, uint32_t parm)
+{
+ struct kvm_s390_irq irq = {
+ .type = KVM_S390_INT_SERVICE,
+ .u.ext.ext_params = parm,
+ };
- if (unlikely(!flic)) {
- flic = KVM_S390_FLIC(s390_get_flic());
+ kvm_s390_inject_flic(fs, &irq);
+}
+
+static void kvm_s390_inject_io(S390FLICState *fs, uint16_t subchannel_id,
+ uint16_t subchannel_nr, uint32_t io_int_parm,
+ uint32_t io_int_word)
+{
+ struct kvm_s390_irq irq = {
+ .u.io.subchannel_id = subchannel_id,
+ .u.io.subchannel_nr = subchannel_nr,
+ .u.io.io_int_parm = io_int_parm,
+ .u.io.io_int_word = io_int_word,
+ };
+
+ if (io_int_word & IO_INT_WORD_AI) {
+ irq.type = KVM_S390_INT_IO(1, 0, 0, 0);
+ } else {
+ irq.type = KVM_S390_INT_IO(0, (subchannel_id & 0xff00) >> 8,
+ (subchannel_id & 0x0006),
+ subchannel_nr);
}
- return flic_enqueue_irqs(irq, sizeof(*irq), flic);
+ kvm_s390_inject_flic(fs, &irq);
+}
+
+static void kvm_s390_inject_crw_mchk(S390FLICState *fs)
+{
+ struct kvm_s390_irq irq = {
+ .type = KVM_S390_MCHK,
+ .u.mchk.cr14 = CR14_CHANNEL_REPORT_SC,
+ .u.mchk.mcic = s390_build_validity_mcic() | MCIC_SC_CP,
+ };
+
+ kvm_s390_inject_flic(fs, &irq);
}
static int kvm_s390_clear_io_flic(S390FLICState *fs, uint16_t subchannel_id,
uint16_t subchannel_nr)
{
- KVMS390FLICState *flic = KVM_S390_FLIC(fs);
+ KVMS390FLICState *flic = s390_get_kvm_flic(fs);
int rc;
uint32_t sid = subchannel_id << 16 | subchannel_nr;
struct kvm_device_attr attr = {
return rc ? -errno : 0;
}
+static int kvm_s390_modify_ais_mode(S390FLICState *fs, uint8_t isc,
+ uint16_t mode)
+{
+ KVMS390FLICState *flic = s390_get_kvm_flic(fs);
+ struct kvm_s390_ais_req req = {
+ .isc = isc,
+ .mode = mode,
+ };
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_AISM,
+ .addr = (uint64_t)&req,
+ };
+
+ if (!fs->ais_supported) {
+ return -ENOSYS;
+ }
+
+ return ioctl(flic->fd, KVM_SET_DEVICE_ATTR, &attr) ? -errno : 0;
+}
+
+static int kvm_s390_inject_airq(S390FLICState *fs, uint8_t type,
+ uint8_t isc, uint8_t flags)
+{
+ KVMS390FLICState *flic = s390_get_kvm_flic(fs);
+ uint32_t id = css_get_adapter_id(type, isc);
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_AIRQ_INJECT,
+ .attr = id,
+ };
+
+ if (!fs->ais_supported) {
+ return -ENOSYS;
+ }
+
+ return ioctl(flic->fd, KVM_SET_DEVICE_ATTR, &attr) ? -errno : 0;
+}
+
/**
* __get_all_irqs - store all pending irqs in buffer
* @flic: pointer to flic device state
static int kvm_s390_register_io_adapter(S390FLICState *fs, uint32_t id,
uint8_t isc, bool swap,
- bool is_maskable)
+ bool is_maskable, uint8_t flags)
{
struct kvm_s390_io_adapter adapter = {
.id = id,
.isc = isc,
.maskable = is_maskable,
.swap = swap,
+ .flags = flags,
};
KVMS390FLICState *flic = KVM_S390_FLIC(fs);
int r;
.group = KVM_DEV_FLIC_ADAPTER_MODIFY,
.addr = (uint64_t)&req,
};
- KVMS390FLICState *flic = KVM_S390_FLIC(fs);
+ KVMS390FLICState *flic = s390_get_kvm_flic(fs);
int r;
if (!kvm_gsi_routing_enabled()) {
return r;
}
+typedef struct KVMS390FLICStateMigTmp {
+ KVMS390FLICState *parent;
+ uint8_t simm;
+ uint8_t nimm;
+} KVMS390FLICStateMigTmp;
+
+static int kvm_flic_ais_pre_save(void *opaque)
+{
+ KVMS390FLICStateMigTmp *tmp = opaque;
+ KVMS390FLICState *flic = tmp->parent;
+ struct kvm_s390_ais_all ais;
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_AISM_ALL,
+ .addr = (uint64_t)&ais,
+ .attr = sizeof(ais),
+ };
+
+ if (ioctl(flic->fd, KVM_GET_DEVICE_ATTR, &attr)) {
+ error_report("Failed to retrieve kvm flic ais states");
+ return -EINVAL;
+ }
+
+ tmp->simm = ais.simm;
+ tmp->nimm = ais.nimm;
+
+ return 0;
+}
+
+static int kvm_flic_ais_post_load(void *opaque, int version_id)
+{
+ KVMS390FLICStateMigTmp *tmp = opaque;
+ KVMS390FLICState *flic = tmp->parent;
+ struct kvm_s390_ais_all ais = {
+ .simm = tmp->simm,
+ .nimm = tmp->nimm,
+ };
+ struct kvm_device_attr attr = {
+ .group = KVM_DEV_FLIC_AISM_ALL,
+ .addr = (uint64_t)&ais,
+ };
+
+ /* This can happen when the user mis-configures its guests in an
+ * incompatible fashion or without a CPU model. For example using
+ * qemu with -cpu host (which is not migration safe) and do a
+ * migration from a host that has AIS to a host that has no AIS.
+ * In that case the target system will reject the migration here.
+ */
+ if (!ais_needed(flic)) {
+ return -ENOSYS;
+ }
+
+ return ioctl(flic->fd, KVM_SET_DEVICE_ATTR, &attr) ? -errno : 0;
+}
+
+static const VMStateDescription kvm_s390_flic_ais_tmp = {
+ .name = "s390-flic-ais-tmp",
+ .pre_save = kvm_flic_ais_pre_save,
+ .post_load = kvm_flic_ais_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(simm, KVMS390FLICStateMigTmp),
+ VMSTATE_UINT8(nimm, KVMS390FLICStateMigTmp),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription kvm_s390_flic_vmstate_ais = {
+ .name = "s390-flic/ais",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ais_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_WITH_TMP(KVMS390FLICState, KVMS390FLICStateMigTmp,
+ kvm_s390_flic_ais_tmp),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static const VMStateDescription kvm_s390_flic_vmstate = {
+ /* should have been like kvm-s390-flic,
+ * can't change without breaking compat */
.name = "s390-flic",
.version_id = FLIC_SAVEVM_VERSION,
.minimum_version_id = FLIC_SAVEVM_VERSION,
.flags = VMS_SINGLE,
},
VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription * []) {
+ &kvm_s390_flic_vmstate_ais,
+ NULL
}
};
+typedef struct KVMS390FLICStateClass {
+ S390FLICStateClass parent_class;
+ DeviceRealize parent_realize;
+} KVMS390FLICStateClass;
+
+#define KVM_S390_FLIC_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(KVMS390FLICStateClass, (obj), TYPE_KVM_S390_FLIC)
+
+#define KVM_S390_FLIC_CLASS(klass) \
+ OBJECT_CLASS_CHECK(KVMS390FLICStateClass, (klass), TYPE_KVM_S390_FLIC)
+
static void kvm_s390_flic_realize(DeviceState *dev, Error **errp)
{
KVMS390FLICState *flic_state = KVM_S390_FLIC(dev);
struct kvm_create_device cd = {0};
struct kvm_device_attr test_attr = {0};
int ret;
+ Error *errp_local = NULL;
+ KVM_S390_FLIC_GET_CLASS(dev)->parent_realize(dev, &errp_local);
+ if (errp_local) {
+ goto fail;
+ }
flic_state->fd = -1;
if (!kvm_check_extension(kvm_state, KVM_CAP_DEVICE_CTRL)) {
+ error_setg_errno(&errp_local, errno, "KVM is missing capability"
+ " KVM_CAP_DEVICE_CTRL");
trace_flic_no_device_api(errno);
- return;
+ goto fail;
}
cd.type = KVM_DEV_TYPE_FLIC;
ret = kvm_vm_ioctl(kvm_state, KVM_CREATE_DEVICE, &cd);
if (ret < 0) {
+ error_setg_errno(&errp_local, errno, "Creating the KVM device failed");
trace_flic_create_device(errno);
- return;
+ goto fail;
}
flic_state->fd = cd.fd;
test_attr.group = KVM_DEV_FLIC_CLEAR_IO_IRQ;
flic_state->clear_io_supported = !ioctl(flic_state->fd,
KVM_HAS_DEVICE_ATTR, test_attr);
-
+ return;
+fail:
+ error_propagate(errp, errp_local);
}
static void kvm_s390_flic_reset(DeviceState *dev)
{
KVMS390FLICState *flic = KVM_S390_FLIC(dev);
+ S390FLICState *fs = S390_FLIC_COMMON(dev);
struct kvm_device_attr attr = {
.group = KVM_DEV_FLIC_CLEAR_IRQS,
};
int rc = 0;
+ uint8_t isc;
if (flic->fd == -1) {
return;
flic_disable_wait_pfault(flic);
+ if (fs->ais_supported) {
+ for (isc = 0; isc <= MAX_ISC; isc++) {
+ rc = kvm_s390_modify_ais_mode(fs, isc, SIC_IRQ_MODE_ALL);
+ if (rc) {
+ error_report("Failed to reset ais mode for isc %d: %s",
+ isc, strerror(-rc));
+ }
+ }
+ }
+
rc = ioctl(flic->fd, KVM_SET_DEVICE_ATTR, &attr);
if (rc) {
trace_flic_reset_failed(errno);
DeviceClass *dc = DEVICE_CLASS(oc);
S390FLICStateClass *fsc = S390_FLIC_COMMON_CLASS(oc);
+ KVM_S390_FLIC_CLASS(oc)->parent_realize = dc->realize;
dc->realize = kvm_s390_flic_realize;
dc->vmsd = &kvm_s390_flic_vmstate;
dc->reset = kvm_s390_flic_reset;
fsc->add_adapter_routes = kvm_s390_add_adapter_routes;
fsc->release_adapter_routes = kvm_s390_release_adapter_routes;
fsc->clear_io_irq = kvm_s390_clear_io_flic;
+ fsc->modify_ais_mode = kvm_s390_modify_ais_mode;
+ fsc->inject_airq = kvm_s390_inject_airq;
+ fsc->inject_service = kvm_s390_inject_service;
+ fsc->inject_io = kvm_s390_inject_io;
+ fsc->inject_crw_mchk = kvm_s390_inject_crw_mchk;
}
static const TypeInfo kvm_s390_flic_info = {
.name = TYPE_KVM_S390_FLIC,
.parent = TYPE_S390_FLIC_COMMON,
.instance_size = sizeof(KVMS390FLICState),
+ .class_size = sizeof(KVMS390FLICStateClass),
.class_init = kvm_s390_flic_class_init,
};