/**
* cpu_resume:
* @cpu: The CPU to resume.
diff --git a/include/hw/ppc/pnv.h b/include/hw/ppc/pnv.h
index c56d152889..b7858d310d 100644
--- a/include/hw/ppc/pnv.h
+++ b/include/hw/ppc/pnv.h
@@ -112,6 +112,8 @@ PnvChip *pnv_chip_add_phb(PnvChip *chip, PnvPHB *phb);
#define PNV_FDT_ADDR 0x01000000
#define PNV_TIMEBASE_FREQ 512000000ULL
+void pnv_cpu_do_nmi_resume(CPUState *cs);
+
/*
* BMC helpers
*/
diff --git a/include/hw/ppc/pnv_core.h b/include/hw/ppc/pnv_core.h
index c8784777a4..1de79a818e 100644
--- a/include/hw/ppc/pnv_core.h
+++ b/include/hw/ppc/pnv_core.h
@@ -109,6 +109,9 @@ OBJECT_DECLARE_TYPE(PnvQuad, PnvQuadClass, PNV_QUAD)
struct PnvQuad {
DeviceState parent_obj;
+ bool special_wakeup_done;
+ bool special_wakeup[4];
+
uint32_t quad_id;
MemoryRegion xscom_regs;
MemoryRegion xscom_qme_regs;
diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c
index 575f18958d..71b2b3806c 100644
--- a/hw/ppc/pnv.c
+++ b/hw/ppc/pnv.c
@@ -2747,11 +2747,24 @@ static void pnv_cpu_do_nmi_on_cpu(CPUState *cs,
run_on_cpu_data arg)
*/
env->spr[SPR_SRR1] |= SRR1_WAKESCOM;
}
+ if (arg.host_int) {
+ cpu_resume(cs);
+ }
+}
+
+static void pnv_cpu_do_nmi(CPUState *cs, int resume)
+{
+ async_run_on_cpu(cs, pnv_cpu_do_nmi_on_cpu, RUN_ON_CPU_HOST_INT(resume));
+}
+
+void pnv_cpu_do_nmi_resume(CPUState *cs)
+{
+ pnv_cpu_do_nmi(cs, 1);
}
-static void pnv_cpu_do_nmi(PnvChip *chip, PowerPCCPU *cpu, void *opaque)
+static void do_pnv_cpu_do_nmi(PnvChip *chip, PowerPCCPU *cpu, void *opaque)
{
- async_run_on_cpu(CPU(cpu), pnv_cpu_do_nmi_on_cpu, RUN_ON_CPU_NULL);
+ pnv_cpu_do_nmi(CPU(cpu), 0);
}
static void pnv_nmi(NMIState *n, int cpu_index, Error **errp)
@@ -2760,7 +2773,7 @@ static void pnv_nmi(NMIState *n, int cpu_index, Error
**errp)
int i;
for (i = 0; i < pnv->num_chips; i++) {
- pnv_chip_foreach_cpu(pnv->chips[i], pnv_cpu_do_nmi, NULL);
+ pnv_chip_foreach_cpu(pnv->chips[i], do_pnv_cpu_do_nmi, NULL);
}
}
diff --git a/hw/ppc/pnv_core.c b/hw/ppc/pnv_core.c
index e03ac5441e..a685a5dc1b 100644
--- a/hw/ppc/pnv_core.c
+++ b/hw/ppc/pnv_core.c
@@ -185,16 +185,40 @@ static const MemoryRegionOps pnv_core_power9_xscom_ops = {
*/
#define PNV10_XSCOM_EC_CORE_THREAD_STATE 0x412
+#define PNV10_XSCOM_EC_CORE_THREAD_INFO 0x413
+#define PNV10_XSCOM_EC_CORE_DIRECT_CONTROLS 0x449
+#define PNV10_XSCOM_EC_CORE_RAS_STATUS 0x454
static uint64_t pnv_core_power10_xscom_read(void *opaque, hwaddr addr,
unsigned int width)
{
+ PnvCore *pc = PNV_CORE(opaque);
+ int nr_threads = CPU_CORE(pc)->nr_threads;
+ int i;
uint32_t offset = addr >> 3;
uint64_t val = 0;
switch (offset) {
case PNV10_XSCOM_EC_CORE_THREAD_STATE:
- val = 0;
+ for (i = 0; i < nr_threads; i++) {
+ PowerPCCPU *cpu = pc->threads[i];
+ CPUState *cs = CPU(cpu);
+
+ if (cs->halted) {
+ val |= PPC_BIT(56 + i);
+ }
+ }
+ break;
+ case PNV10_XSCOM_EC_CORE_THREAD_INFO:
+ break;
+ case PNV10_XSCOM_EC_CORE_RAS_STATUS:
+ for (i = 0; i < nr_threads; i++) {
+ PowerPCCPU *cpu = pc->threads[i];
+ CPUState *cs = CPU(cpu);
+ if (cs->stopped) {
+ val |= PPC_BIT(0 + 8*i) | PPC_BIT(1 + 8*i);
+ }
+ }
break;
default:
qemu_log_mask(LOG_UNIMP, "%s: unimp read 0x%08x\n", __func__,
@@ -207,9 +231,45 @@ static uint64_t pnv_core_power10_xscom_read(void *opaque,
hwaddr addr,
static void pnv_core_power10_xscom_write(void *opaque, hwaddr addr,
uint64_t val, unsigned int width)
{
+ PnvCore *pc = PNV_CORE(opaque);
+ int nr_threads = CPU_CORE(pc)->nr_threads;
+ int i;
uint32_t offset = addr >> 3;
switch (offset) {
+ case PNV10_XSCOM_EC_CORE_DIRECT_CONTROLS:
+ for (i = 0; i < nr_threads; i++) {
+ PowerPCCPU *cpu = pc->threads[i];
+ CPUState *cs = CPU(cpu);
+
+ if (val & PPC_BIT(7 + 8*i)) { /* stop */
+ val &= ~PPC_BIT(7 + 8*i);
+ cpu_pause(cs);
+ }
+ if (val & PPC_BIT(6 + 8*i)) { /* start */
+ val &= ~PPC_BIT(6 + 8*i);
+ cpu_resume(cs);
+ }
+ if (val & PPC_BIT(4 + 8*i)) { /* sreset */
+ val &= ~PPC_BIT(4 + 8*i);
+ pnv_cpu_do_nmi_resume(cs);
+ }
+ if (val & PPC_BIT(3 + 8*i)) { /* clear maint */
+ /*
+ * Hardware has very particular cases for where clear maint
+ * must be used and where start must be used to resume a
+ * thread. These are not modelled exactly, just treat
+ * this and start the same.
+ */
+ val &= ~PPC_BIT(3 + 8*i);
+ cpu_resume(cs);
+ }
+ }
+ if (val) {
+ qemu_log_mask(LOG_UNIMP, "%s: unimp bits in DIRECT_CONTROLS
0x%016lx\n", __func__, val);
+ }
+ break;
+
default:
qemu_log_mask(LOG_UNIMP, "%s: unimp write 0x%08x\n", __func__,
offset);
@@ -528,6 +588,7 @@ static const MemoryRegionOps pnv_quad_power10_xscom_ops = {
static uint64_t pnv_qme_power10_xscom_read(void *opaque, hwaddr addr,
unsigned int width)
{
+ PnvQuad *eq = PNV_QUAD(opaque);
uint32_t offset = addr >> 3;
uint64_t val = -1;
@@ -535,10 +596,14 @@ static uint64_t pnv_qme_power10_xscom_read(void *opaque, hwaddr addr,
* Forth nibble selects the core within a quad, mask it to process read
* for any core.
*/
- switch (offset & ~0xf000) {
- case P10_QME_SPWU_HYP:
+ switch (offset & ~PPC_BITMASK32(16, 19)) {
case P10_QME_SSH_HYP:
- return 0;
+ val = 0;
+ if (eq->special_wakeup_done) {
+ val |= PPC_BIT(1); /* SPWU DONE */
+ val |= PPC_BIT(4); /* SSH SPWU DONE */
+ }
+ break;
default:
qemu_log_mask(LOG_UNIMP, "%s: unimp read 0x%08x\n", __func__,
offset);
@@ -550,9 +615,22 @@ static uint64_t pnv_qme_power10_xscom_read(void *opaque,
hwaddr addr,
static void pnv_qme_power10_xscom_write(void *opaque, hwaddr addr,
uint64_t val, unsigned int width)
{
+ PnvQuad *eq = PNV_QUAD(opaque);
uint32_t offset = addr >> 3;
+ bool set;
+ int i;
- switch (offset) {
+ switch (offset & ~PPC_BITMASK32(16, 19)) {
+ case P10_QME_SPWU_HYP:
+ set = !!(val & PPC_BIT(0));
+ eq->special_wakeup_done = set;
+ for (i = 0; i < 4; i++) {
+ /* These bits select cores in the quad */
+ if (offset & PPC_BIT32(16 + i)) {
+ eq->special_wakeup[i] = set;
+ }
+ }
+ break;
default:
qemu_log_mask(LOG_UNIMP, "%s: unimp write 0x%08x\n", __func__,
offset);
diff --git a/system/cpus.c b/system/cpus.c
index d3640c9503..083abbc393 100644
--- a/system/cpus.c
+++ b/system/cpus.c
@@ -613,6 +613,16 @@ void pause_all_vcpus(void)
bql_lock();
}
+void cpu_pause(CPUState *cpu)
+{
+ if (qemu_cpu_is_self(cpu)) {
+ qemu_cpu_stop(cpu, true);
+ } else {
+ cpu->stop = true;
+ qemu_cpu_kick(cpu);
+ }
+}
+
void cpu_resume(CPUState *cpu)
{
cpu->stop = false;