| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,244 @@ | ||
| .. _cpu-topology-s390x: | ||
|
|
||
| CPU topology on s390x | ||
| ===================== | ||
|
|
||
| Since QEMU 8.2, CPU topology on s390x provides up to 3 levels of | ||
| topology containers: drawers, books and sockets. They define a | ||
| tree-shaped hierarchy. | ||
|
|
||
| The socket container has one or more CPU entries. | ||
| Each of these CPU entries consists of a bitmap and three CPU attributes: | ||
|
|
||
| - CPU type | ||
| - entitlement | ||
| - dedication | ||
|
|
||
| Each bit set in the bitmap correspond to a core-id of a vCPU with matching | ||
| attributes. | ||
|
|
||
| This documentation provides general information on S390 CPU topology, | ||
| how to enable it and explains the new CPU attributes. | ||
| For information on how to modify the S390 CPU topology and how to | ||
| monitor polarization changes, see ``docs/devel/s390-cpu-topology.rst``. | ||
|
|
||
| Prerequisites | ||
| ------------- | ||
|
|
||
| To use the CPU topology, you need to run with KVM on a s390x host that | ||
| uses the Linux kernel v6.0 or newer (which provide the so-called | ||
| ``KVM_CAP_S390_CPU_TOPOLOGY`` capability that allows QEMU to signal the | ||
| CPU topology facility via the so-called STFLE bit 11 to the VM). | ||
|
|
||
| Enabling CPU topology | ||
| --------------------- | ||
|
|
||
| Currently, CPU topology is only enabled in the host model by default. | ||
|
|
||
| Enabling CPU topology in a CPU model is done by setting the CPU flag | ||
| ``ctop`` to ``on`` as in: | ||
|
|
||
| .. code-block:: bash | ||
| -cpu gen16b,ctop=on | ||
| Having the topology disabled by default allows migration between | ||
| old and new QEMU without adding new flags. | ||
|
|
||
| Default topology usage | ||
| ---------------------- | ||
|
|
||
| The CPU topology can be specified on the QEMU command line | ||
| with the ``-smp`` or the ``-device`` QEMU command arguments. | ||
|
|
||
| Note also that since 7.2 threads are no longer supported in the topology | ||
| and the ``-smp`` command line argument accepts only ``threads=1``. | ||
|
|
||
| If none of the containers attributes (drawers, books, sockets) are | ||
| specified for the ``-smp`` flag, the number of these containers | ||
| is 1. | ||
|
|
||
| Thus the following two options will result in the same topology: | ||
|
|
||
| .. code-block:: bash | ||
| -smp cpus=5,drawer=1,books=1,sockets=8,cores=4,maxcpus=32 | ||
| and | ||
|
|
||
| .. code-block:: bash | ||
| -smp cpus=5,sockets=8,cores=4,maxcpus=32 | ||
| When a CPU is defined by the ``-smp`` command argument, its position | ||
| inside the topology is calculated by adding the CPUs to the topology | ||
| based on the core-id starting with core-0 at position 0 of socket-0, | ||
| book-0, drawer-0 and filling all CPUs of socket-0 before filling socket-1 | ||
| of book-0 and so on up to the last socket of the last book of the last | ||
| drawer. | ||
|
|
||
| When a CPU is defined by the ``-device`` command argument, the | ||
| tree topology attributes must all be defined or all not defined. | ||
|
|
||
| .. code-block:: bash | ||
| -device gen16b-s390x-cpu,drawer-id=1,book-id=1,socket-id=2,core-id=1 | ||
| or | ||
|
|
||
| .. code-block:: bash | ||
| -device gen16b-s390x-cpu,core-id=1,dedicated=true | ||
| If none of the tree attributes (drawer, book, sockets), are specified | ||
| for the ``-device`` argument, like for all CPUs defined with the ``-smp`` | ||
| command argument the topology tree attributes will be set by simply | ||
| adding the CPUs to the topology based on the core-id. | ||
|
|
||
| QEMU will not try to resolve collisions and will report an error if the | ||
| CPU topology defined explicitly or implicitly on a ``-device`` | ||
| argument collides with the definition of a CPU implicitly defined | ||
| on the ``-smp`` argument. | ||
|
|
||
| When the topology modifier attributes are not defined for the | ||
| ``-device`` command argument they takes following default values: | ||
|
|
||
| - dedicated: ``false`` | ||
| - entitlement: ``medium`` | ||
|
|
||
|
|
||
| Hot plug | ||
| ++++++++ | ||
|
|
||
| New CPUs can be plugged using the device_add hmp command as in: | ||
|
|
||
| .. code-block:: bash | ||
| (qemu) device_add gen16b-s390x-cpu,core-id=9 | ||
| The placement of the CPU is derived from the core-id as described above. | ||
|
|
||
| The topology can of course also be fully defined: | ||
|
|
||
| .. code-block:: bash | ||
| (qemu) device_add gen16b-s390x-cpu,drawer-id=1,book-id=1,socket-id=2,core-id=1 | ||
| Examples | ||
| ++++++++ | ||
|
|
||
| In the following machine we define 8 sockets with 4 cores each. | ||
|
|
||
| .. code-block:: bash | ||
| $ qemu-system-s390x -m 2G \ | ||
| -cpu gen16b,ctop=on \ | ||
| -smp cpus=5,sockets=8,cores=4,maxcpus=32 \ | ||
| -device host-s390x-cpu,core-id=14 \ | ||
| A new CPUs can be plugged using the device_add hmp command as before: | ||
|
|
||
| .. code-block:: bash | ||
| (qemu) device_add gen16b-s390x-cpu,core-id=9 | ||
| The core-id defines the placement of the core in the topology by | ||
| starting with core 0 in socket 0 up to maxcpus. | ||
|
|
||
| In the example above: | ||
|
|
||
| * There are 5 CPUs provided to the guest with the ``-smp`` command line | ||
| They will take the core-ids 0,1,2,3,4 | ||
| As we have 4 cores in a socket, we have 4 CPUs provided | ||
| to the guest in socket 0, with core-ids 0,1,2,3. | ||
| The last CPU, with core-id 4, will be on socket 1. | ||
|
|
||
| * the core with ID 14 provided by the ``-device`` command line will | ||
| be placed in socket 3, with core-id 14 | ||
|
|
||
| * the core with ID 9 provided by the ``device_add`` qmp command will | ||
| be placed in socket 2, with core-id 9 | ||
|
|
||
|
|
||
| Polarization, entitlement and dedication | ||
| ---------------------------------------- | ||
|
|
||
| Polarization | ||
| ++++++++++++ | ||
|
|
||
| The polarization affects how the CPUs of a shared host are utilized/distributed | ||
| among guests. | ||
| The guest determines the polarization by using the PTF instruction. | ||
|
|
||
| Polarization defines two models of CPU provisioning: horizontal | ||
| and vertical. | ||
|
|
||
| The horizontal polarization is the default model on boot and after | ||
| subsystem reset. When horizontal polarization is in effect all vCPUs should | ||
| have about equal resource provisioning. | ||
|
|
||
| In the vertical polarization model vCPUs are unequal, but overall more resources | ||
| might be available. | ||
| The guest can make use of the vCPU entitlement information provided by the host | ||
| to optimize kernel thread scheduling. | ||
|
|
||
| A subsystem reset puts all vCPU of the configuration into the | ||
| horizontal polarization. | ||
|
|
||
| Entitlement | ||
| +++++++++++ | ||
|
|
||
| The vertical polarization specifies that the guest's vCPU can get | ||
| different real CPU provisioning: | ||
|
|
||
| - a vCPU with vertical high entitlement specifies that this | ||
| vCPU gets 100% of the real CPU provisioning. | ||
|
|
||
| - a vCPU with vertical medium entitlement specifies that this | ||
| vCPU shares the real CPU with other vCPUs. | ||
|
|
||
| - a vCPU with vertical low entitlement specifies that this | ||
| vCPU only gets real CPU provisioning when no other vCPUs needs it. | ||
|
|
||
| In the case a vCPU with vertical high entitlement does not use | ||
| the real CPU, the unused "slack" can be dispatched to other vCPU | ||
| with medium or low entitlement. | ||
|
|
||
| A vCPU can be "dedicated" in which case the vCPU is fully dedicated to a single | ||
| real CPU. | ||
|
|
||
| The dedicated bit is an indication of affinity of a vCPU for a real CPU | ||
| while the entitlement indicates the sharing or exclusivity of use. | ||
|
|
||
| Defining the topology on the command line | ||
| ----------------------------------------- | ||
|
|
||
| The topology can entirely be defined using -device cpu statements, | ||
| with the exception of CPU 0 which must be defined with the -smp | ||
| argument. | ||
|
|
||
| For example, here we set the position of the cores 1,2,3 to | ||
| drawer 1, book 1, socket 2 and cores 0,9 and 14 to drawer 0, | ||
| book 0, socket 0 without defining entitlement or dedication. | ||
| Core 4 will be set on its default position on socket 1 | ||
| (since we have 4 core per socket) and we define it as dedicated and | ||
| with vertical high entitlement. | ||
|
|
||
| .. code-block:: bash | ||
| $ qemu-system-s390x -m 2G \ | ||
| -cpu gen16b,ctop=on \ | ||
| -smp cpus=1,sockets=8,cores=4,maxcpus=32 \ | ||
| \ | ||
| -device gen16b-s390x-cpu,drawer-id=1,book-id=1,socket-id=2,core-id=1 \ | ||
| -device gen16b-s390x-cpu,drawer-id=1,book-id=1,socket-id=2,core-id=2 \ | ||
| -device gen16b-s390x-cpu,drawer-id=1,book-id=1,socket-id=2,core-id=3 \ | ||
| \ | ||
| -device gen16b-s390x-cpu,drawer-id=0,book-id=0,socket-id=0,core-id=9 \ | ||
| -device gen16b-s390x-cpu,drawer-id=0,book-id=0,socket-id=0,core-id=14 \ | ||
| \ | ||
| -device gen16b-s390x-cpu,core-id=4,dedicated=on,entitlement=high | ||
| The entitlement defined for the CPU 4 will only be used after the guest | ||
| successfully enables vertical polarization by using the PTF instruction. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,3 +34,4 @@ Architectural features | |
| .. toctree:: | ||
| s390x/bootdevices | ||
| s390x/protvirt | ||
| s390x/cpu-topology | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||
| /* | ||
| * CPU Topology | ||
| * | ||
| * Copyright IBM Corp. 2022, 2023 | ||
| * Author(s): Pierre Morel <pmorel@linux.ibm.com> | ||
| * | ||
| */ | ||
| #ifndef HW_S390X_CPU_TOPOLOGY_H | ||
| #define HW_S390X_CPU_TOPOLOGY_H | ||
|
|
||
| #ifndef CONFIG_USER_ONLY | ||
|
|
||
| #include "qemu/queue.h" | ||
| #include "hw/boards.h" | ||
| #include "qapi/qapi-types-machine-target.h" | ||
|
|
||
| #define S390_TOPOLOGY_CPU_IFL 0x03 | ||
|
|
||
| typedef struct S390TopologyId { | ||
| uint8_t sentinel; | ||
| uint8_t drawer; | ||
| uint8_t book; | ||
| uint8_t socket; | ||
| uint8_t type; | ||
| uint8_t vertical:1; | ||
| uint8_t entitlement:2; | ||
| uint8_t dedicated; | ||
| uint8_t origin; | ||
| } S390TopologyId; | ||
|
|
||
| typedef struct S390TopologyEntry { | ||
| QTAILQ_ENTRY(S390TopologyEntry) next; | ||
| S390TopologyId id; | ||
| uint64_t mask; | ||
| } S390TopologyEntry; | ||
|
|
||
| typedef struct S390Topology { | ||
| uint8_t *cores_per_socket; | ||
| CpuS390Polarization polarization; | ||
| } S390Topology; | ||
|
|
||
| typedef QTAILQ_HEAD(, S390TopologyEntry) S390TopologyList; | ||
|
|
||
| #ifdef CONFIG_KVM | ||
| bool s390_has_topology(void); | ||
| void s390_topology_setup_cpu(MachineState *ms, S390CPU *cpu, Error **errp); | ||
| void s390_topology_reset(void); | ||
| #else | ||
| static inline bool s390_has_topology(void) | ||
| { | ||
| return false; | ||
| } | ||
| static inline void s390_topology_setup_cpu(MachineState *ms, | ||
| S390CPU *cpu, | ||
| Error **errp) {} | ||
| static inline void s390_topology_reset(void) | ||
| { | ||
| /* Unreachable, CPU topology not implemented for TCG */ | ||
| assert(false); | ||
| } | ||
| #endif | ||
|
|
||
| extern S390Topology s390_topology; | ||
|
|
||
| static inline int s390_std_socket(int n, CpuTopology *smp) | ||
| { | ||
| return (n / smp->cores) % smp->sockets; | ||
| } | ||
|
|
||
| static inline int s390_std_book(int n, CpuTopology *smp) | ||
| { | ||
| return (n / (smp->cores * smp->sockets)) % smp->books; | ||
| } | ||
|
|
||
| static inline int s390_std_drawer(int n, CpuTopology *smp) | ||
| { | ||
| return (n / (smp->cores * smp->sockets * smp->books)) % smp->drawers; | ||
| } | ||
|
|
||
| #endif /* CONFIG_USER_ONLY */ | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # -*- Mode: Python -*- | ||
| # vim: filetype=python | ||
| # | ||
| # This work is licensed under the terms of the GNU GPL, version 2 or later. | ||
| # See the COPYING file in the top-level directory. | ||
|
|
||
| ## | ||
| # = Machines S390 data types | ||
| ## | ||
|
|
||
| ## | ||
| # @CpuS390Entitlement: | ||
| # | ||
| # An enumeration of CPU entitlements that can be assumed by a virtual | ||
| # S390 CPU | ||
| # | ||
| # Since: 8.2 | ||
| ## | ||
| { 'enum': 'CpuS390Entitlement', | ||
| 'prefix': 'S390_CPU_ENTITLEMENT', | ||
| 'data': [ 'auto', 'low', 'medium', 'high' ] } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,334 @@ | ||
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||
| /* | ||
| * QEMU S390x CPU Topology | ||
| * | ||
| * Copyright IBM Corp. 2022, 2023 | ||
| * Author(s): Pierre Morel <pmorel@linux.ibm.com> | ||
| * | ||
| */ | ||
| #include "qemu/osdep.h" | ||
| #include "cpu.h" | ||
| #include "hw/s390x/sclp.h" | ||
| #include "hw/s390x/cpu-topology.h" | ||
|
|
||
| QEMU_BUILD_BUG_ON(S390_CPU_ENTITLEMENT_LOW != 1); | ||
| QEMU_BUILD_BUG_ON(S390_CPU_ENTITLEMENT_MEDIUM != 2); | ||
| QEMU_BUILD_BUG_ON(S390_CPU_ENTITLEMENT_HIGH != 3); | ||
|
|
||
| /** | ||
| * fill_container: | ||
| * @p: The address of the container TLE to fill | ||
| * @level: The level of nesting for this container | ||
| * @id: The container receives a unique ID inside its own container | ||
| * | ||
| * Returns the next free TLE entry. | ||
| */ | ||
| static char *fill_container(char *p, int level, int id) | ||
| { | ||
| SYSIBContainerListEntry *tle = (SYSIBContainerListEntry *)p; | ||
|
|
||
| tle->nl = level; | ||
| tle->id = id; | ||
| return p + sizeof(*tle); | ||
| } | ||
|
|
||
| /** | ||
| * fill_tle_cpu: | ||
| * @p: The address of the CPU TLE to fill | ||
| * @entry: a pointer to the S390TopologyEntry defining this | ||
| * CPU container. | ||
| * | ||
| * Returns the next free TLE entry. | ||
| */ | ||
| static char *fill_tle_cpu(char *p, S390TopologyEntry *entry) | ||
| { | ||
| SysIBCPUListEntry *tle = (SysIBCPUListEntry *)p; | ||
| S390TopologyId topology_id = entry->id; | ||
|
|
||
| tle->nl = 0; | ||
| tle->flags = 0; | ||
| if (topology_id.vertical) { | ||
| tle->flags |= topology_id.entitlement; | ||
| } | ||
| if (topology_id.dedicated) { | ||
| tle->flags |= SYSIB_TLE_DEDICATED; | ||
| } | ||
| tle->type = topology_id.type; | ||
| tle->origin = cpu_to_be16(topology_id.origin * 64); | ||
| tle->mask = cpu_to_be64(entry->mask); | ||
| return p + sizeof(*tle); | ||
| } | ||
|
|
||
| /* | ||
| * Macro to check that the size of data after increment | ||
| * will not get bigger than the size of the SysIB. | ||
| */ | ||
| #define SYSIB_GUARD(data, x) do { \ | ||
| data += x; \ | ||
| if (data > sizeof(SysIB)) { \ | ||
| return 0; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /** | ||
| * stsi_topology_fill_sysib: | ||
| * @p: A pointer to the position of the first TLE | ||
| * @level: The nested level wanted by the guest | ||
| * | ||
| * Fill the SYSIB with the topology information as described in | ||
| * the PoP, nesting containers as appropriate, with the maximum | ||
| * nesting limited by @level. | ||
| * | ||
| * Return value: | ||
| * On success: the size of the SysIB_15x after being filled with TLE. | ||
| * On error: 0 in the case we would overrun the end of the SysIB. | ||
| */ | ||
| static int stsi_topology_fill_sysib(S390TopologyList *topology_list, | ||
| char *p, int level) | ||
| { | ||
| S390TopologyEntry *entry; | ||
| int last_drawer = -1; | ||
| int last_book = -1; | ||
| int last_socket = -1; | ||
| int drawer_id = 0; | ||
| int book_id = 0; | ||
| int socket_id = 0; | ||
| int n = sizeof(SysIB_151x); | ||
|
|
||
| QTAILQ_FOREACH(entry, topology_list, next) { | ||
| bool drawer_change = last_drawer != entry->id.drawer; | ||
| bool book_change = drawer_change || last_book != entry->id.book; | ||
| bool socket_change = book_change || last_socket != entry->id.socket; | ||
|
|
||
| if (level > 3 && drawer_change) { | ||
| SYSIB_GUARD(n, sizeof(SYSIBContainerListEntry)); | ||
| p = fill_container(p, 3, drawer_id++); | ||
| book_id = 0; | ||
| } | ||
| if (level > 2 && book_change) { | ||
| SYSIB_GUARD(n, sizeof(SYSIBContainerListEntry)); | ||
| p = fill_container(p, 2, book_id++); | ||
| socket_id = 0; | ||
| } | ||
| if (socket_change) { | ||
| SYSIB_GUARD(n, sizeof(SYSIBContainerListEntry)); | ||
| p = fill_container(p, 1, socket_id++); | ||
| } | ||
|
|
||
| SYSIB_GUARD(n, sizeof(SysIBCPUListEntry)); | ||
| p = fill_tle_cpu(p, entry); | ||
| last_drawer = entry->id.drawer; | ||
| last_book = entry->id.book; | ||
| last_socket = entry->id.socket; | ||
| } | ||
|
|
||
| return n; | ||
| } | ||
|
|
||
| /** | ||
| * setup_stsi: | ||
| * @topology_list: ordered list of groups of CPUs with same properties | ||
| * @sysib: pointer to a SysIB to be filled with SysIB_151x data | ||
| * @level: Nested level specified by the guest | ||
| * | ||
| * Setup the SYSIB for STSI 15.1, the header as well as the description | ||
| * of the topology. | ||
| */ | ||
| static int setup_stsi(S390TopologyList *topology_list, SysIB_151x *sysib, | ||
| int level) | ||
| { | ||
| sysib->mnest = level; | ||
| switch (level) { | ||
| case 4: | ||
| sysib->mag[S390_TOPOLOGY_MAG4] = current_machine->smp.drawers; | ||
| sysib->mag[S390_TOPOLOGY_MAG3] = current_machine->smp.books; | ||
| sysib->mag[S390_TOPOLOGY_MAG2] = current_machine->smp.sockets; | ||
| sysib->mag[S390_TOPOLOGY_MAG1] = current_machine->smp.cores; | ||
| break; | ||
| case 3: | ||
| sysib->mag[S390_TOPOLOGY_MAG3] = current_machine->smp.drawers * | ||
| current_machine->smp.books; | ||
| sysib->mag[S390_TOPOLOGY_MAG2] = current_machine->smp.sockets; | ||
| sysib->mag[S390_TOPOLOGY_MAG1] = current_machine->smp.cores; | ||
| break; | ||
| case 2: | ||
| sysib->mag[S390_TOPOLOGY_MAG2] = current_machine->smp.drawers * | ||
| current_machine->smp.books * | ||
| current_machine->smp.sockets; | ||
| sysib->mag[S390_TOPOLOGY_MAG1] = current_machine->smp.cores; | ||
| break; | ||
| } | ||
|
|
||
| return stsi_topology_fill_sysib(topology_list, sysib->tle, level); | ||
| } | ||
|
|
||
| /** | ||
| * s390_topology_add_cpu_to_entry: | ||
| * @entry: Topology entry to setup | ||
| * @cpu: the S390CPU to add | ||
| * | ||
| * Set the core bit inside the topology mask. | ||
| */ | ||
| static void s390_topology_add_cpu_to_entry(S390TopologyEntry *entry, | ||
| S390CPU *cpu) | ||
| { | ||
| set_bit(63 - (cpu->env.core_id % 64), &entry->mask); | ||
| } | ||
|
|
||
| /** | ||
| * s390_topology_from_cpu: | ||
| * @cpu: S390CPU to calculate the topology id | ||
| * | ||
| * Initialize the topology id from the CPU environment. | ||
| */ | ||
| static S390TopologyId s390_topology_from_cpu(S390CPU *cpu) | ||
| { | ||
| S390TopologyId topology_id = { | ||
| .drawer = cpu->env.drawer_id, | ||
| .book = cpu->env.book_id, | ||
| .socket = cpu->env.socket_id, | ||
| .type = S390_TOPOLOGY_CPU_IFL, | ||
| .vertical = s390_topology.polarization == S390_CPU_POLARIZATION_VERTICAL, | ||
| .entitlement = cpu->env.entitlement, | ||
| .dedicated = cpu->env.dedicated, | ||
| .origin = cpu->env.core_id / 64, | ||
| }; | ||
|
|
||
| return topology_id; | ||
| } | ||
|
|
||
| /** | ||
| * s390_topology_id_cmp: | ||
| * @l: first S390TopologyId | ||
| * @r: second S390TopologyId | ||
| * | ||
| * Compare two topology ids according to the sorting order specified by the PoP. | ||
| * | ||
| * Returns a negative number if the first id is less than, 0 if it is equal to | ||
| * and positive if it is larger than the second id. | ||
| */ | ||
| static int s390_topology_id_cmp(const S390TopologyId *l, | ||
| const S390TopologyId *r) | ||
| { | ||
| /* | ||
| * lexical order, compare less significant values only if more significant | ||
| * ones are equal | ||
| */ | ||
| return l->sentinel - r->sentinel ?: | ||
| l->drawer - r->drawer ?: | ||
| l->book - r->book ?: | ||
| l->socket - r->socket ?: | ||
| l->type - r->type ?: | ||
| /* logic is inverted for the next three */ | ||
| r->vertical - l->vertical ?: | ||
| r->entitlement - l->entitlement ?: | ||
| r->dedicated - l->dedicated ?: | ||
| l->origin - r->origin; | ||
| } | ||
|
|
||
| static bool s390_topology_id_eq(const S390TopologyId *l, | ||
| const S390TopologyId *r) | ||
| { | ||
| return !s390_topology_id_cmp(l, r); | ||
| } | ||
|
|
||
| static bool s390_topology_id_lt(const S390TopologyId *l, | ||
| const S390TopologyId *r) | ||
| { | ||
| return s390_topology_id_cmp(l, r) < 0; | ||
| } | ||
|
|
||
| /** | ||
| * s390_topology_fill_list_sorted: | ||
| * @topology_list: list to fill | ||
| * | ||
| * Create S390TopologyEntrys as appropriate from all CPUs and fill the | ||
| * topology_list with the entries according to the order specified by the PoP. | ||
| */ | ||
| static void s390_topology_fill_list_sorted(S390TopologyList *topology_list) | ||
| { | ||
| CPUState *cs; | ||
| S390TopologyEntry sentinel = { .id.sentinel = 1 }; | ||
|
|
||
| QTAILQ_INIT(topology_list); | ||
|
|
||
| QTAILQ_INSERT_HEAD(topology_list, &sentinel, next); | ||
|
|
||
| CPU_FOREACH(cs) { | ||
| S390TopologyId id = s390_topology_from_cpu(S390_CPU(cs)); | ||
| S390TopologyEntry *entry = NULL, *tmp; | ||
|
|
||
| QTAILQ_FOREACH(tmp, topology_list, next) { | ||
| if (s390_topology_id_eq(&id, &tmp->id)) { | ||
| entry = tmp; | ||
| break; | ||
| } else if (s390_topology_id_lt(&id, &tmp->id)) { | ||
| entry = g_malloc0(sizeof(*entry)); | ||
| entry->id = id; | ||
| QTAILQ_INSERT_BEFORE(tmp, entry, next); | ||
| break; | ||
| } | ||
| } | ||
| assert(entry); | ||
| s390_topology_add_cpu_to_entry(entry, S390_CPU(cs)); | ||
| } | ||
|
|
||
| QTAILQ_REMOVE(topology_list, &sentinel, next); | ||
| } | ||
|
|
||
| /** | ||
| * s390_topology_empty_list: | ||
| * | ||
| * Clear all entries in the S390Topology list. | ||
| */ | ||
| static void s390_topology_empty_list(S390TopologyList *topology_list) | ||
| { | ||
| S390TopologyEntry *entry = NULL; | ||
| S390TopologyEntry *tmp = NULL; | ||
|
|
||
| QTAILQ_FOREACH_SAFE(entry, topology_list, next, tmp) { | ||
| QTAILQ_REMOVE(topology_list, entry, next); | ||
| g_free(entry); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * insert_stsi_15_1_x: | ||
| * @cpu: the CPU doing the call for which we set CC | ||
| * @sel2: the selector 2, containing the nested level | ||
| * @addr: Guest logical address of the guest SysIB | ||
| * @ar: the access register number | ||
| * @ra: the return address | ||
| * | ||
| * Emulate STSI 15.1.x, that is, perform all necessary checks and | ||
| * fill the SYSIB. | ||
| * In case the topology description is too long to fit into the SYSIB, | ||
| * set CC=3 and abort without writing the SYSIB. | ||
| */ | ||
| void insert_stsi_15_1_x(S390CPU *cpu, int sel2, uint64_t addr, uint8_t ar, uintptr_t ra) | ||
| { | ||
| S390TopologyList topology_list; | ||
| SysIB sysib = {0}; | ||
| int length; | ||
|
|
||
| if (!s390_has_topology() || sel2 < 2 || sel2 > SCLP_READ_SCP_INFO_MNEST) { | ||
| setcc(cpu, 3); | ||
| return; | ||
| } | ||
|
|
||
| s390_topology_fill_list_sorted(&topology_list); | ||
| length = setup_stsi(&topology_list, &sysib.sysib_151x, sel2); | ||
| s390_topology_empty_list(&topology_list); | ||
|
|
||
| if (!length) { | ||
| setcc(cpu, 3); | ||
| return; | ||
| } | ||
|
|
||
| sysib.sysib_151x.length = cpu_to_be16(length); | ||
| if (!s390_cpu_virt_mem_write(cpu, addr, ar, &sysib, length)) { | ||
| setcc(cpu, 0); | ||
| } else { | ||
| s390_cpu_virt_mem_handle_exc(cpu, ra); | ||
| } | ||
| } |