| 1 | /* |
| 2 | * QEMU PowerPC PowerNV Emulation of a few OCC related registers |
| 3 | * |
| 4 | * Copyright (c) 2015-2017, IBM Corporation. |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License, version 2, as |
| 8 | * published by the Free Software Foundation. |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | * GNU General Public License for more details. |
| 14 | * |
| 15 | * You should have received a copy of the GNU General Public License |
| 16 | * along with this program; if not, see <http://www.gnu.org/licenses/>. |
| 17 | */ |
| 18 | |
| 19 | #include "qemu/osdep.h" |
| 20 | #include "target/ppc/cpu.h" |
| 21 | #include "qapi/error.h" |
| 22 | #include "qemu/log.h" |
| 23 | #include "qemu/module.h" |
| 24 | #include "hw/core/irq.h" |
| 25 | #include "hw/core/qdev-properties.h" |
| 26 | #include "hw/ppc/pnv.h" |
| 27 | #include "hw/ppc/pnv_chip.h" |
| 28 | #include "hw/ppc/pnv_xscom.h" |
| 29 | #include "hw/ppc/pnv_occ.h" |
| 30 | |
| 31 | #define P8_HOMER_OPAL_DATA_OFFSET 0x1F8000 |
| 32 | #define P9_HOMER_OPAL_DATA_OFFSET 0x0E2000 |
| 33 | |
| 34 | #define OCB_OCI_OCCMISC 0x4020 |
| 35 | #define OCB_OCI_OCCMISC_AND 0x4021 |
| 36 | #define OCB_OCI_OCCMISC_OR 0x4022 |
| 37 | #define OCCMISC_PSI_IRQ PPC_BIT(0) |
| 38 | #define OCCMISC_IRQ_SHMEM PPC_BIT(3) |
| 39 | |
| 40 | /* OCC sensors */ |
| 41 | #define OCC_SENSOR_DATA_BLOCK_OFFSET 0x0000 |
| 42 | #define OCC_SENSOR_DATA_VALID 0x0001 |
| 43 | #define OCC_SENSOR_DATA_VERSION 0x0002 |
| 44 | #define OCC_SENSOR_DATA_READING_VERSION 0x0004 |
| 45 | #define OCC_SENSOR_DATA_NR_SENSORS 0x0008 |
| 46 | #define OCC_SENSOR_DATA_NAMES_OFFSET 0x0010 |
| 47 | #define OCC_SENSOR_DATA_READING_PING_OFFSET 0x0014 |
| 48 | #define OCC_SENSOR_DATA_READING_PONG_OFFSET 0x000c |
| 49 | #define OCC_SENSOR_DATA_NAME_LENGTH 0x000d |
| 50 | #define OCC_SENSOR_NAME_STRUCTURE_TYPE 0x0023 |
| 51 | #define OCC_SENSOR_LOC_CORE 0x0022 |
| 52 | #define OCC_SENSOR_LOC_GPU 0x0020 |
| 53 | #define OCC_SENSOR_TYPE_POWER 0x0003 |
| 54 | #define OCC_SENSOR_NAME 0x0005 |
| 55 | #define HWMON_SENSORS_MASK 0x001e |
| 56 | |
| 57 | static void pnv_occ_set_misc(PnvOCC *occ, uint64_t val) |
| 58 | { |
| 59 | val &= PPC_BITMASK(0, 18); /* Mask out unimplemented bits */ |
| 60 | |
| 61 | occ->occmisc = val; |
| 62 | |
| 63 | /* |
| 64 | * OCCMISC IRQ bit triggers the interrupt on a 0->1 edge, but not clear |
| 65 | * how that is handled in PSI so it is level-triggered here, which is not |
| 66 | * really correct (but skiboot is okay with it). |
| 67 | */ |
| 68 | qemu_set_irq(occ->psi_irq, !!(val & OCCMISC_PSI_IRQ)); |
| 69 | } |
| 70 | |
| 71 | static void pnv_occ_raise_msg_irq(PnvOCC *occ) |
| 72 | { |
| 73 | pnv_occ_set_misc(occ, occ->occmisc | OCCMISC_PSI_IRQ | OCCMISC_IRQ_SHMEM); |
| 74 | } |
| 75 | |
| 76 | static uint64_t pnv_occ_power8_xscom_read(void *opaque, hwaddr addr, |
| 77 | unsigned size) |
| 78 | { |
| 79 | PnvOCC *occ = PNV_OCC(opaque); |
| 80 | uint32_t offset = addr >> 3; |
| 81 | uint64_t val = 0; |
| 82 | |
| 83 | switch (offset) { |
| 84 | case OCB_OCI_OCCMISC: |
| 85 | val = occ->occmisc; |
| 86 | break; |
| 87 | default: |
| 88 | qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" |
| 89 | HWADDR_PRIx "\n", addr >> 3); |
| 90 | } |
| 91 | return val; |
| 92 | } |
| 93 | |
| 94 | static void pnv_occ_power8_xscom_write(void *opaque, hwaddr addr, |
| 95 | uint64_t val, unsigned size) |
| 96 | { |
| 97 | PnvOCC *occ = PNV_OCC(opaque); |
| 98 | uint32_t offset = addr >> 3; |
| 99 | |
| 100 | switch (offset) { |
| 101 | case OCB_OCI_OCCMISC_AND: |
| 102 | pnv_occ_set_misc(occ, occ->occmisc & val); |
| 103 | break; |
| 104 | case OCB_OCI_OCCMISC_OR: |
| 105 | pnv_occ_set_misc(occ, occ->occmisc | val); |
| 106 | break; |
| 107 | case OCB_OCI_OCCMISC: |
| 108 | pnv_occ_set_misc(occ, val); |
| 109 | break; |
| 110 | default: |
| 111 | qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" |
| 112 | HWADDR_PRIx "\n", addr >> 3); |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | static uint64_t pnv_occ_common_area_read(void *opaque, hwaddr addr, |
| 117 | unsigned width) |
| 118 | { |
| 119 | switch (addr) { |
| 120 | /* |
| 121 | * occ-sensor sanity check that asserts the sensor |
| 122 | * header block |
| 123 | */ |
| 124 | case OCC_SENSOR_DATA_BLOCK_OFFSET: |
| 125 | case OCC_SENSOR_DATA_VALID: |
| 126 | case OCC_SENSOR_DATA_VERSION: |
| 127 | case OCC_SENSOR_DATA_READING_VERSION: |
| 128 | case OCC_SENSOR_DATA_NR_SENSORS: |
| 129 | case OCC_SENSOR_DATA_NAMES_OFFSET: |
| 130 | case OCC_SENSOR_DATA_READING_PING_OFFSET: |
| 131 | case OCC_SENSOR_DATA_READING_PONG_OFFSET: |
| 132 | case OCC_SENSOR_NAME_STRUCTURE_TYPE: |
| 133 | return 1; |
| 134 | case OCC_SENSOR_DATA_NAME_LENGTH: |
| 135 | return 0x30; |
| 136 | case OCC_SENSOR_LOC_CORE: |
| 137 | return 0x0040; |
| 138 | case OCC_SENSOR_TYPE_POWER: |
| 139 | return 0x0080; |
| 140 | case OCC_SENSOR_NAME: |
| 141 | return 0x1000; |
| 142 | case HWMON_SENSORS_MASK: |
| 143 | case OCC_SENSOR_LOC_GPU: |
| 144 | return 0x8e00; |
| 145 | } |
| 146 | return 0; |
| 147 | } |
| 148 | |
| 149 | static void pnv_occ_common_area_write(void *opaque, hwaddr addr, |
| 150 | uint64_t val, unsigned width) |
| 151 | { |
| 152 | /* callback function defined to occ common area write */ |
| 153 | } |
| 154 | |
| 155 | static const MemoryRegionOps pnv_occ_power8_xscom_ops = { |
| 156 | .read = pnv_occ_power8_xscom_read, |
| 157 | .write = pnv_occ_power8_xscom_write, |
| 158 | .valid.min_access_size = 8, |
| 159 | .valid.max_access_size = 8, |
| 160 | .impl.min_access_size = 8, |
| 161 | .impl.max_access_size = 8, |
| 162 | .endianness = DEVICE_BIG_ENDIAN, |
| 163 | }; |
| 164 | |
| 165 | const MemoryRegionOps pnv_occ_sram_ops = { |
| 166 | .read = pnv_occ_common_area_read, |
| 167 | .write = pnv_occ_common_area_write, |
| 168 | .valid.min_access_size = 1, |
| 169 | .valid.max_access_size = 8, |
| 170 | .impl.min_access_size = 1, |
| 171 | .impl.max_access_size = 8, |
| 172 | .endianness = DEVICE_BIG_ENDIAN, |
| 173 | }; |
| 174 | |
| 175 | static void pnv_occ_power8_class_init(ObjectClass *klass, const void *data) |
| 176 | { |
| 177 | PnvOCCClass *poc = PNV_OCC_CLASS(klass); |
| 178 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 179 | |
| 180 | dc->desc = "PowerNV OCC Controller (POWER8)"; |
| 181 | poc->opal_shared_memory_offset = P8_HOMER_OPAL_DATA_OFFSET; |
| 182 | poc->opal_shared_memory_version = 0x02; |
| 183 | poc->xscom_size = PNV_XSCOM_OCC_SIZE; |
| 184 | poc->xscom_ops = &pnv_occ_power8_xscom_ops; |
| 185 | } |
| 186 | |
| 187 | static const TypeInfo pnv_occ_power8_type_info = { |
| 188 | .name = TYPE_PNV8_OCC, |
| 189 | .parent = TYPE_PNV_OCC, |
| 190 | .instance_size = sizeof(PnvOCC), |
| 191 | .class_init = pnv_occ_power8_class_init, |
| 192 | }; |
| 193 | |
| 194 | #define P9_OCB_OCI_OCCMISC 0x6080 |
| 195 | #define P9_OCB_OCI_OCCMISC_CLEAR 0x6081 |
| 196 | #define P9_OCB_OCI_OCCMISC_OR 0x6082 |
| 197 | |
| 198 | /* OCC scratch registers for flag setting */ |
| 199 | #define P9_OCCFLG0 0x60ac |
| 200 | #define P9_OCCFLG7_OR 0x60c3 |
| 201 | |
| 202 | enum ScomType { |
| 203 | SCOM_TYPE_RW = 0, |
| 204 | SCOM_TYPE_WO_CLEAR = 1, |
| 205 | SCOM_TYPE_WO_OR = 2, |
| 206 | }; |
| 207 | |
| 208 | static void rw_occ_flag_regs(PnvOCC *occ, uint32_t offset, bool read, |
| 209 | uint64_t *val) |
| 210 | { |
| 211 | int flag_num; |
| 212 | int flag_type; |
| 213 | |
| 214 | /* |
| 215 | * Each OCCFLG register has SCOM0 - RW, SCOM1 - WO_CLEAR, SCOM2 - WO_OR |
| 216 | * hence divide by 3 to get flag index and mod 3 to get SCOM type. |
| 217 | */ |
| 218 | flag_num = (offset - P9_OCCFLG0) / 3; |
| 219 | flag_type = (offset - P9_OCCFLG0) % 3; |
| 220 | |
| 221 | if (read) { |
| 222 | if (flag_type) { |
| 223 | qemu_log_mask(LOG_GUEST_ERROR, "OCC: Write only register: Ox%" |
| 224 | PRIx32 "\n", offset); |
| 225 | return; |
| 226 | } |
| 227 | *val = occ->occflags[flag_num]; |
| 228 | } else { |
| 229 | switch (flag_type) { |
| 230 | case SCOM_TYPE_RW: |
| 231 | occ->occflags[flag_num] = *val; |
| 232 | break; |
| 233 | case SCOM_TYPE_WO_CLEAR: |
| 234 | occ->occflags[flag_num] &= ~(*val); |
| 235 | break; |
| 236 | case SCOM_TYPE_WO_OR: |
| 237 | occ->occflags[flag_num] |= *val; |
| 238 | } |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | static uint64_t pnv_occ_power9_xscom_read(void *opaque, hwaddr addr, |
| 243 | unsigned size) |
| 244 | { |
| 245 | PnvOCC *occ = PNV_OCC(opaque); |
| 246 | uint32_t offset = addr >> 3; |
| 247 | uint64_t val = 0; |
| 248 | |
| 249 | switch (offset) { |
| 250 | case P9_OCB_OCI_OCCMISC: |
| 251 | val = occ->occmisc; |
| 252 | break; |
| 253 | case P9_OCCFLG0 ... P9_OCCFLG7_OR: |
| 254 | rw_occ_flag_regs(occ, offset, 1, &val); |
| 255 | break; |
| 256 | default: |
| 257 | qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register read: Ox%" |
| 258 | HWADDR_PRIx "\n", addr >> 3); |
| 259 | } |
| 260 | return val; |
| 261 | } |
| 262 | |
| 263 | static void pnv_occ_power9_xscom_write(void *opaque, hwaddr addr, |
| 264 | uint64_t val, unsigned size) |
| 265 | { |
| 266 | PnvOCC *occ = PNV_OCC(opaque); |
| 267 | uint32_t offset = addr >> 3; |
| 268 | |
| 269 | switch (offset) { |
| 270 | case P9_OCB_OCI_OCCMISC_CLEAR: |
| 271 | pnv_occ_set_misc(occ, 0); |
| 272 | break; |
| 273 | case P9_OCB_OCI_OCCMISC_OR: |
| 274 | pnv_occ_set_misc(occ, occ->occmisc | val); |
| 275 | break; |
| 276 | case P9_OCB_OCI_OCCMISC: |
| 277 | pnv_occ_set_misc(occ, val); |
| 278 | break; |
| 279 | case P9_OCCFLG0 ... P9_OCCFLG7_OR: |
| 280 | rw_occ_flag_regs(occ, offset, 0, &val); |
| 281 | break; |
| 282 | default: |
| 283 | qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register write: Ox%" |
| 284 | HWADDR_PRIx "\n", addr >> 3); |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | static const MemoryRegionOps pnv_occ_power9_xscom_ops = { |
| 289 | .read = pnv_occ_power9_xscom_read, |
| 290 | .write = pnv_occ_power9_xscom_write, |
| 291 | .valid.min_access_size = 8, |
| 292 | .valid.max_access_size = 8, |
| 293 | .impl.min_access_size = 8, |
| 294 | .impl.max_access_size = 8, |
| 295 | .endianness = DEVICE_BIG_ENDIAN, |
| 296 | }; |
| 297 | |
| 298 | static void pnv_occ_power9_class_init(ObjectClass *klass, const void *data) |
| 299 | { |
| 300 | PnvOCCClass *poc = PNV_OCC_CLASS(klass); |
| 301 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 302 | |
| 303 | dc->desc = "PowerNV OCC Controller (POWER9)"; |
| 304 | poc->opal_shared_memory_offset = P9_HOMER_OPAL_DATA_OFFSET; |
| 305 | poc->opal_shared_memory_version = 0x90; |
| 306 | poc->xscom_size = PNV9_XSCOM_OCC_SIZE; |
| 307 | poc->xscom_ops = &pnv_occ_power9_xscom_ops; |
| 308 | assert(!dc->user_creatable); |
| 309 | } |
| 310 | |
| 311 | static const TypeInfo pnv_occ_power9_type_info = { |
| 312 | .name = TYPE_PNV9_OCC, |
| 313 | .parent = TYPE_PNV_OCC, |
| 314 | .instance_size = sizeof(PnvOCC), |
| 315 | .class_init = pnv_occ_power9_class_init, |
| 316 | }; |
| 317 | |
| 318 | static void pnv_occ_power10_class_init(ObjectClass *klass, const void *data) |
| 319 | { |
| 320 | PnvOCCClass *poc = PNV_OCC_CLASS(klass); |
| 321 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 322 | |
| 323 | dc->desc = "PowerNV OCC Controller (POWER10)"; |
| 324 | poc->opal_shared_memory_offset = P9_HOMER_OPAL_DATA_OFFSET; |
| 325 | poc->opal_shared_memory_version = 0xA0; |
| 326 | poc->xscom_size = PNV9_XSCOM_OCC_SIZE; |
| 327 | poc->xscom_ops = &pnv_occ_power9_xscom_ops; |
| 328 | assert(!dc->user_creatable); |
| 329 | } |
| 330 | |
| 331 | static const TypeInfo pnv_occ_power10_type_info = { |
| 332 | .name = TYPE_PNV10_OCC, |
| 333 | .parent = TYPE_PNV_OCC, |
| 334 | .class_init = pnv_occ_power10_class_init, |
| 335 | }; |
| 336 | |
| 337 | static bool occ_init_homer_memory(PnvOCC *occ, Error **errp); |
| 338 | static bool occ_model_tick(PnvOCC *occ); |
| 339 | |
| 340 | /* Relatively arbitrary */ |
| 341 | #define OCC_POLL_MS 100 |
| 342 | |
| 343 | static void occ_state_machine_timer(void *opaque) |
| 344 | { |
| 345 | PnvOCC *occ = opaque; |
| 346 | uint64_t next = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + OCC_POLL_MS; |
| 347 | |
| 348 | if (occ_model_tick(occ)) { |
| 349 | timer_mod(&occ->state_machine_timer, next); |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | static void pnv_occ_realize(DeviceState *dev, Error **errp) |
| 354 | { |
| 355 | PnvOCC *occ = PNV_OCC(dev); |
| 356 | PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
| 357 | PnvHomer *homer = occ->homer; |
| 358 | |
| 359 | assert(homer); |
| 360 | |
| 361 | if (!occ_init_homer_memory(occ, errp)) { |
| 362 | return; |
| 363 | } |
| 364 | |
| 365 | occ->occmisc = 0; |
| 366 | |
| 367 | /* XScom region for OCC registers */ |
| 368 | pnv_xscom_region_init(&occ->xscom_regs, OBJECT(dev), poc->xscom_ops, |
| 369 | occ, "xscom-occ", poc->xscom_size); |
| 370 | |
| 371 | /* OCC common area mmio region for OCC SRAM registers */ |
| 372 | memory_region_init_io(&occ->sram_regs, OBJECT(dev), &pnv_occ_sram_ops, |
| 373 | occ, "occ-common-area", |
| 374 | PNV_OCC_SENSOR_DATA_BLOCK_SIZE); |
| 375 | |
| 376 | qdev_init_gpio_out(dev, &occ->psi_irq, 1); |
| 377 | |
| 378 | timer_init_ms(&occ->state_machine_timer, QEMU_CLOCK_VIRTUAL, |
| 379 | occ_state_machine_timer, occ); |
| 380 | timer_mod(&occ->state_machine_timer, OCC_POLL_MS); |
| 381 | } |
| 382 | |
| 383 | static const Property pnv_occ_properties[] = { |
| 384 | DEFINE_PROP_LINK("homer", PnvOCC, homer, TYPE_PNV_HOMER, PnvHomer *), |
| 385 | }; |
| 386 | |
| 387 | static void pnv_occ_class_init(ObjectClass *klass, const void *data) |
| 388 | { |
| 389 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 390 | |
| 391 | dc->realize = pnv_occ_realize; |
| 392 | device_class_set_props(dc, pnv_occ_properties); |
| 393 | dc->user_creatable = false; |
| 394 | } |
| 395 | |
| 396 | static const TypeInfo pnv_occ_type_info = { |
| 397 | .name = TYPE_PNV_OCC, |
| 398 | .parent = TYPE_DEVICE, |
| 399 | .instance_size = sizeof(PnvOCC), |
| 400 | .class_init = pnv_occ_class_init, |
| 401 | .class_size = sizeof(PnvOCCClass), |
| 402 | .abstract = true, |
| 403 | }; |
| 404 | |
| 405 | static void pnv_occ_register_types(void) |
| 406 | { |
| 407 | type_register_static(&pnv_occ_type_info); |
| 408 | type_register_static(&pnv_occ_power8_type_info); |
| 409 | type_register_static(&pnv_occ_power9_type_info); |
| 410 | type_register_static(&pnv_occ_power10_type_info); |
| 411 | } |
| 412 | |
| 413 | type_init(pnv_occ_register_types); |
| 414 | |
| 415 | /* |
| 416 | * From skiboot/hw/occ.c with following changes: |
| 417 | * - tab to space conversion |
| 418 | * - Type conversions u8->uint8_t s8->int8_t __be16->uint16_t etc |
| 419 | * - __packed -> QEMU_PACKED |
| 420 | */ |
| 421 | /* OCC Communication Area for PStates */ |
| 422 | |
| 423 | #define OPAL_DYNAMIC_DATA_OFFSET 0x0B80 |
| 424 | /* relative to HOMER_OPAL_DATA_OFFSET */ |
| 425 | |
| 426 | #define MAX_PSTATES 256 |
| 427 | #define MAX_P8_CORES 12 |
| 428 | #define MAX_P9_CORES 24 |
| 429 | #define MAX_P10_CORES 32 |
| 430 | |
| 431 | #define MAX_OPAL_CMD_DATA_LENGTH 4090 |
| 432 | #define MAX_OCC_RSP_DATA_LENGTH 8698 |
| 433 | |
| 434 | #define P8_PIR_CORE_MASK 0xFFF8 |
| 435 | #define P9_PIR_QUAD_MASK 0xFFF0 |
| 436 | #define P10_PIR_CHIP_MASK 0x0000 |
| 437 | #define FREQ_MAX_IN_DOMAIN 0 |
| 438 | #define FREQ_MOST_RECENTLY_SET 1 |
| 439 | |
| 440 | /** |
| 441 | * OCC-OPAL Shared Memory Region |
| 442 | * |
| 443 | * Reference document : |
| 444 | * https://github.com/open-power/docs/blob/master/occ/OCC_OpenPwr_FW_Interfaces.pdf |
| 445 | * |
| 446 | * Supported layout versions: |
| 447 | * - 0x01, 0x02 : P8 |
| 448 | * https://github.com/open-power/occ/blob/master_p8/src/occ/proc/proc_pstate.h |
| 449 | * |
| 450 | * - 0x90 : P9 |
| 451 | * https://github.com/open-power/occ/blob/master/src/occ_405/proc/proc_pstate.h |
| 452 | * In 0x90 the data is separated into :- |
| 453 | * -- Static Data (struct occ_pstate_table): Data is written once by OCC |
| 454 | * -- Dynamic Data (struct occ_dynamic_data): Data is updated at runtime |
| 455 | * |
| 456 | * struct occ_pstate_table - Pstate table layout |
| 457 | * @valid: Indicates if data is valid |
| 458 | * @version: Layout version [Major/Minor] |
| 459 | * @v2.throttle: Reason for limiting the max pstate |
| 460 | * @v9.occ_role: OCC role (Master/Slave) |
| 461 | * @v#.pstate_min: Minimum pstate ever allowed |
| 462 | * @v#.pstate_nom: Nominal pstate |
| 463 | * @v#.pstate_turbo: Maximum turbo pstate |
| 464 | * @v#.pstate_ultra_turbo: Maximum ultra turbo pstate and the maximum |
| 465 | * pstate ever allowed |
| 466 | * @v#.pstates: Pstate-id and frequency list from Pmax to Pmin |
| 467 | * @v#.pstates.id: Pstate-id |
| 468 | * @v#.pstates.flags: Pstate-flag(reserved) |
| 469 | * @v2.pstates.vdd: Voltage Identifier |
| 470 | * @v2.pstates.vcs: Voltage Identifier |
| 471 | * @v#.pstates.freq_khz: Frequency in KHz |
| 472 | * @v#.core_max[1..N]: Max pstate with N active cores |
| 473 | * @spare/reserved/pad: Unused data |
| 474 | */ |
| 475 | struct occ_pstate_table { |
| 476 | uint8_t valid; |
| 477 | uint8_t version; |
| 478 | union QEMU_PACKED { |
| 479 | struct QEMU_PACKED { /* Version 0x01 and 0x02 */ |
| 480 | uint8_t throttle; |
| 481 | int8_t pstate_min; |
| 482 | int8_t pstate_nom; |
| 483 | int8_t pstate_turbo; |
| 484 | int8_t pstate_ultra_turbo; |
| 485 | uint8_t spare; |
| 486 | uint64_t reserved; |
| 487 | struct QEMU_PACKED { |
| 488 | int8_t id; |
| 489 | uint8_t flags; |
| 490 | uint8_t vdd; |
| 491 | uint8_t vcs; |
| 492 | uint32_t freq_khz; |
| 493 | } pstates[MAX_PSTATES]; |
| 494 | int8_t core_max[MAX_P8_CORES]; |
| 495 | uint8_t pad[100]; |
| 496 | } v2; |
| 497 | struct QEMU_PACKED { /* Version 0x90 */ |
| 498 | uint8_t occ_role; |
| 499 | uint8_t pstate_min; |
| 500 | uint8_t pstate_nom; |
| 501 | uint8_t pstate_turbo; |
| 502 | uint8_t pstate_ultra_turbo; |
| 503 | uint8_t spare; |
| 504 | uint64_t reserved1; |
| 505 | uint64_t reserved2; |
| 506 | struct QEMU_PACKED { |
| 507 | uint8_t id; |
| 508 | uint8_t flags; |
| 509 | uint16_t reserved; |
| 510 | uint32_t freq_khz; |
| 511 | } pstates[MAX_PSTATES]; |
| 512 | uint8_t core_max[MAX_P9_CORES]; |
| 513 | uint8_t pad[56]; |
| 514 | } v9; |
| 515 | struct QEMU_PACKED { /* Version 0xA0 */ |
| 516 | uint8_t occ_role; |
| 517 | uint8_t pstate_min; |
| 518 | uint8_t pstate_fixed_freq; |
| 519 | uint8_t pstate_base; |
| 520 | uint8_t pstate_ultra_turbo; |
| 521 | uint8_t pstate_fmax; |
| 522 | uint8_t minor; |
| 523 | uint8_t pstate_bottom_throttle; |
| 524 | uint8_t spare; |
| 525 | uint8_t spare1; |
| 526 | uint32_t reserved_32; |
| 527 | uint64_t reserved_64; |
| 528 | struct QEMU_PACKED { |
| 529 | uint8_t id; |
| 530 | uint8_t valid; |
| 531 | uint16_t reserved; |
| 532 | uint32_t freq_khz; |
| 533 | } pstates[MAX_PSTATES]; |
| 534 | uint8_t core_max[MAX_P10_CORES]; |
| 535 | uint8_t pad[48]; |
| 536 | } v10; |
| 537 | }; |
| 538 | } QEMU_PACKED; |
| 539 | |
| 540 | /** |
| 541 | * OPAL-OCC Command Response Interface |
| 542 | * |
| 543 | * OPAL-OCC Command Buffer |
| 544 | * |
| 545 | * --------------------------------------------------------------------- |
| 546 | * | OPAL | Cmd | OPAL | | Cmd Data | Cmd Data | OPAL | |
| 547 | * | Cmd | Request | OCC | Reserved | Length | Length | Cmd | |
| 548 | * | Flags | ID | Cmd | | (MSB) | (LSB) | Data... | |
| 549 | * --------------------------------------------------------------------- |
| 550 | * | ….OPAL Command Data up to max of Cmd Data Length 4090 bytes | |
| 551 | * | | |
| 552 | * --------------------------------------------------------------------- |
| 553 | * |
| 554 | * OPAL Command Flag |
| 555 | * |
| 556 | * ----------------------------------------------------------------- |
| 557 | * | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | |
| 558 | * | (msb) | | | | | | | (lsb) | |
| 559 | * ----------------------------------------------------------------- |
| 560 | * |Cmd | | | | | | | | |
| 561 | * |Ready | | | | | | | | |
| 562 | * ----------------------------------------------------------------- |
| 563 | * |
| 564 | * struct opal_command_buffer - Defines the layout of OPAL command buffer |
| 565 | * @flag: Provides general status of the command |
| 566 | * @request_id: Token to identify request |
| 567 | * @cmd: Command sent |
| 568 | * @data_size: Command data length |
| 569 | * @data: Command specific data |
| 570 | * @spare: Unused byte |
| 571 | */ |
| 572 | struct opal_command_buffer { |
| 573 | uint8_t flag; |
| 574 | uint8_t request_id; |
| 575 | uint8_t cmd; |
| 576 | uint8_t spare; |
| 577 | uint16_t data_size; |
| 578 | uint8_t data[MAX_OPAL_CMD_DATA_LENGTH]; |
| 579 | } QEMU_PACKED; |
| 580 | |
| 581 | /** |
| 582 | * OPAL-OCC Response Buffer |
| 583 | * |
| 584 | * --------------------------------------------------------------------- |
| 585 | * | OCC | Cmd | OPAL | Response | Rsp Data | Rsp Data | OPAL | |
| 586 | * | Rsp | Request | OCC | Status | Length | Length | Rsp | |
| 587 | * | Flags | ID | Cmd | | (MSB) | (LSB) | Data... | |
| 588 | * --------------------------------------------------------------------- |
| 589 | * | ….OPAL Response Data up to max of Rsp Data Length 8698 bytes | |
| 590 | * | | |
| 591 | * --------------------------------------------------------------------- |
| 592 | * |
| 593 | * OCC Response Flag |
| 594 | * |
| 595 | * ----------------------------------------------------------------- |
| 596 | * | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | |
| 597 | * | (msb) | | | | | | | (lsb) | |
| 598 | * ----------------------------------------------------------------- |
| 599 | * | | | | | | |OCC in | Rsp | |
| 600 | * | | | | | | |progress|Ready | |
| 601 | * ----------------------------------------------------------------- |
| 602 | * |
| 603 | * struct occ_response_buffer - Defines the layout of OCC response buffer |
| 604 | * @flag: Provides general status of the response |
| 605 | * @request_id: Token to identify request |
| 606 | * @cmd: Command requested |
| 607 | * @status: Indicates success/failure status of |
| 608 | * the command |
| 609 | * @data_size: Response data length |
| 610 | * @data: Response specific data |
| 611 | */ |
| 612 | struct occ_response_buffer { |
| 613 | uint8_t flag; |
| 614 | uint8_t request_id; |
| 615 | uint8_t cmd; |
| 616 | uint8_t status; |
| 617 | uint16_t data_size; |
| 618 | uint8_t data[MAX_OCC_RSP_DATA_LENGTH]; |
| 619 | } QEMU_PACKED; |
| 620 | |
| 621 | /** |
| 622 | * OCC-OPAL Shared Memory Interface Dynamic Data Vx90 |
| 623 | * |
| 624 | * struct occ_dynamic_data - Contains runtime attributes |
| 625 | * @occ_state: Current state of OCC |
| 626 | * @major_version: Major version number |
| 627 | * @minor_version: Minor version number (backwards compatible) |
| 628 | * Version 1 indicates GPU presence populated |
| 629 | * @gpus_present: Bitmask of GPUs present (on systems where GPU |
| 630 | * presence is detected through APSS) |
| 631 | * @cpu_throttle: Reason for limiting the max pstate |
| 632 | * @mem_throttle: Reason for throttling memory |
| 633 | * @quick_pwr_drop: Indicates if QPD is asserted |
| 634 | * @pwr_shifting_ratio: Indicates the current percentage of power to |
| 635 | * take away from the CPU vs GPU when shifting |
| 636 | * power to maintain a power cap. Value of 100 |
| 637 | * means take all power from CPU. |
| 638 | * @pwr_cap_type: Indicates type of power cap in effect |
| 639 | * @hard_min_pwr_cap: Hard minimum system power cap in Watts. |
| 640 | * Guaranteed unless hardware failure |
| 641 | * @max_pwr_cap: Maximum allowed system power cap in Watts |
| 642 | * @cur_pwr_cap: Current system power cap |
| 643 | * @soft_min_pwr_cap: Soft powercap minimum. OCC may or may not be |
| 644 | * able to maintain this |
| 645 | * @spare/reserved: Unused data |
| 646 | * @cmd: Opal Command Buffer |
| 647 | * @rsp: OCC Response Buffer |
| 648 | */ |
| 649 | struct occ_dynamic_data { |
| 650 | uint8_t occ_state; |
| 651 | uint8_t major_version; |
| 652 | uint8_t minor_version; |
| 653 | uint8_t gpus_present; |
| 654 | union QEMU_PACKED { |
| 655 | struct QEMU_PACKED { /* Version 0x90 */ |
| 656 | uint8_t spare1; |
| 657 | } v9; |
| 658 | struct QEMU_PACKED { /* Version 0xA0 */ |
| 659 | uint8_t wof_enabled; |
| 660 | } v10; |
| 661 | }; |
| 662 | uint8_t cpu_throttle; |
| 663 | uint8_t mem_throttle; |
| 664 | uint8_t quick_pwr_drop; |
| 665 | uint8_t pwr_shifting_ratio; |
| 666 | uint8_t pwr_cap_type; |
| 667 | uint16_t hard_min_pwr_cap; |
| 668 | uint16_t max_pwr_cap; |
| 669 | uint16_t cur_pwr_cap; |
| 670 | uint16_t soft_min_pwr_cap; |
| 671 | uint8_t pad[110]; |
| 672 | struct opal_command_buffer cmd; |
| 673 | struct occ_response_buffer rsp; |
| 674 | } QEMU_PACKED; |
| 675 | |
| 676 | enum occ_response_status { |
| 677 | OCC_RSP_SUCCESS = 0x00, |
| 678 | OCC_RSP_INVALID_COMMAND = 0x11, |
| 679 | OCC_RSP_INVALID_CMD_DATA_LENGTH = 0x12, |
| 680 | OCC_RSP_INVALID_DATA = 0x13, |
| 681 | OCC_RSP_INTERNAL_ERROR = 0x15, |
| 682 | }; |
| 683 | |
| 684 | #define OCC_ROLE_SLAVE 0x00 |
| 685 | #define OCC_ROLE_MASTER 0x01 |
| 686 | |
| 687 | #define OCC_FLAG_RSP_READY 0x01 |
| 688 | #define OCC_FLAG_CMD_IN_PROGRESS 0x02 |
| 689 | #define OPAL_FLAG_CMD_READY 0x80 |
| 690 | |
| 691 | #define PCAP_MAX_POWER_W 100 |
| 692 | #define PCAP_SOFT_MIN_POWER_W 20 |
| 693 | #define PCAP_HARD_MIN_POWER_W 10 |
| 694 | |
| 695 | static bool occ_write_static_data(PnvOCC *occ, |
| 696 | struct occ_pstate_table *static_data, |
| 697 | Error **errp) |
| 698 | { |
| 699 | PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
| 700 | PnvHomer *homer = occ->homer; |
| 701 | hwaddr static_addr = homer->base + poc->opal_shared_memory_offset; |
| 702 | MemTxResult ret; |
| 703 | |
| 704 | ret = address_space_write(&address_space_memory, static_addr, |
| 705 | MEMTXATTRS_UNSPECIFIED, static_data, |
| 706 | sizeof(*static_data)); |
| 707 | if (ret != MEMTX_OK) { |
| 708 | error_setg(errp, "OCC: cannot write OCC-OPAL static data"); |
| 709 | return false; |
| 710 | } |
| 711 | |
| 712 | return true; |
| 713 | } |
| 714 | |
| 715 | static bool occ_read_dynamic_data(PnvOCC *occ, |
| 716 | struct occ_dynamic_data *dynamic_data, |
| 717 | Error **errp) |
| 718 | { |
| 719 | PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
| 720 | PnvHomer *homer = occ->homer; |
| 721 | hwaddr static_addr = homer->base + poc->opal_shared_memory_offset; |
| 722 | hwaddr dynamic_addr = static_addr + OPAL_DYNAMIC_DATA_OFFSET; |
| 723 | MemTxResult ret; |
| 724 | |
| 725 | ret = address_space_read(&address_space_memory, dynamic_addr, |
| 726 | MEMTXATTRS_UNSPECIFIED, dynamic_data, |
| 727 | sizeof(*dynamic_data)); |
| 728 | if (ret != MEMTX_OK) { |
| 729 | error_setg(errp, "OCC: cannot read OCC-OPAL dynamic data"); |
| 730 | return false; |
| 731 | } |
| 732 | |
| 733 | return true; |
| 734 | } |
| 735 | |
| 736 | static bool occ_write_dynamic_data(PnvOCC *occ, |
| 737 | struct occ_dynamic_data *dynamic_data, |
| 738 | Error **errp) |
| 739 | { |
| 740 | PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
| 741 | PnvHomer *homer = occ->homer; |
| 742 | hwaddr static_addr = homer->base + poc->opal_shared_memory_offset; |
| 743 | hwaddr dynamic_addr = static_addr + OPAL_DYNAMIC_DATA_OFFSET; |
| 744 | MemTxResult ret; |
| 745 | |
| 746 | ret = address_space_write(&address_space_memory, dynamic_addr, |
| 747 | MEMTXATTRS_UNSPECIFIED, dynamic_data, |
| 748 | sizeof(*dynamic_data)); |
| 749 | if (ret != MEMTX_OK) { |
| 750 | error_setg(errp, "OCC: cannot write OCC-OPAL dynamic data"); |
| 751 | return false; |
| 752 | } |
| 753 | |
| 754 | return true; |
| 755 | } |
| 756 | |
| 757 | static bool occ_opal_send_response(PnvOCC *occ, |
| 758 | struct occ_dynamic_data *dynamic_data, |
| 759 | enum occ_response_status status, |
| 760 | uint8_t *data, uint16_t datalen) |
| 761 | { |
| 762 | struct opal_command_buffer *cmd = &dynamic_data->cmd; |
| 763 | struct occ_response_buffer *rsp = &dynamic_data->rsp; |
| 764 | |
| 765 | rsp->request_id = cmd->request_id; |
| 766 | rsp->cmd = cmd->cmd; |
| 767 | rsp->status = status; |
| 768 | rsp->data_size = cpu_to_be16(datalen); |
| 769 | if (datalen) { |
| 770 | memcpy(rsp->data, data, datalen); |
| 771 | } |
| 772 | if (!occ_write_dynamic_data(occ, dynamic_data, NULL)) { |
| 773 | return false; |
| 774 | } |
| 775 | /* Would be a memory barrier here */ |
| 776 | rsp->flag = OCC_FLAG_RSP_READY; |
| 777 | cmd->flag = 0; |
| 778 | if (!occ_write_dynamic_data(occ, dynamic_data, NULL)) { |
| 779 | return false; |
| 780 | } |
| 781 | |
| 782 | pnv_occ_raise_msg_irq(occ); |
| 783 | |
| 784 | return true; |
| 785 | } |
| 786 | |
| 787 | /* Returns error status */ |
| 788 | static bool occ_opal_process_command(PnvOCC *occ, |
| 789 | struct occ_dynamic_data *dynamic_data) |
| 790 | { |
| 791 | struct opal_command_buffer *cmd = &dynamic_data->cmd; |
| 792 | struct occ_response_buffer *rsp = &dynamic_data->rsp; |
| 793 | |
| 794 | if (rsp->flag == 0) { |
| 795 | /* Spend one "tick" in the in-progress state */ |
| 796 | rsp->flag = OCC_FLAG_CMD_IN_PROGRESS; |
| 797 | return occ_write_dynamic_data(occ, dynamic_data, NULL); |
| 798 | } else if (rsp->flag != OCC_FLAG_CMD_IN_PROGRESS) { |
| 799 | return occ_opal_send_response(occ, dynamic_data, |
| 800 | OCC_RSP_INTERNAL_ERROR, |
| 801 | NULL, 0); |
| 802 | } |
| 803 | |
| 804 | switch (cmd->cmd) { |
| 805 | case 0xD1: { /* SET_POWER_CAP */ |
| 806 | uint16_t data; |
| 807 | if (be16_to_cpu(cmd->data_size) != 2) { |
| 808 | return occ_opal_send_response(occ, dynamic_data, |
| 809 | OCC_RSP_INVALID_CMD_DATA_LENGTH, |
| 810 | (uint8_t *)&dynamic_data->cur_pwr_cap, |
| 811 | 2); |
| 812 | } |
| 813 | data = be16_to_cpu(*(uint16_t *)cmd->data); |
| 814 | if (data == 0) { /* clear power cap */ |
| 815 | dynamic_data->pwr_cap_type = 0x00; /* none */ |
| 816 | data = PCAP_MAX_POWER_W; |
| 817 | } else { |
| 818 | dynamic_data->pwr_cap_type = 0x02; /* user set in-band */ |
| 819 | if (data < PCAP_HARD_MIN_POWER_W) { |
| 820 | data = PCAP_HARD_MIN_POWER_W; |
| 821 | } else if (data > PCAP_MAX_POWER_W) { |
| 822 | data = PCAP_MAX_POWER_W; |
| 823 | } |
| 824 | } |
| 825 | dynamic_data->cur_pwr_cap = cpu_to_be16(data); |
| 826 | return occ_opal_send_response(occ, dynamic_data, |
| 827 | OCC_RSP_SUCCESS, |
| 828 | (uint8_t *)&dynamic_data->cur_pwr_cap, 2); |
| 829 | } |
| 830 | |
| 831 | default: |
| 832 | return occ_opal_send_response(occ, dynamic_data, |
| 833 | OCC_RSP_INVALID_COMMAND, |
| 834 | NULL, 0); |
| 835 | } |
| 836 | g_assert_not_reached(); |
| 837 | } |
| 838 | |
| 839 | static bool occ_model_tick(PnvOCC *occ) |
| 840 | { |
| 841 | QEMU_UNINITIALIZED struct occ_dynamic_data dynamic_data; |
| 842 | |
| 843 | if (!occ_read_dynamic_data(occ, &dynamic_data, NULL)) { |
| 844 | /* Can't move OCC state field to safe because we can't map it! */ |
| 845 | qemu_log("OCC: failed to read HOMER data, shutting down OCC\n"); |
| 846 | return false; |
| 847 | } |
| 848 | if (dynamic_data.cmd.flag == OPAL_FLAG_CMD_READY) { |
| 849 | if (!occ_opal_process_command(occ, &dynamic_data)) { |
| 850 | qemu_log("OCC: failed to write HOMER data, shutting down OCC\n"); |
| 851 | return false; |
| 852 | } |
| 853 | } |
| 854 | |
| 855 | return true; |
| 856 | } |
| 857 | |
| 858 | static bool occ_init_homer_memory(PnvOCC *occ, Error **errp) |
| 859 | { |
| 860 | PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
| 861 | PnvHomer *homer = occ->homer; |
| 862 | PnvChip *chip = homer->chip; |
| 863 | struct occ_pstate_table static_data; |
| 864 | struct occ_dynamic_data dynamic_data; |
| 865 | int i; |
| 866 | |
| 867 | memset(&static_data, 0, sizeof(static_data)); |
| 868 | static_data.valid = 1; |
| 869 | static_data.version = poc->opal_shared_memory_version; |
| 870 | switch (poc->opal_shared_memory_version) { |
| 871 | case 0x02: |
| 872 | static_data.v2.throttle = 0; |
| 873 | static_data.v2.pstate_min = -2; |
| 874 | static_data.v2.pstate_nom = -1; |
| 875 | static_data.v2.pstate_turbo = -1; |
| 876 | static_data.v2.pstate_ultra_turbo = 0; |
| 877 | static_data.v2.pstates[0].id = 0; |
| 878 | static_data.v2.pstates[1].freq_khz = cpu_to_be32(4000000); |
| 879 | static_data.v2.pstates[1].id = -1; |
| 880 | static_data.v2.pstates[1].freq_khz = cpu_to_be32(3000000); |
| 881 | static_data.v2.pstates[2].id = -2; |
| 882 | static_data.v2.pstates[2].freq_khz = cpu_to_be32(2000000); |
| 883 | for (i = 0; i < chip->nr_cores; i++) { |
| 884 | static_data.v2.core_max[i] = 1; |
| 885 | } |
| 886 | break; |
| 887 | case 0x90: |
| 888 | if (chip->chip_id == 0) { |
| 889 | static_data.v9.occ_role = OCC_ROLE_MASTER; |
| 890 | } else { |
| 891 | static_data.v9.occ_role = OCC_ROLE_SLAVE; |
| 892 | } |
| 893 | static_data.v9.pstate_min = 2; |
| 894 | static_data.v9.pstate_nom = 1; |
| 895 | static_data.v9.pstate_turbo = 1; |
| 896 | static_data.v9.pstate_ultra_turbo = 0; |
| 897 | static_data.v9.pstates[0].id = 0; |
| 898 | static_data.v9.pstates[0].freq_khz = cpu_to_be32(4000000); |
| 899 | static_data.v9.pstates[1].id = 1; |
| 900 | static_data.v9.pstates[1].freq_khz = cpu_to_be32(3000000); |
| 901 | static_data.v9.pstates[2].id = 2; |
| 902 | static_data.v9.pstates[2].freq_khz = cpu_to_be32(2000000); |
| 903 | for (i = 0; i < chip->nr_cores; i++) { |
| 904 | static_data.v9.core_max[i] = 1; |
| 905 | } |
| 906 | break; |
| 907 | case 0xA0: |
| 908 | if (chip->chip_id == 0) { |
| 909 | static_data.v10.occ_role = OCC_ROLE_MASTER; |
| 910 | } else { |
| 911 | static_data.v10.occ_role = OCC_ROLE_SLAVE; |
| 912 | } |
| 913 | static_data.v10.pstate_min = 4; |
| 914 | static_data.v10.pstate_fixed_freq = 3; |
| 915 | static_data.v10.pstate_base = 2; |
| 916 | static_data.v10.pstate_ultra_turbo = 0; |
| 917 | static_data.v10.pstate_fmax = 1; |
| 918 | static_data.v10.minor = 0x01; |
| 919 | static_data.v10.pstates[0].valid = 1; |
| 920 | static_data.v10.pstates[0].id = 0; |
| 921 | static_data.v10.pstates[0].freq_khz = cpu_to_be32(4200000); |
| 922 | static_data.v10.pstates[1].valid = 1; |
| 923 | static_data.v10.pstates[1].id = 1; |
| 924 | static_data.v10.pstates[1].freq_khz = cpu_to_be32(4000000); |
| 925 | static_data.v10.pstates[2].valid = 1; |
| 926 | static_data.v10.pstates[2].id = 2; |
| 927 | static_data.v10.pstates[2].freq_khz = cpu_to_be32(3800000); |
| 928 | static_data.v10.pstates[3].valid = 1; |
| 929 | static_data.v10.pstates[3].id = 3; |
| 930 | static_data.v10.pstates[3].freq_khz = cpu_to_be32(3000000); |
| 931 | static_data.v10.pstates[4].valid = 1; |
| 932 | static_data.v10.pstates[4].id = 4; |
| 933 | static_data.v10.pstates[4].freq_khz = cpu_to_be32(2000000); |
| 934 | for (i = 0; i < chip->nr_cores; i++) { |
| 935 | static_data.v10.core_max[i] = 1; |
| 936 | } |
| 937 | break; |
| 938 | default: |
| 939 | g_assert_not_reached(); |
| 940 | } |
| 941 | if (!occ_write_static_data(occ, &static_data, errp)) { |
| 942 | return false; |
| 943 | } |
| 944 | |
| 945 | memset(&dynamic_data, 0, sizeof(dynamic_data)); |
| 946 | dynamic_data.occ_state = 0x3; /* active */ |
| 947 | dynamic_data.major_version = 0x0; |
| 948 | dynamic_data.hard_min_pwr_cap = cpu_to_be16(PCAP_HARD_MIN_POWER_W); |
| 949 | dynamic_data.max_pwr_cap = cpu_to_be16(PCAP_MAX_POWER_W); |
| 950 | dynamic_data.cur_pwr_cap = cpu_to_be16(PCAP_MAX_POWER_W); |
| 951 | dynamic_data.soft_min_pwr_cap = cpu_to_be16(PCAP_SOFT_MIN_POWER_W); |
| 952 | switch (poc->opal_shared_memory_version) { |
| 953 | case 0xA0: |
| 954 | dynamic_data.minor_version = 0x1; |
| 955 | dynamic_data.v10.wof_enabled = 0x1; |
| 956 | break; |
| 957 | case 0x90: |
| 958 | dynamic_data.minor_version = 0x1; |
| 959 | break; |
| 960 | case 0x02: |
| 961 | dynamic_data.minor_version = 0x0; |
| 962 | break; |
| 963 | default: |
| 964 | g_assert_not_reached(); |
| 965 | } |
| 966 | if (!occ_write_dynamic_data(occ, &dynamic_data, errp)) { |
| 967 | return false; |
| 968 | } |
| 969 | |
| 970 | return true; |
| 971 | } |