hw/arm/virt: Advertise GICv5 in the DTB

TODO: bindings not finalised yet.

https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250513-gicv5-host-v4-1-b36e9b15a6c3@kernel.org/
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index 6e2a283..b095545 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -754,6 +754,45 @@
     vms->msi_controller = VIRT_MSI_CTRL_GICV2M;
 }
 
+static void fdt_add_gicv5_node(VirtMachineState *vms)
+{
+    // TODO this is based on the proposed binding in
+    // https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250513-gicv5-host-v4-1-b36e9b15a6c3@kernel.org/
+    // which is not yet final and will likely change
+
+    MachineState *ms = MACHINE(vms);
+    const char *nodename = "/intc";
+    g_autofree char *irsnodename = NULL;
+
+    vms->gic_phandle = qemu_fdt_alloc_phandle(ms->fdt);
+    qemu_fdt_setprop_cell(ms->fdt, "/", "interrupt-parent", vms->gic_phandle);
+
+    qemu_fdt_add_subnode(ms->fdt, nodename);
+    qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "arm,gic-v5");
+    qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 3);
+    qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0);
+    qemu_fdt_setprop_cell(ms->fdt, nodename, "#address-cells", 0x2);
+    qemu_fdt_setprop_cell(ms->fdt, nodename, "#size-cells", 0x2);
+    qemu_fdt_setprop(ms->fdt, nodename, "ranges", NULL, 0);
+
+    irsnodename = g_strdup_printf("/irs@%" PRIx64,
+                                  vms->memmap[VIRT_GICV5_IRS_NS].base);
+    qemu_fdt_setprop_string(ms->fdt, irsnodename, "compatible",
+                            "arm,gic-v5-irs");
+    qemu_fdt_setprop_sized_cells(ms->fdt, irsnodename, "reg",
+                                 2, vms->memmap[VIRT_GICV5_IRS_NS].base,
+                                 2, vms->memmap[VIRT_GICV5_IRS_NS].size);
+    qemu_fdt_setprop_cell(ms->fdt, irsnodename, "#address-cells", 0x2);
+    qemu_fdt_setprop_cell(ms->fdt, irsnodename, "#size-cells", 0x2);
+    qemu_fdt_setprop(ms->fdt, irsnodename, "ranges", NULL, 0);
+
+    // TODO:
+    // cpus
+    // arm,iaffids
+
+    qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", vms->gic_phandle);
+}
+
 static void create_gicv5(VirtMachineState *vms, MemoryRegion *mem)
 {
     MachineState *ms = MACHINE(vms);
@@ -790,6 +829,8 @@
      * communicated directly between a GICv5 IRS and the GICv5 CPU interface
      * via our equivalent of the stream protocol.
      */
+
+    fdt_add_gicv5_node(vms);
 }
 
 /*