| 1 | /* |
| 2 | * AMD/Xilinx Versal family Virtual board. |
| 3 | * |
| 4 | * Copyright (c) 2018 Xilinx Inc. |
| 5 | * Copyright (c) 2025 Advanced Micro Devices, Inc. |
| 6 | * Written by Edgar E. Iglesias |
| 7 | * |
| 8 | * This program is free software; you can redistribute it and/or modify |
| 9 | * it under the terms of the GNU General Public License version 2 or |
| 10 | * (at your option) any later version. |
| 11 | */ |
| 12 | |
| 13 | #include "qemu/osdep.h" |
| 14 | #include "qemu/error-report.h" |
| 15 | #include "qapi/error.h" |
| 16 | #include "system/device_tree.h" |
| 17 | #include "system/address-spaces.h" |
| 18 | #include "hw/block/flash.h" |
| 19 | #include "hw/core/boards.h" |
| 20 | #include "hw/core/sysbus.h" |
| 21 | #include "hw/arm/fdt.h" |
| 22 | #include "hw/arm/xlnx-versal.h" |
| 23 | #include "hw/arm/boot.h" |
| 24 | #include "hw/arm/machines-qom.h" |
| 25 | #include "qom/object.h" |
| 26 | #include "target/arm/cpu.h" |
| 27 | |
| 28 | #define TYPE_XLNX_VERSAL_VIRT_BASE_MACHINE \ |
| 29 | MACHINE_TYPE_NAME("amd-versal-virt-base") |
| 30 | OBJECT_DECLARE_TYPE(VersalVirt, VersalVirtClass, XLNX_VERSAL_VIRT_BASE_MACHINE) |
| 31 | |
| 32 | #define TYPE_XLNX_VERSAL_VIRT_MACHINE MACHINE_TYPE_NAME("amd-versal-virt") |
| 33 | #define TYPE_XLNX_VERSAL2_VIRT_MACHINE MACHINE_TYPE_NAME("amd-versal2-virt") |
| 34 | |
| 35 | #define XLNX_VERSAL_NUM_OSPI_FLASH 4 |
| 36 | |
| 37 | struct VersalVirt { |
| 38 | MachineState parent_obj; |
| 39 | |
| 40 | Versal soc; |
| 41 | |
| 42 | void *fdt; |
| 43 | int fdt_size; |
| 44 | struct arm_boot_info binfo; |
| 45 | |
| 46 | CanBusState **canbus; |
| 47 | |
| 48 | struct { |
| 49 | char *ospi_model; |
| 50 | } cfg; |
| 51 | }; |
| 52 | |
| 53 | struct VersalVirtClass { |
| 54 | MachineClass parent_class; |
| 55 | |
| 56 | VersalVersion version; |
| 57 | }; |
| 58 | |
| 59 | static void fdt_create(VersalVirt *s) |
| 60 | { |
| 61 | MachineClass *mc = MACHINE_GET_CLASS(s); |
| 62 | VersalVirtClass *vvc = XLNX_VERSAL_VIRT_BASE_MACHINE_GET_CLASS(s); |
| 63 | const char versal_compat[] = "amd-versal-virt\0xlnx-versal-virt"; |
| 64 | const char versal2_compat[] = "amd-versal2-virt"; |
| 65 | |
| 66 | s->fdt = create_device_tree(&s->fdt_size); |
| 67 | if (!s->fdt) { |
| 68 | error_report("create_device_tree() failed"); |
| 69 | exit(1); |
| 70 | } |
| 71 | |
| 72 | /* Create /chosen node for load_dtb. */ |
| 73 | qemu_fdt_add_subnode(s->fdt, "/chosen"); |
| 74 | qemu_fdt_add_subnode(s->fdt, "/aliases"); |
| 75 | |
| 76 | /* Header */ |
| 77 | qemu_fdt_setprop_string(s->fdt, "/", "model", mc->desc); |
| 78 | |
| 79 | switch (vvc->version) { |
| 80 | case VERSAL_VER_VERSAL: |
| 81 | qemu_fdt_setprop(s->fdt, "/", "compatible", versal_compat, |
| 82 | sizeof(versal_compat)); |
| 83 | break; |
| 84 | |
| 85 | case VERSAL_VER_VERSAL2: |
| 86 | qemu_fdt_setprop(s->fdt, "/", "compatible", versal2_compat, |
| 87 | sizeof(versal2_compat)); |
| 88 | break; |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | static void fdt_nop_memory_nodes(void *fdt, Error **errp) |
| 93 | { |
| 94 | Error *err = NULL; |
| 95 | char **node_path; |
| 96 | int n = 0; |
| 97 | |
| 98 | node_path = qemu_fdt_node_unit_path(fdt, "memory", &err); |
| 99 | if (err) { |
| 100 | error_propagate(errp, err); |
| 101 | return; |
| 102 | } |
| 103 | while (node_path[n]) { |
| 104 | if (g_str_has_prefix(node_path[n], "/memory")) { |
| 105 | qemu_fdt_nop_node(fdt, node_path[n]); |
| 106 | } |
| 107 | n++; |
| 108 | } |
| 109 | g_strfreev(node_path); |
| 110 | } |
| 111 | |
| 112 | static void versal_virt_modify_dtb(const struct arm_boot_info *binfo, |
| 113 | void *fdt) |
| 114 | { |
| 115 | VersalVirt *s = container_of(binfo, VersalVirt, binfo); |
| 116 | |
| 117 | fdt_nop_memory_nodes(s->fdt, &error_abort); |
| 118 | versal_fdt_add_memory_nodes(&s->soc, binfo->ram_size); |
| 119 | } |
| 120 | |
| 121 | static void *versal_virt_get_dtb(const struct arm_boot_info *binfo, |
| 122 | int *fdt_size) |
| 123 | { |
| 124 | const VersalVirt *board = container_of(binfo, VersalVirt, binfo); |
| 125 | |
| 126 | *fdt_size = board->fdt_size; |
| 127 | return board->fdt; |
| 128 | } |
| 129 | |
| 130 | #define NUM_VIRTIO_TRANSPORT 8 |
| 131 | static void create_virtio_regions(VersalVirt *s) |
| 132 | { |
| 133 | int virtio_mmio_size = 0x200; |
| 134 | int i; |
| 135 | |
| 136 | for (i = 0; i < NUM_VIRTIO_TRANSPORT; i++) { |
| 137 | hwaddr base = versal_get_reserved_mmio_addr(&s->soc) |
| 138 | + i * virtio_mmio_size; |
| 139 | g_autofree char *node = g_strdup_printf("/virtio_mmio@%" PRIx64, base); |
| 140 | int dtb_irq; |
| 141 | MemoryRegion *mr; |
| 142 | DeviceState *dev; |
| 143 | qemu_irq pic_irq; |
| 144 | |
| 145 | pic_irq = versal_get_reserved_irq(&s->soc, i, &dtb_irq); |
| 146 | dev = qdev_new("virtio-mmio"); |
| 147 | object_property_add_child(OBJECT(s), "virtio-mmio[*]", OBJECT(dev)); |
| 148 | sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); |
| 149 | sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic_irq); |
| 150 | mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0); |
| 151 | memory_region_add_subregion(&s->soc.mr_ps, base, mr); |
| 152 | |
| 153 | qemu_fdt_add_subnode(s->fdt, node); |
| 154 | qemu_fdt_setprop(s->fdt, node, "dma-coherent", NULL, 0); |
| 155 | qemu_fdt_setprop_cells(s->fdt, node, "interrupts", |
| 156 | GIC_FDT_IRQ_TYPE_SPI, dtb_irq, |
| 157 | GIC_FDT_IRQ_FLAGS_EDGE_LO_HI); |
| 158 | qemu_fdt_setprop_sized_cells(s->fdt, node, "reg", |
| 159 | 2, base, 2, virtio_mmio_size); |
| 160 | qemu_fdt_setprop_string(s->fdt, node, "compatible", "virtio,mmio"); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | static void bbram_attach_drive(VersalVirt *s) |
| 165 | { |
| 166 | DriveInfo *dinfo; |
| 167 | BlockBackend *blk; |
| 168 | |
| 169 | dinfo = drive_get_by_index(IF_PFLASH, 0); |
| 170 | blk = dinfo ? blk_by_legacy_dinfo(dinfo) : NULL; |
| 171 | if (blk) { |
| 172 | versal_bbram_attach_drive(&s->soc, blk); |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | static void efuse_attach_drive(VersalVirt *s) |
| 177 | { |
| 178 | DriveInfo *dinfo; |
| 179 | BlockBackend *blk; |
| 180 | |
| 181 | dinfo = drive_get_by_index(IF_PFLASH, 1); |
| 182 | blk = dinfo ? blk_by_legacy_dinfo(dinfo) : NULL; |
| 183 | if (blk) { |
| 184 | versal_efuse_attach_drive(&s->soc, blk); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | static void sd_plug_card(VersalVirt *s, int idx, DriveInfo *di) |
| 189 | { |
| 190 | BlockBackend *blk = di ? blk_by_legacy_dinfo(di) : NULL; |
| 191 | |
| 192 | versal_sdhci_plug_card(&s->soc, idx, blk); |
| 193 | } |
| 194 | |
| 195 | static char *versal_get_ospi_model(Object *obj, Error **errp) |
| 196 | { |
| 197 | VersalVirt *s = XLNX_VERSAL_VIRT_BASE_MACHINE(obj); |
| 198 | |
| 199 | return g_strdup(s->cfg.ospi_model); |
| 200 | } |
| 201 | |
| 202 | static void versal_set_ospi_model(Object *obj, const char *value, Error **errp) |
| 203 | { |
| 204 | VersalVirt *s = XLNX_VERSAL_VIRT_BASE_MACHINE(obj); |
| 205 | |
| 206 | g_free(s->cfg.ospi_model); |
| 207 | s->cfg.ospi_model = g_strdup(value); |
| 208 | } |
| 209 | |
| 210 | |
| 211 | static void versal_virt_init(MachineState *machine) |
| 212 | { |
| 213 | VersalVirt *s = XLNX_VERSAL_VIRT_BASE_MACHINE(machine); |
| 214 | VersalVirtClass *vvc = XLNX_VERSAL_VIRT_BASE_MACHINE_GET_CLASS(machine); |
| 215 | int psci_conduit = QEMU_PSCI_CONDUIT_DISABLED; |
| 216 | int i; |
| 217 | |
| 218 | /* |
| 219 | * If the user provides an Operating System to be loaded, we expect them |
| 220 | * to use the -kernel command line option. |
| 221 | * |
| 222 | * Users can load firmware or boot-loaders with the -device loader options. |
| 223 | * |
| 224 | * When loading an OS, we generate a dtb and let arm_load_kernel() select |
| 225 | * where it gets loaded. This dtb will be passed to the kernel in x0. |
| 226 | * |
| 227 | * If there's no -kernel option, we generate a DTB and place it at 0x1000 |
| 228 | * for the bootloaders or firmware to pick up. |
| 229 | * |
| 230 | * If users want to provide their own DTB, they can use the -dtb option. |
| 231 | * These dtb's will have their memory nodes modified to match QEMU's |
| 232 | * selected ram_size option before they get passed to the kernel or fw. |
| 233 | * |
| 234 | * When loading an OS, we turn on QEMU's PSCI implementation with SMC |
| 235 | * as the PSCI conduit. When there's no -kernel, we assume the user |
| 236 | * provides EL3 firmware to handle PSCI. |
| 237 | * |
| 238 | * Even if the user provides a kernel filename, arm_load_kernel() |
| 239 | * may suppress PSCI if it's going to boot that guest code at EL3. |
| 240 | */ |
| 241 | if (machine->kernel_filename) { |
| 242 | psci_conduit = QEMU_PSCI_CONDUIT_SMC; |
| 243 | } |
| 244 | |
| 245 | object_initialize_child(OBJECT(machine), "xlnx-versal", &s->soc, |
| 246 | versal_get_class(vvc->version)); |
| 247 | object_property_set_link(OBJECT(&s->soc), "ddr", OBJECT(machine->ram), |
| 248 | &error_abort); |
| 249 | |
| 250 | for (i = 0; i < versal_get_num_can(vvc->version); i++) { |
| 251 | g_autofree char *prop_name = g_strdup_printf("canbus%d", i); |
| 252 | |
| 253 | object_property_set_link(OBJECT(&s->soc), prop_name, |
| 254 | OBJECT(s->canbus[i]), |
| 255 | &error_abort); |
| 256 | } |
| 257 | |
| 258 | fdt_create(s); |
| 259 | versal_set_fdt(&s->soc, s->fdt); |
| 260 | sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal); |
| 261 | create_virtio_regions(s); |
| 262 | |
| 263 | /* |
| 264 | * Map the SoC address space onto system memory. This will allow virtio and |
| 265 | * other modules unaware of multiple address-spaces to work. |
| 266 | */ |
| 267 | memory_region_add_subregion(get_system_memory(), 0, &s->soc.mr_ps); |
| 268 | |
| 269 | /* Attach bbram backend, if given */ |
| 270 | bbram_attach_drive(s); |
| 271 | |
| 272 | /* Attach efuse backend, if given */ |
| 273 | efuse_attach_drive(s); |
| 274 | |
| 275 | /* Plug SD cards */ |
| 276 | for (i = 0; i < versal_get_num_sdhci(vvc->version); i++) { |
| 277 | sd_plug_card(s, i, drive_get(IF_SD, 0, i)); |
| 278 | } |
| 279 | |
| 280 | s->binfo.ram_size = machine->ram_size; |
| 281 | s->binfo.loader_start = 0x0; |
| 282 | s->binfo.get_dtb = versal_virt_get_dtb; |
| 283 | s->binfo.modify_dtb = versal_virt_modify_dtb; |
| 284 | s->binfo.psci_conduit = psci_conduit; |
| 285 | if (!machine->kernel_filename) { |
| 286 | /* Some boot-loaders (e.g u-boot) don't like blobs at address 0 (NULL). |
| 287 | * Offset things by 4K. */ |
| 288 | s->binfo.loader_start = 0x1000; |
| 289 | s->binfo.dtb_limit = 0x1000000; |
| 290 | } |
| 291 | arm_load_kernel(ARM_CPU(versal_get_boot_cpu(&s->soc)), machine, &s->binfo); |
| 292 | |
| 293 | for (i = 0; i < XLNX_VERSAL_NUM_OSPI_FLASH; i++) { |
| 294 | ObjectClass *flash_klass; |
| 295 | DriveInfo *dinfo = drive_get(IF_MTD, 0, i); |
| 296 | BlockBackend *blk; |
| 297 | const char *mdl; |
| 298 | |
| 299 | if (s->cfg.ospi_model) { |
| 300 | flash_klass = object_class_by_name(s->cfg.ospi_model); |
| 301 | if (!flash_klass || |
| 302 | object_class_is_abstract(flash_klass) || |
| 303 | !object_class_dynamic_cast(flash_klass, TYPE_M25P80)) { |
| 304 | error_report("'%s' is either abstract or" |
| 305 | " not a subtype of m25p80", s->cfg.ospi_model); |
| 306 | exit(1); |
| 307 | } |
| 308 | mdl = s->cfg.ospi_model; |
| 309 | } else { |
| 310 | mdl = "mt35xu01g"; |
| 311 | } |
| 312 | |
| 313 | blk = dinfo ? blk_by_legacy_dinfo(dinfo) : NULL; |
| 314 | versal_ospi_create_flash(&s->soc, i, mdl, blk); |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | static void versal_virt_machine_instance_init(Object *obj) |
| 319 | { |
| 320 | VersalVirt *s = XLNX_VERSAL_VIRT_BASE_MACHINE(obj); |
| 321 | VersalVirtClass *vvc = XLNX_VERSAL_VIRT_BASE_MACHINE_GET_CLASS(s); |
| 322 | size_t i, num_can; |
| 323 | |
| 324 | num_can = versal_get_num_can(vvc->version); |
| 325 | s->canbus = g_new0(CanBusState *, num_can); |
| 326 | |
| 327 | /* |
| 328 | * User can set canbusx properties to can-bus object and optionally connect |
| 329 | * to socketcan interface via command line. |
| 330 | */ |
| 331 | for (i = 0; i < num_can; i++) { |
| 332 | g_autofree char *prop_name = g_strdup_printf("canbus%zu", i); |
| 333 | |
| 334 | object_property_add_link(obj, prop_name, TYPE_CAN_BUS, |
| 335 | (Object **) &s->canbus[i], |
| 336 | object_property_allow_set_link, 0); |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | static void versal_virt_machine_finalize(Object *obj) |
| 341 | { |
| 342 | VersalVirt *s = XLNX_VERSAL_VIRT_BASE_MACHINE(obj); |
| 343 | |
| 344 | g_free(s->cfg.ospi_model); |
| 345 | g_free(s->canbus); |
| 346 | } |
| 347 | |
| 348 | static void versal_virt_machine_class_init_common(ObjectClass *oc) |
| 349 | { |
| 350 | MachineClass *mc = MACHINE_CLASS(oc); |
| 351 | VersalVirtClass *vvc = XLNX_VERSAL_VIRT_BASE_MACHINE_CLASS(mc); |
| 352 | int num_cpu = versal_get_num_cpu(vvc->version); |
| 353 | |
| 354 | mc->no_cdrom = true; |
| 355 | mc->auto_create_sdcard = true; |
| 356 | mc->default_ram_id = "ddr"; |
| 357 | mc->min_cpus = num_cpu; |
| 358 | mc->max_cpus = num_cpu; |
| 359 | mc->default_cpus = num_cpu; |
| 360 | mc->init = versal_virt_init; |
| 361 | |
| 362 | object_class_property_add_str(oc, "ospi-flash", versal_get_ospi_model, |
| 363 | versal_set_ospi_model); |
| 364 | object_class_property_set_description(oc, "ospi-flash", |
| 365 | "Change the OSPI Flash model"); |
| 366 | } |
| 367 | |
| 368 | static void versal_virt_machine_class_init(ObjectClass *oc, const void *data) |
| 369 | { |
| 370 | MachineClass *mc = MACHINE_CLASS(oc); |
| 371 | VersalVirtClass *vvc = XLNX_VERSAL_VIRT_BASE_MACHINE_CLASS(oc); |
| 372 | |
| 373 | mc->desc = "AMD Versal Virtual development board"; |
| 374 | mc->alias = "xlnx-versal-virt"; |
| 375 | vvc->version = VERSAL_VER_VERSAL; |
| 376 | |
| 377 | versal_virt_machine_class_init_common(oc); |
| 378 | } |
| 379 | |
| 380 | static void versal2_virt_machine_class_init(ObjectClass *oc, const void *data) |
| 381 | { |
| 382 | MachineClass *mc = MACHINE_CLASS(oc); |
| 383 | VersalVirtClass *vvc = XLNX_VERSAL_VIRT_BASE_MACHINE_CLASS(oc); |
| 384 | |
| 385 | mc->desc = "AMD Versal Gen 2 Virtual development board"; |
| 386 | vvc->version = VERSAL_VER_VERSAL2; |
| 387 | |
| 388 | versal_virt_machine_class_init_common(oc); |
| 389 | } |
| 390 | |
| 391 | static const TypeInfo versal_virt_base_machine_init_typeinfo = { |
| 392 | .name = TYPE_XLNX_VERSAL_VIRT_BASE_MACHINE, |
| 393 | .parent = TYPE_MACHINE, |
| 394 | .class_size = sizeof(VersalVirtClass), |
| 395 | .instance_init = versal_virt_machine_instance_init, |
| 396 | .instance_size = sizeof(VersalVirt), |
| 397 | .instance_finalize = versal_virt_machine_finalize, |
| 398 | .abstract = true, |
| 399 | }; |
| 400 | |
| 401 | static const TypeInfo versal_virt_machine_init_typeinfo = { |
| 402 | .name = TYPE_XLNX_VERSAL_VIRT_MACHINE, |
| 403 | .parent = TYPE_XLNX_VERSAL_VIRT_BASE_MACHINE, |
| 404 | .class_init = versal_virt_machine_class_init, |
| 405 | .interfaces = aarch64_machine_interfaces, |
| 406 | }; |
| 407 | |
| 408 | static const TypeInfo versal2_virt_machine_init_typeinfo = { |
| 409 | .name = TYPE_XLNX_VERSAL2_VIRT_MACHINE, |
| 410 | .parent = TYPE_XLNX_VERSAL_VIRT_BASE_MACHINE, |
| 411 | .class_init = versal2_virt_machine_class_init, |
| 412 | .interfaces = aarch64_machine_interfaces, |
| 413 | }; |
| 414 | |
| 415 | static void versal_virt_machine_init_register_types(void) |
| 416 | { |
| 417 | type_register_static(&versal_virt_base_machine_init_typeinfo); |
| 418 | type_register_static(&versal_virt_machine_init_typeinfo); |
| 419 | type_register_static(&versal2_virt_machine_init_typeinfo); |
| 420 | } |
| 421 | |
| 422 | type_init(versal_virt_machine_init_register_types) |