xref: /openbmc/qemu/hw/s390x/cpu-topology.c (revision e9206163)
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 /*
3  * CPU Topology
4  *
5  * Copyright IBM Corp. 2022, 2023
6  * Author(s): Pierre Morel <pmorel@linux.ibm.com>
7  *
8  * S390 topology handling can be divided in two parts:
9  *
10  * - The first part in this file is taking care of all common functions
11  *   used by KVM and TCG to create and modify the topology.
12  *
13  * - The second part, building the topology information data for the
14  *   guest with CPU and KVM specificity will be implemented inside
15  *   the target/s390/kvm sub tree.
16  */
17 
18 #include "qemu/osdep.h"
19 #include "qapi/error.h"
20 #include "qemu/error-report.h"
21 #include "hw/qdev-properties.h"
22 #include "hw/boards.h"
23 #include "target/s390x/cpu.h"
24 #include "hw/s390x/s390-virtio-ccw.h"
25 #include "hw/s390x/cpu-topology.h"
26 #include "qapi/qapi-commands-machine-target.h"
27 #include "qapi/qapi-events-machine-target.h"
28 
29 /*
30  * s390_topology is used to keep the topology information.
31  * .cores_per_socket: tracks information on the count of cores
32  *                    per socket.
33  * .polarization: tracks machine polarization.
34  */
35 S390Topology s390_topology = {
36     /* will be initialized after the CPU model is realized */
37     .cores_per_socket = NULL,
38     .polarization = S390_CPU_POLARIZATION_HORIZONTAL,
39 };
40 
41 /**
42  * s390_socket_nb:
43  * @cpu: s390x CPU
44  *
45  * Returns the socket number used inside the cores_per_socket array
46  * for a topology tree entry
47  */
48 static int s390_socket_nb_from_ids(int drawer_id, int book_id, int socket_id)
49 {
50     return (drawer_id * current_machine->smp.books + book_id) *
51            current_machine->smp.sockets + socket_id;
52 }
53 
54 /**
55  * s390_socket_nb:
56  * @cpu: s390x CPU
57  *
58  * Returns the socket number used inside the cores_per_socket array
59  * for a cpu.
60  */
61 static int s390_socket_nb(S390CPU *cpu)
62 {
63     return s390_socket_nb_from_ids(cpu->env.drawer_id, cpu->env.book_id,
64                                    cpu->env.socket_id);
65 }
66 
67 /**
68  * s390_has_topology:
69  *
70  * Return: true if the topology is supported by the machine.
71  */
72 bool s390_has_topology(void)
73 {
74     return s390_has_feat(S390_FEAT_CONFIGURATION_TOPOLOGY);
75 }
76 
77 /**
78  * s390_topology_init:
79  * @ms: the machine state where the machine topology is defined
80  *
81  * Keep track of the machine topology.
82  *
83  * Allocate an array to keep the count of cores per socket.
84  * The index of the array starts at socket 0 from book 0 and
85  * drawer 0 up to the maximum allowed by the machine topology.
86  */
87 static void s390_topology_init(MachineState *ms)
88 {
89     CpuTopology *smp = &ms->smp;
90 
91     s390_topology.cores_per_socket = g_new0(uint8_t, smp->sockets *
92                                             smp->books * smp->drawers);
93 }
94 
95 /*
96  * s390_handle_ptf:
97  *
98  * @register 1: contains the function code
99  *
100  * Function codes 0 (horizontal) and 1 (vertical) define the CPU
101  * polarization requested by the guest.
102  *
103  * Function code 2 is handling topology changes and is interpreted
104  * by the SIE.
105  */
106 void s390_handle_ptf(S390CPU *cpu, uint8_t r1, uintptr_t ra)
107 {
108     CpuS390Polarization polarization;
109     CPUS390XState *env = &cpu->env;
110     uint64_t reg = env->regs[r1];
111     int fc = reg & S390_TOPO_FC_MASK;
112 
113     if (!s390_has_feat(S390_FEAT_CONFIGURATION_TOPOLOGY)) {
114         s390_program_interrupt(env, PGM_OPERATION, ra);
115         return;
116     }
117 
118     if (env->psw.mask & PSW_MASK_PSTATE) {
119         s390_program_interrupt(env, PGM_PRIVILEGED, ra);
120         return;
121     }
122 
123     if (reg & ~S390_TOPO_FC_MASK) {
124         s390_program_interrupt(env, PGM_SPECIFICATION, ra);
125         return;
126     }
127 
128     polarization = S390_CPU_POLARIZATION_VERTICAL;
129     switch (fc) {
130     case 0:
131         polarization = S390_CPU_POLARIZATION_HORIZONTAL;
132         /* fallthrough */
133     case 1:
134         if (s390_topology.polarization == polarization) {
135             env->regs[r1] |= S390_PTF_REASON_DONE;
136             setcc(cpu, 2);
137         } else {
138             s390_topology.polarization = polarization;
139             s390_cpu_topology_set_changed(true);
140             qapi_event_send_cpu_polarization_change(polarization);
141             setcc(cpu, 0);
142         }
143         break;
144     default:
145         /* Note that fc == 2 is interpreted by the SIE */
146         s390_program_interrupt(env, PGM_SPECIFICATION, ra);
147     }
148 }
149 
150 /**
151  * s390_topology_reset:
152  *
153  * Generic reset for CPU topology, calls s390_topology_reset()
154  * to reset the kernel Modified Topology Change Record.
155  */
156 void s390_topology_reset(void)
157 {
158     s390_cpu_topology_set_changed(false);
159     s390_topology.polarization = S390_CPU_POLARIZATION_HORIZONTAL;
160 }
161 
162 /**
163  * s390_topology_cpu_default:
164  * @cpu: pointer to a S390CPU
165  * @errp: Error pointer
166  *
167  * Setup the default topology if no attributes are already set.
168  * Passing a CPU with some, but not all, attributes set is considered
169  * an error.
170  *
171  * The function calculates the (drawer_id, book_id, socket_id)
172  * topology by filling the cores starting from the first socket
173  * (0, 0, 0) up to the last (smp->drawers, smp->books, smp->sockets).
174  *
175  * CPU type and dedication have defaults values set in the
176  * s390x_cpu_properties, entitlement must be adjust depending on the
177  * dedication.
178  *
179  * Returns false if it is impossible to setup a default topology
180  * true otherwise.
181  */
182 static bool s390_topology_cpu_default(S390CPU *cpu, Error **errp)
183 {
184     CpuTopology *smp = &current_machine->smp;
185     CPUS390XState *env = &cpu->env;
186 
187     /* All geometry topology attributes must be set or all unset */
188     if ((env->socket_id < 0 || env->book_id < 0 || env->drawer_id < 0) &&
189         (env->socket_id >= 0 || env->book_id >= 0 || env->drawer_id >= 0)) {
190         error_setg(errp,
191                    "Please define all or none of the topology geometry attributes");
192         return false;
193     }
194 
195     /* If one value is unset all are unset -> calculate defaults */
196     if (env->socket_id < 0) {
197         env->socket_id = s390_std_socket(env->core_id, smp);
198         env->book_id = s390_std_book(env->core_id, smp);
199         env->drawer_id = s390_std_drawer(env->core_id, smp);
200     }
201 
202     /*
203      * When the user specifies the entitlement as 'auto' on the command line,
204      * QEMU will set the entitlement as:
205      * Medium when the CPU is not dedicated.
206      * High when dedicated is true.
207      */
208     if (env->entitlement == S390_CPU_ENTITLEMENT_AUTO) {
209         if (env->dedicated) {
210             env->entitlement = S390_CPU_ENTITLEMENT_HIGH;
211         } else {
212             env->entitlement = S390_CPU_ENTITLEMENT_MEDIUM;
213         }
214     }
215     return true;
216 }
217 
218 /**
219  * s390_topology_check:
220  * @socket_id: socket to check
221  * @book_id: book to check
222  * @drawer_id: drawer to check
223  * @entitlement: entitlement to check
224  * @dedicated: dedication to check
225  * @errp: Error pointer
226  *
227  * The function checks if the topology
228  * attributes fits inside the system topology.
229  *
230  * Returns false if the specified topology does not match with
231  * the machine topology.
232  */
233 static bool s390_topology_check(uint16_t socket_id, uint16_t book_id,
234                                 uint16_t drawer_id, uint16_t entitlement,
235                                 bool dedicated, Error **errp)
236 {
237     CpuTopology *smp = &current_machine->smp;
238 
239     if (socket_id >= smp->sockets) {
240         error_setg(errp, "Unavailable socket: %d", socket_id);
241         return false;
242     }
243     if (book_id >= smp->books) {
244         error_setg(errp, "Unavailable book: %d", book_id);
245         return false;
246     }
247     if (drawer_id >= smp->drawers) {
248         error_setg(errp, "Unavailable drawer: %d", drawer_id);
249         return false;
250     }
251     if (entitlement >= S390_CPU_ENTITLEMENT__MAX) {
252         error_setg(errp, "Unknown entitlement: %d", entitlement);
253         return false;
254     }
255     if (dedicated && (entitlement == S390_CPU_ENTITLEMENT_LOW ||
256                       entitlement == S390_CPU_ENTITLEMENT_MEDIUM)) {
257         error_setg(errp, "A dedicated CPU implies high entitlement");
258         return false;
259     }
260     return true;
261 }
262 
263 /**
264  * s390_topology_need_report
265  * @cpu: Current cpu
266  * @drawer_id: future drawer ID
267  * @book_id: future book ID
268  * @socket_id: future socket ID
269  * @entitlement: future entitlement
270  * @dedicated: future dedicated
271  *
272  * A modified topology change report is needed if the topology
273  * tree or the topology attributes change.
274  */
275 static bool s390_topology_need_report(S390CPU *cpu, int drawer_id,
276                                       int book_id, int socket_id,
277                                       uint16_t entitlement, bool dedicated)
278 {
279     return cpu->env.drawer_id != drawer_id ||
280            cpu->env.book_id != book_id ||
281            cpu->env.socket_id != socket_id ||
282            cpu->env.entitlement != entitlement ||
283            cpu->env.dedicated != dedicated;
284 }
285 
286 /**
287  * s390_update_cpu_props:
288  * @ms: the machine state
289  * @cpu: the CPU for which to update the properties from the environment.
290  *
291  */
292 static void s390_update_cpu_props(MachineState *ms, S390CPU *cpu)
293 {
294     CpuInstanceProperties *props;
295 
296     props = &ms->possible_cpus->cpus[cpu->env.core_id].props;
297 
298     props->socket_id = cpu->env.socket_id;
299     props->book_id = cpu->env.book_id;
300     props->drawer_id = cpu->env.drawer_id;
301 }
302 
303 /**
304  * s390_topology_setup_cpu:
305  * @ms: MachineState used to initialize the topology structure on
306  *      first call.
307  * @cpu: the new S390CPU to insert in the topology structure
308  * @errp: the error pointer
309  *
310  * Called from CPU hotplug to check and setup the CPU attributes
311  * before the CPU is inserted in the topology.
312  * There is no need to update the MTCR explicitly here because it
313  * will be updated by KVM on creation of the new CPU.
314  */
315 void s390_topology_setup_cpu(MachineState *ms, S390CPU *cpu, Error **errp)
316 {
317     int entry;
318 
319     /*
320      * We do not want to initialize the topology if the CPU model
321      * does not support topology, consequently, we have to wait for
322      * the first CPU to be realized, which realizes the CPU model
323      * to initialize the topology structures.
324      *
325      * s390_topology_setup_cpu() is called from the CPU hotplug.
326      */
327     if (!s390_topology.cores_per_socket) {
328         s390_topology_init(ms);
329     }
330 
331     if (!s390_topology_cpu_default(cpu, errp)) {
332         return;
333     }
334 
335     if (!s390_topology_check(cpu->env.socket_id, cpu->env.book_id,
336                              cpu->env.drawer_id, cpu->env.entitlement,
337                              cpu->env.dedicated, errp)) {
338         return;
339     }
340 
341     /* Do we still have space in the socket */
342     entry = s390_socket_nb(cpu);
343     if (s390_topology.cores_per_socket[entry] >= ms->smp.cores) {
344         error_setg(errp, "No more space on this socket");
345         return;
346     }
347 
348     /* Update the count of cores in sockets */
349     s390_topology.cores_per_socket[entry] += 1;
350 
351     /* topology tree is reflected in props */
352     s390_update_cpu_props(ms, cpu);
353 }
354 
355 static void s390_change_topology(uint16_t core_id,
356                                  bool has_socket_id, uint16_t socket_id,
357                                  bool has_book_id, uint16_t book_id,
358                                  bool has_drawer_id, uint16_t drawer_id,
359                                  bool has_entitlement,
360                                  CpuS390Entitlement entitlement,
361                                  bool has_dedicated, bool dedicated,
362                                  Error **errp)
363 {
364     MachineState *ms = current_machine;
365     int old_socket_entry;
366     int new_socket_entry;
367     bool report_needed;
368     S390CPU *cpu;
369 
370     cpu = s390_cpu_addr2state(core_id);
371     if (!cpu) {
372         error_setg(errp, "Core-id %d does not exist!", core_id);
373         return;
374     }
375 
376     /* Get attributes not provided from cpu and verify the new topology */
377     if (!has_socket_id) {
378         socket_id = cpu->env.socket_id;
379     }
380     if (!has_book_id) {
381         book_id = cpu->env.book_id;
382     }
383     if (!has_drawer_id) {
384         drawer_id = cpu->env.drawer_id;
385     }
386     if (!has_dedicated) {
387         dedicated = cpu->env.dedicated;
388     }
389 
390     /*
391      * When the user specifies the entitlement as 'auto' on the command line,
392      * QEMU will set the entitlement as:
393      * Medium when the CPU is not dedicated.
394      * High when dedicated is true.
395      */
396     if (!has_entitlement || entitlement == S390_CPU_ENTITLEMENT_AUTO) {
397         if (dedicated) {
398             entitlement = S390_CPU_ENTITLEMENT_HIGH;
399         } else {
400             entitlement = S390_CPU_ENTITLEMENT_MEDIUM;
401         }
402     }
403 
404     if (!s390_topology_check(socket_id, book_id, drawer_id,
405                              entitlement, dedicated, errp)) {
406         return;
407     }
408 
409     /* Check for space on new socket */
410     old_socket_entry = s390_socket_nb(cpu);
411     new_socket_entry = s390_socket_nb_from_ids(drawer_id, book_id, socket_id);
412 
413     if (new_socket_entry != old_socket_entry) {
414         if (s390_topology.cores_per_socket[new_socket_entry] >=
415             ms->smp.cores) {
416             error_setg(errp, "No more space on this socket");
417             return;
418         }
419         /* Update the count of cores in sockets */
420         s390_topology.cores_per_socket[new_socket_entry] += 1;
421         s390_topology.cores_per_socket[old_socket_entry] -= 1;
422     }
423 
424     /* Check if we will need to report the modified topology */
425     report_needed = s390_topology_need_report(cpu, drawer_id, book_id,
426                                               socket_id, entitlement,
427                                               dedicated);
428 
429     /* All checks done, report new topology into the vCPU */
430     cpu->env.drawer_id = drawer_id;
431     cpu->env.book_id = book_id;
432     cpu->env.socket_id = socket_id;
433     cpu->env.dedicated = dedicated;
434     cpu->env.entitlement = entitlement;
435 
436     /* topology tree is reflected in props */
437     s390_update_cpu_props(ms, cpu);
438 
439     /* Advertise the topology change */
440     if (report_needed) {
441         s390_cpu_topology_set_changed(true);
442     }
443 }
444 
445 void qmp_set_cpu_topology(uint16_t core,
446                           bool has_socket, uint16_t socket,
447                           bool has_book, uint16_t book,
448                           bool has_drawer, uint16_t drawer,
449                           bool has_entitlement, CpuS390Entitlement entitlement,
450                           bool has_dedicated, bool dedicated,
451                           Error **errp)
452 {
453     if (!s390_has_topology()) {
454         error_setg(errp, "This machine doesn't support topology");
455         return;
456     }
457 
458     s390_change_topology(core, has_socket, socket, has_book, book,
459                          has_drawer, drawer, has_entitlement, entitlement,
460                          has_dedicated, dedicated, errp);
461 }
462 
463 CpuPolarizationInfo *qmp_query_s390x_cpu_polarization(Error **errp)
464 {
465     CpuPolarizationInfo *info = g_new0(CpuPolarizationInfo, 1);
466 
467     info->polarization = s390_topology.polarization;
468     return info;
469 }
470