/* * QEMU PowerPC PowerNV Emulation of some ChipTOD behaviour * * Copyright (c) 2022-2023, IBM Corporation. * * SPDX-License-Identifier: GPL-2.0-or-later * * ChipTOD (aka TOD) is a facility implemented in the nest / pervasive. The * purpose is to keep time-of-day across chips and cores. * * There is a master chip TOD, which sends signals to slave chip TODs to * keep them synchronized. There are two sets of configuration registers * called primary and secondary, which can be used fail over. * * The chip TOD also distributes synchronisation signals to the timebase * facility in each of the cores on the chip. In particular there is a * feature that can move the TOD value in the ChipTOD to and from the TB. * * Initialisation typically brings all ChipTOD into sync (see tod_state), * and then brings each core TB into sync with the ChipTODs (see timebase * state and TFMR). This model is a very basic simulation of the init sequence * performed by skiboot. */ #include "qemu/osdep.h" #include "sysemu/reset.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/fdt.h" #include "hw/ppc/ppc.h" #include "hw/ppc/pnv.h" #include "hw/ppc/pnv_chip.h" #include "hw/ppc/pnv_core.h" #include "hw/ppc/pnv_xscom.h" #include "hw/ppc/pnv_chiptod.h" #include "trace.h" #include /* TOD chip XSCOM addresses */ #define TOD_M_PATH_CTRL_REG 0x00000000 /* Master Path ctrl reg */ #define TOD_PRI_PORT_0_CTRL_REG 0x00000001 /* Primary port0 ctrl reg */ #define TOD_PRI_PORT_1_CTRL_REG 0x00000002 /* Primary port1 ctrl reg */ #define TOD_SEC_PORT_0_CTRL_REG 0x00000003 /* Secondary p0 ctrl reg */ #define TOD_SEC_PORT_1_CTRL_REG 0x00000004 /* Secondary p1 ctrl reg */ #define TOD_S_PATH_CTRL_REG 0x00000005 /* Slave Path ctrl reg */ #define TOD_I_PATH_CTRL_REG 0x00000006 /* Internal Path ctrl reg */ /* -- TOD primary/secondary master/slave control register -- */ #define TOD_PSS_MSS_CTRL_REG 0x00000007 /* -- TOD primary/secondary master/slave status register -- */ #define TOD_PSS_MSS_STATUS_REG 0x00000008 /* TOD chip XSCOM addresses */ #define TOD_CHIP_CTRL_REG 0x00000010 /* Chip control reg */ #define TOD_TX_TTYPE_0_REG 0x00000011 #define TOD_TX_TTYPE_1_REG 0x00000012 /* PSS switch reg */ #define TOD_TX_TTYPE_2_REG 0x00000013 /* Enable step checkers */ #define TOD_TX_TTYPE_3_REG 0x00000014 /* Request TOD reg */ #define TOD_TX_TTYPE_4_REG 0x00000015 /* Send TOD reg */ #define TOD_TX_TTYPE_5_REG 0x00000016 /* Invalidate TOD reg */ #define TOD_MOVE_TOD_TO_TB_REG 0x00000017 #define TOD_LOAD_TOD_MOD_REG 0x00000018 #define TOD_LOAD_TOD_REG 0x00000021 #define TOD_START_TOD_REG 0x00000022 #define TOD_FSM_REG 0x00000024 #define TOD_TX_TTYPE_CTRL_REG 0x00000027 /* TX TTYPE Control reg */ #define TOD_TX_TTYPE_PIB_SLAVE_ADDR PPC_BITMASK(26, 31) /* -- TOD Error interrupt register -- */ #define TOD_ERROR_REG 0x00000030 /* PC unit PIB address which recieves the timebase transfer from TOD */ #define PC_TOD 0x4A3 /* * The TOD FSM: * - The reset state is 0 error. * - A hardware error detected will transition to state 0 from any state. * - LOAD_TOD_MOD and TTYPE5 will transition to state 7 from any state. * * | state | action | new | * |------------+------------------------------+-----| * | 0 error | LOAD_TOD_MOD | 7 | * | 0 error | Recv TTYPE5 (invalidate TOD) | 7 | * | 7 not_set | LOAD_TOD (bit-63 = 0) | 2 | * | 7 not_set | LOAD_TOD (bit-63 = 1) | 1 | * | 7 not_set | Recv TTYPE4 (send TOD) | 2 | * | 2 running | | | * | 1 stopped | START_TOD | 2 | * * Note the hardware has additional states but they relate to the sending * and receiving and waiting on synchronisation signals between chips and * are not described or modeled here. */ static uint64_t pnv_chiptod_xscom_read(void *opaque, hwaddr addr, unsigned size) { PnvChipTOD *chiptod = PNV_CHIPTOD(opaque); uint32_t offset = addr >> 3; uint64_t val = 0; switch (offset) { case TOD_PSS_MSS_STATUS_REG: /* * ChipTOD does not support configurations other than primary * master, does not support errors, etc. */ val |= PPC_BITMASK(6, 10); /* STEP checker validity */ val |= PPC_BIT(12); /* Primary config master path select */ if (chiptod->tod_state == tod_running) { val |= PPC_BIT(20); /* Is running */ } val |= PPC_BIT(21); /* Is using primary config */ val |= PPC_BIT(26); /* Is using master path select */ if (chiptod->primary) { val |= PPC_BIT(23); /* Is active master */ } else if (chiptod->secondary) { val |= PPC_BIT(24); /* Is backup master */ } else { val |= PPC_BIT(25); /* Is slave (should backup master set this?) */ } break; case TOD_PSS_MSS_CTRL_REG: val = chiptod->pss_mss_ctrl_reg; break; case TOD_TX_TTYPE_CTRL_REG: val = 0; break; case TOD_ERROR_REG: val = chiptod->tod_error; break; case TOD_FSM_REG: if (chiptod->tod_state == tod_running) { val |= PPC_BIT(4); } break; default: qemu_log_mask(LOG_UNIMP, "pnv_chiptod: unimplemented register: Ox%" HWADDR_PRIx "\n", addr >> 3); } trace_pnv_chiptod_xscom_read(addr >> 3, val); return val; } static void chiptod_receive_ttype(PnvChipTOD *chiptod, uint32_t trigger) { switch (trigger) { case TOD_TX_TTYPE_4_REG: if (chiptod->tod_state != tod_not_set) { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: received TTYPE4 in " " state %d, should be in 7 (TOD_NOT_SET)\n", chiptod->tod_state); } else { chiptod->tod_state = tod_running; } break; case TOD_TX_TTYPE_5_REG: /* Works from any state */ chiptod->tod_state = tod_not_set; break; default: qemu_log_mask(LOG_UNIMP, "pnv_chiptod: received unimplemented " " TTYPE %u\n", trigger); break; } } static void chiptod_power9_broadcast_ttype(PnvChipTOD *sender, uint32_t trigger) { PnvMachineState *pnv = PNV_MACHINE(qdev_get_machine()); int i; for (i = 0; i < pnv->num_chips; i++) { Pnv9Chip *chip9 = PNV9_CHIP(pnv->chips[i]); PnvChipTOD *chiptod = &chip9->chiptod; if (chiptod != sender) { chiptod_receive_ttype(chiptod, trigger); } } } static void chiptod_power10_broadcast_ttype(PnvChipTOD *sender, uint32_t trigger) { PnvMachineState *pnv = PNV_MACHINE(qdev_get_machine()); int i; for (i = 0; i < pnv->num_chips; i++) { Pnv10Chip *chip10 = PNV10_CHIP(pnv->chips[i]); PnvChipTOD *chiptod = &chip10->chiptod; if (chiptod != sender) { chiptod_receive_ttype(chiptod, trigger); } } } static PnvCore *pnv_chip_get_core_by_xscom_base(PnvChip *chip, uint32_t xscom_base) { PnvChipClass *pcc = PNV_CHIP_GET_CLASS(chip); int i; for (i = 0; i < chip->nr_cores; i++) { PnvCore *pc = chip->cores[i]; CPUCore *cc = CPU_CORE(pc); int core_hwid = cc->core_id; if (pcc->xscom_core_base(chip, core_hwid) == xscom_base) { return pc; } } return NULL; } static PnvCore *chiptod_power9_tx_ttype_target(PnvChipTOD *chiptod, uint64_t val) { /* * skiboot uses Core ID for P9, though SCOM should work too. */ if (val & PPC_BIT(35)) { /* SCOM addressing */ uint32_t addr = val >> 32; uint32_t reg = addr & 0xfff; if (reg != PC_TOD) { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: SCOM addressing: " "unimplemented slave register 0x%" PRIx32 "\n", reg); return NULL; } return pnv_chip_get_core_by_xscom_base(chiptod->chip, addr & ~0xfff); } else { /* Core ID addressing */ uint32_t core_id = GETFIELD(TOD_TX_TTYPE_PIB_SLAVE_ADDR, val) & 0x1f; return pnv_chip_find_core(chiptod->chip, core_id); } } static PnvCore *chiptod_power10_tx_ttype_target(PnvChipTOD *chiptod, uint64_t val) { /* * skiboot uses SCOM for P10 because Core ID was unable to be made to * work correctly. For this reason only SCOM addressing is implemented. */ if (val & PPC_BIT(35)) { /* SCOM addressing */ uint32_t addr = val >> 32; uint32_t reg = addr & 0xfff; if (reg != PC_TOD) { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: SCOM addressing: " "unimplemented slave register 0x%" PRIx32 "\n", reg); return NULL; } /* * This may not deal with P10 big-core addressing at the moment. * The big-core code in skiboot syncs small cores, but it targets * the even PIR (first small-core) when syncing second small-core. */ return pnv_chip_get_core_by_xscom_base(chiptod->chip, addr & ~0xfff); } else { /* Core ID addressing */ qemu_log_mask(LOG_UNIMP, "pnv_chiptod: TX TTYPE Core ID " "addressing is not implemented for POWER10\n"); return NULL; } } static void pnv_chiptod_xscom_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { PnvChipTOD *chiptod = PNV_CHIPTOD(opaque); PnvChipTODClass *pctc = PNV_CHIPTOD_GET_CLASS(chiptod); uint32_t offset = addr >> 3; trace_pnv_chiptod_xscom_write(addr >> 3, val); switch (offset) { case TOD_PSS_MSS_CTRL_REG: /* Is this correct? */ if (chiptod->primary) { val |= PPC_BIT(1); /* TOD is master */ } else { val &= ~PPC_BIT(1); } val |= PPC_BIT(2); /* Drawer is master (don't simulate multi-drawer) */ chiptod->pss_mss_ctrl_reg = val & PPC_BITMASK(0, 31); break; case TOD_TX_TTYPE_CTRL_REG: /* * This register sets the target of the TOD value transfer initiated * by TOD_MOVE_TOD_TO_TB. The TOD is able to send the address to * any target register, though in practice only the PC TOD register * should be used. ChipTOD has a "SCOM addressing" mode which fully * specifies the SCOM address, and a core-ID mode which uses the * core ID to target the PC TOD for a given core. */ chiptod->slave_pc_target = pctc->tx_ttype_target(chiptod, val); if (!chiptod->slave_pc_target) { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: xscom write reg" " TOD_TX_TTYPE_CTRL_REG val 0x%" PRIx64 " invalid slave address\n", val); } break; case TOD_ERROR_REG: chiptod->tod_error &= ~val; break; case TOD_LOAD_TOD_MOD_REG: if (!(val & PPC_BIT(0))) { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: xscom write reg" " TOD_LOAD_TOD_MOD_REG with bad val 0x%" PRIx64"\n", val); } else { chiptod->tod_state = tod_not_set; } break; case TOD_LOAD_TOD_REG: if (chiptod->tod_state != tod_not_set) { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: LOAD_TOG_REG in " " state %d, should be in 7 (TOD_NOT_SET)\n", chiptod->tod_state); } else { if (val & PPC_BIT(63)) { chiptod->tod_state = tod_stopped; } else { chiptod->tod_state = tod_running; } } break; case TOD_MOVE_TOD_TO_TB_REG: /* * XXX: it should be a cleaner model to have this drive a SCOM * transaction to the target address, and implement the state machine * in the PnvCore. For now, this hack makes things work. */ if (chiptod->tod_state != tod_running) { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: xscom write reg" " TOD_MOVE_TOD_TO_TB_REG in bad state %d\n", chiptod->tod_state); } else if (!(val & PPC_BIT(0))) { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: xscom write reg" " TOD_MOVE_TOD_TO_TB_REG with bad val 0x%" PRIx64"\n", val); } else if (chiptod->slave_pc_target == NULL) { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: xscom write reg" " TOD_MOVE_TOD_TO_TB_REG with no slave target\n"); } else { PowerPCCPU *cpu = chiptod->slave_pc_target->threads[0]; CPUPPCState *env = &cpu->env; /* * Moving TOD to TB will set the TB of all threads in a * core, so skiboot only does this once per thread0, so * that is where we keep the timebase state machine. * * It is likely possible for TBST to be driven from other * threads in the core, but for now we only implement it for * thread 0. */ if (env->pnv_tod_tbst.tb_ready_for_tod) { env->pnv_tod_tbst.tod_sent_to_tb = 1; } else { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: xscom write reg" " TOD_MOVE_TOD_TO_TB_REG with TB not ready to" " receive TOD\n"); } } break; case TOD_START_TOD_REG: if (chiptod->tod_state != tod_stopped) { qemu_log_mask(LOG_GUEST_ERROR, "pnv_chiptod: LOAD_TOG_REG in " " state %d, should be in 1 (TOD_STOPPED)\n", chiptod->tod_state); } else { chiptod->tod_state = tod_running; } break; case TOD_TX_TTYPE_4_REG: case TOD_TX_TTYPE_5_REG: pctc->broadcast_ttype(chiptod, offset); break; default: qemu_log_mask(LOG_UNIMP, "pnv_chiptod: unimplemented register: Ox%" HWADDR_PRIx "\n", addr >> 3); } } static const MemoryRegionOps pnv_chiptod_xscom_ops = { .read = pnv_chiptod_xscom_read, .write = pnv_chiptod_xscom_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 int pnv_chiptod_dt_xscom(PnvXScomInterface *dev, void *fdt, int xscom_offset, const char compat[], size_t compat_size) { PnvChipTOD *chiptod = PNV_CHIPTOD(dev); g_autofree char *name = NULL; int offset; uint32_t chiptod_pcba = PNV9_XSCOM_CHIPTOD_BASE; uint32_t reg[] = { cpu_to_be32(chiptod_pcba), cpu_to_be32(PNV9_XSCOM_CHIPTOD_SIZE) }; name = g_strdup_printf("chiptod@%x", chiptod_pcba); offset = fdt_add_subnode(fdt, xscom_offset, name); _FDT(offset); if (chiptod->primary) { _FDT((fdt_setprop(fdt, offset, "primary", NULL, 0))); } else if (chiptod->secondary) { _FDT((fdt_setprop(fdt, offset, "secondary", NULL, 0))); } _FDT((fdt_setprop(fdt, offset, "reg", reg, sizeof(reg)))); _FDT((fdt_setprop(fdt, offset, "compatible", compat, compat_size))); return 0; } static int pnv_chiptod_power9_dt_xscom(PnvXScomInterface *dev, void *fdt, int xscom_offset) { const char compat[] = "ibm,power-chiptod\0ibm,power9-chiptod"; return pnv_chiptod_dt_xscom(dev, fdt, xscom_offset, compat, sizeof(compat)); } static Property pnv_chiptod_properties[] = { DEFINE_PROP_BOOL("primary", PnvChipTOD, primary, false), DEFINE_PROP_BOOL("secondary", PnvChipTOD, secondary, false), DEFINE_PROP_LINK("chip", PnvChipTOD , chip, TYPE_PNV_CHIP, PnvChip *), DEFINE_PROP_END_OF_LIST(), }; static void pnv_chiptod_power9_class_init(ObjectClass *klass, void *data) { PnvChipTODClass *pctc = PNV_CHIPTOD_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); PnvXScomInterfaceClass *xdc = PNV_XSCOM_INTERFACE_CLASS(klass); dc->desc = "PowerNV ChipTOD Controller (POWER9)"; device_class_set_props(dc, pnv_chiptod_properties); xdc->dt_xscom = pnv_chiptod_power9_dt_xscom; pctc->broadcast_ttype = chiptod_power9_broadcast_ttype; pctc->tx_ttype_target = chiptod_power9_tx_ttype_target; pctc->xscom_size = PNV_XSCOM_CHIPTOD_SIZE; } static const TypeInfo pnv_chiptod_power9_type_info = { .name = TYPE_PNV9_CHIPTOD, .parent = TYPE_PNV_CHIPTOD, .instance_size = sizeof(PnvChipTOD), .class_init = pnv_chiptod_power9_class_init, .interfaces = (InterfaceInfo[]) { { TYPE_PNV_XSCOM_INTERFACE }, { } } }; static int pnv_chiptod_power10_dt_xscom(PnvXScomInterface *dev, void *fdt, int xscom_offset) { const char compat[] = "ibm,power-chiptod\0ibm,power10-chiptod"; return pnv_chiptod_dt_xscom(dev, fdt, xscom_offset, compat, sizeof(compat)); } static void pnv_chiptod_power10_class_init(ObjectClass *klass, void *data) { PnvChipTODClass *pctc = PNV_CHIPTOD_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); PnvXScomInterfaceClass *xdc = PNV_XSCOM_INTERFACE_CLASS(klass); dc->desc = "PowerNV ChipTOD Controller (POWER10)"; device_class_set_props(dc, pnv_chiptod_properties); xdc->dt_xscom = pnv_chiptod_power10_dt_xscom; pctc->broadcast_ttype = chiptod_power10_broadcast_ttype; pctc->tx_ttype_target = chiptod_power10_tx_ttype_target; pctc->xscom_size = PNV_XSCOM_CHIPTOD_SIZE; } static const TypeInfo pnv_chiptod_power10_type_info = { .name = TYPE_PNV10_CHIPTOD, .parent = TYPE_PNV_CHIPTOD, .instance_size = sizeof(PnvChipTOD), .class_init = pnv_chiptod_power10_class_init, .interfaces = (InterfaceInfo[]) { { TYPE_PNV_XSCOM_INTERFACE }, { } } }; static void pnv_chiptod_reset(void *dev) { PnvChipTOD *chiptod = PNV_CHIPTOD(dev); chiptod->pss_mss_ctrl_reg = 0; if (chiptod->primary) { chiptod->pss_mss_ctrl_reg |= PPC_BIT(1); /* TOD is master */ } /* Drawer is master (we do not simulate multi-drawer) */ chiptod->pss_mss_ctrl_reg |= PPC_BIT(2); chiptod->tod_error = 0; chiptod->tod_state = tod_error; } static void pnv_chiptod_realize(DeviceState *dev, Error **errp) { PnvChipTOD *chiptod = PNV_CHIPTOD(dev); PnvChipTODClass *pctc = PNV_CHIPTOD_GET_CLASS(chiptod); /* XScom regions for ChipTOD registers */ pnv_xscom_region_init(&chiptod->xscom_regs, OBJECT(dev), &pnv_chiptod_xscom_ops, chiptod, "xscom-chiptod", pctc->xscom_size); qemu_register_reset(pnv_chiptod_reset, chiptod); } static void pnv_chiptod_unrealize(DeviceState *dev) { PnvChipTOD *chiptod = PNV_CHIPTOD(dev); qemu_unregister_reset(pnv_chiptod_reset, chiptod); } static void pnv_chiptod_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = pnv_chiptod_realize; dc->unrealize = pnv_chiptod_unrealize; dc->desc = "PowerNV ChipTOD Controller"; dc->user_creatable = false; } static const TypeInfo pnv_chiptod_type_info = { .name = TYPE_PNV_CHIPTOD, .parent = TYPE_DEVICE, .instance_size = sizeof(PnvChipTOD), .class_init = pnv_chiptod_class_init, .class_size = sizeof(PnvChipTODClass), .abstract = true, }; static void pnv_chiptod_register_types(void) { type_register_static(&pnv_chiptod_type_info); type_register_static(&pnv_chiptod_power9_type_info); type_register_static(&pnv_chiptod_power10_type_info); } type_init(pnv_chiptod_register_types);