/* SPDX-License-Identifier: GPL-2.0-only */ /* * s390x smp * Based on Linux's arch/s390/kernel/smp.c and * arch/s390/include/asm/sigp.h * * Copyright (c) 2019 IBM Corp * * Authors: * Janosch Frank */ #include #include #include #include #include #include #include #include #include #include #include "smp.h" #include "sclp.h" static struct cpu *cpus; static struct spinlock lock; extern void smp_cpu_setup_state(void); static void check_idx(uint16_t idx) { assert(idx < smp_query_num_cpus()); } int smp_query_num_cpus(void) { return sclp_get_cpu_num(); } struct lowcore *smp_get_lowcore(uint16_t idx) { if (THIS_CPU->idx == idx) return &lowcore; check_idx(idx); return cpus[idx].lowcore; } int smp_sigp(uint16_t idx, uint8_t order, unsigned long parm, uint32_t *status) { check_idx(idx); return sigp_retry(cpus[idx].addr, order, parm, status); } struct cpu *smp_cpu_from_addr(uint16_t addr) { int i, num = smp_query_num_cpus(); for (i = 0; i < num; i++) { if (cpus[i].addr == addr) return &cpus[i]; } return NULL; } struct cpu *smp_cpu_from_idx(uint16_t idx) { check_idx(idx); return &cpus[idx]; } uint16_t smp_cpu_addr(uint16_t idx) { check_idx(idx); return cpus[idx].addr; } bool smp_cpu_stopped(uint16_t idx) { uint32_t status; if (smp_sigp(idx, SIGP_SENSE, 0, &status) != SIGP_CC_STATUS_STORED) return false; return !!(status & (SIGP_STATUS_CHECK_STOP|SIGP_STATUS_STOPPED)); } bool smp_sense_running_status(uint16_t idx) { if (smp_sigp(idx, SIGP_SENSE_RUNNING, 0, NULL) != SIGP_CC_STATUS_STORED) return true; /* Status stored condition code is equivalent to cpu not running. */ return false; } static int smp_cpu_stop_nolock(uint16_t idx, bool store) { uint8_t order = store ? SIGP_STOP_AND_STORE_STATUS : SIGP_STOP; /* refuse to work on the boot CPU */ if (idx == 0) return -1; if (smp_sigp(idx, order, 0, NULL)) return -1; while (!smp_cpu_stopped(idx)) mb(); /* idx has been already checked by the smp_* functions called above */ cpus[idx].active = false; return 0; } int smp_cpu_stop(uint16_t idx) { int rc; spin_lock(&lock); rc = smp_cpu_stop_nolock(idx, false); spin_unlock(&lock); return rc; } /* * Functionally equivalent to smp_cpu_stop(), but without the * elements that wait/serialize matters itself. * Used to see if KVM itself is serialized correctly. */ int smp_cpu_stop_nowait(uint16_t idx) { check_idx(idx); /* refuse to work on the boot CPU */ if (idx == 0) return -1; spin_lock(&lock); /* Don't suppress a CC2 with sigp_retry() */ if (sigp(cpus[idx].addr, SIGP_STOP, 0, NULL)) { spin_unlock(&lock); return -1; } cpus[idx].active = false; spin_unlock(&lock); return 0; } int smp_cpu_stop_store_status(uint16_t idx) { int rc; spin_lock(&lock); rc = smp_cpu_stop_nolock(idx, true); spin_unlock(&lock); return rc; } static int smp_cpu_restart_nolock(uint16_t idx, struct psw *psw) { int rc; check_idx(idx); if (psw) { cpus[idx].lowcore->restart_new_psw.mask = psw->mask; cpus[idx].lowcore->restart_new_psw.addr = psw->addr; } /* * Stop the cpu, so we don't have a race between a running cpu * and the restart in the test that checks if the cpu is * running after the restart. */ smp_cpu_stop_nolock(idx, false); rc = smp_sigp(idx, SIGP_RESTART, 0, NULL); if (rc) return rc; /* * The order has been accepted, but the actual restart may not * have been performed yet, so wait until the cpu is running. */ while (smp_cpu_stopped(idx)) mb(); cpus[idx].active = true; return 0; } int smp_cpu_restart(uint16_t idx) { int rc; spin_lock(&lock); rc = smp_cpu_restart_nolock(idx, NULL); spin_unlock(&lock); return rc; } /* * Functionally equivalent to smp_cpu_restart(), but without the * elements that wait/serialize matters here in the test. * Used to see if KVM itself is serialized correctly. */ int smp_cpu_restart_nowait(uint16_t idx) { check_idx(idx); spin_lock(&lock); /* Don't suppress a CC2 with sigp_retry() */ if (sigp(cpus[idx].addr, SIGP_RESTART, 0, NULL)) { spin_unlock(&lock); return -1; } cpus[idx].active = true; spin_unlock(&lock); return 0; } int smp_cpu_start(uint16_t idx, struct psw psw) { int rc; spin_lock(&lock); rc = smp_cpu_restart_nolock(idx, &psw); spin_unlock(&lock); return rc; } int smp_cpu_destroy(uint16_t idx) { int rc; spin_lock(&lock); rc = smp_cpu_stop_nolock(idx, false); if (!rc) { free_pages(cpus[idx].lowcore); free_pages(cpus[idx].stack); cpus[idx].lowcore = (void *)-1UL; cpus[idx].stack = (void *)-1UL; } spin_unlock(&lock); return rc; } static int smp_cpu_setup_nolock(uint16_t idx, struct psw psw) { struct lowcore *lc; if (cpus[idx].active) return -1; smp_sigp(idx, SIGP_INITIAL_CPU_RESET, 0, NULL); lc = alloc_pages_flags(1, AREA_DMA31); cpus[idx].lowcore = lc; smp_sigp(idx, SIGP_SET_PREFIX, (unsigned long )lc, NULL); /* Copy all exception psws. */ memcpy(lc, cpus[0].lowcore, 512); lc->this_cpu = &cpus[idx]; /* Setup stack */ cpus[idx].stack = (uint64_t *)alloc_pages(2); /* Start without DAT and any other mask bits. */ lc->sw_int_psw.mask = psw.mask; lc->sw_int_psw.addr = psw.addr; lc->sw_int_grs[14] = psw.addr; lc->sw_int_grs[15] = (uint64_t)cpus[idx].stack + (PAGE_SIZE * 4); lc->restart_new_psw.mask = PSW_MASK_64; lc->restart_new_psw.addr = (uint64_t)smp_cpu_setup_state; lc->sw_int_crs[0] = BIT_ULL(CTL0_AFP); /* Start processing */ smp_cpu_restart_nolock(idx, NULL); /* Wait until the cpu has finished setup and started the provided psw */ while (lc->restart_new_psw.addr != psw.addr) mb(); return 0; } int smp_cpu_setup(uint16_t idx, struct psw psw) { int rc = -1; spin_lock(&lock); if (cpus) { check_idx(idx); rc = smp_cpu_setup_nolock(idx, psw); } spin_unlock(&lock); return rc; } /* * Disregarding state, stop all cpus that once were online except for * calling cpu. */ void smp_teardown(void) { int i = 0; uint16_t this_cpu = stap(); int num = smp_query_num_cpus(); spin_lock(&lock); for (; i < num; i++) { if (cpus[i].active && cpus[i].addr != this_cpu) { sigp_retry(cpus[i].addr, SIGP_STOP, 0, NULL); } } spin_unlock(&lock); } /*Expected to be called from boot cpu */ extern uint64_t *stackptr; void smp_setup(void) { int i = 0; int num = smp_query_num_cpus(); unsigned short cpu0_addr = stap(); struct CPUEntry *entry = sclp_get_cpu_entries(); spin_lock(&lock); if (num > 1) printf("SMP: Initializing, found %d cpus\n", num); cpus = calloc(num, sizeof(*cpus)); for (i = 0; i < num; i++) { cpus[i].addr = entry[i].address; cpus[i].active = false; cpus[i].idx = i; /* * Fill in the boot CPU. If the boot CPU is not at index 0, * swap it with the one at index 0. This guarantees that the * boot CPU will always have index 0. If the boot CPU was * already at index 0, a few extra useless assignments are * performed, but everything will work ok. * Notice that there is no guarantee that the list of CPUs * returned by the Read SCP Info command is in any * particular order, or that its order will stay consistent * across multiple invocations. */ if (entry[i].address == cpu0_addr) { cpus[i].addr = cpus[0].addr; cpus[0].addr = cpu0_addr; cpus[0].stack = stackptr; cpus[0].lowcore = (void *)0; cpus[0].active = true; THIS_CPU = &cpus[0]; } } spin_unlock(&lock); }