123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- /* SPDX-License-Identifier: GPL-2.0-or-later */
- /*
- * CPU Topology
- *
- * Copyright IBM Corp. 2022, 2023
- * Author(s): Pierre Morel <pmorel@linux.ibm.com>
- *
- * S390 topology handling can be divided in two parts:
- *
- * - The first part in this file is taking care of all common functions
- * used by KVM and TCG to create and modify the topology.
- *
- * - The second part, building the topology information data for the
- * guest with CPU and KVM specificity will be implemented inside
- * the target/s390/kvm sub tree.
- */
- #include "qemu/osdep.h"
- #include "qapi/error.h"
- #include "qemu/error-report.h"
- #include "hw/qdev-properties.h"
- #include "hw/boards.h"
- #include "target/s390x/cpu.h"
- #include "hw/s390x/s390-virtio-ccw.h"
- #include "hw/s390x/cpu-topology.h"
- #include "qapi/qapi-commands-machine-target.h"
- #include "qapi/qapi-events-machine-target.h"
- /*
- * s390_topology is used to keep the topology information.
- * .cores_per_socket: tracks information on the count of cores
- * per socket.
- * .polarization: tracks machine polarization.
- */
- S390Topology s390_topology = {
- /* will be initialized after the CPU model is realized */
- .cores_per_socket = NULL,
- .polarization = S390_CPU_POLARIZATION_HORIZONTAL,
- };
- /**
- * s390_socket_nb:
- * @cpu: s390x CPU
- *
- * Returns the socket number used inside the cores_per_socket array
- * for a topology tree entry
- */
- static int s390_socket_nb_from_ids(int drawer_id, int book_id, int socket_id)
- {
- return (drawer_id * current_machine->smp.books + book_id) *
- current_machine->smp.sockets + socket_id;
- }
- /**
- * s390_socket_nb:
- * @cpu: s390x CPU
- *
- * Returns the socket number used inside the cores_per_socket array
- * for a cpu.
- */
- static int s390_socket_nb(S390CPU *cpu)
- {
- return s390_socket_nb_from_ids(cpu->env.drawer_id, cpu->env.book_id,
- cpu->env.socket_id);
- }
- /**
- * s390_has_topology:
- *
- * Return: true if the topology is supported by the machine.
- */
- bool s390_has_topology(void)
- {
- return s390_has_feat(S390_FEAT_CONFIGURATION_TOPOLOGY);
- }
- /**
- * s390_topology_init:
- * @ms: the machine state where the machine topology is defined
- *
- * Keep track of the machine topology.
- *
- * Allocate an array to keep the count of cores per socket.
- * The index of the array starts at socket 0 from book 0 and
- * drawer 0 up to the maximum allowed by the machine topology.
- */
- static void s390_topology_init(MachineState *ms)
- {
- CpuTopology *smp = &ms->smp;
- s390_topology.cores_per_socket = g_new0(uint8_t, smp->sockets *
- smp->books * smp->drawers);
- }
- /*
- * s390_handle_ptf:
- *
- * @register 1: contains the function code
- *
- * Function codes 0 (horizontal) and 1 (vertical) define the CPU
- * polarization requested by the guest.
- *
- * Function code 2 is handling topology changes and is interpreted
- * by the SIE.
- */
- void s390_handle_ptf(S390CPU *cpu, uint8_t r1, uintptr_t ra)
- {
- S390CpuPolarization polarization;
- CPUS390XState *env = &cpu->env;
- uint64_t reg = env->regs[r1];
- int fc = reg & S390_TOPO_FC_MASK;
- if (!s390_has_feat(S390_FEAT_CONFIGURATION_TOPOLOGY)) {
- s390_program_interrupt(env, PGM_OPERATION, ra);
- return;
- }
- if (env->psw.mask & PSW_MASK_PSTATE) {
- s390_program_interrupt(env, PGM_PRIVILEGED, ra);
- return;
- }
- if (reg & ~S390_TOPO_FC_MASK) {
- s390_program_interrupt(env, PGM_SPECIFICATION, ra);
- return;
- }
- polarization = S390_CPU_POLARIZATION_VERTICAL;
- switch (fc) {
- case 0:
- polarization = S390_CPU_POLARIZATION_HORIZONTAL;
- /* fallthrough */
- case 1:
- if (s390_topology.polarization == polarization) {
- env->regs[r1] |= S390_PTF_REASON_DONE;
- setcc(cpu, 2);
- } else {
- s390_topology.polarization = polarization;
- s390_cpu_topology_set_changed(true);
- qapi_event_send_cpu_polarization_change(polarization);
- setcc(cpu, 0);
- }
- break;
- default:
- /* Note that fc == 2 is interpreted by the SIE */
- s390_program_interrupt(env, PGM_SPECIFICATION, ra);
- }
- }
- /**
- * s390_topology_reset:
- *
- * Generic reset for CPU topology, calls s390_topology_reset()
- * to reset the kernel Modified Topology Change Record.
- */
- void s390_topology_reset(void)
- {
- s390_cpu_topology_set_changed(false);
- s390_topology.polarization = S390_CPU_POLARIZATION_HORIZONTAL;
- }
- /**
- * s390_topology_cpu_default:
- * @cpu: pointer to a S390CPU
- * @errp: Error pointer
- *
- * Setup the default topology if no attributes are already set.
- * Passing a CPU with some, but not all, attributes set is considered
- * an error.
- *
- * The function calculates the (drawer_id, book_id, socket_id)
- * topology by filling the cores starting from the first socket
- * (0, 0, 0) up to the last (smp->drawers, smp->books, smp->sockets).
- *
- * CPU type and dedication have defaults values set in the
- * s390x_cpu_properties, entitlement must be adjust depending on the
- * dedication.
- *
- * Returns false if it is impossible to setup a default topology
- * true otherwise.
- */
- static bool s390_topology_cpu_default(S390CPU *cpu, Error **errp)
- {
- CpuTopology *smp = ¤t_machine->smp;
- CPUS390XState *env = &cpu->env;
- /* All geometry topology attributes must be set or all unset */
- if ((env->socket_id < 0 || env->book_id < 0 || env->drawer_id < 0) &&
- (env->socket_id >= 0 || env->book_id >= 0 || env->drawer_id >= 0)) {
- error_setg(errp,
- "Please define all or none of the topology geometry attributes");
- return false;
- }
- /* If one value is unset all are unset -> calculate defaults */
- if (env->socket_id < 0) {
- env->socket_id = s390_std_socket(env->core_id, smp);
- env->book_id = s390_std_book(env->core_id, smp);
- env->drawer_id = s390_std_drawer(env->core_id, smp);
- }
- /*
- * When the user specifies the entitlement as 'auto' on the command line,
- * QEMU will set the entitlement as:
- * Medium when the CPU is not dedicated.
- * High when dedicated is true.
- */
- if (env->entitlement == S390_CPU_ENTITLEMENT_AUTO) {
- if (env->dedicated) {
- env->entitlement = S390_CPU_ENTITLEMENT_HIGH;
- } else {
- env->entitlement = S390_CPU_ENTITLEMENT_MEDIUM;
- }
- }
- return true;
- }
- /**
- * s390_topology_check:
- * @socket_id: socket to check
- * @book_id: book to check
- * @drawer_id: drawer to check
- * @entitlement: entitlement to check
- * @dedicated: dedication to check
- * @errp: Error pointer
- *
- * The function checks if the topology
- * attributes fits inside the system topology.
- *
- * Returns false if the specified topology does not match with
- * the machine topology.
- */
- static bool s390_topology_check(uint16_t socket_id, uint16_t book_id,
- uint16_t drawer_id, uint16_t entitlement,
- bool dedicated, Error **errp)
- {
- CpuTopology *smp = ¤t_machine->smp;
- if (socket_id >= smp->sockets) {
- error_setg(errp, "Unavailable socket: %d", socket_id);
- return false;
- }
- if (book_id >= smp->books) {
- error_setg(errp, "Unavailable book: %d", book_id);
- return false;
- }
- if (drawer_id >= smp->drawers) {
- error_setg(errp, "Unavailable drawer: %d", drawer_id);
- return false;
- }
- if (entitlement >= S390_CPU_ENTITLEMENT__MAX) {
- error_setg(errp, "Unknown entitlement: %d", entitlement);
- return false;
- }
- if (dedicated && (entitlement == S390_CPU_ENTITLEMENT_LOW ||
- entitlement == S390_CPU_ENTITLEMENT_MEDIUM)) {
- error_setg(errp, "A dedicated CPU implies high entitlement");
- return false;
- }
- return true;
- }
- /**
- * s390_topology_need_report
- * @cpu: Current cpu
- * @drawer_id: future drawer ID
- * @book_id: future book ID
- * @socket_id: future socket ID
- * @entitlement: future entitlement
- * @dedicated: future dedicated
- *
- * A modified topology change report is needed if the topology
- * tree or the topology attributes change.
- */
- static bool s390_topology_need_report(S390CPU *cpu, int drawer_id,
- int book_id, int socket_id,
- uint16_t entitlement, bool dedicated)
- {
- return cpu->env.drawer_id != drawer_id ||
- cpu->env.book_id != book_id ||
- cpu->env.socket_id != socket_id ||
- cpu->env.entitlement != entitlement ||
- cpu->env.dedicated != dedicated;
- }
- /**
- * s390_update_cpu_props:
- * @ms: the machine state
- * @cpu: the CPU for which to update the properties from the environment.
- *
- */
- static void s390_update_cpu_props(MachineState *ms, S390CPU *cpu)
- {
- CpuInstanceProperties *props;
- props = &ms->possible_cpus->cpus[cpu->env.core_id].props;
- props->socket_id = cpu->env.socket_id;
- props->book_id = cpu->env.book_id;
- props->drawer_id = cpu->env.drawer_id;
- }
- /**
- * s390_topology_setup_cpu:
- * @ms: MachineState used to initialize the topology structure on
- * first call.
- * @cpu: the new S390CPU to insert in the topology structure
- * @errp: the error pointer
- *
- * Called from CPU hotplug to check and setup the CPU attributes
- * before the CPU is inserted in the topology.
- * There is no need to update the MTCR explicitly here because it
- * will be updated by KVM on creation of the new CPU.
- */
- void s390_topology_setup_cpu(MachineState *ms, S390CPU *cpu, Error **errp)
- {
- int entry;
- /*
- * We do not want to initialize the topology if the CPU model
- * does not support topology, consequently, we have to wait for
- * the first CPU to be realized, which realizes the CPU model
- * to initialize the topology structures.
- *
- * s390_topology_setup_cpu() is called from the CPU hotplug.
- */
- if (!s390_topology.cores_per_socket) {
- s390_topology_init(ms);
- }
- if (!s390_topology_cpu_default(cpu, errp)) {
- return;
- }
- if (!s390_topology_check(cpu->env.socket_id, cpu->env.book_id,
- cpu->env.drawer_id, cpu->env.entitlement,
- cpu->env.dedicated, errp)) {
- return;
- }
- /* Do we still have space in the socket */
- entry = s390_socket_nb(cpu);
- if (s390_topology.cores_per_socket[entry] >= ms->smp.cores) {
- error_setg(errp, "No more space on this socket");
- return;
- }
- /* Update the count of cores in sockets */
- s390_topology.cores_per_socket[entry] += 1;
- /* topology tree is reflected in props */
- s390_update_cpu_props(ms, cpu);
- }
- static void s390_change_topology(uint16_t core_id,
- bool has_socket_id, uint16_t socket_id,
- bool has_book_id, uint16_t book_id,
- bool has_drawer_id, uint16_t drawer_id,
- bool has_entitlement,
- S390CpuEntitlement entitlement,
- bool has_dedicated, bool dedicated,
- Error **errp)
- {
- MachineState *ms = current_machine;
- int old_socket_entry;
- int new_socket_entry;
- bool report_needed;
- S390CPU *cpu;
- cpu = s390_cpu_addr2state(core_id);
- if (!cpu) {
- error_setg(errp, "Core-id %d does not exist!", core_id);
- return;
- }
- /* Get attributes not provided from cpu and verify the new topology */
- if (!has_socket_id) {
- socket_id = cpu->env.socket_id;
- }
- if (!has_book_id) {
- book_id = cpu->env.book_id;
- }
- if (!has_drawer_id) {
- drawer_id = cpu->env.drawer_id;
- }
- if (!has_dedicated) {
- dedicated = cpu->env.dedicated;
- }
- /*
- * When the user specifies the entitlement as 'auto' on the command line,
- * QEMU will set the entitlement as:
- * Medium when the CPU is not dedicated.
- * High when dedicated is true.
- */
- if (!has_entitlement || entitlement == S390_CPU_ENTITLEMENT_AUTO) {
- if (dedicated) {
- entitlement = S390_CPU_ENTITLEMENT_HIGH;
- } else {
- entitlement = S390_CPU_ENTITLEMENT_MEDIUM;
- }
- }
- if (!s390_topology_check(socket_id, book_id, drawer_id,
- entitlement, dedicated, errp)) {
- return;
- }
- /* Check for space on new socket */
- old_socket_entry = s390_socket_nb(cpu);
- new_socket_entry = s390_socket_nb_from_ids(drawer_id, book_id, socket_id);
- if (new_socket_entry != old_socket_entry) {
- if (s390_topology.cores_per_socket[new_socket_entry] >=
- ms->smp.cores) {
- error_setg(errp, "No more space on this socket");
- return;
- }
- /* Update the count of cores in sockets */
- s390_topology.cores_per_socket[new_socket_entry] += 1;
- s390_topology.cores_per_socket[old_socket_entry] -= 1;
- }
- /* Check if we will need to report the modified topology */
- report_needed = s390_topology_need_report(cpu, drawer_id, book_id,
- socket_id, entitlement,
- dedicated);
- /* All checks done, report new topology into the vCPU */
- cpu->env.drawer_id = drawer_id;
- cpu->env.book_id = book_id;
- cpu->env.socket_id = socket_id;
- cpu->env.dedicated = dedicated;
- cpu->env.entitlement = entitlement;
- /* topology tree is reflected in props */
- s390_update_cpu_props(ms, cpu);
- /* Advertise the topology change */
- if (report_needed) {
- s390_cpu_topology_set_changed(true);
- }
- }
- void qmp_set_cpu_topology(uint16_t core,
- bool has_socket, uint16_t socket,
- bool has_book, uint16_t book,
- bool has_drawer, uint16_t drawer,
- bool has_entitlement, S390CpuEntitlement entitlement,
- bool has_dedicated, bool dedicated,
- Error **errp)
- {
- if (!s390_has_topology()) {
- error_setg(errp, "This machine doesn't support topology");
- return;
- }
- s390_change_topology(core, has_socket, socket, has_book, book,
- has_drawer, drawer, has_entitlement, entitlement,
- has_dedicated, dedicated, errp);
- }
- CpuPolarizationInfo *qmp_query_s390x_cpu_polarization(Error **errp)
- {
- CpuPolarizationInfo *info = g_new0(CpuPolarizationInfo, 1);
- info->polarization = s390_topology.polarization;
- return info;
- }
|