| 1 | /* |
| 2 | * QEMU PowerPC PowerNV Emulation of some SBE behaviour |
| 3 | * |
| 4 | * Copyright (c) 2022, 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_xscom.h" |
| 28 | #include "hw/ppc/pnv_sbe.h" |
| 29 | #include "hw/ppc/pnv_mpipl.h" |
| 30 | #include "system/cpus.h" |
| 31 | #include "system/runstate.h" |
| 32 | #include "trace.h" |
| 33 | |
| 34 | /* |
| 35 | * Most register and command definitions come from skiboot. |
| 36 | * |
| 37 | * xscom addresses are adjusted to be relative to xscom subregion bases |
| 38 | */ |
| 39 | |
| 40 | /* |
| 41 | * SBE MBOX register address |
| 42 | * Reg 0 - 3 : Host to send command packets to SBE |
| 43 | * Reg 4 - 7 : SBE to send response packets to Host |
| 44 | */ |
| 45 | #define PSU_HOST_SBE_MBOX_REG0 0x00000000 |
| 46 | #define PSU_HOST_SBE_MBOX_REG1 0x00000001 |
| 47 | #define PSU_HOST_SBE_MBOX_REG2 0x00000002 |
| 48 | #define PSU_HOST_SBE_MBOX_REG3 0x00000003 |
| 49 | #define PSU_HOST_SBE_MBOX_REG4 0x00000004 |
| 50 | #define PSU_HOST_SBE_MBOX_REG5 0x00000005 |
| 51 | #define PSU_HOST_SBE_MBOX_REG6 0x00000006 |
| 52 | #define PSU_HOST_SBE_MBOX_REG7 0x00000007 |
| 53 | #define PSU_SBE_DOORBELL_REG_RW 0x00000010 |
| 54 | #define PSU_SBE_DOORBELL_REG_AND 0x00000011 |
| 55 | #define PSU_SBE_DOORBELL_REG_OR 0x00000012 |
| 56 | #define PSU_HOST_DOORBELL_REG_RW 0x00000013 |
| 57 | #define PSU_HOST_DOORBELL_REG_AND 0x00000014 |
| 58 | #define PSU_HOST_DOORBELL_REG_OR 0x00000015 |
| 59 | |
| 60 | /* |
| 61 | * Doorbell register to trigger SBE interrupt. Set by OPAL to inform |
| 62 | * the SBE about a waiting message in the Host/SBE mailbox registers |
| 63 | */ |
| 64 | #define HOST_SBE_MSG_WAITING PPC_BIT(0) |
| 65 | |
| 66 | /* |
| 67 | * Doorbell register for host bridge interrupt. Set by the SBE to inform |
| 68 | * host about a response message in the Host/SBE mailbox registers |
| 69 | */ |
| 70 | #define SBE_HOST_RESPONSE_WAITING PPC_BIT(0) |
| 71 | #define SBE_HOST_MSG_READ PPC_BIT(1) |
| 72 | #define SBE_HOST_STOP15_EXIT PPC_BIT(2) |
| 73 | #define SBE_HOST_RESET PPC_BIT(3) |
| 74 | #define SBE_HOST_PASSTHROUGH PPC_BIT(4) |
| 75 | #define SBE_HOST_TIMER_EXPIRY PPC_BIT(14) |
| 76 | #define SBE_HOST_RESPONSE_MASK (PPC_BITMASK(0, 4) | \ |
| 77 | SBE_HOST_TIMER_EXPIRY) |
| 78 | |
| 79 | /* SBE Control Register */ |
| 80 | #define SBE_CONTROL_REG_RW 0x00000000 |
| 81 | |
| 82 | /* SBE interrupt s0/s1 bits */ |
| 83 | #define SBE_CONTROL_REG_S0 PPC_BIT(14) |
| 84 | #define SBE_CONTROL_REG_S1 PPC_BIT(15) |
| 85 | |
| 86 | static void pnv_sbe_set_host_doorbell(PnvSBE *sbe, uint64_t val) |
| 87 | { |
| 88 | val &= SBE_HOST_RESPONSE_MASK; /* Is this right? What does HW do? */ |
| 89 | sbe->host_doorbell = val; |
| 90 | |
| 91 | trace_pnv_sbe_reg_set_host_doorbell(val); |
| 92 | qemu_set_irq(sbe->psi_irq, !!val); |
| 93 | } |
| 94 | |
| 95 | struct sbe_msg { |
| 96 | uint64_t reg[4]; |
| 97 | }; |
| 98 | |
| 99 | static uint64_t pnv_sbe_power9_xscom_ctrl_read(void *opaque, hwaddr addr, |
| 100 | unsigned size) |
| 101 | { |
| 102 | uint32_t offset = addr >> 3; |
| 103 | uint64_t val = 0; |
| 104 | |
| 105 | switch (offset) { |
| 106 | default: |
| 107 | qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" |
| 108 | HWADDR_PRIx "\n", addr >> 3); |
| 109 | } |
| 110 | |
| 111 | trace_pnv_sbe_xscom_ctrl_read(addr, val); |
| 112 | |
| 113 | return val; |
| 114 | } |
| 115 | |
| 116 | static void pnv_sbe_power9_xscom_ctrl_write(void *opaque, hwaddr addr, |
| 117 | uint64_t val, unsigned size) |
| 118 | { |
| 119 | PnvMachineState *pnv = PNV_MACHINE(qdev_get_machine()); |
| 120 | PnvSBE *sbe = opaque; |
| 121 | uint32_t offset = addr >> 3; |
| 122 | |
| 123 | trace_pnv_sbe_xscom_ctrl_write(addr, val); |
| 124 | |
| 125 | switch (offset) { |
| 126 | case SBE_CONTROL_REG_RW: |
| 127 | switch (val) { |
| 128 | case SBE_CONTROL_REG_S0: |
| 129 | qemu_log_mask(LOG_UNIMP, "SBE: S0 Interrupt triggered\n"); |
| 130 | |
| 131 | pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell | SBE_HOST_RESPONSE_MASK); |
| 132 | |
| 133 | /* Preserve memory regions and CPU state, if MPIPL is registered */ |
| 134 | do_mpipl_preserve(pnv); |
| 135 | |
| 136 | /* |
| 137 | * Control may not come back here as 'do_mpipl_preserve' triggers |
| 138 | * a guest reboot |
| 139 | */ |
| 140 | break; |
| 141 | case SBE_CONTROL_REG_S1: |
| 142 | qemu_log_mask(LOG_UNIMP, "SBE: S1 Interrupt triggered\n"); |
| 143 | break; |
| 144 | default: |
| 145 | qemu_log_mask(LOG_UNIMP, |
| 146 | "SBE: CONTROL_REG_RW: Unknown value: Ox%." |
| 147 | HWADDR_PRIx "\n", val); |
| 148 | } |
| 149 | break; |
| 150 | default: |
| 151 | qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" |
| 152 | HWADDR_PRIx "\n", addr >> 3); |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | static const MemoryRegionOps pnv_sbe_power9_xscom_ctrl_ops = { |
| 157 | .read = pnv_sbe_power9_xscom_ctrl_read, |
| 158 | .write = pnv_sbe_power9_xscom_ctrl_write, |
| 159 | .valid.min_access_size = 8, |
| 160 | .valid.max_access_size = 8, |
| 161 | .impl.min_access_size = 8, |
| 162 | .impl.max_access_size = 8, |
| 163 | .endianness = DEVICE_BIG_ENDIAN, |
| 164 | }; |
| 165 | |
| 166 | /* SBE Target Type */ |
| 167 | #define SBE_TARGET_TYPE_PROC 0x00 |
| 168 | #define SBE_TARGET_TYPE_EX 0x01 |
| 169 | #define SBE_TARGET_TYPE_PERV 0x02 |
| 170 | #define SBE_TARGET_TYPE_MCS 0x03 |
| 171 | #define SBE_TARGET_TYPE_EQ 0x04 |
| 172 | #define SBE_TARGET_TYPE_CORE 0x05 |
| 173 | |
| 174 | /* SBE MBOX command class */ |
| 175 | #define SBE_MCLASS_FIRST 0xD1 |
| 176 | #define SBE_MCLASS_CORE_STATE 0xD1 |
| 177 | #define SBE_MCLASS_SCOM 0xD2 |
| 178 | #define SBE_MCLASS_RING 0xD3 |
| 179 | #define SBE_MCLASS_TIMER 0xD4 |
| 180 | #define SBE_MCLASS_MPIPL 0xD5 |
| 181 | #define SBE_MCLASS_SECURITY 0xD6 |
| 182 | #define SBE_MCLASS_GENERIC 0xD7 |
| 183 | #define SBE_MCLASS_LAST 0xD7 |
| 184 | |
| 185 | /* |
| 186 | * Commands are provided in xxyy form where: |
| 187 | * - xx : command class |
| 188 | * - yy : command |
| 189 | * |
| 190 | * Both request and response message uses same seq ID, |
| 191 | * command class and command. |
| 192 | */ |
| 193 | #define SBE_CMD_CTRL_DEADMAN_LOOP 0xD101 |
| 194 | #define SBE_CMD_MULTI_SCOM 0xD201 |
| 195 | #define SBE_CMD_PUT_RING_FORM_IMAGE 0xD301 |
| 196 | #define SBE_CMD_CONTROL_TIMER 0xD401 |
| 197 | #define SBE_CMD_GET_ARCHITECTED_REG 0xD501 |
| 198 | #define SBE_CMD_CLR_ARCHITECTED_REG 0xD502 |
| 199 | #define SBE_CMD_SET_UNSEC_MEM_WINDOW 0xD601 |
| 200 | #define SBE_CMD_GET_SBE_FFDC 0xD701 |
| 201 | #define SBE_CMD_GET_CAPABILITY 0xD702 |
| 202 | #define SBE_CMD_READ_SBE_SEEPROM 0xD703 |
| 203 | #define SBE_CMD_SET_FFDC_ADDR 0xD704 |
| 204 | #define SBE_CMD_QUIESCE_SBE 0xD705 |
| 205 | #define SBE_CMD_SET_FABRIC_ID_MAP 0xD706 |
| 206 | #define SBE_CMD_STASH_MPIPL_CONFIG 0xD707 |
| 207 | |
| 208 | /* SBE MBOX control flags */ |
| 209 | |
| 210 | /* Generic flags */ |
| 211 | #define SBE_CMD_CTRL_RESP_REQ 0x0100 |
| 212 | #define SBE_CMD_CTRL_ACK_REQ 0x0200 |
| 213 | |
| 214 | /* Deadman loop */ |
| 215 | #define CTRL_DEADMAN_LOOP_START 0x0001 |
| 216 | #define CTRL_DEADMAN_LOOP_STOP 0x0002 |
| 217 | |
| 218 | /* Control timer */ |
| 219 | #define CONTROL_TIMER_START 0x0001 |
| 220 | #define CONTROL_TIMER_STOP 0x0002 |
| 221 | |
| 222 | /* Stash MPIPL config */ |
| 223 | #define SBE_STASH_KEY_SKIBOOT_BASE 0x03 |
| 224 | |
| 225 | static void sbe_timer(void *opaque) |
| 226 | { |
| 227 | PnvSBE *sbe = opaque; |
| 228 | |
| 229 | trace_pnv_sbe_cmd_timer_expired(); |
| 230 | |
| 231 | pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell | SBE_HOST_TIMER_EXPIRY); |
| 232 | } |
| 233 | |
| 234 | static void do_sbe_msg(PnvSBE *sbe) |
| 235 | { |
| 236 | PnvMachineState *pnv = PNV_MACHINE(qdev_get_machine()); |
| 237 | MachineState *machine = MACHINE(pnv); |
| 238 | struct sbe_msg msg; |
| 239 | uint16_t cmd, ctrl_flags, seq_id; |
| 240 | uint64_t mbox_val; |
| 241 | int i; |
| 242 | |
| 243 | memset(&msg, 0, sizeof(msg)); |
| 244 | |
| 245 | for (i = 0; i < 4; i++) { |
| 246 | msg.reg[i] = sbe->mbox[i]; |
| 247 | } |
| 248 | |
| 249 | cmd = msg.reg[0]; |
| 250 | seq_id = msg.reg[0] >> 16; |
| 251 | ctrl_flags = msg.reg[0] >> 32; |
| 252 | |
| 253 | trace_pnv_sbe_msg_recv(cmd, seq_id, ctrl_flags); |
| 254 | |
| 255 | if (ctrl_flags & SBE_CMD_CTRL_ACK_REQ) { |
| 256 | pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell | SBE_HOST_MSG_READ); |
| 257 | } |
| 258 | |
| 259 | switch (cmd) { |
| 260 | case SBE_CMD_CONTROL_TIMER: |
| 261 | if (ctrl_flags & CONTROL_TIMER_START) { |
| 262 | uint64_t us = msg.reg[1]; |
| 263 | trace_pnv_sbe_cmd_timer_start(us); |
| 264 | timer_mod(sbe->timer, qemu_clock_get_us(QEMU_CLOCK_VIRTUAL) + us); |
| 265 | } |
| 266 | if (ctrl_flags & CONTROL_TIMER_STOP) { |
| 267 | trace_pnv_sbe_cmd_timer_stop(); |
| 268 | timer_del(sbe->timer); |
| 269 | } |
| 270 | break; |
| 271 | case SBE_CMD_STASH_MPIPL_CONFIG: |
| 272 | /* key = sbe->mbox[1] */ |
| 273 | switch (sbe->mbox[1]) { |
| 274 | case SBE_STASH_KEY_SKIBOOT_BASE: |
| 275 | mbox_val = sbe->mbox[2]; |
| 276 | if (mbox_val >= machine->ram_size) { |
| 277 | qemu_log_mask(LOG_GUEST_ERROR, |
| 278 | "SBE: skiboot_base 0x%" PRIx64 \ |
| 279 | "exceeds RAM size 0x" RAM_ADDR_FMT "\n", |
| 280 | mbox_val, machine->ram_size); |
| 281 | return; |
| 282 | } |
| 283 | |
| 284 | pnv->mpipl_state.skiboot_base = mbox_val; |
| 285 | qemu_log_mask(LOG_UNIMP, |
| 286 | "Stashing skiboot base: 0x%" HWADDR_PRIx "\n", |
| 287 | pnv->mpipl_state.skiboot_base); |
| 288 | |
| 289 | /* |
| 290 | * Set the response register. |
| 291 | * |
| 292 | * Currently setting the same sequence number in |
| 293 | * response as we got in the request. |
| 294 | */ |
| 295 | sbe->mbox[4] = sbe->mbox[0]; /* sequence number */ |
| 296 | pnv_sbe_set_host_doorbell(sbe, |
| 297 | sbe->host_doorbell | SBE_HOST_RESPONSE_WAITING); |
| 298 | |
| 299 | break; |
| 300 | default: |
| 301 | qemu_log_mask(LOG_UNIMP, |
| 302 | "SBE: CMD_STASH_MPIPL_CONFIG: Unimplemented key: 0x" TARGET_FMT_lx "\n", |
| 303 | sbe->mbox[1]); |
| 304 | } |
| 305 | break; |
| 306 | default: |
| 307 | qemu_log_mask(LOG_UNIMP, "SBE Unimplemented command: 0x%x\n", cmd); |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | static void pnv_sbe_set_sbe_doorbell(PnvSBE *sbe, uint64_t val) |
| 312 | { |
| 313 | val &= HOST_SBE_MSG_WAITING; |
| 314 | sbe->sbe_doorbell = val; |
| 315 | |
| 316 | if (val & HOST_SBE_MSG_WAITING) { |
| 317 | sbe->sbe_doorbell &= ~HOST_SBE_MSG_WAITING; |
| 318 | do_sbe_msg(sbe); |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | static uint64_t pnv_sbe_power9_xscom_mbox_read(void *opaque, hwaddr addr, |
| 323 | unsigned size) |
| 324 | { |
| 325 | PnvSBE *sbe = PNV_SBE(opaque); |
| 326 | uint32_t offset = addr >> 3; |
| 327 | uint64_t val = 0; |
| 328 | |
| 329 | if (offset <= PSU_HOST_SBE_MBOX_REG7) { |
| 330 | uint32_t idx = offset - PSU_HOST_SBE_MBOX_REG0; |
| 331 | val = sbe->mbox[idx]; |
| 332 | } else { |
| 333 | switch (offset) { |
| 334 | case PSU_SBE_DOORBELL_REG_RW: |
| 335 | val = sbe->sbe_doorbell; |
| 336 | break; |
| 337 | case PSU_HOST_DOORBELL_REG_RW: |
| 338 | val = sbe->host_doorbell; |
| 339 | break; |
| 340 | default: |
| 341 | qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" |
| 342 | HWADDR_PRIx "\n", addr >> 3); |
| 343 | } |
| 344 | } |
| 345 | |
| 346 | trace_pnv_sbe_xscom_mbox_read(addr, val); |
| 347 | |
| 348 | return val; |
| 349 | } |
| 350 | |
| 351 | static void pnv_sbe_power9_xscom_mbox_write(void *opaque, hwaddr addr, |
| 352 | uint64_t val, unsigned size) |
| 353 | { |
| 354 | PnvSBE *sbe = PNV_SBE(opaque); |
| 355 | uint32_t offset = addr >> 3; |
| 356 | |
| 357 | trace_pnv_sbe_xscom_mbox_write(addr, val); |
| 358 | |
| 359 | if (offset <= PSU_HOST_SBE_MBOX_REG7) { |
| 360 | uint32_t idx = offset - PSU_HOST_SBE_MBOX_REG0; |
| 361 | sbe->mbox[idx] = val; |
| 362 | } else { |
| 363 | switch (offset) { |
| 364 | case PSU_SBE_DOORBELL_REG_RW: |
| 365 | pnv_sbe_set_sbe_doorbell(sbe, val); |
| 366 | break; |
| 367 | case PSU_SBE_DOORBELL_REG_AND: |
| 368 | pnv_sbe_set_sbe_doorbell(sbe, sbe->sbe_doorbell & val); |
| 369 | break; |
| 370 | case PSU_SBE_DOORBELL_REG_OR: |
| 371 | pnv_sbe_set_sbe_doorbell(sbe, sbe->sbe_doorbell | val); |
| 372 | break; |
| 373 | |
| 374 | case PSU_HOST_DOORBELL_REG_RW: |
| 375 | pnv_sbe_set_host_doorbell(sbe, val); |
| 376 | break; |
| 377 | case PSU_HOST_DOORBELL_REG_AND: |
| 378 | pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell & val); |
| 379 | break; |
| 380 | case PSU_HOST_DOORBELL_REG_OR: |
| 381 | pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell | val); |
| 382 | break; |
| 383 | |
| 384 | default: |
| 385 | qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" |
| 386 | HWADDR_PRIx "\n", addr >> 3); |
| 387 | } |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | static const MemoryRegionOps pnv_sbe_power9_xscom_mbox_ops = { |
| 392 | .read = pnv_sbe_power9_xscom_mbox_read, |
| 393 | .write = pnv_sbe_power9_xscom_mbox_write, |
| 394 | .valid.min_access_size = 8, |
| 395 | .valid.max_access_size = 8, |
| 396 | .impl.min_access_size = 8, |
| 397 | .impl.max_access_size = 8, |
| 398 | .endianness = DEVICE_BIG_ENDIAN, |
| 399 | }; |
| 400 | |
| 401 | static void pnv_sbe_power9_class_init(ObjectClass *klass, const void *data) |
| 402 | { |
| 403 | PnvSBEClass *psc = PNV_SBE_CLASS(klass); |
| 404 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 405 | |
| 406 | dc->desc = "PowerNV SBE Controller (POWER9)"; |
| 407 | psc->xscom_ctrl_size = PNV9_XSCOM_SBE_CTRL_SIZE; |
| 408 | psc->xscom_ctrl_ops = &pnv_sbe_power9_xscom_ctrl_ops; |
| 409 | psc->xscom_mbox_size = PNV9_XSCOM_SBE_MBOX_SIZE; |
| 410 | psc->xscom_mbox_ops = &pnv_sbe_power9_xscom_mbox_ops; |
| 411 | } |
| 412 | |
| 413 | static const TypeInfo pnv_sbe_power9_type_info = { |
| 414 | .name = TYPE_PNV9_SBE, |
| 415 | .parent = TYPE_PNV_SBE, |
| 416 | .instance_size = sizeof(PnvSBE), |
| 417 | .class_init = pnv_sbe_power9_class_init, |
| 418 | }; |
| 419 | |
| 420 | static void pnv_sbe_power10_class_init(ObjectClass *klass, const void *data) |
| 421 | { |
| 422 | PnvSBEClass *psc = PNV_SBE_CLASS(klass); |
| 423 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 424 | |
| 425 | dc->desc = "PowerNV SBE Controller (POWER10)"; |
| 426 | psc->xscom_ctrl_size = PNV10_XSCOM_SBE_CTRL_SIZE; |
| 427 | psc->xscom_ctrl_ops = &pnv_sbe_power9_xscom_ctrl_ops; |
| 428 | psc->xscom_mbox_size = PNV10_XSCOM_SBE_MBOX_SIZE; |
| 429 | psc->xscom_mbox_ops = &pnv_sbe_power9_xscom_mbox_ops; |
| 430 | } |
| 431 | |
| 432 | static const TypeInfo pnv_sbe_power10_type_info = { |
| 433 | .name = TYPE_PNV10_SBE, |
| 434 | .parent = TYPE_PNV9_SBE, |
| 435 | .class_init = pnv_sbe_power10_class_init, |
| 436 | }; |
| 437 | |
| 438 | static void pnv_sbe_realize(DeviceState *dev, Error **errp) |
| 439 | { |
| 440 | PnvSBE *sbe = PNV_SBE(dev); |
| 441 | PnvSBEClass *psc = PNV_SBE_GET_CLASS(sbe); |
| 442 | |
| 443 | /* XScom regions for SBE registers */ |
| 444 | pnv_xscom_region_init(&sbe->xscom_ctrl_regs, OBJECT(dev), |
| 445 | psc->xscom_ctrl_ops, sbe, "xscom-sbe-ctrl", |
| 446 | psc->xscom_ctrl_size); |
| 447 | pnv_xscom_region_init(&sbe->xscom_mbox_regs, OBJECT(dev), |
| 448 | psc->xscom_mbox_ops, sbe, "xscom-sbe-mbox", |
| 449 | psc->xscom_mbox_size); |
| 450 | |
| 451 | qdev_init_gpio_out(dev, &sbe->psi_irq, 1); |
| 452 | |
| 453 | sbe->timer = timer_new_us(QEMU_CLOCK_VIRTUAL, sbe_timer, sbe); |
| 454 | } |
| 455 | |
| 456 | static void pnv_sbe_class_init(ObjectClass *klass, const void *data) |
| 457 | { |
| 458 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 459 | |
| 460 | dc->realize = pnv_sbe_realize; |
| 461 | dc->desc = "PowerNV SBE Controller"; |
| 462 | dc->user_creatable = false; |
| 463 | } |
| 464 | |
| 465 | static const TypeInfo pnv_sbe_type_info = { |
| 466 | .name = TYPE_PNV_SBE, |
| 467 | .parent = TYPE_DEVICE, |
| 468 | .instance_size = sizeof(PnvSBE), |
| 469 | .class_init = pnv_sbe_class_init, |
| 470 | .class_size = sizeof(PnvSBEClass), |
| 471 | .abstract = true, |
| 472 | }; |
| 473 | |
| 474 | static void pnv_sbe_register_types(void) |
| 475 | { |
| 476 | type_register_static(&pnv_sbe_type_info); |
| 477 | type_register_static(&pnv_sbe_power9_type_info); |
| 478 | type_register_static(&pnv_sbe_power10_type_info); |
| 479 | } |
| 480 | |
| 481 | type_init(pnv_sbe_register_types); |