]>
Commit | Line | Data |
---|---|---|
df1d8a1f PB |
1 | /* |
2 | * MIPS Boston development board emulation. | |
3 | * | |
4 | * Copyright (c) 2016 Imagination Technologies | |
5 | * | |
6 | * This library is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Lesser General Public | |
8 | * License as published by the Free Software Foundation; either | |
4a129ccd | 9 | * version 2.1 of the License, or (at your option) any later version. |
df1d8a1f PB |
10 | * |
11 | * This library is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * Lesser General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU Lesser General Public | |
17 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include "qemu/osdep.h" | |
fc6b3cf9 | 21 | #include "qemu/units.h" |
df1d8a1f | 22 | |
10e3f30f | 23 | #include "elf.h" |
df1d8a1f PB |
24 | #include "hw/boards.h" |
25 | #include "hw/char/serial.h" | |
df1d8a1f PB |
26 | #include "hw/ide/pci.h" |
27 | #include "hw/ide/ahci.h" | |
28 | #include "hw/loader.h" | |
29 | #include "hw/loader-fit.h" | |
112658eb | 30 | #include "hw/mips/bootloader.h" |
df1d8a1f | 31 | #include "hw/mips/cps.h" |
df1d8a1f | 32 | #include "hw/pci-host/xilinx-pcie.h" |
6b290b41 | 33 | #include "hw/qdev-clock.h" |
a27bd6c7 | 34 | #include "hw/qdev-properties.h" |
df1d8a1f | 35 | #include "qapi/error.h" |
df1d8a1f PB |
36 | #include "qemu/error-report.h" |
37 | #include "qemu/log.h" | |
8228e353 | 38 | #include "chardev/char.h" |
df1d8a1f PB |
39 | #include "sysemu/device_tree.h" |
40 | #include "sysemu/sysemu.h" | |
41 | #include "sysemu/qtest.h" | |
54d31236 | 42 | #include "sysemu/runstate.h" |
df1d8a1f PB |
43 | |
44 | #include <libfdt.h> | |
db1015e9 | 45 | #include "qom/object.h" |
df1d8a1f | 46 | |
27cf0896 | 47 | #define TYPE_BOSTON "mips-boston" |
db1015e9 | 48 | typedef struct BostonState BostonState; |
8110fa1d | 49 | DECLARE_INSTANCE_CHECKER(BostonState, BOSTON, |
27cf0896 | 50 | TYPE_BOSTON) |
df1d8a1f | 51 | |
72303899 JY |
52 | #define FDT_IRQ_TYPE_NONE 0 |
53 | #define FDT_IRQ_TYPE_LEVEL_HIGH 4 | |
54 | #define FDT_GIC_SHARED 0 | |
55 | #define FDT_GIC_LOCAL 1 | |
56 | #define FDT_BOSTON_CLK_SYS 1 | |
57 | #define FDT_BOSTON_CLK_CPU 2 | |
58 | #define FDT_PCI_IRQ_MAP_PINS 4 | |
59 | #define FDT_PCI_IRQ_MAP_DESCS 6 | |
60 | ||
db1015e9 | 61 | struct BostonState { |
df1d8a1f PB |
62 | SysBusDevice parent_obj; |
63 | ||
64 | MachineState *mach; | |
2d5fac80 | 65 | MIPSCPSState cps; |
490a9d9b | 66 | SerialMM *uart; |
6b290b41 | 67 | Clock *cpuclk; |
df1d8a1f PB |
68 | |
69 | CharBackend lcd_display; | |
70 | char lcd_content[8]; | |
71 | bool lcd_inited; | |
72 | ||
73 | hwaddr kernel_entry; | |
74 | hwaddr fdt_base; | |
db1015e9 | 75 | }; |
df1d8a1f | 76 | |
e07f3e26 JY |
77 | enum { |
78 | BOSTON_LOWDDR, | |
79 | BOSTON_PCIE0, | |
80 | BOSTON_PCIE1, | |
81 | BOSTON_PCIE2, | |
82 | BOSTON_PCIE2_MMIO, | |
83 | BOSTON_CM, | |
84 | BOSTON_GIC, | |
85 | BOSTON_CDMM, | |
86 | BOSTON_CPC, | |
87 | BOSTON_PLATREG, | |
88 | BOSTON_UART, | |
89 | BOSTON_LCD, | |
90 | BOSTON_FLASH, | |
91 | BOSTON_PCIE1_MMIO, | |
92 | BOSTON_PCIE0_MMIO, | |
93 | BOSTON_HIGHDDR, | |
94 | }; | |
95 | ||
96 | static const MemMapEntry boston_memmap[] = { | |
97 | [BOSTON_LOWDDR] = { 0x0, 0x10000000 }, | |
98 | [BOSTON_PCIE0] = { 0x10000000, 0x2000000 }, | |
99 | [BOSTON_PCIE1] = { 0x12000000, 0x2000000 }, | |
100 | [BOSTON_PCIE2] = { 0x14000000, 0x2000000 }, | |
101 | [BOSTON_PCIE2_MMIO] = { 0x16000000, 0x100000 }, | |
102 | [BOSTON_CM] = { 0x16100000, 0x20000 }, | |
103 | [BOSTON_GIC] = { 0x16120000, 0x20000 }, | |
104 | [BOSTON_CDMM] = { 0x16140000, 0x8000 }, | |
105 | [BOSTON_CPC] = { 0x16200000, 0x8000 }, | |
106 | [BOSTON_PLATREG] = { 0x17ffd000, 0x1000 }, | |
107 | [BOSTON_UART] = { 0x17ffe000, 0x20 }, | |
108 | [BOSTON_LCD] = { 0x17fff000, 0x8 }, | |
109 | [BOSTON_FLASH] = { 0x18000000, 0x8000000 }, | |
110 | [BOSTON_PCIE1_MMIO] = { 0x20000000, 0x20000000 }, | |
111 | [BOSTON_PCIE0_MMIO] = { 0x40000000, 0x40000000 }, | |
112 | [BOSTON_HIGHDDR] = { 0x80000000, 0x0 }, | |
113 | }; | |
114 | ||
df1d8a1f PB |
115 | enum boston_plat_reg { |
116 | PLAT_FPGA_BUILD = 0x00, | |
117 | PLAT_CORE_CL = 0x04, | |
118 | PLAT_WRAPPER_CL = 0x08, | |
119 | PLAT_SYSCLK_STATUS = 0x0c, | |
120 | PLAT_SOFTRST_CTL = 0x10, | |
121 | #define PLAT_SOFTRST_CTL_SYSRESET (1 << 4) | |
122 | PLAT_DDR3_STATUS = 0x14, | |
123 | #define PLAT_DDR3_STATUS_LOCKED (1 << 0) | |
124 | #define PLAT_DDR3_STATUS_CALIBRATED (1 << 2) | |
125 | PLAT_PCIE_STATUS = 0x18, | |
126 | #define PLAT_PCIE_STATUS_PCIE0_LOCKED (1 << 0) | |
127 | #define PLAT_PCIE_STATUS_PCIE1_LOCKED (1 << 8) | |
128 | #define PLAT_PCIE_STATUS_PCIE2_LOCKED (1 << 16) | |
129 | PLAT_FLASH_CTL = 0x1c, | |
130 | PLAT_SPARE0 = 0x20, | |
131 | PLAT_SPARE1 = 0x24, | |
132 | PLAT_SPARE2 = 0x28, | |
133 | PLAT_SPARE3 = 0x2c, | |
134 | PLAT_MMCM_DIV = 0x30, | |
135 | #define PLAT_MMCM_DIV_CLK0DIV_SHIFT 0 | |
136 | #define PLAT_MMCM_DIV_INPUT_SHIFT 8 | |
137 | #define PLAT_MMCM_DIV_MUL_SHIFT 16 | |
138 | #define PLAT_MMCM_DIV_CLK1DIV_SHIFT 24 | |
139 | PLAT_BUILD_CFG = 0x34, | |
140 | #define PLAT_BUILD_CFG_IOCU_EN (1 << 0) | |
141 | #define PLAT_BUILD_CFG_PCIE0_EN (1 << 1) | |
142 | #define PLAT_BUILD_CFG_PCIE1_EN (1 << 2) | |
143 | #define PLAT_BUILD_CFG_PCIE2_EN (1 << 3) | |
144 | PLAT_DDR_CFG = 0x38, | |
145 | #define PLAT_DDR_CFG_SIZE (0xf << 0) | |
146 | #define PLAT_DDR_CFG_MHZ (0xfff << 4) | |
147 | PLAT_NOC_PCIE0_ADDR = 0x3c, | |
148 | PLAT_NOC_PCIE1_ADDR = 0x40, | |
149 | PLAT_NOC_PCIE2_ADDR = 0x44, | |
150 | PLAT_SYS_CTL = 0x48, | |
151 | }; | |
152 | ||
083b266f | 153 | static void boston_lcd_event(void *opaque, QEMUChrEvent event) |
df1d8a1f PB |
154 | { |
155 | BostonState *s = opaque; | |
156 | if (event == CHR_EVENT_OPENED && !s->lcd_inited) { | |
157 | qemu_chr_fe_printf(&s->lcd_display, " "); | |
158 | s->lcd_inited = true; | |
159 | } | |
160 | } | |
161 | ||
162 | static uint64_t boston_lcd_read(void *opaque, hwaddr addr, | |
163 | unsigned size) | |
164 | { | |
165 | BostonState *s = opaque; | |
166 | uint64_t val = 0; | |
167 | ||
168 | switch (size) { | |
169 | case 8: | |
170 | val |= (uint64_t)s->lcd_content[(addr + 7) & 0x7] << 56; | |
171 | val |= (uint64_t)s->lcd_content[(addr + 6) & 0x7] << 48; | |
172 | val |= (uint64_t)s->lcd_content[(addr + 5) & 0x7] << 40; | |
173 | val |= (uint64_t)s->lcd_content[(addr + 4) & 0x7] << 32; | |
174 | /* fall through */ | |
175 | case 4: | |
176 | val |= (uint64_t)s->lcd_content[(addr + 3) & 0x7] << 24; | |
177 | val |= (uint64_t)s->lcd_content[(addr + 2) & 0x7] << 16; | |
178 | /* fall through */ | |
179 | case 2: | |
180 | val |= (uint64_t)s->lcd_content[(addr + 1) & 0x7] << 8; | |
181 | /* fall through */ | |
182 | case 1: | |
183 | val |= (uint64_t)s->lcd_content[(addr + 0) & 0x7]; | |
184 | break; | |
185 | } | |
186 | ||
187 | return val; | |
188 | } | |
189 | ||
190 | static void boston_lcd_write(void *opaque, hwaddr addr, | |
191 | uint64_t val, unsigned size) | |
192 | { | |
193 | BostonState *s = opaque; | |
194 | ||
195 | switch (size) { | |
196 | case 8: | |
197 | s->lcd_content[(addr + 7) & 0x7] = val >> 56; | |
198 | s->lcd_content[(addr + 6) & 0x7] = val >> 48; | |
199 | s->lcd_content[(addr + 5) & 0x7] = val >> 40; | |
200 | s->lcd_content[(addr + 4) & 0x7] = val >> 32; | |
201 | /* fall through */ | |
202 | case 4: | |
203 | s->lcd_content[(addr + 3) & 0x7] = val >> 24; | |
204 | s->lcd_content[(addr + 2) & 0x7] = val >> 16; | |
205 | /* fall through */ | |
206 | case 2: | |
207 | s->lcd_content[(addr + 1) & 0x7] = val >> 8; | |
208 | /* fall through */ | |
209 | case 1: | |
210 | s->lcd_content[(addr + 0) & 0x7] = val; | |
211 | break; | |
212 | } | |
213 | ||
214 | qemu_chr_fe_printf(&s->lcd_display, | |
215 | "\r%-8.8s", s->lcd_content); | |
216 | } | |
217 | ||
218 | static const MemoryRegionOps boston_lcd_ops = { | |
219 | .read = boston_lcd_read, | |
220 | .write = boston_lcd_write, | |
221 | .endianness = DEVICE_NATIVE_ENDIAN, | |
222 | }; | |
223 | ||
224 | static uint64_t boston_platreg_read(void *opaque, hwaddr addr, | |
225 | unsigned size) | |
226 | { | |
227 | BostonState *s = opaque; | |
228 | uint32_t gic_freq, val; | |
229 | ||
230 | if (size != 4) { | |
c4c98835 | 231 | qemu_log_mask(LOG_UNIMP, "%uB platform register read\n", size); |
df1d8a1f PB |
232 | return 0; |
233 | } | |
234 | ||
235 | switch (addr & 0xffff) { | |
236 | case PLAT_FPGA_BUILD: | |
237 | case PLAT_CORE_CL: | |
238 | case PLAT_WRAPPER_CL: | |
239 | return 0; | |
240 | case PLAT_DDR3_STATUS: | |
241 | return PLAT_DDR3_STATUS_LOCKED | PLAT_DDR3_STATUS_CALIBRATED; | |
242 | case PLAT_MMCM_DIV: | |
2d5fac80 | 243 | gic_freq = mips_gictimer_get_freq(s->cps.gic.gic_timer) / 1000000; |
df1d8a1f PB |
244 | val = gic_freq << PLAT_MMCM_DIV_INPUT_SHIFT; |
245 | val |= 1 << PLAT_MMCM_DIV_MUL_SHIFT; | |
246 | val |= 1 << PLAT_MMCM_DIV_CLK0DIV_SHIFT; | |
247 | val |= 1 << PLAT_MMCM_DIV_CLK1DIV_SHIFT; | |
248 | return val; | |
249 | case PLAT_BUILD_CFG: | |
250 | val = PLAT_BUILD_CFG_PCIE0_EN; | |
251 | val |= PLAT_BUILD_CFG_PCIE1_EN; | |
252 | val |= PLAT_BUILD_CFG_PCIE2_EN; | |
253 | return val; | |
254 | case PLAT_DDR_CFG: | |
d23b6caa | 255 | val = s->mach->ram_size / GiB; |
df1d8a1f PB |
256 | assert(!(val & ~PLAT_DDR_CFG_SIZE)); |
257 | val |= PLAT_DDR_CFG_MHZ; | |
258 | return val; | |
259 | default: | |
c4c98835 | 260 | qemu_log_mask(LOG_UNIMP, "Read platform register 0x%" HWADDR_PRIx "\n", |
df1d8a1f PB |
261 | addr & 0xffff); |
262 | return 0; | |
263 | } | |
264 | } | |
265 | ||
266 | static void boston_platreg_write(void *opaque, hwaddr addr, | |
267 | uint64_t val, unsigned size) | |
268 | { | |
269 | if (size != 4) { | |
c4c98835 | 270 | qemu_log_mask(LOG_UNIMP, "%uB platform register write\n", size); |
df1d8a1f PB |
271 | return; |
272 | } | |
273 | ||
274 | switch (addr & 0xffff) { | |
275 | case PLAT_FPGA_BUILD: | |
276 | case PLAT_CORE_CL: | |
277 | case PLAT_WRAPPER_CL: | |
278 | case PLAT_DDR3_STATUS: | |
279 | case PLAT_PCIE_STATUS: | |
280 | case PLAT_MMCM_DIV: | |
281 | case PLAT_BUILD_CFG: | |
282 | case PLAT_DDR_CFG: | |
283 | /* read only */ | |
284 | break; | |
285 | case PLAT_SOFTRST_CTL: | |
286 | if (val & PLAT_SOFTRST_CTL_SYSRESET) { | |
cf83f140 | 287 | qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); |
df1d8a1f PB |
288 | } |
289 | break; | |
290 | default: | |
291 | qemu_log_mask(LOG_UNIMP, "Write platform register 0x%" HWADDR_PRIx | |
c4c98835 | 292 | " = 0x%" PRIx64 "\n", addr & 0xffff, val); |
df1d8a1f PB |
293 | break; |
294 | } | |
295 | } | |
296 | ||
297 | static const MemoryRegionOps boston_platreg_ops = { | |
298 | .read = boston_platreg_read, | |
299 | .write = boston_platreg_write, | |
300 | .endianness = DEVICE_NATIVE_ENDIAN, | |
301 | }; | |
302 | ||
6b290b41 PMD |
303 | static void mips_boston_instance_init(Object *obj) |
304 | { | |
305 | BostonState *s = BOSTON(obj); | |
306 | ||
307 | s->cpuclk = qdev_init_clock_out(DEVICE(obj), "cpu-refclk"); | |
308 | clock_set_hz(s->cpuclk, 1000000000); /* 1 GHz */ | |
309 | } | |
310 | ||
df1d8a1f | 311 | static const TypeInfo boston_device = { |
27cf0896 | 312 | .name = TYPE_BOSTON, |
df1d8a1f PB |
313 | .parent = TYPE_SYS_BUS_DEVICE, |
314 | .instance_size = sizeof(BostonState), | |
6b290b41 | 315 | .instance_init = mips_boston_instance_init, |
df1d8a1f PB |
316 | }; |
317 | ||
318 | static void boston_register_types(void) | |
319 | { | |
320 | type_register_static(&boston_device); | |
321 | } | |
322 | type_init(boston_register_types) | |
323 | ||
283eae17 | 324 | static void gen_firmware(uint32_t *p, hwaddr kernel_entry, hwaddr fdt_addr) |
df1d8a1f | 325 | { |
e07f3e26 | 326 | uint64_t regaddr; |
df1d8a1f PB |
327 | |
328 | /* Move CM GCRs */ | |
e07f3e26 JY |
329 | regaddr = cpu_mips_phys_to_kseg1(NULL, GCR_BASE_ADDR + GCR_BASE_OFS), |
330 | bl_gen_write_ulong(&p, regaddr, | |
331 | boston_memmap[BOSTON_CM].base); | |
df1d8a1f PB |
332 | |
333 | /* Move & enable GIC GCRs */ | |
e07f3e26 JY |
334 | regaddr = cpu_mips_phys_to_kseg1(NULL, boston_memmap[BOSTON_CM].base |
335 | + GCR_GIC_BASE_OFS), | |
336 | bl_gen_write_ulong(&p, regaddr, | |
337 | boston_memmap[BOSTON_GIC].base | GCR_GIC_BASE_GICEN_MSK); | |
df1d8a1f PB |
338 | |
339 | /* Move & enable CPC GCRs */ | |
e07f3e26 JY |
340 | regaddr = cpu_mips_phys_to_kseg1(NULL, boston_memmap[BOSTON_CM].base |
341 | + GCR_CPC_BASE_OFS), | |
342 | bl_gen_write_ulong(&p, regaddr, | |
343 | boston_memmap[BOSTON_CPC].base | GCR_CPC_BASE_CPCEN_MSK); | |
df1d8a1f PB |
344 | |
345 | /* | |
346 | * Setup argument registers to follow the UHI boot protocol: | |
347 | * | |
348 | * a0/$4 = -2 | |
349 | * a1/$5 = virtual address of FDT | |
350 | * a2/$6 = 0 | |
351 | * a3/$7 = 0 | |
352 | */ | |
112658eb | 353 | bl_gen_jump_kernel(&p, 0, (int32_t)-2, fdt_addr, 0, 0, kernel_entry); |
df1d8a1f PB |
354 | } |
355 | ||
356 | static const void *boston_fdt_filter(void *opaque, const void *fdt_orig, | |
357 | const void *match_data, hwaddr *load_addr) | |
358 | { | |
359 | BostonState *s = BOSTON(opaque); | |
360 | MachineState *machine = s->mach; | |
361 | const char *cmdline; | |
362 | int err; | |
bf4ee88a PM |
363 | size_t ram_low_sz, ram_high_sz; |
364 | size_t fdt_sz = fdt_totalsize(fdt_orig) * 2; | |
365 | g_autofree void *fdt = g_malloc0(fdt_sz); | |
df1d8a1f PB |
366 | |
367 | err = fdt_open_into(fdt_orig, fdt, fdt_sz); | |
368 | if (err) { | |
369 | fprintf(stderr, "unable to open FDT\n"); | |
370 | return NULL; | |
371 | } | |
372 | ||
373 | cmdline = (machine->kernel_cmdline && machine->kernel_cmdline[0]) | |
374 | ? machine->kernel_cmdline : " "; | |
375 | err = qemu_fdt_setprop_string(fdt, "/chosen", "bootargs", cmdline); | |
376 | if (err < 0) { | |
377 | fprintf(stderr, "couldn't set /chosen/bootargs\n"); | |
378 | return NULL; | |
379 | } | |
380 | ||
d23b6caa | 381 | ram_low_sz = MIN(256 * MiB, machine->ram_size); |
df1d8a1f PB |
382 | ram_high_sz = machine->ram_size - ram_low_sz; |
383 | qemu_fdt_setprop_sized_cells(fdt, "/memory@0", "reg", | |
e07f3e26 JY |
384 | 1, boston_memmap[BOSTON_LOWDDR].base, 1, ram_low_sz, |
385 | 1, boston_memmap[BOSTON_HIGHDDR].base + ram_low_sz, | |
386 | 1, ram_high_sz); | |
df1d8a1f PB |
387 | |
388 | fdt = g_realloc(fdt, fdt_totalsize(fdt)); | |
389 | qemu_fdt_dumpdtb(fdt, fdt_sz); | |
390 | ||
391 | s->fdt_base = *load_addr; | |
392 | ||
bf4ee88a | 393 | return g_steal_pointer(&fdt); |
df1d8a1f PB |
394 | } |
395 | ||
396 | static const void *boston_kernel_filter(void *opaque, const void *kernel, | |
397 | hwaddr *load_addr, hwaddr *entry_addr) | |
398 | { | |
399 | BostonState *s = BOSTON(opaque); | |
400 | ||
401 | s->kernel_entry = *entry_addr; | |
402 | ||
403 | return kernel; | |
404 | } | |
405 | ||
406 | static const struct fit_loader_match boston_matches[] = { | |
407 | { "img,boston" }, | |
408 | { NULL }, | |
409 | }; | |
410 | ||
411 | static const struct fit_loader boston_fit_loader = { | |
412 | .matches = boston_matches, | |
413 | .addr_to_phys = cpu_mips_kseg0_to_phys, | |
414 | .fdt_filter = boston_fdt_filter, | |
415 | .kernel_filter = boston_kernel_filter, | |
416 | }; | |
417 | ||
418 | static inline XilinxPCIEHost * | |
419 | xilinx_pcie_init(MemoryRegion *sys_mem, uint32_t bus_nr, | |
420 | hwaddr cfg_base, uint64_t cfg_size, | |
421 | hwaddr mmio_base, uint64_t mmio_size, | |
422 | qemu_irq irq, bool link_up) | |
423 | { | |
424 | DeviceState *dev; | |
425 | MemoryRegion *cfg, *mmio; | |
426 | ||
3e80f690 | 427 | dev = qdev_new(TYPE_XILINX_PCIE_HOST); |
df1d8a1f PB |
428 | |
429 | qdev_prop_set_uint32(dev, "bus_nr", bus_nr); | |
430 | qdev_prop_set_uint64(dev, "cfg_base", cfg_base); | |
431 | qdev_prop_set_uint64(dev, "cfg_size", cfg_size); | |
432 | qdev_prop_set_uint64(dev, "mmio_base", mmio_base); | |
433 | qdev_prop_set_uint64(dev, "mmio_size", mmio_size); | |
434 | qdev_prop_set_bit(dev, "link_up", link_up); | |
435 | ||
3c6ef471 | 436 | sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); |
df1d8a1f PB |
437 | |
438 | cfg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0); | |
439 | memory_region_add_subregion_overlap(sys_mem, cfg_base, cfg, 0); | |
440 | ||
441 | mmio = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1); | |
442 | memory_region_add_subregion_overlap(sys_mem, 0, mmio, 0); | |
443 | ||
444 | qdev_connect_gpio_out_named(dev, "interrupt_out", 0, irq); | |
445 | ||
446 | return XILINX_PCIE_HOST(dev); | |
447 | } | |
448 | ||
72303899 JY |
449 | |
450 | static void fdt_create_pcie(void *fdt, int gic_ph, int irq, hwaddr reg_base, | |
451 | hwaddr reg_size, hwaddr mmio_base, hwaddr mmio_size) | |
452 | { | |
453 | int i; | |
454 | char *name, *intc_name; | |
455 | uint32_t intc_ph; | |
456 | uint32_t interrupt_map[FDT_PCI_IRQ_MAP_PINS][FDT_PCI_IRQ_MAP_DESCS]; | |
457 | ||
458 | intc_ph = qemu_fdt_alloc_phandle(fdt); | |
459 | name = g_strdup_printf("/soc/pci@%" HWADDR_PRIx, reg_base); | |
460 | qemu_fdt_add_subnode(fdt, name); | |
461 | qemu_fdt_setprop_string(fdt, name, "compatible", | |
462 | "xlnx,axi-pcie-host-1.00.a"); | |
463 | qemu_fdt_setprop_string(fdt, name, "device_type", "pci"); | |
464 | qemu_fdt_setprop_cells(fdt, name, "reg", reg_base, reg_size); | |
465 | ||
466 | qemu_fdt_setprop_cell(fdt, name, "#address-cells", 3); | |
467 | qemu_fdt_setprop_cell(fdt, name, "#size-cells", 2); | |
468 | qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 1); | |
469 | ||
470 | qemu_fdt_setprop_cell(fdt, name, "interrupt-parent", gic_ph); | |
471 | qemu_fdt_setprop_cells(fdt, name, "interrupts", FDT_GIC_SHARED, irq, | |
472 | FDT_IRQ_TYPE_LEVEL_HIGH); | |
473 | ||
474 | qemu_fdt_setprop_cells(fdt, name, "ranges", 0x02000000, 0, mmio_base, | |
475 | mmio_base, 0, mmio_size); | |
476 | qemu_fdt_setprop_cells(fdt, name, "bus-range", 0x00, 0xff); | |
477 | ||
478 | ||
479 | ||
480 | intc_name = g_strdup_printf("%s/interrupt-controller", name); | |
481 | qemu_fdt_add_subnode(fdt, intc_name); | |
482 | qemu_fdt_setprop(fdt, intc_name, "interrupt-controller", NULL, 0); | |
483 | qemu_fdt_setprop_cell(fdt, intc_name, "#address-cells", 0); | |
484 | qemu_fdt_setprop_cell(fdt, intc_name, "#interrupt-cells", 1); | |
485 | qemu_fdt_setprop_cell(fdt, intc_name, "phandle", intc_ph); | |
486 | ||
487 | qemu_fdt_setprop_cells(fdt, name, "interrupt-map-mask", 0, 0, 0, 7); | |
488 | for (i = 0; i < FDT_PCI_IRQ_MAP_PINS; i++) { | |
489 | uint32_t *irqmap = interrupt_map[i]; | |
490 | ||
491 | irqmap[0] = cpu_to_be32(0); | |
492 | irqmap[1] = cpu_to_be32(0); | |
493 | irqmap[2] = cpu_to_be32(0); | |
494 | irqmap[3] = cpu_to_be32(i + 1); | |
495 | irqmap[4] = cpu_to_be32(intc_ph); | |
496 | irqmap[5] = cpu_to_be32(i + 1); | |
497 | } | |
498 | qemu_fdt_setprop(fdt, name, "interrupt-map", | |
499 | &interrupt_map, sizeof(interrupt_map)); | |
500 | ||
501 | g_free(intc_name); | |
502 | g_free(name); | |
503 | } | |
504 | ||
505 | static const void *create_fdt(BostonState *s, | |
506 | const MemMapEntry *memmap, int *dt_size) | |
507 | { | |
508 | void *fdt; | |
509 | int cpu; | |
510 | MachineState *mc = s->mach; | |
511 | uint32_t platreg_ph, gic_ph, clk_ph; | |
512 | char *name, *gic_name, *platreg_name, *stdout_name; | |
513 | static const char * const syscon_compat[2] = { | |
514 | "img,boston-platform-regs", "syscon" | |
515 | }; | |
516 | ||
517 | fdt = create_device_tree(dt_size); | |
518 | if (!fdt) { | |
519 | error_report("create_device_tree() failed"); | |
520 | exit(1); | |
521 | } | |
522 | ||
523 | platreg_ph = qemu_fdt_alloc_phandle(fdt); | |
524 | gic_ph = qemu_fdt_alloc_phandle(fdt); | |
525 | clk_ph = qemu_fdt_alloc_phandle(fdt); | |
526 | ||
527 | qemu_fdt_setprop_string(fdt, "/", "model", "img,boston"); | |
528 | qemu_fdt_setprop_string(fdt, "/", "compatible", "img,boston"); | |
529 | qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x1); | |
530 | qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x1); | |
531 | ||
532 | ||
533 | qemu_fdt_add_subnode(fdt, "/cpus"); | |
534 | qemu_fdt_setprop_cell(fdt, "/cpus", "#size-cells", 0x0); | |
535 | qemu_fdt_setprop_cell(fdt, "/cpus", "#address-cells", 0x1); | |
536 | ||
537 | for (cpu = 0; cpu < mc->smp.cpus; cpu++) { | |
538 | name = g_strdup_printf("/cpus/cpu@%d", cpu); | |
539 | qemu_fdt_add_subnode(fdt, name); | |
540 | qemu_fdt_setprop_string(fdt, name, "compatible", "img,mips"); | |
541 | qemu_fdt_setprop_string(fdt, name, "status", "okay"); | |
542 | qemu_fdt_setprop_cell(fdt, name, "reg", cpu); | |
543 | qemu_fdt_setprop_string(fdt, name, "device_type", "cpu"); | |
544 | qemu_fdt_setprop_cells(fdt, name, "clocks", clk_ph, FDT_BOSTON_CLK_CPU); | |
545 | g_free(name); | |
546 | } | |
547 | ||
548 | qemu_fdt_add_subnode(fdt, "/soc"); | |
549 | qemu_fdt_setprop(fdt, "/soc", "ranges", NULL, 0); | |
550 | qemu_fdt_setprop_string(fdt, "/soc", "compatible", "simple-bus"); | |
551 | qemu_fdt_setprop_cell(fdt, "/soc", "#size-cells", 0x1); | |
552 | qemu_fdt_setprop_cell(fdt, "/soc", "#address-cells", 0x1); | |
553 | ||
554 | fdt_create_pcie(fdt, gic_ph, 2, | |
555 | memmap[BOSTON_PCIE0].base, memmap[BOSTON_PCIE0].size, | |
556 | memmap[BOSTON_PCIE0_MMIO].base, memmap[BOSTON_PCIE0_MMIO].size); | |
557 | ||
558 | fdt_create_pcie(fdt, gic_ph, 1, | |
559 | memmap[BOSTON_PCIE1].base, memmap[BOSTON_PCIE1].size, | |
560 | memmap[BOSTON_PCIE1_MMIO].base, memmap[BOSTON_PCIE1_MMIO].size); | |
561 | ||
562 | fdt_create_pcie(fdt, gic_ph, 0, | |
563 | memmap[BOSTON_PCIE2].base, memmap[BOSTON_PCIE2].size, | |
564 | memmap[BOSTON_PCIE2_MMIO].base, memmap[BOSTON_PCIE2_MMIO].size); | |
565 | ||
566 | /* GIC with it's timer node */ | |
567 | gic_name = g_strdup_printf("/soc/interrupt-controller@%" HWADDR_PRIx, | |
568 | memmap[BOSTON_GIC].base); | |
569 | qemu_fdt_add_subnode(fdt, gic_name); | |
570 | qemu_fdt_setprop_string(fdt, gic_name, "compatible", "mti,gic"); | |
571 | qemu_fdt_setprop_cells(fdt, gic_name, "reg", memmap[BOSTON_GIC].base, | |
572 | memmap[BOSTON_GIC].size); | |
573 | qemu_fdt_setprop(fdt, gic_name, "interrupt-controller", NULL, 0); | |
574 | qemu_fdt_setprop_cell(fdt, gic_name, "#interrupt-cells", 3); | |
575 | qemu_fdt_setprop_cell(fdt, gic_name, "phandle", gic_ph); | |
576 | ||
577 | name = g_strdup_printf("%s/timer", gic_name); | |
578 | qemu_fdt_add_subnode(fdt, name); | |
579 | qemu_fdt_setprop_string(fdt, name, "compatible", "mti,gic-timer"); | |
580 | qemu_fdt_setprop_cells(fdt, name, "interrupts", FDT_GIC_LOCAL, 1, | |
581 | FDT_IRQ_TYPE_NONE); | |
582 | qemu_fdt_setprop_cells(fdt, name, "clocks", clk_ph, FDT_BOSTON_CLK_CPU); | |
583 | g_free(name); | |
584 | g_free(gic_name); | |
585 | ||
586 | /* CDMM node */ | |
587 | name = g_strdup_printf("/soc/cdmm@%" HWADDR_PRIx, memmap[BOSTON_CDMM].base); | |
588 | qemu_fdt_add_subnode(fdt, name); | |
589 | qemu_fdt_setprop_string(fdt, name, "compatible", "mti,mips-cdmm"); | |
590 | qemu_fdt_setprop_cells(fdt, name, "reg", memmap[BOSTON_CDMM].base, | |
591 | memmap[BOSTON_CDMM].size); | |
592 | g_free(name); | |
593 | ||
594 | /* CPC node */ | |
595 | name = g_strdup_printf("/soc/cpc@%" HWADDR_PRIx, memmap[BOSTON_CPC].base); | |
596 | qemu_fdt_add_subnode(fdt, name); | |
597 | qemu_fdt_setprop_string(fdt, name, "compatible", "mti,mips-cpc"); | |
598 | qemu_fdt_setprop_cells(fdt, name, "reg", memmap[BOSTON_CPC].base, | |
599 | memmap[BOSTON_CPC].size); | |
600 | g_free(name); | |
601 | ||
602 | /* platreg and it's clk node */ | |
603 | platreg_name = g_strdup_printf("/soc/system-controller@%" HWADDR_PRIx, | |
604 | memmap[BOSTON_PLATREG].base); | |
605 | qemu_fdt_add_subnode(fdt, platreg_name); | |
606 | qemu_fdt_setprop_string_array(fdt, platreg_name, "compatible", | |
607 | (char **)&syscon_compat, | |
608 | ARRAY_SIZE(syscon_compat)); | |
609 | qemu_fdt_setprop_cells(fdt, platreg_name, "reg", | |
610 | memmap[BOSTON_PLATREG].base, | |
611 | memmap[BOSTON_PLATREG].size); | |
612 | qemu_fdt_setprop_cell(fdt, platreg_name, "phandle", platreg_ph); | |
613 | ||
614 | name = g_strdup_printf("%s/clock", platreg_name); | |
615 | qemu_fdt_add_subnode(fdt, name); | |
616 | qemu_fdt_setprop_string(fdt, name, "compatible", "img,boston-clock"); | |
617 | qemu_fdt_setprop_cell(fdt, name, "#clock-cells", 1); | |
618 | qemu_fdt_setprop_cell(fdt, name, "phandle", clk_ph); | |
619 | g_free(name); | |
620 | g_free(platreg_name); | |
621 | ||
622 | /* reboot node */ | |
623 | name = g_strdup_printf("/soc/reboot"); | |
624 | qemu_fdt_add_subnode(fdt, name); | |
625 | qemu_fdt_setprop_string(fdt, name, "compatible", "syscon-reboot"); | |
626 | qemu_fdt_setprop_cell(fdt, name, "regmap", platreg_ph); | |
627 | qemu_fdt_setprop_cell(fdt, name, "offset", 0x10); | |
628 | qemu_fdt_setprop_cell(fdt, name, "mask", 0x10); | |
629 | g_free(name); | |
630 | ||
631 | /* uart node */ | |
632 | name = g_strdup_printf("/soc/uart@%" HWADDR_PRIx, memmap[BOSTON_UART].base); | |
633 | qemu_fdt_add_subnode(fdt, name); | |
634 | qemu_fdt_setprop_string(fdt, name, "compatible", "ns16550a"); | |
635 | qemu_fdt_setprop_cells(fdt, name, "reg", memmap[BOSTON_UART].base, | |
636 | memmap[BOSTON_UART].size); | |
637 | qemu_fdt_setprop_cell(fdt, name, "reg-shift", 0x2); | |
638 | qemu_fdt_setprop_cell(fdt, name, "interrupt-parent", gic_ph); | |
639 | qemu_fdt_setprop_cells(fdt, name, "interrupts", FDT_GIC_SHARED, 3, | |
640 | FDT_IRQ_TYPE_LEVEL_HIGH); | |
641 | qemu_fdt_setprop_cells(fdt, name, "clocks", clk_ph, FDT_BOSTON_CLK_SYS); | |
642 | ||
643 | qemu_fdt_add_subnode(fdt, "/chosen"); | |
644 | stdout_name = g_strdup_printf("%s:115200", name); | |
645 | qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", stdout_name); | |
646 | g_free(stdout_name); | |
647 | g_free(name); | |
648 | ||
649 | /* lcd node */ | |
650 | name = g_strdup_printf("/soc/lcd@%" HWADDR_PRIx, memmap[BOSTON_LCD].base); | |
651 | qemu_fdt_add_subnode(fdt, name); | |
652 | qemu_fdt_setprop_string(fdt, name, "compatible", "img,boston-lcd"); | |
653 | qemu_fdt_setprop_cells(fdt, name, "reg", memmap[BOSTON_LCD].base, | |
654 | memmap[BOSTON_LCD].size); | |
655 | g_free(name); | |
656 | ||
657 | name = g_strdup_printf("/memory@0"); | |
658 | qemu_fdt_add_subnode(fdt, name); | |
659 | qemu_fdt_setprop_string(fdt, name, "device_type", "memory"); | |
660 | g_free(name); | |
661 | ||
662 | return fdt; | |
663 | } | |
664 | ||
df1d8a1f PB |
665 | static void boston_mach_init(MachineState *machine) |
666 | { | |
667 | DeviceState *dev; | |
668 | BostonState *s; | |
9389d6ce | 669 | MemoryRegion *flash, *ddr_low_alias, *lcd, *platreg; |
df1d8a1f PB |
670 | MemoryRegion *sys_mem = get_system_memory(); |
671 | XilinxPCIEHost *pcie2; | |
672 | PCIDevice *ahci; | |
673 | DriveInfo *hd[6]; | |
674 | Chardev *chr; | |
675 | int fw_size, fit_err; | |
df1d8a1f | 676 | |
d23b6caa PMD |
677 | if ((machine->ram_size % GiB) || |
678 | (machine->ram_size > (2 * GiB))) { | |
df1d8a1f PB |
679 | error_report("Memory size must be 1GB or 2GB"); |
680 | exit(1); | |
681 | } | |
682 | ||
27cf0896 | 683 | dev = qdev_new(TYPE_BOSTON); |
3c6ef471 | 684 | sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); |
df1d8a1f PB |
685 | |
686 | s = BOSTON(dev); | |
687 | s->mach = machine; | |
df1d8a1f | 688 | |
ac70f976 | 689 | if (!cpu_type_supports_cps_smp(machine->cpu_type)) { |
df1d8a1f PB |
690 | error_report("Boston requires CPUs which support CPS"); |
691 | exit(1); | |
692 | } | |
693 | ||
0074fce6 | 694 | object_initialize_child(OBJECT(machine), "cps", &s->cps, TYPE_MIPS_CPS); |
5325cc34 | 695 | object_property_set_str(OBJECT(&s->cps), "cpu-type", machine->cpu_type, |
932d3a65 | 696 | &error_fatal); |
5325cc34 | 697 | object_property_set_int(OBJECT(&s->cps), "num-vp", machine->smp.cpus, |
932d3a65 | 698 | &error_fatal); |
6b290b41 PMD |
699 | qdev_connect_clock_in(DEVICE(&s->cps), "clk-in", |
700 | qdev_get_clock_out(dev, "cpu-refclk")); | |
0074fce6 | 701 | sysbus_realize(SYS_BUS_DEVICE(&s->cps), &error_fatal); |
df1d8a1f | 702 | |
2d5fac80 | 703 | sysbus_mmio_map_overlap(SYS_BUS_DEVICE(&s->cps), 0, 0, 1); |
df1d8a1f PB |
704 | |
705 | flash = g_new(MemoryRegion, 1); | |
e07f3e26 JY |
706 | memory_region_init_rom(flash, NULL, "boston.flash", |
707 | boston_memmap[BOSTON_FLASH].size, &error_fatal); | |
708 | memory_region_add_subregion_overlap(sys_mem, | |
709 | boston_memmap[BOSTON_FLASH].base, | |
710 | flash, 0); | |
df1d8a1f | 711 | |
e07f3e26 JY |
712 | memory_region_add_subregion_overlap(sys_mem, |
713 | boston_memmap[BOSTON_HIGHDDR].base, | |
714 | machine->ram, 0); | |
df1d8a1f PB |
715 | |
716 | ddr_low_alias = g_new(MemoryRegion, 1); | |
717 | memory_region_init_alias(ddr_low_alias, NULL, "boston_low.ddr", | |
9389d6ce IM |
718 | machine->ram, 0, |
719 | MIN(machine->ram_size, (256 * MiB))); | |
df1d8a1f PB |
720 | memory_region_add_subregion_overlap(sys_mem, 0, ddr_low_alias, 0); |
721 | ||
722 | xilinx_pcie_init(sys_mem, 0, | |
e07f3e26 JY |
723 | boston_memmap[BOSTON_PCIE0].base, |
724 | boston_memmap[BOSTON_PCIE0].size, | |
725 | boston_memmap[BOSTON_PCIE0_MMIO].base, | |
726 | boston_memmap[BOSTON_PCIE0_MMIO].size, | |
2d5fac80 | 727 | get_cps_irq(&s->cps, 2), false); |
df1d8a1f PB |
728 | |
729 | xilinx_pcie_init(sys_mem, 1, | |
e07f3e26 JY |
730 | boston_memmap[BOSTON_PCIE1].base, |
731 | boston_memmap[BOSTON_PCIE1].size, | |
732 | boston_memmap[BOSTON_PCIE1_MMIO].base, | |
733 | boston_memmap[BOSTON_PCIE1_MMIO].size, | |
2d5fac80 | 734 | get_cps_irq(&s->cps, 1), false); |
df1d8a1f PB |
735 | |
736 | pcie2 = xilinx_pcie_init(sys_mem, 2, | |
e07f3e26 JY |
737 | boston_memmap[BOSTON_PCIE2].base, |
738 | boston_memmap[BOSTON_PCIE2].size, | |
739 | boston_memmap[BOSTON_PCIE2_MMIO].base, | |
740 | boston_memmap[BOSTON_PCIE2_MMIO].size, | |
2d5fac80 | 741 | get_cps_irq(&s->cps, 0), true); |
df1d8a1f PB |
742 | |
743 | platreg = g_new(MemoryRegion, 1); | |
744 | memory_region_init_io(platreg, NULL, &boston_platreg_ops, s, | |
e07f3e26 JY |
745 | "boston-platregs", |
746 | boston_memmap[BOSTON_PLATREG].size); | |
747 | memory_region_add_subregion_overlap(sys_mem, | |
748 | boston_memmap[BOSTON_PLATREG].base, platreg, 0); | |
df1d8a1f | 749 | |
e07f3e26 | 750 | s->uart = serial_mm_init(sys_mem, boston_memmap[BOSTON_UART].base, 2, |
2d5fac80 | 751 | get_cps_irq(&s->cps, 3), 10000000, |
9bca0edb | 752 | serial_hd(0), DEVICE_NATIVE_ENDIAN); |
df1d8a1f PB |
753 | |
754 | lcd = g_new(MemoryRegion, 1); | |
755 | memory_region_init_io(lcd, NULL, &boston_lcd_ops, s, "boston-lcd", 0x8); | |
e07f3e26 JY |
756 | memory_region_add_subregion_overlap(sys_mem, |
757 | boston_memmap[BOSTON_LCD].base, lcd, 0); | |
df1d8a1f | 758 | |
4ad6f6cb | 759 | chr = qemu_chr_new("lcd", "vc:320x240", NULL); |
df1d8a1f PB |
760 | qemu_chr_fe_init(&s->lcd_display, chr, NULL); |
761 | qemu_chr_fe_set_handlers(&s->lcd_display, NULL, NULL, | |
81517ba3 | 762 | boston_lcd_event, NULL, s, NULL, true); |
df1d8a1f PB |
763 | |
764 | ahci = pci_create_simple_multifunction(&PCI_BRIDGE(&pcie2->root)->sec_bus, | |
765 | PCI_DEVFN(0, 0), | |
766 | true, TYPE_ICH9_AHCI); | |
bbe3179a JS |
767 | g_assert(ARRAY_SIZE(hd) == ahci_get_num_ports(ahci)); |
768 | ide_drive_get(hd, ahci_get_num_ports(ahci)); | |
df1d8a1f PB |
769 | ahci_ide_create_devs(ahci, hd); |
770 | ||
771 | if (machine->firmware) { | |
772 | fw_size = load_image_targphys(machine->firmware, | |
d23b6caa | 773 | 0x1fc00000, 4 * MiB); |
df1d8a1f | 774 | if (fw_size == -1) { |
036a2604 | 775 | error_report("unable to load firmware image '%s'", |
df1d8a1f PB |
776 | machine->firmware); |
777 | exit(1); | |
778 | } | |
779 | } else if (machine->kernel_filename) { | |
d77c462b JY |
780 | uint64_t kernel_entry, kernel_high; |
781 | ssize_t kernel_size; | |
10e3f30f JY |
782 | |
783 | kernel_size = load_elf(machine->kernel_filename, NULL, | |
784 | cpu_mips_kseg0_to_phys, NULL, | |
785 | &kernel_entry, NULL, &kernel_high, | |
786 | NULL, 0, EM_MIPS, 1, 0); | |
787 | ||
d77c462b | 788 | if (kernel_size > 0) { |
72303899 JY |
789 | int dt_size; |
790 | g_autofree const void *dtb_file_data, *dtb_load_data; | |
10e3f30f JY |
791 | hwaddr dtb_paddr = QEMU_ALIGN_UP(kernel_high, 64 * KiB); |
792 | hwaddr dtb_vaddr = cpu_mips_phys_to_kseg0(NULL, dtb_paddr); | |
793 | ||
794 | s->kernel_entry = kernel_entry; | |
795 | if (machine->dtb) { | |
10e3f30f | 796 | dtb_file_data = load_device_tree(machine->dtb, &dt_size); |
72303899 JY |
797 | } else { |
798 | dtb_file_data = create_fdt(s, boston_memmap, &dt_size); | |
10e3f30f | 799 | } |
72303899 JY |
800 | |
801 | dtb_load_data = boston_fdt_filter(s, dtb_file_data, | |
802 | NULL, &dtb_vaddr); | |
803 | ||
804 | /* Calculate real fdt size after filter */ | |
805 | dt_size = fdt_totalsize(dtb_load_data); | |
806 | rom_add_blob_fixed("dtb", dtb_load_data, dt_size, dtb_paddr); | |
10e3f30f JY |
807 | } else { |
808 | /* Try to load file as FIT */ | |
809 | fit_err = load_fit(&boston_fit_loader, machine->kernel_filename, s); | |
810 | if (fit_err) { | |
811 | error_report("unable to load kernel image"); | |
812 | exit(1); | |
813 | } | |
df1d8a1f PB |
814 | } |
815 | ||
816 | gen_firmware(memory_region_get_ram_ptr(flash) + 0x7c00000, | |
283eae17 | 817 | s->kernel_entry, s->fdt_base); |
df1d8a1f | 818 | } else if (!qtest_enabled()) { |
036a2604 | 819 | error_report("Please provide either a -kernel or -bios argument"); |
df1d8a1f PB |
820 | exit(1); |
821 | } | |
822 | } | |
823 | ||
824 | static void boston_mach_class_init(MachineClass *mc) | |
825 | { | |
826 | mc->desc = "MIPS Boston"; | |
827 | mc->init = boston_mach_init; | |
828 | mc->block_default_type = IF_IDE; | |
d23b6caa | 829 | mc->default_ram_size = 1 * GiB; |
9389d6ce | 830 | mc->default_ram_id = "boston.ddr"; |
df1d8a1f | 831 | mc->max_cpus = 16; |
a7519f2b | 832 | mc->default_cpu_type = MIPS_CPU_TYPE_NAME("I6400"); |
df1d8a1f PB |
833 | } |
834 | ||
835 | DEFINE_MACHINE("boston", boston_mach_class_init) |