2023-10-16 21:39:07 +03:00
|
|
|
/* 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"
|
2023-10-16 21:39:13 +03:00
|
|
|
#include "qapi/qapi-commands-machine-target.h"
|
2023-10-16 21:39:07 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* s390_topology is used to keep the topology information.
|
|
|
|
* .cores_per_socket: tracks information on the count of cores
|
|
|
|
* per socket.
|
2023-10-16 21:39:08 +03:00
|
|
|
* .polarization: tracks machine polarization.
|
2023-10-16 21:39:07 +03:00
|
|
|
*/
|
|
|
|
S390Topology s390_topology = {
|
|
|
|
/* will be initialized after the CPU model is realized */
|
|
|
|
.cores_per_socket = NULL,
|
2023-10-16 21:39:08 +03:00
|
|
|
.polarization = S390_CPU_POLARIZATION_HORIZONTAL,
|
2023-10-16 21:39:07 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
{
|
2023-10-16 21:39:12 +03:00
|
|
|
return s390_has_feat(S390_FEAT_CONFIGURATION_TOPOLOGY);
|
2023-10-16 21:39:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
|
2023-10-16 21:39:11 +03:00
|
|
|
/*
|
|
|
|
* 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)
|
|
|
|
{
|
|
|
|
CpuS390Polarization 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);
|
|
|
|
setcc(cpu, 0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* Note that fc == 2 is interpreted by the SIE */
|
|
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-16 21:39:10 +03:00
|
|
|
/**
|
|
|
|
* 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);
|
2023-10-16 21:39:11 +03:00
|
|
|
s390_topology.polarization = S390_CPU_POLARIZATION_HORIZONTAL;
|
2023-10-16 21:39:10 +03:00
|
|
|
}
|
|
|
|
|
2023-10-16 21:39:07 +03:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2023-10-16 21:39:13 +03:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2023-10-16 21:39:07 +03:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
2023-10-16 21:39:13 +03:00
|
|
|
|
|
|
|
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,
|
|
|
|
CpuS390Entitlement 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, CpuS390Entitlement 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);
|
|
|
|
}
|