diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2024-02-23 18:59:11 +0000 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2024-02-23 18:59:11 +0000 |
commit | 91e3bf2e925671eb37e3b71cf7fdeb6b7f30248c (patch) | |
tree | 1acdff9476c4fc16f41e2039b86118aae18a7ebf /hw | |
parent | 3d54cbf269d63ff1d500b35b2bcf4565ff8ad485 (diff) | |
parent | 4acc505d2236190efea94746e7f22e2c07bce5d6 (diff) | |
download | qemu-91e3bf2e925671eb37e3b71cf7fdeb6b7f30248c.zip qemu-91e3bf2e925671eb37e3b71cf7fdeb6b7f30248c.tar.gz qemu-91e3bf2e925671eb37e3b71cf7fdeb6b7f30248c.tar.bz2 |
Merge tag 'pull-ppc-for-9.0-20240224' of https://gitlab.com/npiggin/qemu into staging
* Avocado tests for ppc64 to boot FreeBSD, run guests with emulated
or nested hypervisor facilities, among other things.
* Update ppc64 CPU defaults to Power10.
* Add a new powernv10-rainier machine to better capture differences
between the different Power10 systems.
* Implement more device models for powernv.
* 4xx TLB flushing performance and correctness improvements.
* Correct gdb implementation to access some important SPRs.
* Misc cleanups and bug fixes.
# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCgAdFiEETkN92lZhb0MpsKeVZ7MCdqhiHK4FAmXYuX0ACgkQZ7MCdqhi
# HK6t1Q/9Hxw+MseFUa/6sbWX6mhv/8emrFFOwI9qxapxDoMyic+SjIhR5PPCYh6t
# TLE1vJiV54XYB3286hz3eQfDxfHNjkgsF7PYp9SEd6D1rMT9ESxeu5NkifenEfP0
# UoTFXJyfg/OF1h+JQRrVv1m+D4mqGGNCQB4QiU3DYTmRhrhp7H3mKfUX/KvkEwiX
# EqZibmrqb9SVSjT66LBQzY328mEH4nipF33QtYKfYjb6kMe8ACSznL2VYP0NmacU
# T+3eHJeLtOLeRlHwYfADx2ekRHlsJuE9/fMMHJHb2qxJkHSQ7yGBqSLESAe6kNP8
# TnKJ9x4433K7IjFqaoiDONrMVJbVZDh/DUh1WWdY14iiUOYEy7uLkLtmThmNSyUB
# 622Rd5Ch09JWzA/tg1aC9mR2f9boe9/Z1VeHeN8j+sVj1e6MEh8un8SER3X+9TDz
# myGLsmPXQnu1yjebycuE+9RAPbR9npOAkQpE5ZfDwjUM7y4s4jzZUKUoIhtCXeEF
# eIykVnaGbPlEBGpuf+E+w2ZxhZUIfxRUhuunK8Ib4TE8khJn/Ir4BxoLweSnqtKM
# O4xiFvHm72RUVK232Kox5HWbFJ8XSLBUb3ABNGbXXynzAMD+THB4ImFBbysOmIkR
# xcF1tWQ+xoMMcCxbx73b0PhO5AR/PgYc2ctug9rAc9fh4ypJLEs=
# =LZzb
# -----END PGP SIGNATURE-----
# gpg: Signature made Fri 23 Feb 2024 15:27:57 GMT
# gpg: using RSA key 4E437DDA56616F4329B0A79567B30276A8621CAE
# gpg: Good signature from "Nicholas Piggin <npiggin@gmail.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg: There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 4E43 7DDA 5661 6F43 29B0 A795 67B3 0276 A862 1CAE
* tag 'pull-ppc-for-9.0-20240224' of https://gitlab.com/npiggin/qemu: (47 commits)
target/ppc: optimise ppcemb_tlb_t flushing
target/ppc: 440 optimise tlbwe TLB flushing
target/ppc: 4xx optimise tlbwe_lo TLB flushing
target/ppc: 4xx don't flush TLB for a newly written software TLB entry
target/ppc: Factor out 4xx ppcemb_tlb_t flushing
target/ppc: Fix 440 tlbwe TLB invalidation gaps
target/ppc: Add SMT support to time facilities
target/ppc: Implement core timebase state machine and TFMR
ppc/pnv: Implement the ChipTOD to Core transfer
ppc/pnv: Wire ChipTOD model to powernv9 and powernv10 machines
ppc/pnv: Add POWER9/10 chiptod model
target/ppc: Fix move-to timebase SPR access permissions
target/ppc: Improve timebase register defines naming
target/ppc: Rename TBL to TB on 64-bit
target/ppc: Update gdbstub to read SPR's CFAR, DEC, HDEC, TB-L/U
hw/ppc: N1 chiplet wiring
hw/ppc: Add N1 chiplet model
hw/ppc: Add pnv nest pervasive common chiplet model
ppc/pnv: Test pnv i2c master and connected devices
ppc/pnv: Add a pca9554 I2C device to powernv10-rainier
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw')
-rw-r--r-- | hw/misc/Kconfig | 4 | ||||
-rw-r--r-- | hw/misc/meson.build | 1 | ||||
-rw-r--r-- | hw/misc/pca9552.c | 58 | ||||
-rw-r--r-- | hw/misc/pca9554.c | 328 | ||||
-rw-r--r-- | hw/ppc/Kconfig | 2 | ||||
-rw-r--r-- | hw/ppc/meson.build | 5 | ||||
-rw-r--r-- | hw/ppc/pnv.c | 131 | ||||
-rw-r--r-- | hw/ppc/pnv_chiptod.c | 586 | ||||
-rw-r--r-- | hw/ppc/pnv_i2c.c | 146 | ||||
-rw-r--r-- | hw/ppc/pnv_n1_chiplet.c | 173 | ||||
-rw-r--r-- | hw/ppc/pnv_nest_pervasive.c | 208 | ||||
-rw-r--r-- | hw/ppc/spapr.c | 12 | ||||
-rw-r--r-- | hw/ppc/spapr_hcall.c | 12 | ||||
-rw-r--r-- | hw/ppc/spapr_irq.c | 6 | ||||
-rw-r--r-- | hw/ppc/spapr_vhyp_mmu.c (renamed from hw/ppc/spapr_softmmu.c) | 13 | ||||
-rw-r--r-- | hw/ppc/trace-events | 4 |
16 files changed, 1531 insertions, 158 deletions
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 4fc6b29..83ad849 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -34,6 +34,10 @@ config PCA9552 bool depends on I2C +config PCA9554 + bool + depends on I2C + config I2C_ECHO bool default y if TEST_DEVICES diff --git a/hw/misc/meson.build b/hw/misc/meson.build index e4ef1da..7466868 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -4,6 +4,7 @@ system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c')) system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c')) system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c')) system_ss.add(when: 'CONFIG_PCA9552', if_true: files('pca9552.c')) +system_ss.add(when: 'CONFIG_PCA9554', if_true: files('pca9554.c')) system_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c')) system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c')) system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c')) diff --git a/hw/misc/pca9552.c b/hw/misc/pca9552.c index 72b6534..2ae13af 100644 --- a/hw/misc/pca9552.c +++ b/hw/misc/pca9552.c @@ -36,11 +36,16 @@ typedef struct PCA955xClass PCA955xClass; DECLARE_CLASS_CHECKERS(PCA955xClass, PCA955X, TYPE_PCA955X) - +/* + * Note: The LED_ON and LED_OFF configuration values for the PCA955X + * chips are the reverse of the PCA953X family of chips. + */ #define PCA9552_LED_ON 0x0 #define PCA9552_LED_OFF 0x1 #define PCA9552_LED_PWM0 0x2 #define PCA9552_LED_PWM1 0x3 +#define PCA9552_PIN_LOW 0x0 +#define PCA9552_PIN_HIZ 0x1 static const char *led_state[] = {"on", "off", "pwm0", "pwm1"}; @@ -107,17 +112,27 @@ static void pca955x_update_pin_input(PCA955xState *s) for (i = 0; i < k->pin_count; i++) { uint8_t input_reg = PCA9552_INPUT0 + (i / 8); - uint8_t input_shift = (i % 8); + uint8_t bit_mask = 1 << (i % 8); uint8_t config = pca955x_pin_get_config(s, i); + uint8_t old_value = s->regs[input_reg] & bit_mask; + uint8_t new_value; switch (config) { case PCA9552_LED_ON: - qemu_set_irq(s->gpio[i], 1); - s->regs[input_reg] |= 1 << input_shift; + /* Pin is set to 0V to turn on LED */ + s->regs[input_reg] &= ~bit_mask; break; case PCA9552_LED_OFF: - qemu_set_irq(s->gpio[i], 0); - s->regs[input_reg] &= ~(1 << input_shift); + /* + * Pin is set to Hi-Z to turn off LED and + * pullup sets it to a logical 1 unless + * external device drives it low. + */ + if (s->ext_state[i] == PCA9552_PIN_LOW) { + s->regs[input_reg] &= ~bit_mask; + } else { + s->regs[input_reg] |= bit_mask; + } break; case PCA9552_LED_PWM0: case PCA9552_LED_PWM1: @@ -125,6 +140,12 @@ static void pca955x_update_pin_input(PCA955xState *s) default: break; } + + /* update irq state only if pin state changed */ + new_value = s->regs[input_reg] & bit_mask; + if (new_value != old_value) { + qemu_set_irq(s->gpio_out[i], !!new_value); + } } } @@ -332,6 +353,7 @@ static const VMStateDescription pca9552_vmstate = { VMSTATE_UINT8(len, PCA955xState), VMSTATE_UINT8(pointer, PCA955xState), VMSTATE_UINT8_ARRAY(regs, PCA955xState, PCA955X_NR_REGS), + VMSTATE_UINT8_ARRAY(ext_state, PCA955xState, PCA955X_PIN_COUNT_MAX), VMSTATE_I2C_SLAVE(i2c, PCA955xState), VMSTATE_END_OF_LIST() } @@ -350,6 +372,7 @@ static void pca9552_reset(DeviceState *dev) s->regs[PCA9552_LS2] = 0x55; s->regs[PCA9552_LS3] = 0x55; + memset(s->ext_state, PCA9552_PIN_HIZ, PCA955X_PIN_COUNT_MAX); pca955x_update_pin_input(s); s->pointer = 0xFF; @@ -372,6 +395,26 @@ static void pca955x_initfn(Object *obj) } } +static void pca955x_set_ext_state(PCA955xState *s, int pin, int level) +{ + if (s->ext_state[pin] != level) { + uint16_t pins_status = pca955x_pins_get_status(s); + s->ext_state[pin] = level; + pca955x_update_pin_input(s); + pca955x_display_pins_status(s, pins_status); + } +} + +static void pca955x_gpio_in_handler(void *opaque, int pin, int level) +{ + + PCA955xState *s = PCA955X(opaque); + PCA955xClass *k = PCA955X_GET_CLASS(s); + + assert((pin >= 0) && (pin < k->pin_count)); + pca955x_set_ext_state(s, pin, level); +} + static void pca955x_realize(DeviceState *dev, Error **errp) { PCA955xClass *k = PCA955X_GET_CLASS(dev); @@ -381,7 +424,8 @@ static void pca955x_realize(DeviceState *dev, Error **errp) s->description = g_strdup("pca-unspecified"); } - qdev_init_gpio_out(dev, s->gpio, k->pin_count); + qdev_init_gpio_out(dev, s->gpio_out, k->pin_count); + qdev_init_gpio_in(dev, pca955x_gpio_in_handler, k->pin_count); } static Property pca955x_properties[] = { diff --git a/hw/misc/pca9554.c b/hw/misc/pca9554.c new file mode 100644 index 0000000..778b32e --- /dev/null +++ b/hw/misc/pca9554.c @@ -0,0 +1,328 @@ +/* + * PCA9554 I/O port + * + * Copyright (c) 2023, IBM Corporation. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/bitops.h" +#include "hw/qdev-properties.h" +#include "hw/misc/pca9554.h" +#include "hw/misc/pca9554_regs.h" +#include "hw/irq.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "trace.h" +#include "qom/object.h" + +struct PCA9554Class { + /*< private >*/ + I2CSlaveClass parent_class; + /*< public >*/ +}; +typedef struct PCA9554Class PCA9554Class; + +DECLARE_CLASS_CHECKERS(PCA9554Class, PCA9554, + TYPE_PCA9554) + +#define PCA9554_PIN_LOW 0x0 +#define PCA9554_PIN_HIZ 0x1 + +static const char *pin_state[] = {"low", "high"}; + +static void pca9554_update_pin_input(PCA9554State *s) +{ + int i; + uint8_t config = s->regs[PCA9554_CONFIG]; + uint8_t output = s->regs[PCA9554_OUTPUT]; + uint8_t internal_state = config | output; + + for (i = 0; i < PCA9554_PIN_COUNT; i++) { + uint8_t bit_mask = 1 << i; + uint8_t internal_pin_state = (internal_state >> i) & 0x1; + uint8_t old_value = s->regs[PCA9554_INPUT] & bit_mask; + uint8_t new_value; + + switch (internal_pin_state) { + case PCA9554_PIN_LOW: + s->regs[PCA9554_INPUT] &= ~bit_mask; + break; + case PCA9554_PIN_HIZ: + /* + * pullup sets it to a logical 1 unless + * external device drives it low. + */ + if (s->ext_state[i] == PCA9554_PIN_LOW) { + s->regs[PCA9554_INPUT] &= ~bit_mask; + } else { + s->regs[PCA9554_INPUT] |= bit_mask; + } + break; + default: + break; + } + + /* update irq state only if pin state changed */ + new_value = s->regs[PCA9554_INPUT] & bit_mask; + if (new_value != old_value) { + if (new_value) { + /* changed from 0 to 1 */ + qemu_set_irq(s->gpio_out[i], 1); + } else { + /* changed from 1 to 0 */ + qemu_set_irq(s->gpio_out[i], 0); + } + } + } +} + +static uint8_t pca9554_read(PCA9554State *s, uint8_t reg) +{ + switch (reg) { + case PCA9554_INPUT: + return s->regs[PCA9554_INPUT] ^ s->regs[PCA9554_POLARITY]; + case PCA9554_OUTPUT: + case PCA9554_POLARITY: + case PCA9554_CONFIG: + return s->regs[reg]; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected read to register %d\n", + __func__, reg); + return 0xFF; + } +} + +static void pca9554_write(PCA9554State *s, uint8_t reg, uint8_t data) +{ + switch (reg) { + case PCA9554_OUTPUT: + case PCA9554_CONFIG: + s->regs[reg] = data; + pca9554_update_pin_input(s); + break; + case PCA9554_POLARITY: + s->regs[reg] = data; + break; + case PCA9554_INPUT: + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected write to register %d\n", + __func__, reg); + } +} + +static uint8_t pca9554_recv(I2CSlave *i2c) +{ + PCA9554State *s = PCA9554(i2c); + uint8_t ret; + + ret = pca9554_read(s, s->pointer & 0x3); + + return ret; +} + +static int pca9554_send(I2CSlave *i2c, uint8_t data) +{ + PCA9554State *s = PCA9554(i2c); + + /* First byte sent by is the register address */ + if (s->len == 0) { + s->pointer = data; + s->len++; + } else { + pca9554_write(s, s->pointer & 0x3, data); + } + + return 0; +} + +static int pca9554_event(I2CSlave *i2c, enum i2c_event event) +{ + PCA9554State *s = PCA9554(i2c); + + s->len = 0; + return 0; +} + +static void pca9554_get_pin(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + PCA9554State *s = PCA9554(obj); + int pin, rc; + uint8_t state; + + rc = sscanf(name, "pin%2d", &pin); + if (rc != 1) { + error_setg(errp, "%s: error reading %s", __func__, name); + return; + } + if (pin < 0 || pin > PCA9554_PIN_COUNT) { + error_setg(errp, "%s invalid pin %s", __func__, name); + return; + } + + state = pca9554_read(s, PCA9554_CONFIG); + state |= pca9554_read(s, PCA9554_OUTPUT); + state = (state >> pin) & 0x1; + visit_type_str(v, name, (char **)&pin_state[state], errp); +} + +static void pca9554_set_pin(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + PCA9554State *s = PCA9554(obj); + int pin, rc, val; + uint8_t state, mask; + char *state_str; + + if (!visit_type_str(v, name, &state_str, errp)) { + return; + } + rc = sscanf(name, "pin%2d", &pin); + if (rc != 1) { + error_setg(errp, "%s: error reading %s", __func__, name); + return; + } + if (pin < 0 || pin > PCA9554_PIN_COUNT) { + error_setg(errp, "%s invalid pin %s", __func__, name); + return; + } + + for (state = 0; state < ARRAY_SIZE(pin_state); state++) { + if (!strcmp(state_str, pin_state[state])) { + break; + } + } + if (state >= ARRAY_SIZE(pin_state)) { + error_setg(errp, "%s invalid pin state %s", __func__, state_str); + return; + } + + /* First, modify the output register bit */ + val = pca9554_read(s, PCA9554_OUTPUT); + mask = 0x1 << pin; + if (state == PCA9554_PIN_LOW) { + val &= ~(mask); + } else { + val |= mask; + } + pca9554_write(s, PCA9554_OUTPUT, val); + + /* Then, clear the config register bit for output mode */ + val = pca9554_read(s, PCA9554_CONFIG); + val &= ~mask; + pca9554_write(s, PCA9554_CONFIG, val); +} + +static const VMStateDescription pca9554_vmstate = { + .name = "PCA9554", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8(len, PCA9554State), + VMSTATE_UINT8(pointer, PCA9554State), + VMSTATE_UINT8_ARRAY(regs, PCA9554State, PCA9554_NR_REGS), + VMSTATE_UINT8_ARRAY(ext_state, PCA9554State, PCA9554_PIN_COUNT), + VMSTATE_I2C_SLAVE(i2c, PCA9554State), + VMSTATE_END_OF_LIST() + } +}; + +static void pca9554_reset(DeviceState *dev) +{ + PCA9554State *s = PCA9554(dev); + + s->regs[PCA9554_INPUT] = 0xFF; + s->regs[PCA9554_OUTPUT] = 0xFF; + s->regs[PCA9554_POLARITY] = 0x0; /* No pins are inverted */ + s->regs[PCA9554_CONFIG] = 0xFF; /* All pins are inputs */ + + memset(s->ext_state, PCA9554_PIN_HIZ, PCA9554_PIN_COUNT); + pca9554_update_pin_input(s); + + s->pointer = 0x0; + s->len = 0; +} + +static void pca9554_initfn(Object *obj) +{ + int pin; + + for (pin = 0; pin < PCA9554_PIN_COUNT; pin++) { + char *name; + + name = g_strdup_printf("pin%d", pin); + object_property_add(obj, name, "bool", pca9554_get_pin, pca9554_set_pin, + NULL, NULL); + g_free(name); + } +} + +static void pca9554_set_ext_state(PCA9554State *s, int pin, int level) +{ + if (s->ext_state[pin] != level) { + s->ext_state[pin] = level; + pca9554_update_pin_input(s); + } +} + +static void pca9554_gpio_in_handler(void *opaque, int pin, int level) +{ + + PCA9554State *s = PCA9554(opaque); + + assert((pin >= 0) && (pin < PCA9554_PIN_COUNT)); + pca9554_set_ext_state(s, pin, level); +} + +static void pca9554_realize(DeviceState *dev, Error **errp) +{ + PCA9554State *s = PCA9554(dev); + + if (!s->description) { + s->description = g_strdup("pca9554"); + } + + qdev_init_gpio_out(dev, s->gpio_out, PCA9554_PIN_COUNT); + qdev_init_gpio_in(dev, pca9554_gpio_in_handler, PCA9554_PIN_COUNT); +} + +static Property pca9554_properties[] = { + DEFINE_PROP_STRING("description", PCA9554State, description), + DEFINE_PROP_END_OF_LIST(), +}; + +static void pca9554_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + + k->event = pca9554_event; + k->recv = pca9554_recv; + k->send = pca9554_send; + dc->realize = pca9554_realize; + dc->reset = pca9554_reset; + dc->vmsd = &pca9554_vmstate; + device_class_set_props(dc, pca9554_properties); +} + +static const TypeInfo pca9554_info = { + .name = TYPE_PCA9554, + .parent = TYPE_I2C_SLAVE, + .instance_init = pca9554_initfn, + .instance_size = sizeof(PCA9554State), + .class_init = pca9554_class_init, + .class_size = sizeof(PCA9554Class), + .abstract = false, +}; + +static void pca9554_register_types(void) +{ + type_register_static(&pca9554_info); +} + +type_init(pca9554_register_types) diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig index 99d571f..a890699 100644 --- a/hw/ppc/Kconfig +++ b/hw/ppc/Kconfig @@ -32,6 +32,8 @@ config POWERNV select XIVE select FDT_PPC select PCI_POWERNV + select PCA9552 + select PCA9554 config PPC405 bool diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build index da14fcc..d096636 100644 --- a/hw/ppc/meson.build +++ b/hw/ppc/meson.build @@ -31,7 +31,7 @@ ppc_ss.add(when: 'CONFIG_PSERIES', if_true: files( 'pef.c', )) ppc_ss.add(when: ['CONFIG_PSERIES', 'CONFIG_TCG'], if_true: files( - 'spapr_softmmu.c', + 'spapr_vhyp_mmu.c', )) ppc_ss.add(when: 'CONFIG_SPAPR_RNG', if_true: files('spapr_rng.c')) if host_os == 'linux' @@ -48,11 +48,14 @@ ppc_ss.add(when: 'CONFIG_POWERNV', if_true: files( 'pnv_i2c.c', 'pnv_lpc.c', 'pnv_psi.c', + 'pnv_chiptod.c', 'pnv_occ.c', 'pnv_sbe.c', 'pnv_bmc.c', 'pnv_homer.c', 'pnv_pnor.c', + 'pnv_nest_pervasive.c', + 'pnv_n1_chiplet.c', )) # PowerPC 4xx boards ppc_ss.add(when: 'CONFIG_PPC405', if_true: files( diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c index 0297871..0b47b92 100644 --- a/hw/ppc/pnv.c +++ b/hw/ppc/pnv.c @@ -790,6 +790,7 @@ static void pnv_init(MachineState *machine) const char *bios_name = machine->firmware ?: FW_FILE_NAME; PnvMachineState *pnv = PNV_MACHINE(machine); MachineClass *mc = MACHINE_GET_CLASS(machine); + PnvMachineClass *pmc = PNV_MACHINE_GET_CLASS(machine); char *fw_filename; long fw_size; uint64_t chip_ram_start = 0; @@ -979,6 +980,13 @@ static void pnv_init(MachineState *machine) */ pnv->powerdown_notifier.notify = pnv_powerdown_notify; qemu_register_powerdown_notifier(&pnv->powerdown_notifier); + + /* + * Create/Connect any machine-specific I2C devices + */ + if (pmc->i2c_init) { + pmc->i2c_init(pnv); + } } /* @@ -1419,6 +1427,8 @@ static void pnv_chip_power9_instance_init(Object *obj) object_initialize_child(obj, "lpc", &chip9->lpc, TYPE_PNV9_LPC); + object_initialize_child(obj, "chiptod", &chip9->chiptod, TYPE_PNV9_CHIPTOD); + object_initialize_child(obj, "occ", &chip9->occ, TYPE_PNV9_OCC); object_initialize_child(obj, "sbe", &chip9->sbe, TYPE_PNV9_SBE); @@ -1565,6 +1575,19 @@ static void pnv_chip_power9_realize(DeviceState *dev, Error **errp) chip->dt_isa_nodename = g_strdup_printf("/lpcm-opb@%" PRIx64 "/lpc@0", (uint64_t) PNV9_LPCM_BASE(chip)); + /* ChipTOD */ + object_property_set_bool(OBJECT(&chip9->chiptod), "primary", + chip->chip_id == 0, &error_abort); + object_property_set_bool(OBJECT(&chip9->chiptod), "secondary", + chip->chip_id == 1, &error_abort); + object_property_set_link(OBJECT(&chip9->chiptod), "chip", OBJECT(chip), + &error_abort); + if (!qdev_realize(DEVICE(&chip9->chiptod), NULL, errp)) { + return; + } + pnv_xscom_add_subregion(chip, PNV9_XSCOM_CHIPTOD_BASE, + &chip9->chiptod.xscom_regs); + /* Create the simplified OCC model */ if (!qdev_realize(DEVICE(&chip9->occ), NULL, errp)) { return; @@ -1677,9 +1700,13 @@ static void pnv_chip_power10_instance_init(Object *obj) "xive-fabric"); object_initialize_child(obj, "psi", &chip10->psi, TYPE_PNV10_PSI); object_initialize_child(obj, "lpc", &chip10->lpc, TYPE_PNV10_LPC); + object_initialize_child(obj, "chiptod", &chip10->chiptod, + TYPE_PNV10_CHIPTOD); object_initialize_child(obj, "occ", &chip10->occ, TYPE_PNV10_OCC); object_initialize_child(obj, "sbe", &chip10->sbe, TYPE_PNV10_SBE); object_initialize_child(obj, "homer", &chip10->homer, TYPE_PNV10_HOMER); + object_initialize_child(obj, "n1-chiplet", &chip10->n1_chiplet, + TYPE_PNV_N1_CHIPLET); chip->num_pecs = pcc->num_pecs; @@ -1810,6 +1837,19 @@ static void pnv_chip_power10_realize(DeviceState *dev, Error **errp) chip->dt_isa_nodename = g_strdup_printf("/lpcm-opb@%" PRIx64 "/lpc@0", (uint64_t) PNV10_LPCM_BASE(chip)); + /* ChipTOD */ + object_property_set_bool(OBJECT(&chip10->chiptod), "primary", + chip->chip_id == 0, &error_abort); + object_property_set_bool(OBJECT(&chip10->chiptod), "secondary", + chip->chip_id == 1, &error_abort); + object_property_set_link(OBJECT(&chip10->chiptod), "chip", OBJECT(chip), + &error_abort); + if (!qdev_realize(DEVICE(&chip10->chiptod), NULL, errp)) { + return; + } + pnv_xscom_add_subregion(chip, PNV10_XSCOM_CHIPTOD_BASE, + &chip10->chiptod.xscom_regs); + /* Create the simplified OCC model */ if (!qdev_realize(DEVICE(&chip10->occ), NULL, errp)) { return; @@ -1849,6 +1889,19 @@ static void pnv_chip_power10_realize(DeviceState *dev, Error **errp) memory_region_add_subregion(get_system_memory(), PNV10_HOMER_BASE(chip), &chip10->homer.regs); + /* N1 chiplet */ + if (!qdev_realize(DEVICE(&chip10->n1_chiplet), NULL, errp)) { + return; + } + pnv_xscom_add_subregion(chip, PNV10_XSCOM_N1_CHIPLET_CTRL_REGS_BASE, + &chip10->n1_chiplet.nest_pervasive.xscom_ctrl_regs_mr); + + pnv_xscom_add_subregion(chip, PNV10_XSCOM_N1_PB_SCOM_EQ_BASE, + &chip10->n1_chiplet.xscom_pb_eq_mr); + + pnv_xscom_add_subregion(chip, PNV10_XSCOM_N1_PB_SCOM_ES_BASE, + &chip10->n1_chiplet.xscom_pb_es_mr); + /* PHBs */ pnv_chip_power10_phb_realize(chip, &local_err); if (local_err) { @@ -1879,6 +1932,39 @@ static void pnv_chip_power10_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(DEVICE(&chip10->psi), PSIHB9_IRQ_SBE_I2C)); } + +} + +static void pnv_rainier_i2c_init(PnvMachineState *pnv) +{ + int i; + for (i = 0; i < pnv->num_chips; i++) { + Pnv10Chip *chip10 = PNV10_CHIP(pnv->chips[i]); + + /* + * Add a PCA9552 I2C device for PCIe hotplug control + * to engine 2, bus 1, address 0x63 + */ + I2CSlave *dev = i2c_slave_create_simple(chip10->i2c[2].busses[1], + "pca9552", 0x63); + + /* + * Connect PCA9552 GPIO pins 0-4 (SLOTx_EN) outputs to GPIO pins 5-9 + * (SLOTx_PG) inputs in order to fake the pgood state of PCIe slots + * after hypervisor code sets a SLOTx_EN pin high. + */ + qdev_connect_gpio_out(DEVICE(dev), 0, qdev_get_gpio_in(DEVICE(dev), 5)); + qdev_connect_gpio_out(DEVICE(dev), 1, qdev_get_gpio_in(DEVICE(dev), 6)); + qdev_connect_gpio_out(DEVICE(dev), 2, qdev_get_gpio_in(DEVICE(dev), 7)); + qdev_connect_gpio_out(DEVICE(dev), 3, qdev_get_gpio_in(DEVICE(dev), 8)); + qdev_connect_gpio_out(DEVICE(dev), 4, qdev_get_gpio_in(DEVICE(dev), 9)); + + /* + * Add a PCA9554 I2C device for cable card presence detection + * to engine 2, bus 1, address 0x25 + */ + i2c_slave_create_simple(chip10->i2c[2].busses[1], "pca9554", 0x25); + } } static uint32_t pnv_chip_power10_xscom_pcba(PnvChip *chip, uint64_t addr) @@ -2035,6 +2121,21 @@ static void pnv_chip_class_init(ObjectClass *klass, void *data) dc->desc = "PowerNV Chip"; } +PnvCore *pnv_chip_find_core(PnvChip *chip, uint32_t core_id) +{ + int i; + + for (i = 0; i < chip->nr_cores; i++) { + PnvCore *pc = chip->cores[i]; + CPUCore *cc = CPU_CORE(pc); + + if (cc->core_id == core_id) { + return pc; + } + } + return NULL; +} + PowerPCCPU *pnv_chip_find_cpu(PnvChip *chip, uint32_t pir) { int i, j; @@ -2242,8 +2343,6 @@ static void pnv_machine_power9_class_init(ObjectClass *oc, void *data) xfc->match_nvt = pnv_match_nvt; - mc->alias = "powernv"; - pmc->compat = compat; pmc->compat_size = sizeof(compat); pmc->dt_power_mgt = pnv_dt_power_mgt; @@ -2251,7 +2350,7 @@ static void pnv_machine_power9_class_init(ObjectClass *oc, void *data) machine_class_allow_dynamic_sysbus_dev(mc, TYPE_PNV_PHB); } -static void pnv_machine_power10_class_init(ObjectClass *oc, void *data) +static void pnv_machine_p10_common_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); PnvMachineClass *pmc = PNV_MACHINE_CLASS(oc); @@ -2263,10 +2362,11 @@ static void pnv_machine_power10_class_init(ObjectClass *oc, void *data) { TYPE_PNV_PHB_ROOT_PORT, "version", "5" }, }; - mc->desc = "IBM PowerNV (Non-Virtualized) POWER10"; mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("power10_v2.0"); compat_props_add(mc->compat_props, phb_compat, G_N_ELEMENTS(phb_compat)); + mc->alias = "powernv"; + pmc->compat = compat; pmc->compat_size = sizeof(compat); pmc->dt_power_mgt = pnv_dt_power_mgt; @@ -2276,6 +2376,24 @@ static void pnv_machine_power10_class_init(ObjectClass *oc, void *data) machine_class_allow_dynamic_sysbus_dev(mc, TYPE_PNV_PHB); } +static void pnv_machine_power10_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + pnv_machine_p10_common_class_init(oc, data); + mc->desc = "IBM PowerNV (Non-Virtualized) POWER10"; +} + +static void pnv_machine_p10_rainier_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + PnvMachineClass *pmc = PNV_MACHINE_CLASS(oc); + + pnv_machine_p10_common_class_init(oc, data); + mc->desc = "IBM PowerNV (Non-Virtualized) POWER10 Rainier"; + pmc->i2c_init = pnv_rainier_i2c_init; +} + static bool pnv_machine_get_hb(Object *obj, Error **errp) { PnvMachineState *pnv = PNV_MACHINE(obj); @@ -2382,6 +2500,11 @@ static void pnv_machine_class_init(ObjectClass *oc, void *data) static const TypeInfo types[] = { { + .name = MACHINE_TYPE_NAME("powernv10-rainier"), + .parent = MACHINE_TYPE_NAME("powernv10"), + .class_init = pnv_machine_p10_rainier_class_init, + }, + { .name = MACHINE_TYPE_NAME("powernv10"), .parent = TYPE_PNV_MACHINE, .class_init = pnv_machine_power10_class_init, diff --git a/hw/ppc/pnv_chiptod.c b/hw/ppc/pnv_chiptod.c new file mode 100644 index 0000000..3831a72 --- /dev/null +++ b/hw/ppc/pnv_chiptod.c @@ -0,0 +1,586 @@ +/* + * 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 <libfdt.h> + +/* 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); diff --git a/hw/ppc/pnv_i2c.c b/hw/ppc/pnv_i2c.c index 656a48e..4581cc5 100644 --- a/hw/ppc/pnv_i2c.c +++ b/hw/ppc/pnv_i2c.c @@ -22,136 +22,7 @@ #include <libfdt.h> -/* I2C FIFO register */ -#define I2C_FIFO_REG 0x4 -#define I2C_FIFO PPC_BITMASK(0, 7) - -/* I2C command register */ -#define I2C_CMD_REG 0x5 -#define I2C_CMD_WITH_START PPC_BIT(0) -#define I2C_CMD_WITH_ADDR PPC_BIT(1) -#define I2C_CMD_READ_CONT PPC_BIT(2) -#define I2C_CMD_WITH_STOP PPC_BIT(3) -#define I2C_CMD_INTR_STEERING PPC_BITMASK(6, 7) /* P9 */ -#define I2C_CMD_INTR_STEER_HOST 1 -#define I2C_CMD_INTR_STEER_OCC 2 -#define I2C_CMD_DEV_ADDR PPC_BITMASK(8, 14) -#define I2C_CMD_READ_NOT_WRITE PPC_BIT(15) -#define I2C_CMD_LEN_BYTES PPC_BITMASK(16, 31) -#define I2C_MAX_TFR_LEN 0xfff0ull - -/* I2C mode register */ -#define I2C_MODE_REG 0x6 -#define I2C_MODE_BIT_RATE_DIV PPC_BITMASK(0, 15) -#define I2C_MODE_PORT_NUM PPC_BITMASK(16, 21) -#define I2C_MODE_ENHANCED PPC_BIT(28) -#define I2C_MODE_DIAGNOSTIC PPC_BIT(29) -#define I2C_MODE_PACING_ALLOW PPC_BIT(30) -#define I2C_MODE_WRAP PPC_BIT(31) - -/* I2C watermark register */ -#define I2C_WATERMARK_REG 0x7 -#define I2C_WATERMARK_HIGH PPC_BITMASK(16, 19) -#define I2C_WATERMARK_LOW PPC_BITMASK(24, 27) - -/* - * I2C interrupt mask and condition registers - * - * NB: The function of 0x9 and 0xa changes depending on whether you're reading - * or writing to them. When read they return the interrupt condition bits - * and on writes they update the interrupt mask register. - * - * The bit definitions are the same for all the interrupt registers. - */ -#define I2C_INTR_MASK_REG 0x8 - -#define I2C_INTR_RAW_COND_REG 0x9 /* read */ -#define I2C_INTR_MASK_OR_REG 0x9 /* write*/ - -#define I2C_INTR_COND_REG 0xa /* read */ -#define I2C_INTR_MASK_AND_REG 0xa /* write */ - -#define I2C_INTR_ALL PPC_BITMASK(16, 31) -#define I2C_INTR_INVALID_CMD PPC_BIT(16) -#define I2C_INTR_LBUS_PARITY_ERR PPC_BIT(17) -#define I2C_INTR_BKEND_OVERRUN_ERR PPC_BIT(18) -#define I2C_INTR_BKEND_ACCESS_ERR PPC_BIT(19) -#define I2C_INTR_ARBT_LOST_ERR PPC_BIT(20) -#define I2C_INTR_NACK_RCVD_ERR PPC_BIT(21) -#define I2C_INTR_DATA_REQ PPC_BIT(22) -#define I2C_INTR_CMD_COMP PPC_BIT(23) -#define I2C_INTR_STOP_ERR PPC_BIT(24) -#define I2C_INTR_I2C_BUSY PPC_BIT(25) -#define I2C_INTR_NOT_I2C_BUSY PPC_BIT(26) -#define I2C_INTR_SCL_EQ_1 PPC_BIT(28) -#define I2C_INTR_SCL_EQ_0 PPC_BIT(29) -#define I2C_INTR_SDA_EQ_1 PPC_BIT(30) -#define I2C_INTR_SDA_EQ_0 PPC_BIT(31) - -/* I2C status register */ -#define I2C_RESET_I2C_REG 0xb /* write */ -#define I2C_RESET_ERRORS 0xc -#define I2C_STAT_REG 0xb /* read */ -#define I2C_STAT_INVALID_CMD PPC_BIT(0) -#define I2C_STAT_LBUS_PARITY_ERR PPC_BIT(1) -#define I2C_STAT_BKEND_OVERRUN_ERR PPC_BIT(2) -#define I2C_STAT_BKEND_ACCESS_ERR PPC_BIT(3) -#define I2C_STAT_ARBT_LOST_ERR PPC_BIT(4) -#define I2C_STAT_NACK_RCVD_ERR PPC_BIT(5) -#define I2C_STAT_DATA_REQ PPC_BIT(6) -#define I2C_STAT_CMD_COMP PPC_BIT(7) -#define I2C_STAT_STOP_ERR PPC_BIT(8) -#define I2C_STAT_UPPER_THRS PPC_BITMASK(9, 15) -#define I2C_STAT_ANY_I2C_INTR PPC_BIT(16) -#define I2C_STAT_PORT_HISTORY_BUSY PPC_BIT(19) -#define I2C_STAT_SCL_INPUT_LEVEL PPC_BIT(20) -#define I2C_STAT_SDA_INPUT_LEVEL PPC_BIT(21) -#define I2C_STAT_PORT_BUSY PPC_BIT(22) -#define I2C_STAT_INTERFACE_BUSY PPC_BIT(23) -#define I2C_STAT_FIFO_ENTRY_COUNT PPC_BITMASK(24, 31) - -#define I2C_STAT_ANY_ERR (I2C_STAT_INVALID_CMD | I2C_STAT_LBUS_PARITY_ERR | \ - I2C_STAT_BKEND_OVERRUN_ERR | \ - I2C_STAT_BKEND_ACCESS_ERR | I2C_STAT_ARBT_LOST_ERR | \ - I2C_STAT_NACK_RCVD_ERR | I2C_STAT_STOP_ERR) - - -#define I2C_INTR_ACTIVE \ - ((I2C_STAT_ANY_ERR >> 16) | I2C_INTR_CMD_COMP | I2C_INTR_DATA_REQ) - -/* Pseudo-status used for timeouts */ -#define I2C_STAT_PSEUDO_TIMEOUT PPC_BIT(63) - -/* I2C extended status register */ -#define I2C_EXTD_STAT_REG 0xc -#define I2C_EXTD_STAT_FIFO_SIZE PPC_BITMASK(0, 7) -#define I2C_EXTD_STAT_MSM_CURSTATE PPC_BITMASK(11, 15) -#define I2C_EXTD_STAT_SCL_IN_SYNC PPC_BIT(16) -#define I2C_EXTD_STAT_SDA_IN_SYNC PPC_BIT(17) -#define I2C_EXTD_STAT_S_SCL PPC_BIT(18) -#define I2C_EXTD_STAT_S_SDA PPC_BIT(19) -#define I2C_EXTD_STAT_M_SCL PPC_BIT(20) -#define I2C_EXTD_STAT_M_SDA PPC_BIT(21) -#define I2C_EXTD_STAT_HIGH_WATER PPC_BIT(22) -#define I2C_EXTD_STAT_LOW_WATER PPC_BIT(23) -#define I2C_EXTD_STAT_I2C_BUSY PPC_BIT(24) -#define I2C_EXTD_STAT_SELF_BUSY PPC_BIT(25) -#define I2C_EXTD_STAT_I2C_VERSION PPC_BITMASK(27, 31) - -/* I2C residual front end/back end length */ -#define I2C_RESIDUAL_LEN_REG 0xd -#define I2C_RESIDUAL_FRONT_END PPC_BITMASK(0, 15) -#define I2C_RESIDUAL_BACK_END PPC_BITMASK(16, 31) - -/* Port busy register */ -#define I2C_PORT_BUSY_REG 0xe -#define I2C_SET_S_SCL_REG 0xd -#define I2C_RESET_S_SCL_REG 0xf -#define I2C_SET_S_SDA_REG 0x10 -#define I2C_RESET_S_SDA_REG 0x11 - -#define PNV_I2C_FIFO_SIZE 8 -#define PNV_I2C_MAX_BUSSES 64 +#include "hw/i2c/pnv_i2c_regs.h" static I2CBus *pnv_i2c_get_bus(PnvI2C *i2c) { @@ -629,6 +500,19 @@ static int pnv_i2c_dt_xscom(PnvXScomInterface *dev, void *fdt, return 0; } +static void pnv_i2c_sys_reset(void *dev) +{ + int port; + PnvI2C *i2c = PNV_I2C(dev); + + pnv_i2c_reset(dev); + + /* reset all buses connected to this i2c controller */ + for (port = 0; port < i2c->num_busses; port++) { + bus_cold_reset(BUS(i2c->busses[port])); + } +} + static void pnv_i2c_realize(DeviceState *dev, Error **errp) { PnvI2C *i2c = PNV_I2C(dev); @@ -654,7 +538,7 @@ static void pnv_i2c_realize(DeviceState *dev, Error **errp) fifo8_create(&i2c->fifo, PNV_I2C_FIFO_SIZE); - qemu_register_reset(pnv_i2c_reset, dev); + qemu_register_reset(pnv_i2c_sys_reset, dev); qdev_init_gpio_out(DEVICE(dev), &i2c->psi_irq, 1); } diff --git a/hw/ppc/pnv_n1_chiplet.c b/hw/ppc/pnv_n1_chiplet.c new file mode 100644 index 0000000..03ff9fb --- /dev/null +++ b/hw/ppc/pnv_n1_chiplet.c @@ -0,0 +1,173 @@ +/* + * QEMU PowerPC N1 chiplet model + * + * Copyright (c) 2023, IBM Corporation. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/qdev-properties.h" +#include "hw/ppc/pnv.h" +#include "hw/ppc/pnv_xscom.h" +#include "hw/ppc/pnv_n1_chiplet.h" +#include "hw/ppc/pnv_nest_pervasive.h" + +/* + * The n1 chiplet contains chiplet control unit, + * PowerBus/RaceTrack/Bridge logic, nest Memory Management Unit(nMMU) + * and more. + * + * In this model Nest1 chiplet control registers are modelled via common + * nest pervasive model and few PowerBus racetrack registers are modelled. + */ + +#define PB_SCOM_EQ0_HP_MODE2_CURR 0xe +#define PB_SCOM_ES3_MODE 0x8a + +static uint64_t pnv_n1_chiplet_pb_scom_eq_read(void *opaque, hwaddr addr, + unsigned size) +{ + PnvN1Chiplet *n1_chiplet = PNV_N1_CHIPLET(opaque); + uint32_t reg = addr >> 3; + uint64_t val = ~0ull; + + switch (reg) { + case PB_SCOM_EQ0_HP_MODE2_CURR: + val = n1_chiplet->eq[0].hp_mode2_curr; + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: Invalid xscom read at 0x%" PRIx32 "\n", + __func__, reg); + } + return val; +} + +static void pnv_n1_chiplet_pb_scom_eq_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + PnvN1Chiplet *n1_chiplet = PNV_N1_CHIPLET(opaque); + uint32_t reg = addr >> 3; + + switch (reg) { + case PB_SCOM_EQ0_HP_MODE2_CURR: + n1_chiplet->eq[0].hp_mode2_curr = val; + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: Invalid xscom write at 0x%" PRIx32 "\n", + __func__, reg); + } +} + +static const MemoryRegionOps pnv_n1_chiplet_pb_scom_eq_ops = { + .read = pnv_n1_chiplet_pb_scom_eq_read, + .write = pnv_n1_chiplet_pb_scom_eq_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 uint64_t pnv_n1_chiplet_pb_scom_es_read(void *opaque, hwaddr addr, + unsigned size) +{ + PnvN1Chiplet *n1_chiplet = PNV_N1_CHIPLET(opaque); + uint32_t reg = addr >> 3; + uint64_t val = ~0ull; + + switch (reg) { + case PB_SCOM_ES3_MODE: + val = n1_chiplet->es[3].mode; + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: Invalid xscom read at 0x%" PRIx32 "\n", + __func__, reg); + } + return val; +} + +static void pnv_n1_chiplet_pb_scom_es_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + PnvN1Chiplet *n1_chiplet = PNV_N1_CHIPLET(opaque); + uint32_t reg = addr >> 3; + + switch (reg) { + case PB_SCOM_ES3_MODE: + n1_chiplet->es[3].mode = val; + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: Invalid xscom write at 0x%" PRIx32 "\n", + __func__, reg); + } +} + +static const MemoryRegionOps pnv_n1_chiplet_pb_scom_es_ops = { + .read = pnv_n1_chiplet_pb_scom_es_read, + .write = pnv_n1_chiplet_pb_scom_es_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_n1_chiplet_realize(DeviceState *dev, Error **errp) +{ + PnvN1Chiplet *n1_chiplet = PNV_N1_CHIPLET(dev); + + /* Realize nest pervasive common chiplet model */ + if (!qdev_realize(DEVICE(&n1_chiplet->nest_pervasive), NULL, errp)) { + return; + } + + /* Nest1 chiplet power bus EQ xscom region */ + pnv_xscom_region_init(&n1_chiplet->xscom_pb_eq_mr, OBJECT(n1_chiplet), + &pnv_n1_chiplet_pb_scom_eq_ops, n1_chiplet, + "xscom-n1-chiplet-pb-scom-eq", + PNV10_XSCOM_N1_PB_SCOM_EQ_SIZE); + + /* Nest1 chiplet power bus ES xscom region */ + pnv_xscom_region_init(&n1_chiplet->xscom_pb_es_mr, OBJECT(n1_chiplet), + &pnv_n1_chiplet_pb_scom_es_ops, n1_chiplet, + "xscom-n1-chiplet-pb-scom-es", + PNV10_XSCOM_N1_PB_SCOM_ES_SIZE); +} + +static void pnv_n1_chiplet_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "PowerNV n1 chiplet"; + dc->realize = pnv_n1_chiplet_realize; +} + +static void pnv_n1_chiplet_instance_init(Object *obj) +{ + PnvN1Chiplet *n1_chiplet = PNV_N1_CHIPLET(obj); + + object_initialize_child(OBJECT(n1_chiplet), "nest-pervasive-common", + &n1_chiplet->nest_pervasive, + TYPE_PNV_NEST_CHIPLET_PERVASIVE); +} + +static const TypeInfo pnv_n1_chiplet_info = { + .name = TYPE_PNV_N1_CHIPLET, + .parent = TYPE_DEVICE, + .instance_init = pnv_n1_chiplet_instance_init, + .instance_size = sizeof(PnvN1Chiplet), + .class_init = pnv_n1_chiplet_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_PNV_XSCOM_INTERFACE }, + { } + } +}; + +static void pnv_n1_chiplet_register_types(void) +{ + type_register_static(&pnv_n1_chiplet_info); +} + +type_init(pnv_n1_chiplet_register_types); diff --git a/hw/ppc/pnv_nest_pervasive.c b/hw/ppc/pnv_nest_pervasive.c new file mode 100644 index 0000000..7747675 --- /dev/null +++ b/hw/ppc/pnv_nest_pervasive.c @@ -0,0 +1,208 @@ +/* + * QEMU PowerPC nest pervasive common chiplet model + * + * Copyright (c) 2023, IBM Corporation. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/qdev-properties.h" +#include "hw/ppc/pnv.h" +#include "hw/ppc/pnv_xscom.h" +#include "hw/ppc/pnv_nest_pervasive.h" + +/* + * Status, configuration, and control units in POWER chips is provided + * by the pervasive subsystem, which connects registers to the SCOM bus, + * which can be programmed by processor cores, other units on the chip, + * BMCs, or other POWER chips. + * + * A POWER10 chip is divided into logical units called chiplets. Chiplets + * are broadly divided into "core chiplets" (with the processor cores) and + * "nest chiplets" (with everything else). Each chiplet has an attachment + * to the pervasive bus (PIB) and with chiplet-specific registers. + * All nest chiplets have a common basic set of registers. + * + * This model will provide the registers functionality for common registers of + * nest unit (PB Chiplet, PCI Chiplets, MC Chiplet, PAU Chiplets) + * + * Currently this model provide the read/write functionality of chiplet control + * scom registers. + */ + +#define CPLT_CONF0 0x08 +#define CPLT_CONF0_OR 0x18 +#define CPLT_CONF0_CLEAR 0x28 +#define CPLT_CONF1 0x09 +#define CPLT_CONF1_OR 0x19 +#define CPLT_CONF1_CLEAR 0x29 +#define CPLT_STAT0 0x100 +#define CPLT_MASK0 0x101 +#define CPLT_PROTECT_MODE 0x3FE +#define CPLT_ATOMIC_CLOCK 0x3FF + +static uint64_t pnv_chiplet_ctrl_read(void *opaque, hwaddr addr, unsigned size) +{ + PnvNestChipletPervasive *nest_pervasive = PNV_NEST_CHIPLET_PERVASIVE( + opaque); + uint32_t reg = addr >> 3; + uint64_t val = ~0ull; + + /* CPLT_CTRL0 to CPLT_CTRL5 */ + for (int i = 0; i < PNV_CPLT_CTRL_SIZE; i++) { + if (reg == i) { + return nest_pervasive->control_regs.cplt_ctrl[i]; + } else if ((reg == (i + 0x10)) || (reg == (i + 0x20))) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Write only register, ignoring " + "xscom read at 0x%" PRIx32 "\n", + __func__, reg); + return val; + } + } + + switch (reg) { + case CPLT_CONF0: + val = nest_pervasive->control_regs.cplt_cfg0; + break; + case CPLT_CONF0_OR: + case CPLT_CONF0_CLEAR: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Write only register, ignoring " + "xscom read at 0x%" PRIx32 "\n", + __func__, reg); + break; + case CPLT_CONF1: + val = nest_pervasive->control_regs.cplt_cfg1; + break; + case CPLT_CONF1_OR: + case CPLT_CONF1_CLEAR: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Write only register, ignoring " + "xscom read at 0x%" PRIx32 "\n", + __func__, reg); + break; + case CPLT_STAT0: + val = nest_pervasive->control_regs.cplt_stat0; + break; + case CPLT_MASK0: + val = nest_pervasive->control_regs.cplt_mask0; + break; + case CPLT_PROTECT_MODE: + val = nest_pervasive->control_regs.ctrl_protect_mode; + break; + case CPLT_ATOMIC_CLOCK: + val = nest_pervasive->control_regs.ctrl_atomic_lock; + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: Chiplet_control_regs: Invalid xscom " + "read at 0x%" PRIx32 "\n", __func__, reg); + } + return val; +} + +static void pnv_chiplet_ctrl_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + PnvNestChipletPervasive *nest_pervasive = PNV_NEST_CHIPLET_PERVASIVE( + opaque); + uint32_t reg = addr >> 3; + + /* CPLT_CTRL0 to CPLT_CTRL5 */ + for (int i = 0; i < PNV_CPLT_CTRL_SIZE; i++) { + if (reg == i) { + nest_pervasive->control_regs.cplt_ctrl[i] = val; + return; + } else if (reg == (i + 0x10)) { + nest_pervasive->control_regs.cplt_ctrl[i] |= val; + return; + } else if (reg == (i + 0x20)) { + nest_pervasive->control_regs.cplt_ctrl[i] &= ~val; + return; + } + } + + switch (reg) { + case CPLT_CONF0: + nest_pervasive->control_regs.cplt_cfg0 = val; + break; + case CPLT_CONF0_OR: + nest_pervasive->control_regs.cplt_cfg0 |= val; + break; + case CPLT_CONF0_CLEAR: + nest_pervasive->control_regs.cplt_cfg0 &= ~val; + break; + case CPLT_CONF1: + nest_pervasive->control_regs.cplt_cfg1 = val; + break; + case CPLT_CONF1_OR: + nest_pervasive->control_regs.cplt_cfg1 |= val; + break; + case CPLT_CONF1_CLEAR: + nest_pervasive->control_regs.cplt_cfg1 &= ~val; + break; + case CPLT_STAT0: + nest_pervasive->control_regs.cplt_stat0 = val; + break; + case CPLT_MASK0: + nest_pervasive->control_regs.cplt_mask0 = val; + break; + case CPLT_PROTECT_MODE: + nest_pervasive->control_regs.ctrl_protect_mode = val; + break; + case CPLT_ATOMIC_CLOCK: + nest_pervasive->control_regs.ctrl_atomic_lock = val; + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: Chiplet_control_regs: Invalid xscom " + "write at 0x%" PRIx32 "\n", + __func__, reg); + } +} + +static const MemoryRegionOps pnv_nest_pervasive_control_xscom_ops = { + .read = pnv_chiplet_ctrl_read, + .write = pnv_chiplet_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_nest_pervasive_realize(DeviceState *dev, Error **errp) +{ + PnvNestChipletPervasive *nest_pervasive = PNV_NEST_CHIPLET_PERVASIVE(dev); + + /* Chiplet control scoms */ + pnv_xscom_region_init(&nest_pervasive->xscom_ctrl_regs_mr, + OBJECT(nest_pervasive), + &pnv_nest_pervasive_control_xscom_ops, + nest_pervasive, "pervasive-control", + PNV10_XSCOM_CHIPLET_CTRL_REGS_SIZE); +} + +static void pnv_nest_pervasive_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "PowerNV nest pervasive chiplet"; + dc->realize = pnv_nest_pervasive_realize; +} + +static const TypeInfo pnv_nest_pervasive_info = { + .name = TYPE_PNV_NEST_CHIPLET_PERVASIVE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(PnvNestChipletPervasive), + .class_init = pnv_nest_pervasive_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_PNV_XSCOM_INTERFACE }, + { } + } +}; + +static void pnv_nest_pervasive_register_types(void) +{ + type_register_static(&pnv_nest_pervasive_info); +} + +type_init(pnv_nest_pervasive_register_types); diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c index 0d72d28..d1c6d70 100644 --- a/hw/ppc/spapr.c +++ b/hw/ppc/spapr.c @@ -4639,13 +4639,10 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data) mc->block_default_type = IF_SCSI; /* - * Setting max_cpus to INT32_MAX. Both KVM and TCG max_cpus values - * should be limited by the host capability instead of hardcoded. - * max_cpus for KVM guests will be checked in kvm_init(), and TCG - * guests are welcome to have as many CPUs as the host are capable - * of emulate. + * While KVM determines max cpus in kvm_init() using kvm_max_vcpus(), + * In TCG the limit is restricted by the range of CPU IPIs available. */ - mc->max_cpus = INT32_MAX; + mc->max_cpus = SPAPR_IRQ_NR_IPIS; mc->no_parallel = 1; mc->default_boot_order = ""; @@ -4667,7 +4664,7 @@ static void spapr_machine_class_init(ObjectClass *oc, void *data) smc->dr_lmb_enabled = true; smc->update_dt_enabled = true; - mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("power9_v2.2"); + mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("power10_v2.0"); mc->has_hotpluggable_cpus = true; mc->nvdimm_supported = true; smc->resize_hpt_default = SPAPR_RESIZE_HPT_ENABLED; @@ -5086,6 +5083,7 @@ static void spapr_machine_2_11_class_options(MachineClass *mc) spapr_machine_2_12_class_options(mc); smc->default_caps.caps[SPAPR_CAP_HTM] = SPAPR_CAP_ON; compat_props_add(mc->compat_props, hw_compat_2_11, hw_compat_2_11_len); + mc->deprecation_reason = "old and not maintained - use a 2.12+ version"; } DEFINE_SPAPR_MACHINE(2_11, "2.11", false); diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c index fcefd1d..75c2d12 100644 --- a/hw/ppc/spapr_hcall.c +++ b/hw/ppc/spapr_hcall.c @@ -123,9 +123,11 @@ static target_ulong h_resize_hpt_prepare(PowerPCCPU *cpu, if (kvm_enabled()) { return H_HARDWARE; + } else if (tcg_enabled()) { + return vhyp_mmu_resize_hpt_prepare(cpu, spapr, shift); + } else { + g_assert_not_reached(); } - - return softmmu_resize_hpt_prepare(cpu, spapr, shift); } static void do_push_sregs_to_kvm_pr(CPUState *cs, run_on_cpu_data data) @@ -191,9 +193,11 @@ static target_ulong h_resize_hpt_commit(PowerPCCPU *cpu, if (kvm_enabled()) { return H_HARDWARE; + } else if (tcg_enabled()) { + return vhyp_mmu_resize_hpt_commit(cpu, spapr, flags, shift); + } else { + g_assert_not_reached(); } - - return softmmu_resize_hpt_commit(cpu, spapr, flags, shift); } diff --git a/hw/ppc/spapr_irq.c b/hw/ppc/spapr_irq.c index a0d1e12..97b2fc4 100644 --- a/hw/ppc/spapr_irq.c +++ b/hw/ppc/spapr_irq.c @@ -23,6 +23,8 @@ #include "trace.h" +QEMU_BUILD_BUG_ON(SPAPR_IRQ_NR_IPIS > SPAPR_XIRQ_BASE); + static const TypeInfo spapr_intc_info = { .name = TYPE_SPAPR_INTC, .parent = TYPE_INTERFACE, @@ -329,7 +331,7 @@ void spapr_irq_init(SpaprMachineState *spapr, Error **errp) int i; dev = qdev_new(TYPE_SPAPR_XIVE); - qdev_prop_set_uint32(dev, "nr-irqs", smc->nr_xirqs + SPAPR_XIRQ_BASE); + qdev_prop_set_uint32(dev, "nr-irqs", smc->nr_xirqs + SPAPR_IRQ_NR_IPIS); /* * 8 XIVE END structures per CPU. One for each available * priority @@ -356,7 +358,7 @@ void spapr_irq_init(SpaprMachineState *spapr, Error **errp) } spapr->qirqs = qemu_allocate_irqs(spapr_set_irq, spapr, - smc->nr_xirqs + SPAPR_XIRQ_BASE); + smc->nr_xirqs + SPAPR_IRQ_NR_IPIS); /* * Mostly we don't actually need this until reset, except that not diff --git a/hw/ppc/spapr_softmmu.c b/hw/ppc/spapr_vhyp_mmu.c index fc1bbc0..b3dd8b3 100644 --- a/hw/ppc/spapr_softmmu.c +++ b/hw/ppc/spapr_vhyp_mmu.c @@ -1,3 +1,12 @@ +/* + * MMU hypercalls for the sPAPR (pseries) vHyp hypervisor that is used by TCG + * + * Copyright (c) 2004-2007 Fabrice Bellard + * Copyright (c) 2007 Jocelyn Mayer + * Copyright (c) 2010 David Gibson, IBM Corporation. + * + * SPDX-License-Identifier: MIT + */ #include "qemu/osdep.h" #include "qemu/cutils.h" #include "qemu/memalign.h" @@ -369,7 +378,7 @@ static void cancel_hpt_prepare(SpaprMachineState *spapr) free_pending_hpt(pending); } -target_ulong softmmu_resize_hpt_prepare(PowerPCCPU *cpu, +target_ulong vhyp_mmu_resize_hpt_prepare(PowerPCCPU *cpu, SpaprMachineState *spapr, target_ulong shift) { @@ -553,7 +562,7 @@ static int rehash_hpt(PowerPCCPU *cpu, return H_SUCCESS; } -target_ulong softmmu_resize_hpt_commit(PowerPCCPU *cpu, +target_ulong vhyp_mmu_resize_hpt_commit(PowerPCCPU *cpu, SpaprMachineState *spapr, target_ulong flags, target_ulong shift) diff --git a/hw/ppc/trace-events b/hw/ppc/trace-events index 157ea75..bf29bbf 100644 --- a/hw/ppc/trace-events +++ b/hw/ppc/trace-events @@ -95,6 +95,10 @@ vof_write(uint32_t ih, unsigned cb, const char *msg) "ih=0x%x [%u] \"%s\"" vof_avail(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64 vof_claimed(uint64_t start, uint64_t end, uint64_t size) "0x%"PRIx64"..0x%"PRIx64" size=0x%"PRIx64 +# pnv_chiptod.c +pnv_chiptod_xscom_read(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val 0x%" PRIx64 +pnv_chiptod_xscom_write(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val 0x%" PRIx64 + # pnv_sbe.c pnv_sbe_xscom_ctrl_read(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val 0x%" PRIx64 pnv_sbe_xscom_ctrl_write(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val 0x%" PRIx64 |