/* * QEMU PowerPC PowerNV Emulation of some SBE behaviour * * Copyright (c) 2022, IBM Corporation. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "qemu/osdep.h" #include "target/ppc/cpu.h" #include "qapi/error.h" #include "qemu/log.h" #include "qemu/module.h" #include "hw/irq.h" #include "hw/qdev-properties.h" #include "hw/ppc/pnv.h" #include "hw/ppc/pnv_xscom.h" #include "hw/ppc/pnv_sbe.h" #include "trace.h" /* * Most register and command definitions come from skiboot. * * xscom addresses are adjusted to be relative to xscom subregion bases */ /* * SBE MBOX register address * Reg 0 - 3 : Host to send command packets to SBE * Reg 4 - 7 : SBE to send response packets to Host */ #define PSU_HOST_SBE_MBOX_REG0 0x00000000 #define PSU_HOST_SBE_MBOX_REG1 0x00000001 #define PSU_HOST_SBE_MBOX_REG2 0x00000002 #define PSU_HOST_SBE_MBOX_REG3 0x00000003 #define PSU_HOST_SBE_MBOX_REG4 0x00000004 #define PSU_HOST_SBE_MBOX_REG5 0x00000005 #define PSU_HOST_SBE_MBOX_REG6 0x00000006 #define PSU_HOST_SBE_MBOX_REG7 0x00000007 #define PSU_SBE_DOORBELL_REG_RW 0x00000010 #define PSU_SBE_DOORBELL_REG_AND 0x00000011 #define PSU_SBE_DOORBELL_REG_OR 0x00000012 #define PSU_HOST_DOORBELL_REG_RW 0x00000013 #define PSU_HOST_DOORBELL_REG_AND 0x00000014 #define PSU_HOST_DOORBELL_REG_OR 0x00000015 /* * Doorbell register to trigger SBE interrupt. Set by OPAL to inform * the SBE about a waiting message in the Host/SBE mailbox registers */ #define HOST_SBE_MSG_WAITING PPC_BIT(0) /* * Doorbell register for host bridge interrupt. Set by the SBE to inform * host about a response message in the Host/SBE mailbox registers */ #define SBE_HOST_RESPONSE_WAITING PPC_BIT(0) #define SBE_HOST_MSG_READ PPC_BIT(1) #define SBE_HOST_STOP15_EXIT PPC_BIT(2) #define SBE_HOST_RESET PPC_BIT(3) #define SBE_HOST_PASSTHROUGH PPC_BIT(4) #define SBE_HOST_TIMER_EXPIRY PPC_BIT(14) #define SBE_HOST_RESPONSE_MASK (PPC_BITMASK(0, 4) | \ SBE_HOST_TIMER_EXPIRY) /* SBE Control Register */ #define SBE_CONTROL_REG_RW 0x00000000 /* SBE interrupt s0/s1 bits */ #define SBE_CONTROL_REG_S0 PPC_BIT(14) #define SBE_CONTROL_REG_S1 PPC_BIT(15) struct sbe_msg { uint64_t reg[4]; }; static uint64_t pnv_sbe_power9_xscom_ctrl_read(void *opaque, hwaddr addr, unsigned size) { uint32_t offset = addr >> 3; uint64_t val = 0; switch (offset) { default: qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" HWADDR_PRIx "\n", addr >> 3); } trace_pnv_sbe_xscom_ctrl_read(addr, val); return val; } static void pnv_sbe_power9_xscom_ctrl_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { uint32_t offset = addr >> 3; trace_pnv_sbe_xscom_ctrl_write(addr, val); switch (offset) { default: qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" HWADDR_PRIx "\n", addr >> 3); } } static const MemoryRegionOps pnv_sbe_power9_xscom_ctrl_ops = { .read = pnv_sbe_power9_xscom_ctrl_read, .write = pnv_sbe_power9_xscom_ctrl_write, .valid.min_access_size = 8, .valid.max_access_size = 8, .impl.min_access_size = 8, .impl.max_access_size = 8, .endianness = DEVICE_BIG_ENDIAN, }; static void pnv_sbe_set_host_doorbell(PnvSBE *sbe, uint64_t val) { val &= SBE_HOST_RESPONSE_MASK; /* Is this right? What does HW do? */ sbe->host_doorbell = val; trace_pnv_sbe_reg_set_host_doorbell(val); qemu_set_irq(sbe->psi_irq, !!val); } /* SBE Target Type */ #define SBE_TARGET_TYPE_PROC 0x00 #define SBE_TARGET_TYPE_EX 0x01 #define SBE_TARGET_TYPE_PERV 0x02 #define SBE_TARGET_TYPE_MCS 0x03 #define SBE_TARGET_TYPE_EQ 0x04 #define SBE_TARGET_TYPE_CORE 0x05 /* SBE MBOX command class */ #define SBE_MCLASS_FIRST 0xD1 #define SBE_MCLASS_CORE_STATE 0xD1 #define SBE_MCLASS_SCOM 0xD2 #define SBE_MCLASS_RING 0xD3 #define SBE_MCLASS_TIMER 0xD4 #define SBE_MCLASS_MPIPL 0xD5 #define SBE_MCLASS_SECURITY 0xD6 #define SBE_MCLASS_GENERIC 0xD7 #define SBE_MCLASS_LAST 0xD7 /* * Commands are provided in xxyy form where: * - xx : command class * - yy : command * * Both request and response message uses same seq ID, * command class and command. */ #define SBE_CMD_CTRL_DEADMAN_LOOP 0xD101 #define SBE_CMD_MULTI_SCOM 0xD201 #define SBE_CMD_PUT_RING_FORM_IMAGE 0xD301 #define SBE_CMD_CONTROL_TIMER 0xD401 #define SBE_CMD_GET_ARCHITECTED_REG 0xD501 #define SBE_CMD_CLR_ARCHITECTED_REG 0xD502 #define SBE_CMD_SET_UNSEC_MEM_WINDOW 0xD601 #define SBE_CMD_GET_SBE_FFDC 0xD701 #define SBE_CMD_GET_CAPABILITY 0xD702 #define SBE_CMD_READ_SBE_SEEPROM 0xD703 #define SBE_CMD_SET_FFDC_ADDR 0xD704 #define SBE_CMD_QUIESCE_SBE 0xD705 #define SBE_CMD_SET_FABRIC_ID_MAP 0xD706 #define SBE_CMD_STASH_MPIPL_CONFIG 0xD707 /* SBE MBOX control flags */ /* Generic flags */ #define SBE_CMD_CTRL_RESP_REQ 0x0100 #define SBE_CMD_CTRL_ACK_REQ 0x0200 /* Deadman loop */ #define CTRL_DEADMAN_LOOP_START 0x0001 #define CTRL_DEADMAN_LOOP_STOP 0x0002 /* Control timer */ #define CONTROL_TIMER_START 0x0001 #define CONTROL_TIMER_STOP 0x0002 /* Stash MPIPL config */ #define SBE_STASH_KEY_SKIBOOT_BASE 0x03 static void sbe_timer(void *opaque) { PnvSBE *sbe = opaque; trace_pnv_sbe_cmd_timer_expired(); pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell | SBE_HOST_TIMER_EXPIRY); } static void do_sbe_msg(PnvSBE *sbe) { struct sbe_msg msg; uint16_t cmd, ctrl_flags, seq_id; int i; memset(&msg, 0, sizeof(msg)); for (i = 0; i < 4; i++) { msg.reg[i] = sbe->mbox[i]; } cmd = msg.reg[0]; seq_id = msg.reg[0] >> 16; ctrl_flags = msg.reg[0] >> 32; trace_pnv_sbe_msg_recv(cmd, seq_id, ctrl_flags); if (ctrl_flags & SBE_CMD_CTRL_ACK_REQ) { pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell | SBE_HOST_MSG_READ); } switch (cmd) { case SBE_CMD_CONTROL_TIMER: if (ctrl_flags & CONTROL_TIMER_START) { uint64_t us = msg.reg[1]; trace_pnv_sbe_cmd_timer_start(us); timer_mod(sbe->timer, qemu_clock_get_us(QEMU_CLOCK_VIRTUAL) + us); } if (ctrl_flags & CONTROL_TIMER_STOP) { trace_pnv_sbe_cmd_timer_stop(); timer_del(sbe->timer); } break; default: qemu_log_mask(LOG_UNIMP, "SBE Unimplemented command: 0x%x\n", cmd); } } static void pnv_sbe_set_sbe_doorbell(PnvSBE *sbe, uint64_t val) { val &= HOST_SBE_MSG_WAITING; sbe->sbe_doorbell = val; if (val & HOST_SBE_MSG_WAITING) { sbe->sbe_doorbell &= ~HOST_SBE_MSG_WAITING; do_sbe_msg(sbe); } } static uint64_t pnv_sbe_power9_xscom_mbox_read(void *opaque, hwaddr addr, unsigned size) { PnvSBE *sbe = PNV_SBE(opaque); uint32_t offset = addr >> 3; uint64_t val = 0; if (offset <= PSU_HOST_SBE_MBOX_REG7) { uint32_t idx = offset - PSU_HOST_SBE_MBOX_REG0; val = sbe->mbox[idx]; } else { switch (offset) { case PSU_SBE_DOORBELL_REG_RW: val = sbe->sbe_doorbell; break; case PSU_HOST_DOORBELL_REG_RW: val = sbe->host_doorbell; break; default: qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" HWADDR_PRIx "\n", addr >> 3); } } trace_pnv_sbe_xscom_mbox_read(addr, val); return val; } static void pnv_sbe_power9_xscom_mbox_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { PnvSBE *sbe = PNV_SBE(opaque); uint32_t offset = addr >> 3; trace_pnv_sbe_xscom_mbox_write(addr, val); if (offset <= PSU_HOST_SBE_MBOX_REG7) { uint32_t idx = offset - PSU_HOST_SBE_MBOX_REG0; sbe->mbox[idx] = val; } else { switch (offset) { case PSU_SBE_DOORBELL_REG_RW: pnv_sbe_set_sbe_doorbell(sbe, val); break; case PSU_SBE_DOORBELL_REG_AND: pnv_sbe_set_sbe_doorbell(sbe, sbe->sbe_doorbell & val); break; case PSU_SBE_DOORBELL_REG_OR: pnv_sbe_set_sbe_doorbell(sbe, sbe->sbe_doorbell | val); break; case PSU_HOST_DOORBELL_REG_RW: pnv_sbe_set_host_doorbell(sbe, val); break; case PSU_HOST_DOORBELL_REG_AND: pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell & val); break; case PSU_HOST_DOORBELL_REG_OR: pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell | val); break; default: qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" HWADDR_PRIx "\n", addr >> 3); } } } static const MemoryRegionOps pnv_sbe_power9_xscom_mbox_ops = { .read = pnv_sbe_power9_xscom_mbox_read, .write = pnv_sbe_power9_xscom_mbox_write, .valid.min_access_size = 8, .valid.max_access_size = 8, .impl.min_access_size = 8, .impl.max_access_size = 8, .endianness = DEVICE_BIG_ENDIAN, }; static void pnv_sbe_power9_class_init(ObjectClass *klass, void *data) { PnvSBEClass *psc = PNV_SBE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); dc->desc = "PowerNV SBE Controller (POWER9)"; psc->xscom_ctrl_size = PNV9_XSCOM_SBE_CTRL_SIZE; psc->xscom_ctrl_ops = &pnv_sbe_power9_xscom_ctrl_ops; psc->xscom_mbox_size = PNV9_XSCOM_SBE_MBOX_SIZE; psc->xscom_mbox_ops = &pnv_sbe_power9_xscom_mbox_ops; } static const TypeInfo pnv_sbe_power9_type_info = { .name = TYPE_PNV9_SBE, .parent = TYPE_PNV_SBE, .instance_size = sizeof(PnvSBE), .class_init = pnv_sbe_power9_class_init, }; static void pnv_sbe_power10_class_init(ObjectClass *klass, void *data) { PnvSBEClass *psc = PNV_SBE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); dc->desc = "PowerNV SBE Controller (POWER10)"; psc->xscom_ctrl_size = PNV10_XSCOM_SBE_CTRL_SIZE; psc->xscom_ctrl_ops = &pnv_sbe_power9_xscom_ctrl_ops; psc->xscom_mbox_size = PNV10_XSCOM_SBE_MBOX_SIZE; psc->xscom_mbox_ops = &pnv_sbe_power9_xscom_mbox_ops; } static const TypeInfo pnv_sbe_power10_type_info = { .name = TYPE_PNV10_SBE, .parent = TYPE_PNV9_SBE, .class_init = pnv_sbe_power10_class_init, }; static void pnv_sbe_realize(DeviceState *dev, Error **errp) { PnvSBE *sbe = PNV_SBE(dev); PnvSBEClass *psc = PNV_SBE_GET_CLASS(sbe); /* XScom regions for SBE registers */ pnv_xscom_region_init(&sbe->xscom_ctrl_regs, OBJECT(dev), psc->xscom_ctrl_ops, sbe, "xscom-sbe-ctrl", psc->xscom_ctrl_size); pnv_xscom_region_init(&sbe->xscom_mbox_regs, OBJECT(dev), psc->xscom_mbox_ops, sbe, "xscom-sbe-mbox", psc->xscom_mbox_size); qdev_init_gpio_out(DEVICE(dev), &sbe->psi_irq, 1); sbe->timer = timer_new_us(QEMU_CLOCK_VIRTUAL, sbe_timer, sbe); } static void pnv_sbe_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = pnv_sbe_realize; dc->desc = "PowerNV SBE Controller"; dc->user_creatable = false; } static const TypeInfo pnv_sbe_type_info = { .name = TYPE_PNV_SBE, .parent = TYPE_DEVICE, .instance_size = sizeof(PnvSBE), .class_init = pnv_sbe_class_init, .class_size = sizeof(PnvSBEClass), .abstract = true, }; static void pnv_sbe_register_types(void) { type_register_static(&pnv_sbe_type_info); type_register_static(&pnv_sbe_power9_type_info); type_register_static(&pnv_sbe_power10_type_info); } type_init(pnv_sbe_register_types);