| /* |
| * QEMU PowerPC PowerNV Emulation of a few OCC related registers |
| * |
| * Copyright (c) 2015-2017, 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 <http://www.gnu.org/licenses/>. |
| */ |
| |
| #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_chip.h" |
| #include "hw/ppc/pnv_xscom.h" |
| #include "hw/ppc/pnv_occ.h" |
| |
| #define P8_HOMER_OPAL_DATA_OFFSET 0x1F8000 |
| #define P9_HOMER_OPAL_DATA_OFFSET 0x0E2000 |
| |
| #define OCB_OCI_OCCMISC 0x4020 |
| #define OCB_OCI_OCCMISC_AND 0x4021 |
| #define OCB_OCI_OCCMISC_OR 0x4022 |
| #define OCCMISC_PSI_IRQ PPC_BIT(0) |
| #define OCCMISC_IRQ_SHMEM PPC_BIT(3) |
| |
| /* OCC sensors */ |
| #define OCC_SENSOR_DATA_BLOCK_OFFSET 0x0000 |
| #define OCC_SENSOR_DATA_VALID 0x0001 |
| #define OCC_SENSOR_DATA_VERSION 0x0002 |
| #define OCC_SENSOR_DATA_READING_VERSION 0x0004 |
| #define OCC_SENSOR_DATA_NR_SENSORS 0x0008 |
| #define OCC_SENSOR_DATA_NAMES_OFFSET 0x0010 |
| #define OCC_SENSOR_DATA_READING_PING_OFFSET 0x0014 |
| #define OCC_SENSOR_DATA_READING_PONG_OFFSET 0x000c |
| #define OCC_SENSOR_DATA_NAME_LENGTH 0x000d |
| #define OCC_SENSOR_NAME_STRUCTURE_TYPE 0x0023 |
| #define OCC_SENSOR_LOC_CORE 0x0022 |
| #define OCC_SENSOR_LOC_GPU 0x0020 |
| #define OCC_SENSOR_TYPE_POWER 0x0003 |
| #define OCC_SENSOR_NAME 0x0005 |
| #define HWMON_SENSORS_MASK 0x001e |
| |
| static void pnv_occ_set_misc(PnvOCC *occ, uint64_t val) |
| { |
| val &= PPC_BITMASK(0, 18); /* Mask out unimplemented bits */ |
| |
| occ->occmisc = val; |
| |
| /* |
| * OCCMISC IRQ bit triggers the interrupt on a 0->1 edge, but not clear |
| * how that is handled in PSI so it is level-triggered here, which is not |
| * really correct (but skiboot is okay with it). |
| */ |
| qemu_set_irq(occ->psi_irq, !!(val & OCCMISC_PSI_IRQ)); |
| } |
| |
| static void pnv_occ_raise_msg_irq(PnvOCC *occ) |
| { |
| pnv_occ_set_misc(occ, occ->occmisc | OCCMISC_PSI_IRQ | OCCMISC_IRQ_SHMEM); |
| } |
| |
| static uint64_t pnv_occ_power8_xscom_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| PnvOCC *occ = PNV_OCC(opaque); |
| uint32_t offset = addr >> 3; |
| uint64_t val = 0; |
| |
| switch (offset) { |
| case OCB_OCI_OCCMISC: |
| val = occ->occmisc; |
| break; |
| default: |
| qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" |
| HWADDR_PRIx "\n", addr >> 3); |
| } |
| return val; |
| } |
| |
| static void pnv_occ_power8_xscom_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| PnvOCC *occ = PNV_OCC(opaque); |
| uint32_t offset = addr >> 3; |
| |
| switch (offset) { |
| case OCB_OCI_OCCMISC_AND: |
| pnv_occ_set_misc(occ, occ->occmisc & val); |
| break; |
| case OCB_OCI_OCCMISC_OR: |
| pnv_occ_set_misc(occ, occ->occmisc | val); |
| break; |
| case OCB_OCI_OCCMISC: |
| pnv_occ_set_misc(occ, val); |
| break; |
| default: |
| qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" |
| HWADDR_PRIx "\n", addr >> 3); |
| } |
| } |
| |
| static uint64_t pnv_occ_common_area_read(void *opaque, hwaddr addr, |
| unsigned width) |
| { |
| switch (addr) { |
| /* |
| * occ-sensor sanity check that asserts the sensor |
| * header block |
| */ |
| case OCC_SENSOR_DATA_BLOCK_OFFSET: |
| case OCC_SENSOR_DATA_VALID: |
| case OCC_SENSOR_DATA_VERSION: |
| case OCC_SENSOR_DATA_READING_VERSION: |
| case OCC_SENSOR_DATA_NR_SENSORS: |
| case OCC_SENSOR_DATA_NAMES_OFFSET: |
| case OCC_SENSOR_DATA_READING_PING_OFFSET: |
| case OCC_SENSOR_DATA_READING_PONG_OFFSET: |
| case OCC_SENSOR_NAME_STRUCTURE_TYPE: |
| return 1; |
| case OCC_SENSOR_DATA_NAME_LENGTH: |
| return 0x30; |
| case OCC_SENSOR_LOC_CORE: |
| return 0x0040; |
| case OCC_SENSOR_TYPE_POWER: |
| return 0x0080; |
| case OCC_SENSOR_NAME: |
| return 0x1000; |
| case HWMON_SENSORS_MASK: |
| case OCC_SENSOR_LOC_GPU: |
| return 0x8e00; |
| } |
| return 0; |
| } |
| |
| static void pnv_occ_common_area_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned width) |
| { |
| /* callback function defined to occ common area write */ |
| return; |
| } |
| |
| static const MemoryRegionOps pnv_occ_power8_xscom_ops = { |
| .read = pnv_occ_power8_xscom_read, |
| .write = pnv_occ_power8_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, |
| }; |
| |
| const MemoryRegionOps pnv_occ_sram_ops = { |
| .read = pnv_occ_common_area_read, |
| .write = pnv_occ_common_area_write, |
| .valid.min_access_size = 1, |
| .valid.max_access_size = 8, |
| .impl.min_access_size = 1, |
| .impl.max_access_size = 8, |
| .endianness = DEVICE_BIG_ENDIAN, |
| }; |
| |
| static void pnv_occ_power8_class_init(ObjectClass *klass, void *data) |
| { |
| PnvOCCClass *poc = PNV_OCC_CLASS(klass); |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->desc = "PowerNV OCC Controller (POWER8)"; |
| poc->opal_shared_memory_offset = P8_HOMER_OPAL_DATA_OFFSET; |
| poc->opal_shared_memory_version = 0x02; |
| poc->xscom_size = PNV_XSCOM_OCC_SIZE; |
| poc->xscom_ops = &pnv_occ_power8_xscom_ops; |
| } |
| |
| static const TypeInfo pnv_occ_power8_type_info = { |
| .name = TYPE_PNV8_OCC, |
| .parent = TYPE_PNV_OCC, |
| .instance_size = sizeof(PnvOCC), |
| .class_init = pnv_occ_power8_class_init, |
| }; |
| |
| #define P9_OCB_OCI_OCCMISC 0x6080 |
| #define P9_OCB_OCI_OCCMISC_CLEAR 0x6081 |
| #define P9_OCB_OCI_OCCMISC_OR 0x6082 |
| |
| |
| static uint64_t pnv_occ_power9_xscom_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| PnvOCC *occ = PNV_OCC(opaque); |
| uint32_t offset = addr >> 3; |
| uint64_t val = 0; |
| |
| switch (offset) { |
| case P9_OCB_OCI_OCCMISC: |
| val = occ->occmisc; |
| break; |
| default: |
| qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" |
| HWADDR_PRIx "\n", addr >> 3); |
| } |
| return val; |
| } |
| |
| static void pnv_occ_power9_xscom_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| PnvOCC *occ = PNV_OCC(opaque); |
| uint32_t offset = addr >> 3; |
| |
| switch (offset) { |
| case P9_OCB_OCI_OCCMISC_CLEAR: |
| pnv_occ_set_misc(occ, 0); |
| break; |
| case P9_OCB_OCI_OCCMISC_OR: |
| pnv_occ_set_misc(occ, occ->occmisc | val); |
| break; |
| case P9_OCB_OCI_OCCMISC: |
| pnv_occ_set_misc(occ, val); |
| break; |
| default: |
| qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" |
| HWADDR_PRIx "\n", addr >> 3); |
| } |
| } |
| |
| static const MemoryRegionOps pnv_occ_power9_xscom_ops = { |
| .read = pnv_occ_power9_xscom_read, |
| .write = pnv_occ_power9_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 void pnv_occ_power9_class_init(ObjectClass *klass, void *data) |
| { |
| PnvOCCClass *poc = PNV_OCC_CLASS(klass); |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->desc = "PowerNV OCC Controller (POWER9)"; |
| poc->opal_shared_memory_offset = P9_HOMER_OPAL_DATA_OFFSET; |
| poc->opal_shared_memory_version = 0x90; |
| poc->xscom_size = PNV9_XSCOM_OCC_SIZE; |
| poc->xscom_ops = &pnv_occ_power9_xscom_ops; |
| assert(!dc->user_creatable); |
| } |
| |
| static const TypeInfo pnv_occ_power9_type_info = { |
| .name = TYPE_PNV9_OCC, |
| .parent = TYPE_PNV_OCC, |
| .instance_size = sizeof(PnvOCC), |
| .class_init = pnv_occ_power9_class_init, |
| }; |
| |
| static void pnv_occ_power10_class_init(ObjectClass *klass, void *data) |
| { |
| PnvOCCClass *poc = PNV_OCC_CLASS(klass); |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->desc = "PowerNV OCC Controller (POWER10)"; |
| poc->opal_shared_memory_offset = P9_HOMER_OPAL_DATA_OFFSET; |
| poc->opal_shared_memory_version = 0xA0; |
| poc->xscom_size = PNV9_XSCOM_OCC_SIZE; |
| poc->xscom_ops = &pnv_occ_power9_xscom_ops; |
| assert(!dc->user_creatable); |
| } |
| |
| static const TypeInfo pnv_occ_power10_type_info = { |
| .name = TYPE_PNV10_OCC, |
| .parent = TYPE_PNV_OCC, |
| .class_init = pnv_occ_power10_class_init, |
| }; |
| |
| static bool occ_init_homer_memory(PnvOCC *occ, Error **errp); |
| static bool occ_model_tick(PnvOCC *occ); |
| |
| /* Relatively arbitrary */ |
| #define OCC_POLL_MS 100 |
| |
| static void occ_state_machine_timer(void *opaque) |
| { |
| PnvOCC *occ = opaque; |
| uint64_t next = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + OCC_POLL_MS; |
| |
| if (occ_model_tick(occ)) { |
| timer_mod(&occ->state_machine_timer, next); |
| } |
| } |
| |
| static void pnv_occ_realize(DeviceState *dev, Error **errp) |
| { |
| PnvOCC *occ = PNV_OCC(dev); |
| PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
| PnvHomer *homer = occ->homer; |
| |
| assert(homer); |
| |
| if (!occ_init_homer_memory(occ, errp)) { |
| return; |
| } |
| |
| occ->occmisc = 0; |
| |
| /* XScom region for OCC registers */ |
| pnv_xscom_region_init(&occ->xscom_regs, OBJECT(dev), poc->xscom_ops, |
| occ, "xscom-occ", poc->xscom_size); |
| |
| /* OCC common area mmio region for OCC SRAM registers */ |
| memory_region_init_io(&occ->sram_regs, OBJECT(dev), &pnv_occ_sram_ops, |
| occ, "occ-common-area", |
| PNV_OCC_SENSOR_DATA_BLOCK_SIZE); |
| |
| qdev_init_gpio_out(dev, &occ->psi_irq, 1); |
| |
| timer_init_ms(&occ->state_machine_timer, QEMU_CLOCK_VIRTUAL, |
| occ_state_machine_timer, occ); |
| timer_mod(&occ->state_machine_timer, OCC_POLL_MS); |
| } |
| |
| static const Property pnv_occ_properties[] = { |
| DEFINE_PROP_LINK("homer", PnvOCC, homer, TYPE_PNV_HOMER, PnvHomer *), |
| }; |
| |
| static void pnv_occ_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->realize = pnv_occ_realize; |
| device_class_set_props(dc, pnv_occ_properties); |
| dc->user_creatable = false; |
| } |
| |
| static const TypeInfo pnv_occ_type_info = { |
| .name = TYPE_PNV_OCC, |
| .parent = TYPE_DEVICE, |
| .instance_size = sizeof(PnvOCC), |
| .class_init = pnv_occ_class_init, |
| .class_size = sizeof(PnvOCCClass), |
| .abstract = true, |
| }; |
| |
| static void pnv_occ_register_types(void) |
| { |
| type_register_static(&pnv_occ_type_info); |
| type_register_static(&pnv_occ_power8_type_info); |
| type_register_static(&pnv_occ_power9_type_info); |
| type_register_static(&pnv_occ_power10_type_info); |
| } |
| |
| type_init(pnv_occ_register_types); |
| |
| /* |
| * From skiboot/hw/occ.c with following changes: |
| * - tab to space conversion |
| * - Type conversions u8->uint8_t s8->int8_t __be16->uint16_t etc |
| * - __packed -> QEMU_PACKED |
| */ |
| /* OCC Communication Area for PStates */ |
| |
| #define OPAL_DYNAMIC_DATA_OFFSET 0x0B80 |
| /* relative to HOMER_OPAL_DATA_OFFSET */ |
| |
| #define MAX_PSTATES 256 |
| #define MAX_P8_CORES 12 |
| #define MAX_P9_CORES 24 |
| #define MAX_P10_CORES 32 |
| |
| #define MAX_OPAL_CMD_DATA_LENGTH 4090 |
| #define MAX_OCC_RSP_DATA_LENGTH 8698 |
| |
| #define P8_PIR_CORE_MASK 0xFFF8 |
| #define P9_PIR_QUAD_MASK 0xFFF0 |
| #define P10_PIR_CHIP_MASK 0x0000 |
| #define FREQ_MAX_IN_DOMAIN 0 |
| #define FREQ_MOST_RECENTLY_SET 1 |
| |
| /** |
| * OCC-OPAL Shared Memory Region |
| * |
| * Reference document : |
| * https://github.com/open-power/docs/blob/master/occ/OCC_OpenPwr_FW_Interfaces.pdf |
| * |
| * Supported layout versions: |
| * - 0x01, 0x02 : P8 |
| * https://github.com/open-power/occ/blob/master_p8/src/occ/proc/proc_pstate.h |
| * |
| * - 0x90 : P9 |
| * https://github.com/open-power/occ/blob/master/src/occ_405/proc/proc_pstate.h |
| * In 0x90 the data is separated into :- |
| * -- Static Data (struct occ_pstate_table): Data is written once by OCC |
| * -- Dynamic Data (struct occ_dynamic_data): Data is updated at runtime |
| * |
| * struct occ_pstate_table - Pstate table layout |
| * @valid: Indicates if data is valid |
| * @version: Layout version [Major/Minor] |
| * @v2.throttle: Reason for limiting the max pstate |
| * @v9.occ_role: OCC role (Master/Slave) |
| * @v#.pstate_min: Minimum pstate ever allowed |
| * @v#.pstate_nom: Nominal pstate |
| * @v#.pstate_turbo: Maximum turbo pstate |
| * @v#.pstate_ultra_turbo: Maximum ultra turbo pstate and the maximum |
| * pstate ever allowed |
| * @v#.pstates: Pstate-id and frequency list from Pmax to Pmin |
| * @v#.pstates.id: Pstate-id |
| * @v#.pstates.flags: Pstate-flag(reserved) |
| * @v2.pstates.vdd: Voltage Identifier |
| * @v2.pstates.vcs: Voltage Identifier |
| * @v#.pstates.freq_khz: Frequency in KHz |
| * @v#.core_max[1..N]: Max pstate with N active cores |
| * @spare/reserved/pad: Unused data |
| */ |
| struct occ_pstate_table { |
| uint8_t valid; |
| uint8_t version; |
| union QEMU_PACKED { |
| struct QEMU_PACKED { /* Version 0x01 and 0x02 */ |
| uint8_t throttle; |
| int8_t pstate_min; |
| int8_t pstate_nom; |
| int8_t pstate_turbo; |
| int8_t pstate_ultra_turbo; |
| uint8_t spare; |
| uint64_t reserved; |
| struct QEMU_PACKED { |
| int8_t id; |
| uint8_t flags; |
| uint8_t vdd; |
| uint8_t vcs; |
| uint32_t freq_khz; |
| } pstates[MAX_PSTATES]; |
| int8_t core_max[MAX_P8_CORES]; |
| uint8_t pad[100]; |
| } v2; |
| struct QEMU_PACKED { /* Version 0x90 */ |
| uint8_t occ_role; |
| uint8_t pstate_min; |
| uint8_t pstate_nom; |
| uint8_t pstate_turbo; |
| uint8_t pstate_ultra_turbo; |
| uint8_t spare; |
| uint64_t reserved1; |
| uint64_t reserved2; |
| struct QEMU_PACKED { |
| uint8_t id; |
| uint8_t flags; |
| uint16_t reserved; |
| uint32_t freq_khz; |
| } pstates[MAX_PSTATES]; |
| uint8_t core_max[MAX_P9_CORES]; |
| uint8_t pad[56]; |
| } v9; |
| struct QEMU_PACKED { /* Version 0xA0 */ |
| uint8_t occ_role; |
| uint8_t pstate_min; |
| uint8_t pstate_fixed_freq; |
| uint8_t pstate_base; |
| uint8_t pstate_ultra_turbo; |
| uint8_t pstate_fmax; |
| uint8_t minor; |
| uint8_t pstate_bottom_throttle; |
| uint8_t spare; |
| uint8_t spare1; |
| uint32_t reserved_32; |
| uint64_t reserved_64; |
| struct QEMU_PACKED { |
| uint8_t id; |
| uint8_t valid; |
| uint16_t reserved; |
| uint32_t freq_khz; |
| } pstates[MAX_PSTATES]; |
| uint8_t core_max[MAX_P10_CORES]; |
| uint8_t pad[48]; |
| } v10; |
| }; |
| } QEMU_PACKED; |
| |
| /** |
| * OPAL-OCC Command Response Interface |
| * |
| * OPAL-OCC Command Buffer |
| * |
| * --------------------------------------------------------------------- |
| * | OPAL | Cmd | OPAL | | Cmd Data | Cmd Data | OPAL | |
| * | Cmd | Request | OCC | Reserved | Length | Length | Cmd | |
| * | Flags | ID | Cmd | | (MSB) | (LSB) | Data... | |
| * --------------------------------------------------------------------- |
| * | ….OPAL Command Data up to max of Cmd Data Length 4090 bytes | |
| * | | |
| * --------------------------------------------------------------------- |
| * |
| * OPAL Command Flag |
| * |
| * ----------------------------------------------------------------- |
| * | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | |
| * | (msb) | | | | | | | (lsb) | |
| * ----------------------------------------------------------------- |
| * |Cmd | | | | | | | | |
| * |Ready | | | | | | | | |
| * ----------------------------------------------------------------- |
| * |
| * struct opal_command_buffer - Defines the layout of OPAL command buffer |
| * @flag: Provides general status of the command |
| * @request_id: Token to identify request |
| * @cmd: Command sent |
| * @data_size: Command data length |
| * @data: Command specific data |
| * @spare: Unused byte |
| */ |
| struct opal_command_buffer { |
| uint8_t flag; |
| uint8_t request_id; |
| uint8_t cmd; |
| uint8_t spare; |
| uint16_t data_size; |
| uint8_t data[MAX_OPAL_CMD_DATA_LENGTH]; |
| } QEMU_PACKED; |
| |
| /** |
| * OPAL-OCC Response Buffer |
| * |
| * --------------------------------------------------------------------- |
| * | OCC | Cmd | OPAL | Response | Rsp Data | Rsp Data | OPAL | |
| * | Rsp | Request | OCC | Status | Length | Length | Rsp | |
| * | Flags | ID | Cmd | | (MSB) | (LSB) | Data... | |
| * --------------------------------------------------------------------- |
| * | ….OPAL Response Data up to max of Rsp Data Length 8698 bytes | |
| * | | |
| * --------------------------------------------------------------------- |
| * |
| * OCC Response Flag |
| * |
| * ----------------------------------------------------------------- |
| * | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | |
| * | (msb) | | | | | | | (lsb) | |
| * ----------------------------------------------------------------- |
| * | | | | | | |OCC in | Rsp | |
| * | | | | | | |progress|Ready | |
| * ----------------------------------------------------------------- |
| * |
| * struct occ_response_buffer - Defines the layout of OCC response buffer |
| * @flag: Provides general status of the response |
| * @request_id: Token to identify request |
| * @cmd: Command requested |
| * @status: Indicates success/failure status of |
| * the command |
| * @data_size: Response data length |
| * @data: Response specific data |
| */ |
| struct occ_response_buffer { |
| uint8_t flag; |
| uint8_t request_id; |
| uint8_t cmd; |
| uint8_t status; |
| uint16_t data_size; |
| uint8_t data[MAX_OCC_RSP_DATA_LENGTH]; |
| } QEMU_PACKED; |
| |
| /** |
| * OCC-OPAL Shared Memory Interface Dynamic Data Vx90 |
| * |
| * struct occ_dynamic_data - Contains runtime attributes |
| * @occ_state: Current state of OCC |
| * @major_version: Major version number |
| * @minor_version: Minor version number (backwards compatible) |
| * Version 1 indicates GPU presence populated |
| * @gpus_present: Bitmask of GPUs present (on systems where GPU |
| * presence is detected through APSS) |
| * @cpu_throttle: Reason for limiting the max pstate |
| * @mem_throttle: Reason for throttling memory |
| * @quick_pwr_drop: Indicates if QPD is asserted |
| * @pwr_shifting_ratio: Indicates the current percentage of power to |
| * take away from the CPU vs GPU when shifting |
| * power to maintain a power cap. Value of 100 |
| * means take all power from CPU. |
| * @pwr_cap_type: Indicates type of power cap in effect |
| * @hard_min_pwr_cap: Hard minimum system power cap in Watts. |
| * Guaranteed unless hardware failure |
| * @max_pwr_cap: Maximum allowed system power cap in Watts |
| * @cur_pwr_cap: Current system power cap |
| * @soft_min_pwr_cap: Soft powercap minimum. OCC may or may not be |
| * able to maintain this |
| * @spare/reserved: Unused data |
| * @cmd: Opal Command Buffer |
| * @rsp: OCC Response Buffer |
| */ |
| struct occ_dynamic_data { |
| uint8_t occ_state; |
| uint8_t major_version; |
| uint8_t minor_version; |
| uint8_t gpus_present; |
| union QEMU_PACKED { |
| struct QEMU_PACKED { /* Version 0x90 */ |
| uint8_t spare1; |
| } v9; |
| struct QEMU_PACKED { /* Version 0xA0 */ |
| uint8_t wof_enabled; |
| } v10; |
| }; |
| uint8_t cpu_throttle; |
| uint8_t mem_throttle; |
| uint8_t quick_pwr_drop; |
| uint8_t pwr_shifting_ratio; |
| uint8_t pwr_cap_type; |
| uint16_t hard_min_pwr_cap; |
| uint16_t max_pwr_cap; |
| uint16_t cur_pwr_cap; |
| uint16_t soft_min_pwr_cap; |
| uint8_t pad[110]; |
| struct opal_command_buffer cmd; |
| struct occ_response_buffer rsp; |
| } QEMU_PACKED; |
| |
| enum occ_response_status { |
| OCC_RSP_SUCCESS = 0x00, |
| OCC_RSP_INVALID_COMMAND = 0x11, |
| OCC_RSP_INVALID_CMD_DATA_LENGTH = 0x12, |
| OCC_RSP_INVALID_DATA = 0x13, |
| OCC_RSP_INTERNAL_ERROR = 0x15, |
| }; |
| |
| #define OCC_ROLE_SLAVE 0x00 |
| #define OCC_ROLE_MASTER 0x01 |
| |
| #define OCC_FLAG_RSP_READY 0x01 |
| #define OCC_FLAG_CMD_IN_PROGRESS 0x02 |
| #define OPAL_FLAG_CMD_READY 0x80 |
| |
| #define PCAP_MAX_POWER_W 100 |
| #define PCAP_SOFT_MIN_POWER_W 20 |
| #define PCAP_HARD_MIN_POWER_W 10 |
| |
| static bool occ_write_static_data(PnvOCC *occ, |
| struct occ_pstate_table *static_data, |
| Error **errp) |
| { |
| PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
| PnvHomer *homer = occ->homer; |
| hwaddr static_addr = homer->base + poc->opal_shared_memory_offset; |
| MemTxResult ret; |
| |
| ret = address_space_write(&address_space_memory, static_addr, |
| MEMTXATTRS_UNSPECIFIED, static_data, |
| sizeof(*static_data)); |
| if (ret != MEMTX_OK) { |
| error_setg(errp, "OCC: cannot write OCC-OPAL static data"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool occ_read_dynamic_data(PnvOCC *occ, |
| struct occ_dynamic_data *dynamic_data, |
| Error **errp) |
| { |
| PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
| PnvHomer *homer = occ->homer; |
| hwaddr static_addr = homer->base + poc->opal_shared_memory_offset; |
| hwaddr dynamic_addr = static_addr + OPAL_DYNAMIC_DATA_OFFSET; |
| MemTxResult ret; |
| |
| ret = address_space_read(&address_space_memory, dynamic_addr, |
| MEMTXATTRS_UNSPECIFIED, dynamic_data, |
| sizeof(*dynamic_data)); |
| if (ret != MEMTX_OK) { |
| error_setg(errp, "OCC: cannot read OCC-OPAL dynamic data"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool occ_write_dynamic_data(PnvOCC *occ, |
| struct occ_dynamic_data *dynamic_data, |
| Error **errp) |
| { |
| PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
| PnvHomer *homer = occ->homer; |
| hwaddr static_addr = homer->base + poc->opal_shared_memory_offset; |
| hwaddr dynamic_addr = static_addr + OPAL_DYNAMIC_DATA_OFFSET; |
| MemTxResult ret; |
| |
| ret = address_space_write(&address_space_memory, dynamic_addr, |
| MEMTXATTRS_UNSPECIFIED, dynamic_data, |
| sizeof(*dynamic_data)); |
| if (ret != MEMTX_OK) { |
| error_setg(errp, "OCC: cannot write OCC-OPAL dynamic data"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool occ_opal_send_response(PnvOCC *occ, |
| struct occ_dynamic_data *dynamic_data, |
| enum occ_response_status status, |
| uint8_t *data, uint16_t datalen) |
| { |
| struct opal_command_buffer *cmd = &dynamic_data->cmd; |
| struct occ_response_buffer *rsp = &dynamic_data->rsp; |
| |
| rsp->request_id = cmd->request_id; |
| rsp->cmd = cmd->cmd; |
| rsp->status = status; |
| rsp->data_size = cpu_to_be16(datalen); |
| if (datalen) { |
| memcpy(rsp->data, data, datalen); |
| } |
| if (!occ_write_dynamic_data(occ, dynamic_data, NULL)) { |
| return false; |
| } |
| /* Would be a memory barrier here */ |
| rsp->flag = OCC_FLAG_RSP_READY; |
| cmd->flag = 0; |
| if (!occ_write_dynamic_data(occ, dynamic_data, NULL)) { |
| return false; |
| } |
| |
| pnv_occ_raise_msg_irq(occ); |
| |
| return true; |
| } |
| |
| /* Returns error status */ |
| static bool occ_opal_process_command(PnvOCC *occ, |
| struct occ_dynamic_data *dynamic_data) |
| { |
| struct opal_command_buffer *cmd = &dynamic_data->cmd; |
| struct occ_response_buffer *rsp = &dynamic_data->rsp; |
| |
| if (rsp->flag == 0) { |
| /* Spend one "tick" in the in-progress state */ |
| rsp->flag = OCC_FLAG_CMD_IN_PROGRESS; |
| return occ_write_dynamic_data(occ, dynamic_data, NULL); |
| } else if (rsp->flag != OCC_FLAG_CMD_IN_PROGRESS) { |
| return occ_opal_send_response(occ, dynamic_data, |
| OCC_RSP_INTERNAL_ERROR, |
| NULL, 0); |
| } |
| |
| switch (cmd->cmd) { |
| case 0xD1: { /* SET_POWER_CAP */ |
| uint16_t data; |
| if (be16_to_cpu(cmd->data_size) != 2) { |
| return occ_opal_send_response(occ, dynamic_data, |
| OCC_RSP_INVALID_CMD_DATA_LENGTH, |
| (uint8_t *)&dynamic_data->cur_pwr_cap, |
| 2); |
| } |
| data = be16_to_cpu(*(uint16_t *)cmd->data); |
| if (data == 0) { /* clear power cap */ |
| dynamic_data->pwr_cap_type = 0x00; /* none */ |
| data = PCAP_MAX_POWER_W; |
| } else { |
| dynamic_data->pwr_cap_type = 0x02; /* user set in-band */ |
| if (data < PCAP_HARD_MIN_POWER_W) { |
| data = PCAP_HARD_MIN_POWER_W; |
| } else if (data > PCAP_MAX_POWER_W) { |
| data = PCAP_MAX_POWER_W; |
| } |
| } |
| dynamic_data->cur_pwr_cap = cpu_to_be16(data); |
| return occ_opal_send_response(occ, dynamic_data, |
| OCC_RSP_SUCCESS, |
| (uint8_t *)&dynamic_data->cur_pwr_cap, 2); |
| } |
| |
| default: |
| return occ_opal_send_response(occ, dynamic_data, |
| OCC_RSP_INVALID_COMMAND, |
| NULL, 0); |
| } |
| g_assert_not_reached(); |
| } |
| |
| static bool occ_model_tick(PnvOCC *occ) |
| { |
| struct occ_dynamic_data dynamic_data; |
| |
| if (!occ_read_dynamic_data(occ, &dynamic_data, NULL)) { |
| /* Can't move OCC state field to safe because we can't map it! */ |
| qemu_log("OCC: failed to read HOMER data, shutting down OCC\n"); |
| return false; |
| } |
| if (dynamic_data.cmd.flag == OPAL_FLAG_CMD_READY) { |
| if (!occ_opal_process_command(occ, &dynamic_data)) { |
| qemu_log("OCC: failed to write HOMER data, shutting down OCC\n"); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool occ_init_homer_memory(PnvOCC *occ, Error **errp) |
| { |
| PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
| PnvHomer *homer = occ->homer; |
| PnvChip *chip = homer->chip; |
| struct occ_pstate_table static_data; |
| struct occ_dynamic_data dynamic_data; |
| int i; |
| |
| memset(&static_data, 0, sizeof(static_data)); |
| static_data.valid = 1; |
| static_data.version = poc->opal_shared_memory_version; |
| switch (poc->opal_shared_memory_version) { |
| case 0x02: |
| static_data.v2.throttle = 0; |
| static_data.v2.pstate_min = -2; |
| static_data.v2.pstate_nom = -1; |
| static_data.v2.pstate_turbo = -1; |
| static_data.v2.pstate_ultra_turbo = 0; |
| static_data.v2.pstates[0].id = 0; |
| static_data.v2.pstates[1].freq_khz = cpu_to_be32(4000000); |
| static_data.v2.pstates[1].id = -1; |
| static_data.v2.pstates[1].freq_khz = cpu_to_be32(3000000); |
| static_data.v2.pstates[2].id = -2; |
| static_data.v2.pstates[2].freq_khz = cpu_to_be32(2000000); |
| for (i = 0; i < chip->nr_cores; i++) { |
| static_data.v2.core_max[i] = 1; |
| } |
| break; |
| case 0x90: |
| if (chip->chip_id == 0) { |
| static_data.v9.occ_role = OCC_ROLE_MASTER; |
| } else { |
| static_data.v9.occ_role = OCC_ROLE_SLAVE; |
| } |
| static_data.v9.pstate_min = 2; |
| static_data.v9.pstate_nom = 1; |
| static_data.v9.pstate_turbo = 1; |
| static_data.v9.pstate_ultra_turbo = 0; |
| static_data.v9.pstates[0].id = 0; |
| static_data.v9.pstates[0].freq_khz = cpu_to_be32(4000000); |
| static_data.v9.pstates[1].id = 1; |
| static_data.v9.pstates[1].freq_khz = cpu_to_be32(3000000); |
| static_data.v9.pstates[2].id = 2; |
| static_data.v9.pstates[2].freq_khz = cpu_to_be32(2000000); |
| for (i = 0; i < chip->nr_cores; i++) { |
| static_data.v9.core_max[i] = 1; |
| } |
| break; |
| case 0xA0: |
| if (chip->chip_id == 0) { |
| static_data.v10.occ_role = OCC_ROLE_MASTER; |
| } else { |
| static_data.v10.occ_role = OCC_ROLE_SLAVE; |
| } |
| static_data.v10.pstate_min = 4; |
| static_data.v10.pstate_fixed_freq = 3; |
| static_data.v10.pstate_base = 2; |
| static_data.v10.pstate_ultra_turbo = 0; |
| static_data.v10.pstate_fmax = 1; |
| static_data.v10.minor = 0x01; |
| static_data.v10.pstates[0].valid = 1; |
| static_data.v10.pstates[0].id = 0; |
| static_data.v10.pstates[0].freq_khz = cpu_to_be32(4200000); |
| static_data.v10.pstates[1].valid = 1; |
| static_data.v10.pstates[1].id = 1; |
| static_data.v10.pstates[1].freq_khz = cpu_to_be32(4000000); |
| static_data.v10.pstates[2].valid = 1; |
| static_data.v10.pstates[2].id = 2; |
| static_data.v10.pstates[2].freq_khz = cpu_to_be32(3800000); |
| static_data.v10.pstates[3].valid = 1; |
| static_data.v10.pstates[3].id = 3; |
| static_data.v10.pstates[3].freq_khz = cpu_to_be32(3000000); |
| static_data.v10.pstates[4].valid = 1; |
| static_data.v10.pstates[4].id = 4; |
| static_data.v10.pstates[4].freq_khz = cpu_to_be32(2000000); |
| for (i = 0; i < chip->nr_cores; i++) { |
| static_data.v10.core_max[i] = 1; |
| } |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| if (!occ_write_static_data(occ, &static_data, errp)) { |
| return false; |
| } |
| |
| memset(&dynamic_data, 0, sizeof(dynamic_data)); |
| dynamic_data.occ_state = 0x3; /* active */ |
| dynamic_data.major_version = 0x0; |
| dynamic_data.hard_min_pwr_cap = cpu_to_be16(PCAP_HARD_MIN_POWER_W); |
| dynamic_data.max_pwr_cap = cpu_to_be16(PCAP_MAX_POWER_W); |
| dynamic_data.cur_pwr_cap = cpu_to_be16(PCAP_MAX_POWER_W); |
| dynamic_data.soft_min_pwr_cap = cpu_to_be16(PCAP_SOFT_MIN_POWER_W); |
| switch (poc->opal_shared_memory_version) { |
| case 0xA0: |
| dynamic_data.minor_version = 0x1; |
| dynamic_data.v10.wof_enabled = 0x1; |
| break; |
| case 0x90: |
| dynamic_data.minor_version = 0x1; |
| break; |
| case 0x02: |
| dynamic_data.minor_version = 0x0; |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| if (!occ_write_dynamic_data(occ, &dynamic_data, errp)) { |
| return false; |
| } |
| |
| return true; |
| } |