| 1 | /* |
| 2 | * Protected Virtualization functions |
| 3 | * |
| 4 | * Copyright IBM Corp. 2020 |
| 5 | * Author(s): |
| 6 | * Janosch Frank <frankja@linux.ibm.com> |
| 7 | * |
| 8 | * This work is licensed under the terms of the GNU GPL, version 2 or (at |
| 9 | * your option) any later version. See the COPYING file in the top-level |
| 10 | * directory. |
| 11 | */ |
| 12 | #include "qemu/osdep.h" |
| 13 | |
| 14 | #include <linux/kvm.h> |
| 15 | |
| 16 | #include "qemu/units.h" |
| 17 | #include "qapi/error.h" |
| 18 | #include "qemu/error-report.h" |
| 19 | #include "system/kvm.h" |
| 20 | #include "system/cpus.h" |
| 21 | #include "qom/object_interfaces.h" |
| 22 | #include "system/confidential-guest-support.h" |
| 23 | #include "hw/s390x/ipl.h" |
| 24 | #include "hw/s390x/sclp.h" |
| 25 | #include "target/s390x/kvm/kvm_s390x.h" |
| 26 | #include "target/s390x/kvm/pv.h" |
| 27 | |
| 28 | bool s390_is_pv(void) |
| 29 | { |
| 30 | static S390CcwMachineState *ccw; |
| 31 | Object *obj; |
| 32 | |
| 33 | if (ccw) { |
| 34 | return ccw->pv; |
| 35 | } |
| 36 | |
| 37 | /* we have to bail out for the "none" machine */ |
| 38 | obj = object_dynamic_cast(qdev_get_machine(), TYPE_S390_CCW_MACHINE); |
| 39 | if (!obj) { |
| 40 | return false; |
| 41 | } |
| 42 | ccw = S390_CCW_MACHINE(obj); |
| 43 | return ccw->pv; |
| 44 | } |
| 45 | |
| 46 | static bool info_valid; |
| 47 | static struct kvm_s390_pv_info_vm info_vm; |
| 48 | static struct kvm_s390_pv_info_dump info_dump; |
| 49 | |
| 50 | static int __s390_pv_cmd(uint32_t cmd, const char *cmdname, void *data, |
| 51 | struct S390PVResponse *pv_resp) |
| 52 | { |
| 53 | struct kvm_pv_cmd pv_cmd = { |
| 54 | .cmd = cmd, |
| 55 | .data = (uint64_t)data, |
| 56 | }; |
| 57 | int rc; |
| 58 | |
| 59 | do { |
| 60 | rc = kvm_vm_ioctl(kvm_state, KVM_S390_PV_COMMAND, &pv_cmd); |
| 61 | } while (rc == -EINTR); |
| 62 | |
| 63 | if (rc) { |
| 64 | error_report("KVM PV command %d (%s) failed: header rc %x rrc %x " |
| 65 | "IOCTL rc: %d", cmd, cmdname, pv_cmd.rc, pv_cmd.rrc, |
| 66 | rc); |
| 67 | } |
| 68 | if (pv_resp) { |
| 69 | pv_resp->cmd = cmd; |
| 70 | pv_resp->rc = pv_cmd.rc; |
| 71 | pv_resp->rrc = pv_cmd.rrc; |
| 72 | } |
| 73 | return rc; |
| 74 | } |
| 75 | |
| 76 | /* |
| 77 | * This macro lets us pass the command as a string to the function so |
| 78 | * we can print it on an error. |
| 79 | */ |
| 80 | #define s390_pv_cmd(cmd, data) __s390_pv_cmd(cmd, #cmd, data, NULL) |
| 81 | #define s390_pv_cmd_pv_resp(cmd, data, pv_resp) \ |
| 82 | __s390_pv_cmd(cmd, #cmd, data, pv_resp) |
| 83 | |
| 84 | static void s390_pv_cmd_exit(uint32_t cmd, void *data) |
| 85 | { |
| 86 | if (s390_pv_cmd(cmd, data)) { |
| 87 | exit(1); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | int s390_pv_query_info(void) |
| 92 | { |
| 93 | struct kvm_s390_pv_info info = { |
| 94 | .header.id = KVM_PV_INFO_VM, |
| 95 | .header.len_max = sizeof(info.header) + sizeof(info.vm), |
| 96 | }; |
| 97 | int rc; |
| 98 | |
| 99 | /* Info API's first user is dump so they are bundled */ |
| 100 | if (!kvm_s390_get_protected_dump()) { |
| 101 | return 0; |
| 102 | } |
| 103 | |
| 104 | rc = s390_pv_cmd(KVM_PV_INFO, &info); |
| 105 | if (rc) { |
| 106 | error_report("KVM PV INFO cmd %x failed: %s", |
| 107 | info.header.id, strerror(-rc)); |
| 108 | return rc; |
| 109 | } |
| 110 | memcpy(&info_vm, &info.vm, sizeof(info.vm)); |
| 111 | |
| 112 | info.header.id = KVM_PV_INFO_DUMP; |
| 113 | info.header.len_max = sizeof(info.header) + sizeof(info.dump); |
| 114 | rc = s390_pv_cmd(KVM_PV_INFO, &info); |
| 115 | if (rc) { |
| 116 | error_report("KVM PV INFO cmd %x failed: %s", |
| 117 | info.header.id, strerror(-rc)); |
| 118 | return rc; |
| 119 | } |
| 120 | |
| 121 | memcpy(&info_dump, &info.dump, sizeof(info.dump)); |
| 122 | info_valid = true; |
| 123 | |
| 124 | return rc; |
| 125 | } |
| 126 | |
| 127 | int s390_pv_vm_enable(void) |
| 128 | { |
| 129 | return s390_pv_cmd(KVM_PV_ENABLE, NULL); |
| 130 | } |
| 131 | |
| 132 | void s390_pv_vm_disable(void) |
| 133 | { |
| 134 | s390_pv_cmd_exit(KVM_PV_DISABLE, NULL); |
| 135 | } |
| 136 | |
| 137 | static void *s390_pv_do_unprot_async_fn(void *p) |
| 138 | { |
| 139 | s390_pv_cmd_exit(KVM_PV_ASYNC_CLEANUP_PERFORM, NULL); |
| 140 | return NULL; |
| 141 | } |
| 142 | |
| 143 | bool s390_pv_vm_try_disable_async(S390CcwMachineState *ms) |
| 144 | { |
| 145 | /* |
| 146 | * t is only needed to create the thread; once qemu_thread_create |
| 147 | * returns, it can safely be discarded. |
| 148 | */ |
| 149 | QemuThread t; |
| 150 | |
| 151 | /* |
| 152 | * If the feature is not present or if the VM is not larger than 2 GiB, |
| 153 | * KVM_PV_ASYNC_CLEANUP_PREPARE fill fail; no point in attempting it. |
| 154 | */ |
| 155 | if (s390_get_memory_limit(ms) <= 2 * GiB || |
| 156 | !kvm_check_extension(kvm_state, KVM_CAP_S390_PROTECTED_ASYNC_DISABLE)) { |
| 157 | return false; |
| 158 | } |
| 159 | if (s390_pv_cmd(KVM_PV_ASYNC_CLEANUP_PREPARE, NULL) != 0) { |
| 160 | return false; |
| 161 | } |
| 162 | |
| 163 | qemu_thread_create(&t, "async_cleanup", s390_pv_do_unprot_async_fn, NULL, |
| 164 | QEMU_THREAD_DETACHED); |
| 165 | |
| 166 | return true; |
| 167 | } |
| 168 | |
| 169 | #define UV_RC_SSC_INVAL_HOSTKEY 0x0108 |
| 170 | int s390_pv_set_sec_parms(uint64_t origin, uint64_t length, |
| 171 | struct S390PVResponse *pv_resp, Error **errp) |
| 172 | { |
| 173 | int ret; |
| 174 | struct kvm_s390_pv_sec_parm args = { |
| 175 | .origin = origin, |
| 176 | .length = length, |
| 177 | }; |
| 178 | |
| 179 | ret = s390_pv_cmd_pv_resp(KVM_PV_SET_SEC_PARMS, &args, pv_resp); |
| 180 | if (ret) { |
| 181 | error_setg(errp, "Failed to set secure execution parameters"); |
| 182 | if (pv_resp->rc == UV_RC_SSC_INVAL_HOSTKEY) { |
| 183 | error_append_hint(errp, "Please check whether the image is " |
| 184 | "correctly encrypted for this host\n"); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | return ret; |
| 189 | } |
| 190 | |
| 191 | /* |
| 192 | * Called for each component in the SE type IPL parameter block 0. |
| 193 | */ |
| 194 | int s390_pv_unpack(uint64_t addr, uint64_t size, |
| 195 | uint64_t tweak, struct S390PVResponse *pv_resp) |
| 196 | { |
| 197 | struct kvm_s390_pv_unp args = { |
| 198 | .addr = addr, |
| 199 | .size = size, |
| 200 | .tweak = tweak, |
| 201 | }; |
| 202 | |
| 203 | return s390_pv_cmd_pv_resp(KVM_PV_UNPACK, &args, pv_resp); |
| 204 | } |
| 205 | |
| 206 | void s390_pv_prep_reset(void) |
| 207 | { |
| 208 | s390_pv_cmd_exit(KVM_PV_PREP_RESET, NULL); |
| 209 | } |
| 210 | |
| 211 | int s390_pv_verify(struct S390PVResponse *pv_resp) |
| 212 | { |
| 213 | return s390_pv_cmd_pv_resp(KVM_PV_VERIFY, NULL, pv_resp); |
| 214 | } |
| 215 | |
| 216 | void s390_pv_unshare(void) |
| 217 | { |
| 218 | s390_pv_cmd_exit(KVM_PV_UNSHARE_ALL, NULL); |
| 219 | } |
| 220 | |
| 221 | void s390_pv_inject_reset_error(CPUState *cs, |
| 222 | struct S390PVResponse pv_resp) |
| 223 | { |
| 224 | int r1 = (cs->kvm_run->s390_sieic.ipa & 0x00f0) >> 4; |
| 225 | CPUS390XState *env = &S390_CPU(cs)->env; |
| 226 | |
| 227 | union { |
| 228 | struct { |
| 229 | uint16_t pv_cmd; |
| 230 | uint16_t pv_rrc; |
| 231 | uint16_t pv_rc; |
| 232 | uint16_t diag_rc; |
| 233 | }; |
| 234 | uint64_t regs; |
| 235 | } resp = { |
| 236 | .pv_cmd = pv_resp.cmd, |
| 237 | .pv_rrc = pv_resp.rrc, |
| 238 | .pv_rc = pv_resp.rc, |
| 239 | .diag_rc = DIAG_308_RC_INVAL_FOR_PV |
| 240 | }; |
| 241 | |
| 242 | /* Report that we are unable to enter protected mode */ |
| 243 | env->regs[r1 + 1] = resp.regs; |
| 244 | } |
| 245 | |
| 246 | uint64_t kvm_s390_pv_dmp_get_size_cpu(void) |
| 247 | { |
| 248 | return info_dump.dump_cpu_buffer_len; |
| 249 | } |
| 250 | |
| 251 | uint64_t kvm_s390_pv_dmp_get_size_completion_data(void) |
| 252 | { |
| 253 | return info_dump.dump_config_finalize_len; |
| 254 | } |
| 255 | |
| 256 | uint64_t kvm_s390_pv_dmp_get_size_mem_state(void) |
| 257 | { |
| 258 | return info_dump.dump_config_mem_buffer_per_1m; |
| 259 | } |
| 260 | |
| 261 | bool kvm_s390_pv_info_basic_valid(void) |
| 262 | { |
| 263 | return info_valid; |
| 264 | } |
| 265 | |
| 266 | static int s390_pv_dump_cmd(uint64_t subcmd, uint64_t uaddr, uint64_t gaddr, |
| 267 | uint64_t len) |
| 268 | { |
| 269 | struct kvm_s390_pv_dmp dmp = { |
| 270 | .subcmd = subcmd, |
| 271 | .buff_addr = uaddr, |
| 272 | .buff_len = len, |
| 273 | .gaddr = gaddr, |
| 274 | }; |
| 275 | int ret; |
| 276 | |
| 277 | ret = s390_pv_cmd(KVM_PV_DUMP, (void *)&dmp); |
| 278 | if (ret) { |
| 279 | error_report("KVM DUMP command %ld failed", subcmd); |
| 280 | } |
| 281 | return ret; |
| 282 | } |
| 283 | |
| 284 | int kvm_s390_dump_cpu(S390CPU *cpu, void *buff) |
| 285 | { |
| 286 | struct kvm_s390_pv_dmp dmp = { |
| 287 | .subcmd = KVM_PV_DUMP_CPU, |
| 288 | .buff_addr = (uint64_t)buff, |
| 289 | .gaddr = 0, |
| 290 | .buff_len = info_dump.dump_cpu_buffer_len, |
| 291 | }; |
| 292 | struct kvm_pv_cmd pv = { |
| 293 | .cmd = KVM_PV_DUMP, |
| 294 | .data = (uint64_t)&dmp, |
| 295 | }; |
| 296 | |
| 297 | return kvm_vcpu_ioctl(CPU(cpu), KVM_S390_PV_CPU_COMMAND, &pv); |
| 298 | } |
| 299 | |
| 300 | int kvm_s390_dump_init(void) |
| 301 | { |
| 302 | return s390_pv_dump_cmd(KVM_PV_DUMP_INIT, 0, 0, 0); |
| 303 | } |
| 304 | |
| 305 | int kvm_s390_dump_mem_state(uint64_t gaddr, size_t len, void *dest) |
| 306 | { |
| 307 | return s390_pv_dump_cmd(KVM_PV_DUMP_CONFIG_STOR_STATE, (uint64_t)dest, |
| 308 | gaddr, len); |
| 309 | } |
| 310 | |
| 311 | int kvm_s390_dump_completion_data(void *buff) |
| 312 | { |
| 313 | return s390_pv_dump_cmd(KVM_PV_DUMP_COMPLETE, (uint64_t)buff, 0, |
| 314 | info_dump.dump_config_finalize_len); |
| 315 | } |
| 316 | |
| 317 | #define TYPE_S390_PV_GUEST "s390-pv-guest" |
| 318 | OBJECT_DECLARE_SIMPLE_TYPE(S390PVGuest, S390_PV_GUEST) |
| 319 | |
| 320 | /** |
| 321 | * S390PVGuest: |
| 322 | * |
| 323 | * The S390PVGuest object is basically a dummy used to tell the |
| 324 | * confidential guest support system to use s390's PV mechanism. |
| 325 | * |
| 326 | * # $QEMU \ |
| 327 | * -object s390-pv-guest,id=pv0 \ |
| 328 | * -machine ...,confidential-guest-support=pv0 |
| 329 | */ |
| 330 | struct S390PVGuest { |
| 331 | ConfidentialGuestSupport parent_obj; |
| 332 | }; |
| 333 | |
| 334 | typedef struct S390PVGuestClass S390PVGuestClass; |
| 335 | |
| 336 | struct S390PVGuestClass { |
| 337 | ConfidentialGuestSupportClass parent_class; |
| 338 | }; |
| 339 | |
| 340 | /* |
| 341 | * If protected virtualization is enabled, the amount of data that the |
| 342 | * Read SCP Info Service Call can use is limited to one page. The |
| 343 | * available space also depends on the Extended-Length SCCB (ELS) |
| 344 | * feature which can take more buffer space to store feature |
| 345 | * information. This impacts the maximum number of CPUs supported in |
| 346 | * the machine. |
| 347 | */ |
| 348 | static uint32_t s390_pv_get_max_cpus(void) |
| 349 | { |
| 350 | int offset_cpu = s390_has_feat(S390_FEAT_EXTENDED_LENGTH_SCCB) ? |
| 351 | offsetof(ReadInfo, entries) : SCLP_READ_SCP_INFO_FIXED_CPU_OFFSET; |
| 352 | |
| 353 | return (TARGET_PAGE_SIZE - offset_cpu) / sizeof(CPUEntry); |
| 354 | } |
| 355 | |
| 356 | static bool s390_pv_check_cpus(Error **errp) |
| 357 | { |
| 358 | MachineState *ms = MACHINE(qdev_get_machine()); |
| 359 | uint32_t pv_max_cpus = s390_pv_get_max_cpus(); |
| 360 | |
| 361 | if (ms->smp.max_cpus > pv_max_cpus) { |
| 362 | error_setg(errp, "Protected VMs support a maximum of %d CPUs", |
| 363 | pv_max_cpus); |
| 364 | return false; |
| 365 | } |
| 366 | |
| 367 | return true; |
| 368 | } |
| 369 | |
| 370 | static bool s390_pv_guest_check(ConfidentialGuestSupport *cgs, Error **errp) |
| 371 | { |
| 372 | return s390_pv_check_cpus(errp); |
| 373 | } |
| 374 | |
| 375 | static int s390_pv_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) |
| 376 | { |
| 377 | if (!object_dynamic_cast(OBJECT(cgs), TYPE_S390_PV_GUEST)) { |
| 378 | return 0; |
| 379 | } |
| 380 | |
| 381 | if (!kvm_enabled()) { |
| 382 | error_setg(errp, "Protected Virtualization requires KVM"); |
| 383 | return -1; |
| 384 | } |
| 385 | |
| 386 | if (!s390_has_feat(S390_FEAT_UNPACK)) { |
| 387 | error_setg(errp, |
| 388 | "CPU model does not support Protected Virtualization"); |
| 389 | return -1; |
| 390 | } |
| 391 | |
| 392 | if (!s390_pv_guest_check(cgs, errp)) { |
| 393 | return -1; |
| 394 | } |
| 395 | |
| 396 | cgs->ready = true; |
| 397 | |
| 398 | return 0; |
| 399 | } |
| 400 | |
| 401 | OBJECT_DEFINE_TYPE_WITH_INTERFACES(S390PVGuest, |
| 402 | s390_pv_guest, |
| 403 | S390_PV_GUEST, |
| 404 | CONFIDENTIAL_GUEST_SUPPORT, |
| 405 | { TYPE_USER_CREATABLE }, |
| 406 | { NULL }) |
| 407 | |
| 408 | static void s390_pv_guest_class_init(ObjectClass *oc, const void *data) |
| 409 | { |
| 410 | ConfidentialGuestSupportClass *klass = CONFIDENTIAL_GUEST_SUPPORT_CLASS(oc); |
| 411 | |
| 412 | klass->kvm_init = s390_pv_kvm_init; |
| 413 | } |
| 414 | |
| 415 | static void s390_pv_guest_init(Object *obj) |
| 416 | { |
| 417 | } |
| 418 | |
| 419 | static void s390_pv_guest_finalize(Object *obj) |
| 420 | { |
| 421 | } |