| 1 | /* |
| 2 | * RISC-V board helpers for FDT generation. |
| 3 | * |
| 4 | * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. |
| 5 | * |
| 6 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 7 | */ |
| 8 | |
| 9 | #include "qemu/osdep.h" |
| 10 | |
| 11 | #include "qemu/error-report.h" |
| 12 | #include "system/device_tree.h" |
| 13 | #include "hw/core/boards.h" |
| 14 | #include "hw/riscv/fdt-common.h" |
| 15 | #include "target/riscv/cpu_bits.h" |
| 16 | |
| 17 | void *create_board_device_tree(const char *model, const char *compatible, |
| 18 | int *fdt_size) |
| 19 | { |
| 20 | void *fdt = create_device_tree(fdt_size); |
| 21 | |
| 22 | if (!fdt) { |
| 23 | error_report("create_device_tree() failed"); |
| 24 | exit(1); |
| 25 | } |
| 26 | |
| 27 | qemu_fdt_setprop_string(fdt, "/", "model", model); |
| 28 | qemu_fdt_setprop_string(fdt, "/", "compatible", compatible); |
| 29 | qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x2); |
| 30 | qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x2); |
| 31 | |
| 32 | qemu_fdt_add_subnode(fdt, "/soc"); |
| 33 | qemu_fdt_setprop(fdt, "/soc", "ranges", NULL, 0); |
| 34 | qemu_fdt_setprop_string(fdt, "/soc", "compatible", "simple-bus"); |
| 35 | qemu_fdt_setprop_cell(fdt, "/soc", "#size-cells", 0x2); |
| 36 | qemu_fdt_setprop_cell(fdt, "/soc", "#address-cells", 0x2); |
| 37 | |
| 38 | return fdt; |
| 39 | } |
| 40 | |
| 41 | void create_fdt_socket_memory(void *fdt, hwaddr addr, uint64_t size, |
| 42 | int socket_id, bool numa_enabled) |
| 43 | { |
| 44 | g_autofree char *mem_name = g_strdup_printf("/memory@%"HWADDR_PRIx, addr); |
| 45 | |
| 46 | qemu_fdt_add_subnode(fdt, mem_name); |
| 47 | qemu_fdt_setprop_sized_cells(fdt, mem_name, "reg", 2, addr, 2, size); |
| 48 | qemu_fdt_setprop_string(fdt, mem_name, "device_type", "memory"); |
| 49 | |
| 50 | if (numa_enabled) { |
| 51 | qemu_fdt_setprop_cell(fdt, mem_name, "numa-node-id", socket_id); |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | void create_fdt_socket_clint(void *fdt, hwaddr addr, uint64_t size, |
| 56 | int socket_id, uint32_t *intc_phandles, |
| 57 | int num_harts, bool numa_enabled) |
| 58 | { |
| 59 | g_autofree uint32_t *clint_cells = g_new0(uint32_t, num_harts * 4); |
| 60 | g_autofree char *clint_name = NULL; |
| 61 | static const char * const clint_compat[2] = { |
| 62 | "sifive,clint0", "riscv,clint0" |
| 63 | }; |
| 64 | |
| 65 | for (int cpu = 0; cpu < num_harts; cpu++) { |
| 66 | clint_cells[cpu * 4 + 0] = cpu_to_be32(intc_phandles[cpu]); |
| 67 | clint_cells[cpu * 4 + 1] = cpu_to_be32(IRQ_M_SOFT); |
| 68 | clint_cells[cpu * 4 + 2] = cpu_to_be32(intc_phandles[cpu]); |
| 69 | clint_cells[cpu * 4 + 3] = cpu_to_be32(IRQ_M_TIMER); |
| 70 | } |
| 71 | |
| 72 | clint_name = g_strdup_printf("/soc/clint@%"HWADDR_PRIx, addr); |
| 73 | qemu_fdt_add_subnode(fdt, clint_name); |
| 74 | qemu_fdt_setprop_string_array(fdt, clint_name, "compatible", |
| 75 | (char **)&clint_compat, |
| 76 | ARRAY_SIZE(clint_compat)); |
| 77 | qemu_fdt_setprop_sized_cells(fdt, clint_name, "reg", |
| 78 | 2, addr, 2, size); |
| 79 | qemu_fdt_setprop(fdt, clint_name, "interrupts-extended", |
| 80 | clint_cells, num_harts * sizeof(uint32_t) * 4); |
| 81 | |
| 82 | if (numa_enabled) { |
| 83 | qemu_fdt_setprop_cell(fdt, clint_name, "numa-node-id", socket_id); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | void fdt_create_cpu_socket_subnode(void *fdt, uint64_t timebase_frequency) |
| 88 | { |
| 89 | qemu_fdt_add_subnode(fdt, "/cpus"); |
| 90 | qemu_fdt_setprop_cell(fdt, "/cpus", "timebase-frequency", |
| 91 | timebase_frequency); |
| 92 | qemu_fdt_setprop_cell(fdt, "/cpus", "#size-cells", 0x0); |
| 93 | qemu_fdt_setprop_cell(fdt, "/cpus", "#address-cells", 0x1); |
| 94 | qemu_fdt_add_subnode(fdt, "/cpus/cpu-map"); |
| 95 | } |
| 96 | |
| 97 | static void |
| 98 | create_fdt_socket_cpu_internal(void *fdt, char *clust_name, RISCVCPU *cpu_ptr, |
| 99 | int cpu, int socket_id, int socket_hartid_base, |
| 100 | uint32_t *phandle, uint32_t *intc_phandles, |
| 101 | bool numa_enabled, bool is_32_bit) |
| 102 | { |
| 103 | g_autofree char *cpu_name = NULL; |
| 104 | g_autofree char *core_name = NULL; |
| 105 | g_autofree char *intc_name = NULL; |
| 106 | uint32_t cpu_phandle = (*phandle)++; |
| 107 | bool is_sifive_u = cpu_ptr == NULL; |
| 108 | |
| 109 | cpu_name = g_strdup_printf("/cpus/cpu@%d", socket_hartid_base + cpu); |
| 110 | |
| 111 | /* |
| 112 | * The sifive_u board has an exclusive satp and riscv,isa |
| 113 | * schema that can't be shared with other boards, so part |
| 114 | * of the CPU FDT creation (i.e. the /cpus/cpu@N subnode) |
| 115 | * is still being done by the board. |
| 116 | */ |
| 117 | if (!is_sifive_u) { |
| 118 | int8_t satp_mode_max = cpu_ptr->cfg.max_satp_mode; |
| 119 | |
| 120 | qemu_fdt_add_subnode(fdt, cpu_name); |
| 121 | |
| 122 | if (satp_mode_max != -1) { |
| 123 | g_autofree char *sv_name = NULL; |
| 124 | sv_name = g_strdup_printf("riscv,%s", |
| 125 | satp_mode_str(satp_mode_max, is_32_bit)); |
| 126 | qemu_fdt_setprop_string(fdt, cpu_name, "mmu-type", sv_name); |
| 127 | } |
| 128 | riscv_isa_write_fdt(cpu_ptr, fdt, cpu_name); |
| 129 | |
| 130 | if (cpu_ptr->cfg.ext_zicbom) { |
| 131 | qemu_fdt_setprop_cell(fdt, cpu_name, "riscv,cbom-block-size", |
| 132 | cpu_ptr->cfg.cbom_blocksize); |
| 133 | } |
| 134 | |
| 135 | if (cpu_ptr->cfg.ext_zicboz) { |
| 136 | qemu_fdt_setprop_cell(fdt, cpu_name, "riscv,cboz-block-size", |
| 137 | cpu_ptr->cfg.cboz_blocksize); |
| 138 | } |
| 139 | |
| 140 | if (cpu_ptr->cfg.ext_zicbop) { |
| 141 | qemu_fdt_setprop_cell(fdt, cpu_name, "riscv,cbop-block-size", |
| 142 | cpu_ptr->cfg.cbop_blocksize); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | qemu_fdt_setprop_string(fdt, cpu_name, "compatible", "riscv"); |
| 147 | qemu_fdt_setprop_string(fdt, cpu_name, "status", "okay"); |
| 148 | qemu_fdt_setprop_cell(fdt, cpu_name, "reg", |
| 149 | socket_hartid_base + cpu); |
| 150 | qemu_fdt_setprop_string(fdt, cpu_name, "device_type", "cpu"); |
| 151 | if (numa_enabled) { |
| 152 | qemu_fdt_setprop_cell(fdt, cpu_name, "numa-node-id", socket_id); |
| 153 | } |
| 154 | qemu_fdt_setprop_cell(fdt, cpu_name, "phandle", cpu_phandle); |
| 155 | |
| 156 | intc_phandles[cpu] = (*phandle)++; |
| 157 | |
| 158 | intc_name = g_strdup_printf("%s/interrupt-controller", cpu_name); |
| 159 | qemu_fdt_add_subnode(fdt, intc_name); |
| 160 | qemu_fdt_setprop_cell(fdt, intc_name, "phandle", |
| 161 | intc_phandles[cpu]); |
| 162 | qemu_fdt_setprop_string(fdt, intc_name, "compatible", |
| 163 | "riscv,cpu-intc"); |
| 164 | qemu_fdt_setprop(fdt, intc_name, "interrupt-controller", NULL, 0); |
| 165 | qemu_fdt_setprop_cell(fdt, intc_name, "#interrupt-cells", 1); |
| 166 | |
| 167 | core_name = g_strdup_printf("%s/core%d", clust_name, cpu); |
| 168 | qemu_fdt_add_subnode(fdt, core_name); |
| 169 | qemu_fdt_setprop_cell(fdt, core_name, "cpu", cpu_phandle); |
| 170 | } |
| 171 | |
| 172 | void create_fdt_socket_cpus(void *fdt, RISCVCPU *socket_harts, |
| 173 | int socket_id, int num_harts_socket, |
| 174 | int socket_hartid_base, uint32_t *phandle, |
| 175 | uint32_t *intc_phandles, bool numa_enabled, |
| 176 | bool is_32_bit) |
| 177 | { |
| 178 | g_autofree char *clust_name = NULL; |
| 179 | |
| 180 | clust_name = g_strdup_printf("/cpus/cpu-map/cluster%d", socket_id); |
| 181 | qemu_fdt_add_subnode(fdt, clust_name); |
| 182 | |
| 183 | for (int cpu = num_harts_socket - 1; cpu >= 0; cpu--) { |
| 184 | RISCVCPU *cpu_ptr = &socket_harts[cpu]; |
| 185 | |
| 186 | create_fdt_socket_cpu_internal(fdt, clust_name, cpu_ptr, cpu, |
| 187 | socket_id, socket_hartid_base, |
| 188 | phandle, intc_phandles, numa_enabled, |
| 189 | is_32_bit); |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | void |
| 194 | create_fdt_socket_cpu_sifive(void *fdt, char *clust_name, |
| 195 | int cpu_id, int socket_id, |
| 196 | int socket_hartid_base, uint32_t *phandle, |
| 197 | uint32_t *intc_phandles) |
| 198 | { |
| 199 | create_fdt_socket_cpu_internal(fdt, clust_name, NULL, cpu_id, |
| 200 | socket_id, socket_hartid_base, |
| 201 | phandle, intc_phandles, false, false); |
| 202 | } |
| 203 | |
| 204 | void create_fdt_plic(void *fdt, hwaddr addr, uint64_t size, |
| 205 | uint32_t plic_phandle, uint32_t int_cells, |
| 206 | uint32_t addr_cells, uint32_t *plic_cells, |
| 207 | uint32_t cells_size, uint32_t ndev_sources, |
| 208 | bool numa_enabled, int socket_id) |
| 209 | { |
| 210 | g_autofree char *nodename = NULL; |
| 211 | static const char * const plic_compat[2] = { |
| 212 | "sifive,plic-1.0.0", "riscv,plic0" |
| 213 | }; |
| 214 | |
| 215 | nodename = g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIx, addr); |
| 216 | |
| 217 | qemu_fdt_add_subnode(fdt, nodename); |
| 218 | qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells", int_cells); |
| 219 | qemu_fdt_setprop_cell(fdt, nodename, "#address-cells", addr_cells); |
| 220 | qemu_fdt_setprop_string_array(fdt, nodename, "compatible", |
| 221 | (char **)&plic_compat, ARRAY_SIZE(plic_compat)); |
| 222 | qemu_fdt_setprop(fdt, nodename, "interrupt-controller", NULL, 0); |
| 223 | qemu_fdt_setprop(fdt, nodename, "interrupts-extended", |
| 224 | plic_cells, cells_size); |
| 225 | qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", |
| 226 | 2, addr, 2, size); |
| 227 | qemu_fdt_setprop_cell(fdt, nodename, "riscv,ndev", ndev_sources); |
| 228 | if (numa_enabled) { |
| 229 | qemu_fdt_setprop_cell(fdt, nodename, "numa-node-id", socket_id); |
| 230 | } |
| 231 | qemu_fdt_setprop_cell(fdt, nodename, "phandle", plic_phandle); |
| 232 | } |
| 233 | |
| 234 | /* |
| 235 | * To keep it simple, any event can be mapped to any programmable counters in |
| 236 | * QEMU. The generic cycle & instruction count events can also be monitored |
| 237 | * using programmable counters. In that case, mcycle & minstret must continue |
| 238 | * to provide the correct value as well. Heterogeneous PMU per hart is not |
| 239 | * supported yet. Thus, number of counters are same across all harts. |
| 240 | */ |
| 241 | void riscv_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name) |
| 242 | { |
| 243 | uint32_t fdt_event_ctr_map[15] = {}; |
| 244 | |
| 245 | /* |
| 246 | * The event encoding is specified in the SBI specification |
| 247 | * Event idx is a 20bits wide number encoded as follows: |
| 248 | * event_idx[19:16] = type |
| 249 | * event_idx[15:0] = code |
| 250 | * The code field in cache events are encoded as follows: |
| 251 | * event_idx.code[15:3] = cache_id |
| 252 | * event_idx.code[2:1] = op_id |
| 253 | * event_idx.code[0:0] = result_id |
| 254 | */ |
| 255 | |
| 256 | /* SBI_PMU_HW_CPU_CYCLES: 0x01 : type(0x00) */ |
| 257 | fdt_event_ctr_map[0] = cpu_to_be32(0x00000001); |
| 258 | fdt_event_ctr_map[1] = cpu_to_be32(0x00000001); |
| 259 | fdt_event_ctr_map[2] = cpu_to_be32(cmask | 1 << 0); |
| 260 | |
| 261 | /* SBI_PMU_HW_INSTRUCTIONS: 0x02 : type(0x00) */ |
| 262 | fdt_event_ctr_map[3] = cpu_to_be32(0x00000002); |
| 263 | fdt_event_ctr_map[4] = cpu_to_be32(0x00000002); |
| 264 | fdt_event_ctr_map[5] = cpu_to_be32(cmask | 1 << 2); |
| 265 | |
| 266 | /* SBI_PMU_HW_CACHE_DTLB : 0x03 READ : 0x00 MISS : 0x00 type(0x01) */ |
| 267 | fdt_event_ctr_map[6] = cpu_to_be32(0x00010019); |
| 268 | fdt_event_ctr_map[7] = cpu_to_be32(0x00010019); |
| 269 | fdt_event_ctr_map[8] = cpu_to_be32(cmask); |
| 270 | |
| 271 | /* SBI_PMU_HW_CACHE_DTLB : 0x03 WRITE : 0x01 MISS : 0x00 type(0x01) */ |
| 272 | fdt_event_ctr_map[9] = cpu_to_be32(0x0001001B); |
| 273 | fdt_event_ctr_map[10] = cpu_to_be32(0x0001001B); |
| 274 | fdt_event_ctr_map[11] = cpu_to_be32(cmask); |
| 275 | |
| 276 | /* SBI_PMU_HW_CACHE_ITLB : 0x04 READ : 0x00 MISS : 0x00 type(0x01) */ |
| 277 | fdt_event_ctr_map[12] = cpu_to_be32(0x00010021); |
| 278 | fdt_event_ctr_map[13] = cpu_to_be32(0x00010021); |
| 279 | fdt_event_ctr_map[14] = cpu_to_be32(cmask); |
| 280 | |
| 281 | /* This a OpenSBI specific DT property documented in OpenSBI docs */ |
| 282 | qemu_fdt_setprop(fdt, pmu_name, "riscv,event-to-mhpmcounters", |
| 283 | fdt_event_ctr_map, sizeof(fdt_event_ctr_map)); |
| 284 | } |