]>
Commit | Line | Data |
---|---|---|
e029bb00 HD |
1 | /* |
2 | * HP-PARISC Astro/Pluto/Ike/REO system bus adapter (SBA) | |
3 | * with Elroy PCI bus (LBA) adapter emulation | |
4 | * Found in C3000 and similar machines | |
5 | * | |
6 | * (C) 2023 by Helge Deller <deller@gmx.de> | |
7 | * | |
8 | * This work is licensed under the GNU GPL license version 2 or later. | |
9 | * | |
10 | * Chip documentation is available at: | |
11 | * https://parisc.wiki.kernel.org/index.php/Technical_Documentation | |
12 | * | |
13 | * TODO: | |
14 | * - All user-added devices are currently attached to the first | |
15 | * Elroy (PCI bus) only for now. To fix this additional work in | |
16 | * SeaBIOS and this driver is needed. See "user_creatable" flag below. | |
17 | * - GMMIO (Greater than 4 GB MMIO) register | |
18 | */ | |
19 | ||
20 | #define TYPE_ASTRO_IOMMU_MEMORY_REGION "astro-iommu-memory-region" | |
21 | ||
fd842b2f HD |
22 | #define F_EXTEND(addr) ((addr) | MAKE_64BIT_MASK(32, 32)) |
23 | ||
e029bb00 HD |
24 | #include "qemu/osdep.h" |
25 | #include "qemu/module.h" | |
26 | #include "qemu/units.h" | |
27 | #include "qapi/error.h" | |
28 | #include "hw/irq.h" | |
29 | #include "hw/pci/pci_device.h" | |
30 | #include "hw/pci/pci_bus.h" | |
31 | #include "hw/qdev-properties.h" | |
32 | #include "hw/pci-host/astro.h" | |
33 | #include "hw/hppa/hppa_hardware.h" | |
34 | #include "migration/vmstate.h" | |
8066102d | 35 | #include "target/hppa/cpu.h" |
e029bb00 HD |
36 | #include "trace.h" |
37 | #include "qom/object.h" | |
38 | ||
39 | /* | |
40 | * Helper functions | |
41 | */ | |
42 | ||
43 | static uint64_t mask_32bit_val(hwaddr addr, unsigned size, uint64_t val) | |
44 | { | |
45 | if (size == 8) { | |
46 | return val; | |
47 | } | |
48 | if (addr & 4) { | |
49 | val >>= 32; | |
50 | } else { | |
51 | val = (uint32_t) val; | |
52 | } | |
53 | return val; | |
54 | } | |
55 | ||
56 | static void put_val_in_int64(uint64_t *p, hwaddr addr, unsigned size, | |
57 | uint64_t val) | |
58 | { | |
59 | if (size == 8) { | |
60 | *p = val; | |
61 | } else if (size == 4) { | |
62 | if (addr & 4) { | |
63 | *p = ((*p << 32) >> 32) | (val << 32); | |
64 | } else { | |
65 | *p = ((*p >> 32) << 32) | (uint32_t) val; | |
66 | } | |
67 | } | |
68 | } | |
69 | ||
70 | static void put_val_in_arrary(uint64_t *array, hwaddr start_addr, | |
71 | hwaddr addr, unsigned size, uint64_t val) | |
72 | { | |
73 | int index; | |
74 | ||
75 | index = (addr - start_addr) / 8; | |
76 | put_val_in_int64(&array[index], addr, size, val); | |
77 | } | |
78 | ||
79 | ||
80 | /* | |
81 | * The Elroy PCI host bridge. We have at least 4 of those under Astro. | |
82 | */ | |
83 | ||
84 | static MemTxResult elroy_chip_read_with_attrs(void *opaque, hwaddr addr, | |
85 | uint64_t *data, unsigned size, | |
86 | MemTxAttrs attrs) | |
87 | { | |
88 | MemTxResult ret = MEMTX_OK; | |
89 | ElroyState *s = opaque; | |
90 | uint64_t val = -1; | |
91 | int index; | |
92 | ||
93 | switch ((addr >> 3) << 3) { | |
94 | case 0x0008: | |
95 | val = 0x6000005; /* func_class */ | |
96 | break; | |
97 | case 0x0058: | |
98 | /* | |
99 | * Scratch register, but firmware initializes it with the | |
100 | * PCI BUS number and Linux/HP-UX uses it then. | |
101 | */ | |
102 | val = s->pci_bus_num; | |
103 | /* Upper byte holds the end of this bus number */ | |
104 | val |= s->pci_bus_num << 8; | |
105 | break; | |
106 | case 0x0080: | |
107 | val = s->arb_mask; /* set ARB mask */ | |
108 | break; | |
109 | case 0x0108: | |
110 | val = s->status_control; | |
111 | break; | |
112 | case 0x200 ... 0x250 - 1: /* LMMIO, GMMIO, WLMMIO, WGMMIO, ... */ | |
113 | index = (addr - 0x200) / 8; | |
114 | val = s->mmio_base[index]; | |
115 | break; | |
116 | case 0x0680: | |
117 | val = s->error_config; | |
118 | break; | |
119 | case 0x0688: | |
120 | val = 0; /* ERROR_STATUS */ | |
121 | break; | |
122 | case 0x0800: /* IOSAPIC_REG_SELECT */ | |
123 | val = s->iosapic_reg_select; | |
124 | break; | |
125 | case 0x0808: | |
126 | val = UINT64_MAX; /* XXX: tbc. */ | |
127 | g_assert_not_reached(); | |
128 | break; | |
129 | case 0x0810: /* IOSAPIC_REG_WINDOW */ | |
130 | switch (s->iosapic_reg_select) { | |
131 | case 0x01: /* IOSAPIC_REG_VERSION */ | |
132 | val = (32 << 16) | 1; /* upper 16bit holds max entries */ | |
133 | break; | |
134 | default: | |
135 | if (s->iosapic_reg_select < ARRAY_SIZE(s->iosapic_reg)) { | |
136 | val = s->iosapic_reg[s->iosapic_reg_select]; | |
137 | } else { | |
138 | trace_iosapic_reg_read(s->iosapic_reg_select, size, val); | |
139 | g_assert_not_reached(); | |
140 | } | |
141 | } | |
142 | trace_iosapic_reg_read(s->iosapic_reg_select, size, val); | |
143 | break; | |
144 | default: | |
145 | trace_elroy_read(addr, size, val); | |
146 | g_assert_not_reached(); | |
147 | } | |
148 | trace_elroy_read(addr, size, val); | |
149 | ||
150 | /* for 32-bit accesses mask return value */ | |
151 | val = mask_32bit_val(addr, size, val); | |
152 | ||
153 | trace_astro_chip_read(addr, size, val); | |
154 | *data = val; | |
155 | return ret; | |
156 | } | |
157 | ||
158 | ||
159 | static MemTxResult elroy_chip_write_with_attrs(void *opaque, hwaddr addr, | |
160 | uint64_t val, unsigned size, | |
161 | MemTxAttrs attrs) | |
162 | { | |
163 | ElroyState *s = opaque; | |
164 | int i; | |
165 | ||
166 | trace_elroy_write(addr, size, val); | |
167 | ||
168 | switch ((addr >> 3) << 3) { | |
169 | case 0x080: | |
170 | put_val_in_int64(&s->arb_mask, addr, size, val); | |
171 | break; | |
172 | case 0x0108: | |
173 | put_val_in_int64(&s->status_control, addr, size, val); | |
174 | break; | |
175 | case 0x200 ... 0x250 - 1: /* LMMIO, GMMIO, WLMMIO, WGMMIO, ... */ | |
176 | put_val_in_arrary(s->mmio_base, 0x200, addr, size, val); | |
177 | break; | |
178 | case 0x0680: | |
179 | put_val_in_int64(&s->error_config, addr, size, val); | |
180 | break; | |
181 | case 0x0800: /* IOSAPIC_REG_SELECT */ | |
182 | s->iosapic_reg_select = val; | |
183 | break; | |
184 | case 0x0810: /* IOSAPIC_REG_WINDOW */ | |
185 | trace_iosapic_reg_write(s->iosapic_reg_select, size, val); | |
186 | if (s->iosapic_reg_select < ARRAY_SIZE(s->iosapic_reg)) { | |
187 | s->iosapic_reg[s->iosapic_reg_select] = val; | |
188 | } else { | |
189 | g_assert_not_reached(); | |
190 | } | |
191 | break; | |
192 | case 0x0840: /* IOSAPIC_REG_EOI */ | |
193 | val = le64_to_cpu(val); | |
194 | val &= 63; | |
195 | for (i = 0; i < ELROY_IRQS; i++) { | |
196 | if ((s->iosapic_reg[0x10 + 2 * i] & 63) == val) { | |
197 | s->ilr &= ~(1ull << i); | |
198 | } | |
199 | } | |
200 | break; | |
201 | default: | |
202 | g_assert_not_reached(); | |
203 | } | |
204 | return MEMTX_OK; | |
205 | } | |
206 | ||
207 | static const MemoryRegionOps elroy_chip_ops = { | |
208 | .read_with_attrs = elroy_chip_read_with_attrs, | |
209 | .write_with_attrs = elroy_chip_write_with_attrs, | |
210 | .endianness = DEVICE_LITTLE_ENDIAN, | |
211 | .valid = { | |
212 | .min_access_size = 4, | |
213 | .max_access_size = 8, | |
214 | }, | |
215 | .impl = { | |
216 | .min_access_size = 4, | |
217 | .max_access_size = 8, | |
218 | }, | |
219 | }; | |
220 | ||
221 | ||
222 | /* Unlike pci_config_data_le_ops, no check of high bit set in config_reg. */ | |
223 | ||
224 | static uint64_t elroy_config_data_read(void *opaque, hwaddr addr, unsigned len) | |
225 | { | |
226 | uint64_t val; | |
227 | ||
228 | PCIHostState *s = opaque; | |
229 | val = pci_data_read(s->bus, s->config_reg | (addr & 3), len); | |
230 | trace_elroy_pci_config_data_read(s->config_reg | (addr & 3), len, val); | |
231 | return val; | |
232 | } | |
233 | ||
234 | static void elroy_config_data_write(void *opaque, hwaddr addr, | |
235 | uint64_t val, unsigned len) | |
236 | { | |
237 | PCIHostState *s = opaque; | |
238 | pci_data_write(s->bus, s->config_reg | (addr & 3), val, len); | |
239 | trace_elroy_pci_config_data_write(s->config_reg | (addr & 3), len, val); | |
240 | } | |
241 | ||
242 | static const MemoryRegionOps elroy_config_data_ops = { | |
243 | .read = elroy_config_data_read, | |
244 | .write = elroy_config_data_write, | |
245 | .endianness = DEVICE_LITTLE_ENDIAN, | |
246 | }; | |
247 | ||
248 | static uint64_t elroy_config_addr_read(void *opaque, hwaddr addr, unsigned len) | |
249 | { | |
250 | ElroyState *s = opaque; | |
251 | return s->config_reg_elroy; | |
252 | } | |
253 | ||
254 | static void elroy_config_addr_write(void *opaque, hwaddr addr, | |
255 | uint64_t val, unsigned len) | |
256 | { | |
257 | PCIHostState *s = opaque; | |
258 | ElroyState *es = opaque; | |
259 | es->config_reg_elroy = val; /* keep a copy of original value */ | |
260 | s->config_reg = val; | |
261 | } | |
262 | ||
263 | static const MemoryRegionOps elroy_config_addr_ops = { | |
264 | .read = elroy_config_addr_read, | |
265 | .write = elroy_config_addr_write, | |
266 | .valid.min_access_size = 4, | |
267 | .valid.max_access_size = 8, | |
268 | .endianness = DEVICE_LITTLE_ENDIAN, | |
269 | }; | |
270 | ||
271 | ||
e029bb00 HD |
272 | /* Handle PCI-to-system address translation. */ |
273 | static IOMMUTLBEntry astro_translate_iommu(IOMMUMemoryRegion *iommu, | |
274 | hwaddr addr, | |
275 | IOMMUAccessFlags flag, | |
276 | int iommu_idx) | |
277 | { | |
278 | AstroState *s = container_of(iommu, AstroState, iommu); | |
8066102d | 279 | hwaddr pdir_ptr, index, ibase; |
e029bb00 HD |
280 | hwaddr addr_mask = 0xfff; /* 4k translation */ |
281 | uint64_t entry; | |
282 | ||
283 | #define IOVP_SHIFT 12 /* equals PAGE_SHIFT */ | |
284 | #define PDIR_INDEX(iovp) ((iovp) >> IOVP_SHIFT) | |
e029bb00 HD |
285 | #define SBA_PDIR_VALID_BIT 0x8000000000000000ULL |
286 | ||
8066102d HD |
287 | addr &= ~addr_mask; |
288 | ||
289 | /* | |
290 | * Default translation: "32-bit PCI Addressing on 40-bit Runway". | |
291 | * For addresses in the 32-bit memory address range ... and then | |
292 | * language which not-coincidentally matches the PSW.W=0 mapping. | |
293 | */ | |
294 | if (addr <= UINT32_MAX) { | |
295 | entry = hppa_abs_to_phys_pa2_w0(addr); | |
296 | } else { | |
297 | entry = addr; | |
298 | } | |
299 | ||
e029bb00 HD |
300 | /* "range enable" flag cleared? */ |
301 | if ((s->tlb_ibase & 1) == 0) { | |
8066102d | 302 | goto skip; |
e029bb00 HD |
303 | } |
304 | ||
e029bb00 | 305 | ibase = s->tlb_ibase & ~1ULL; |
8066102d | 306 | if ((addr & s->tlb_imask) != ibase) { |
e029bb00 | 307 | /* do not translate this one! */ |
8066102d | 308 | goto skip; |
e029bb00 | 309 | } |
8066102d HD |
310 | |
311 | index = PDIR_INDEX(addr); | |
e029bb00 HD |
312 | pdir_ptr = s->tlb_pdir_base + index * sizeof(entry); |
313 | entry = ldq_le_phys(&address_space_memory, pdir_ptr); | |
8066102d | 314 | |
e029bb00 | 315 | if (!(entry & SBA_PDIR_VALID_BIT)) { /* I/O PDIR entry valid ? */ |
8066102d HD |
316 | /* failure */ |
317 | return (IOMMUTLBEntry) { .perm = IOMMU_NONE }; | |
e029bb00 | 318 | } |
8066102d | 319 | |
e029bb00 HD |
320 | entry &= ~SBA_PDIR_VALID_BIT; |
321 | entry >>= IOVP_SHIFT; | |
322 | entry <<= 12; | |
e029bb00 | 323 | |
8066102d HD |
324 | skip: |
325 | return (IOMMUTLBEntry) { | |
326 | .target_as = &address_space_memory, | |
327 | .iova = addr, | |
328 | .translated_addr = entry, | |
329 | .addr_mask = addr_mask, | |
330 | .perm = IOMMU_RW, | |
331 | }; | |
e029bb00 HD |
332 | } |
333 | ||
334 | static AddressSpace *elroy_pcihost_set_iommu(PCIBus *bus, void *opaque, | |
335 | int devfn) | |
336 | { | |
337 | ElroyState *s = opaque; | |
338 | return &s->astro->iommu_as; | |
339 | } | |
340 | ||
ba7d12eb YL |
341 | static const PCIIOMMUOps elroy_pcihost_iommu_ops = { |
342 | .get_address_space = elroy_pcihost_set_iommu, | |
343 | }; | |
344 | ||
e029bb00 HD |
345 | /* |
346 | * Encoding in IOSAPIC: | |
347 | * base_addr == 0xfffa0000, we want to get 0xa0ff0000. | |
348 | * eid 0x0ff00000 -> 0x00ff0000 | |
349 | * id 0x000ff000 -> 0xff000000 | |
350 | */ | |
351 | #define SWIZZLE_HPA(a) \ | |
352 | ((((a) & 0x0ff00000) >> 4) | (((a) & 0x000ff000) << 12)) | |
353 | #define UNSWIZZLE_HPA(a) \ | |
354 | (((((a) << 4) & 0x0ff00000) | (((a) >> 12) & 0x000ff000) | 0xf0000000)) | |
355 | ||
356 | /* bits in the "low" I/O Sapic IRdT entry */ | |
357 | #define IOSAPIC_IRDT_DISABLE 0x10000 /* if bit is set, mask this irq */ | |
358 | #define IOSAPIC_IRDT_PO_LOW 0x02000 | |
359 | #define IOSAPIC_IRDT_LEVEL_TRIG 0x08000 | |
360 | #define IOSAPIC_IRDT_MODE_LPRI 0x00100 | |
361 | ||
362 | #define CPU_IRQ_OFFSET 2 | |
363 | ||
364 | static void elroy_set_irq(void *opaque, int irq, int level) | |
365 | { | |
366 | ElroyState *s = opaque; | |
367 | uint32_t bit; | |
368 | uint32_t old_ilr = s->ilr; | |
369 | hwaddr cpu_hpa; | |
370 | uint32_t val; | |
371 | ||
372 | val = s->iosapic_reg[0x10 + 2 * irq]; | |
373 | cpu_hpa = s->iosapic_reg[0x11 + 2 * irq]; | |
374 | /* low nibble of val has value to write into CPU irq reg */ | |
375 | bit = 1u << (val & (ELROY_IRQS - 1)); | |
376 | cpu_hpa = UNSWIZZLE_HPA(cpu_hpa); | |
377 | ||
378 | if (level && (!(val & IOSAPIC_IRDT_DISABLE)) && cpu_hpa) { | |
379 | uint32_t ena = bit & ~old_ilr; | |
380 | s->ilr = old_ilr | bit; | |
381 | if (ena != 0) { | |
64bf0967 | 382 | stl_be_phys(&address_space_memory, F_EXTEND(cpu_hpa), val & 63); |
e029bb00 HD |
383 | } |
384 | } else { | |
385 | s->ilr = old_ilr & ~bit; | |
386 | } | |
387 | } | |
388 | ||
389 | static int elroy_pci_map_irq(PCIDevice *d, int irq_num) | |
390 | { | |
391 | int slot = PCI_SLOT(d->devfn); | |
392 | ||
393 | assert(irq_num >= 0 && irq_num < ELROY_IRQS); | |
394 | return slot & (ELROY_IRQS - 1); | |
395 | } | |
396 | ||
397 | static void elroy_reset(DeviceState *dev) | |
398 | { | |
399 | ElroyState *s = ELROY_PCI_HOST_BRIDGE(dev); | |
400 | int irq; | |
401 | ||
402 | /* | |
403 | * Make sure to disable interrupts at reboot, otherwise the Linux kernel | |
404 | * serial8250_config_port() in drivers/tty/serial/8250/8250_port.c | |
405 | * will hang during autoconfig(). | |
406 | */ | |
407 | s->ilr = 0; | |
408 | for (irq = 0; irq < ELROY_IRQS; irq++) { | |
409 | s->iosapic_reg[0x10 + 2 * irq] = IOSAPIC_IRDT_PO_LOW | | |
410 | IOSAPIC_IRDT_LEVEL_TRIG | (irq + CPU_IRQ_OFFSET) | | |
411 | IOSAPIC_IRDT_DISABLE; | |
412 | s->iosapic_reg[0x11 + 2 * irq] = SWIZZLE_HPA(CPU_HPA); | |
413 | } | |
414 | } | |
415 | ||
416 | static void elroy_pcihost_init(Object *obj) | |
417 | { | |
418 | ElroyState *s = ELROY_PCI_HOST_BRIDGE(obj); | |
419 | PCIHostState *phb = PCI_HOST_BRIDGE(obj); | |
420 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); | |
421 | ||
422 | /* Elroy config access from CPU. */ | |
423 | memory_region_init_io(&s->this_mem, OBJECT(s), &elroy_chip_ops, | |
424 | s, "elroy", 0x2000); | |
425 | ||
426 | /* Elroy PCI config. */ | |
427 | memory_region_init_io(&phb->conf_mem, OBJECT(phb), | |
428 | &elroy_config_addr_ops, DEVICE(s), | |
429 | "pci-conf-idx", 8); | |
430 | memory_region_init_io(&phb->data_mem, OBJECT(phb), | |
431 | &elroy_config_data_ops, DEVICE(s), | |
432 | "pci-conf-data", 8); | |
433 | memory_region_add_subregion(&s->this_mem, 0x40, | |
434 | &phb->conf_mem); | |
435 | memory_region_add_subregion(&s->this_mem, 0x48, | |
436 | &phb->data_mem); | |
437 | ||
438 | /* Elroy PCI bus memory. */ | |
439 | memory_region_init(&s->pci_mmio, OBJECT(s), "pci-mmio", UINT64_MAX); | |
440 | memory_region_init_io(&s->pci_io, OBJECT(s), &unassigned_io_ops, obj, | |
441 | "pci-isa-mmio", | |
442 | ((uint32_t) IOS_DIST_BASE_SIZE) / ROPES_PER_IOC); | |
443 | ||
444 | phb->bus = pci_register_root_bus(DEVICE(s), "pci", | |
445 | elroy_set_irq, elroy_pci_map_irq, s, | |
446 | &s->pci_mmio, &s->pci_io, | |
447 | PCI_DEVFN(0, 0), ELROY_IRQS, TYPE_PCI_BUS); | |
448 | ||
449 | sysbus_init_mmio(sbd, &s->this_mem); | |
450 | ||
451 | qdev_init_gpio_in(DEVICE(obj), elroy_set_irq, ELROY_IRQS); | |
452 | } | |
453 | ||
454 | static Property elroy_pcihost_properties[] = { | |
455 | DEFINE_PROP_END_OF_LIST(), | |
456 | }; | |
457 | ||
458 | static const VMStateDescription vmstate_elroy = { | |
459 | .name = "Elroy", | |
460 | .version_id = 1, | |
461 | .minimum_version_id = 1, | |
462 | .fields = (VMStateField[]) { | |
463 | VMSTATE_UINT64(hpa, ElroyState), | |
464 | VMSTATE_UINT32(pci_bus_num, ElroyState), | |
465 | VMSTATE_UINT64(config_address, ElroyState), | |
466 | VMSTATE_UINT64(config_reg_elroy, ElroyState), | |
467 | VMSTATE_UINT64(status_control, ElroyState), | |
468 | VMSTATE_UINT64(arb_mask, ElroyState), | |
469 | VMSTATE_UINT64_ARRAY(mmio_base, ElroyState, (0x0250 - 0x200) / 8), | |
470 | VMSTATE_UINT64(error_config, ElroyState), | |
471 | VMSTATE_UINT32(iosapic_reg_select, ElroyState), | |
472 | VMSTATE_UINT64_ARRAY(iosapic_reg, ElroyState, 0x20), | |
473 | VMSTATE_UINT32(ilr, ElroyState), | |
474 | VMSTATE_END_OF_LIST() | |
475 | } | |
476 | }; | |
477 | ||
478 | static void elroy_pcihost_class_init(ObjectClass *klass, void *data) | |
479 | { | |
480 | DeviceClass *dc = DEVICE_CLASS(klass); | |
481 | ||
482 | dc->reset = elroy_reset; | |
483 | device_class_set_props(dc, elroy_pcihost_properties); | |
484 | dc->vmsd = &vmstate_elroy; | |
485 | dc->user_creatable = false; | |
486 | } | |
487 | ||
488 | static const TypeInfo elroy_pcihost_info = { | |
489 | .name = TYPE_ELROY_PCI_HOST_BRIDGE, | |
490 | .parent = TYPE_PCI_HOST_BRIDGE, | |
491 | .instance_init = elroy_pcihost_init, | |
492 | .instance_size = sizeof(ElroyState), | |
493 | .class_init = elroy_pcihost_class_init, | |
494 | }; | |
495 | ||
496 | static void elroy_register_types(void) | |
497 | { | |
498 | type_register_static(&elroy_pcihost_info); | |
499 | } | |
500 | ||
501 | type_init(elroy_register_types) | |
502 | ||
503 | ||
504 | static ElroyState *elroy_init(int num) | |
505 | { | |
506 | DeviceState *dev; | |
507 | ||
508 | dev = qdev_new(TYPE_ELROY_PCI_HOST_BRIDGE); | |
509 | dev->id = g_strdup_printf("elroy%d", num); | |
510 | sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); | |
511 | ||
512 | return ELROY_PCI_HOST_BRIDGE(dev); | |
513 | } | |
514 | ||
515 | /* | |
516 | * Astro Runway chip. | |
517 | */ | |
518 | ||
519 | static MemTxResult astro_chip_read_with_attrs(void *opaque, hwaddr addr, | |
520 | uint64_t *data, unsigned size, | |
521 | MemTxAttrs attrs) | |
522 | { | |
523 | AstroState *s = opaque; | |
524 | MemTxResult ret = MEMTX_OK; | |
525 | uint64_t val = -1; | |
526 | int index; | |
527 | ||
528 | switch ((addr >> 3) << 3) { | |
529 | /* R2I registers */ | |
530 | case 0x0000: /* ID */ | |
531 | val = (0x01 << 3) | 0x01ULL; | |
532 | break; | |
533 | case 0x0008: /* IOC_CTRL */ | |
534 | val = s->ioc_ctrl; | |
535 | break; | |
536 | case 0x0010: /* TOC_CLIENT_ID */ | |
537 | break; | |
538 | case 0x0030: /* HP-UX 10.20 and 11.11 reads it. No idea. */ | |
539 | val = -1; | |
540 | break; | |
541 | case 0x0300 ... 0x03d8: /* LMMIO_DIRECT0_BASE... */ | |
542 | index = (addr - 0x300) / 8; | |
543 | val = s->ioc_ranges[index]; | |
544 | break; | |
545 | case 0x10200: | |
546 | val = 0; | |
547 | break; | |
548 | case 0x10220: | |
549 | case 0x10230: /* HP-UX 11.11 reads it. No idea. */ | |
550 | val = -1; | |
551 | break; | |
552 | case 0x22108: /* IOC STATUS_CONTROL */ | |
553 | val = s->ioc_status_ctrl; | |
554 | break; | |
555 | case 0x20200 ... 0x20240 - 1: /* IOC Rope0_Control ... */ | |
556 | index = (addr - 0x20200) / 8; | |
557 | val = s->ioc_rope_control[index]; | |
558 | break; | |
559 | case 0x20040: /* IOC Rope config */ | |
560 | val = s->ioc_rope_config; | |
561 | break; | |
562 | case 0x20050: /* IOC Rope debug */ | |
563 | val = 0; | |
564 | break; | |
565 | case 0x20108: /* IOC STATUS_CONTROL */ | |
566 | val = s->ioc_status_control; | |
567 | break; | |
568 | case 0x20310: /* IOC_PCOM */ | |
569 | val = s->tlb_pcom; | |
570 | /* TODO: flush iommu */ | |
571 | break; | |
572 | case 0x20400: | |
573 | val = s->ioc_flush_control; | |
574 | break; | |
575 | /* empty placeholders for non-existent elroys */ | |
576 | #define EMPTY_PORT(x) case x: case x+8: val = 0; break; \ | |
577 | case x+40: case x+48: val = UINT64_MAX; break; | |
578 | EMPTY_PORT(0x30000) | |
579 | EMPTY_PORT(0x32000) | |
580 | EMPTY_PORT(0x34000) | |
581 | EMPTY_PORT(0x36000) | |
582 | EMPTY_PORT(0x38000) | |
583 | EMPTY_PORT(0x3a000) | |
584 | EMPTY_PORT(0x3c000) | |
585 | EMPTY_PORT(0x3e000) | |
586 | #undef EMPTY_PORT | |
587 | ||
588 | default: | |
589 | trace_astro_chip_read(addr, size, val); | |
590 | g_assert_not_reached(); | |
591 | } | |
592 | ||
593 | /* for 32-bit accesses mask return value */ | |
594 | val = mask_32bit_val(addr, size, val); | |
595 | ||
596 | trace_astro_chip_read(addr, size, val); | |
597 | *data = val; | |
598 | return ret; | |
599 | } | |
600 | ||
601 | static MemTxResult astro_chip_write_with_attrs(void *opaque, hwaddr addr, | |
602 | uint64_t val, unsigned size, | |
603 | MemTxAttrs attrs) | |
604 | { | |
605 | AstroState *s = opaque; | |
606 | ||
607 | trace_astro_chip_write(addr, size, val); | |
608 | ||
609 | switch ((addr >> 3) << 3) { | |
610 | case 0x0000: /* ID */ | |
611 | break; | |
612 | case 0x0008: /* IOC_CTRL */ | |
613 | val &= 0x0ffffff; | |
614 | put_val_in_int64(&s->ioc_ctrl, addr, size, val); | |
615 | break; | |
616 | case 0x0010: /* TOC_CLIENT_ID */ | |
617 | break; | |
618 | case 0x0030: /* HP-UX 10.20 and 11.11 reads it. No idea. */ | |
619 | break; | |
620 | case 0x0300 ... 0x03d8 - 1: /* LMMIO_DIRECT0_BASE... */ | |
621 | put_val_in_arrary(s->ioc_ranges, 0x300, addr, size, val); | |
622 | break; | |
623 | case 0x10200: | |
624 | case 0x10220: | |
625 | case 0x10230: /* HP-UX 11.11 reads it. No idea. */ | |
626 | break; | |
627 | case 0x22108: /* IOC STATUS_CONTROL */ | |
628 | put_val_in_int64(&s->ioc_status_ctrl, addr, size, val); | |
629 | break; | |
630 | case 0x20200 ... 0x20240 - 1: /* IOC Rope0_Control ... */ | |
631 | put_val_in_arrary(s->ioc_rope_control, 0x20200, addr, size, val); | |
632 | break; | |
633 | case 0x20040: /* IOC Rope config */ | |
634 | put_val_in_int64(&s->ioc_rope_config, addr, size, val); | |
635 | break; | |
636 | case 0x20300: | |
637 | put_val_in_int64(&s->tlb_ibase, addr, size, val); | |
638 | break; | |
639 | case 0x20308: | |
640 | put_val_in_int64(&s->tlb_imask, addr, size, val); | |
641 | break; | |
642 | case 0x20310: | |
643 | put_val_in_int64(&s->tlb_pcom, addr, size, val); | |
644 | /* TODO: flush iommu */ | |
645 | break; | |
646 | case 0x20318: | |
647 | put_val_in_int64(&s->tlb_tcnfg, addr, size, val); | |
648 | break; | |
649 | case 0x20320: | |
650 | put_val_in_int64(&s->tlb_pdir_base, addr, size, val); | |
651 | break; | |
652 | /* | |
653 | * empty placeholders for non-existent elroys, e.g. | |
654 | * func_class, pci config & data | |
655 | */ | |
656 | #define EMPTY_PORT(x) case x: case x+8: case x+0x40: case x+0x48: | |
657 | EMPTY_PORT(0x30000) | |
658 | EMPTY_PORT(0x32000) | |
659 | EMPTY_PORT(0x34000) | |
660 | EMPTY_PORT(0x36000) | |
661 | EMPTY_PORT(0x38000) | |
662 | EMPTY_PORT(0x3a000) | |
663 | EMPTY_PORT(0x3c000) | |
664 | EMPTY_PORT(0x3e000) | |
665 | break; | |
666 | #undef EMPTY_PORT | |
667 | ||
668 | default: | |
669 | /* Controlled by astro_chip_mem_valid above. */ | |
670 | trace_astro_chip_write(addr, size, val); | |
671 | g_assert_not_reached(); | |
672 | } | |
673 | return MEMTX_OK; | |
674 | } | |
675 | ||
676 | static const MemoryRegionOps astro_chip_ops = { | |
677 | .read_with_attrs = astro_chip_read_with_attrs, | |
678 | .write_with_attrs = astro_chip_write_with_attrs, | |
679 | .endianness = DEVICE_LITTLE_ENDIAN, | |
680 | .valid = { | |
681 | .min_access_size = 4, | |
682 | .max_access_size = 8, | |
683 | }, | |
684 | .impl = { | |
685 | .min_access_size = 4, | |
686 | .max_access_size = 8, | |
687 | }, | |
688 | }; | |
689 | ||
690 | static const VMStateDescription vmstate_astro = { | |
691 | .name = "Astro", | |
692 | .version_id = 1, | |
693 | .minimum_version_id = 1, | |
694 | .fields = (VMStateField[]) { | |
695 | VMSTATE_UINT64(ioc_ctrl, AstroState), | |
696 | VMSTATE_UINT64(ioc_status_ctrl, AstroState), | |
697 | VMSTATE_UINT64_ARRAY(ioc_ranges, AstroState, (0x03d8 - 0x300) / 8), | |
698 | VMSTATE_UINT64(ioc_rope_config, AstroState), | |
699 | VMSTATE_UINT64(ioc_status_control, AstroState), | |
700 | VMSTATE_UINT64(ioc_flush_control, AstroState), | |
701 | VMSTATE_UINT64_ARRAY(ioc_rope_control, AstroState, 8), | |
702 | VMSTATE_UINT64(tlb_ibase, AstroState), | |
703 | VMSTATE_UINT64(tlb_imask, AstroState), | |
704 | VMSTATE_UINT64(tlb_pcom, AstroState), | |
705 | VMSTATE_UINT64(tlb_tcnfg, AstroState), | |
706 | VMSTATE_UINT64(tlb_pdir_base, AstroState), | |
707 | VMSTATE_END_OF_LIST() | |
708 | } | |
709 | }; | |
710 | ||
711 | static void astro_reset(DeviceState *dev) | |
712 | { | |
713 | AstroState *s = ASTRO_CHIP(dev); | |
714 | int i; | |
715 | ||
716 | s->ioc_ctrl = 0x29cf; | |
717 | s->ioc_rope_config = 0xc5f; | |
718 | s->ioc_flush_control = 0xb03; | |
719 | s->ioc_status_control = 0; | |
720 | memset(&s->ioc_rope_control, 0, sizeof(s->ioc_rope_control)); | |
721 | ||
722 | /* | |
723 | * The SBA BASE/MASK registers control CPU -> IO routing. | |
724 | * The LBA BASE/MASK registers control IO -> System routing (in Elroy) | |
725 | */ | |
726 | memset(&s->ioc_ranges, 0, sizeof(s->ioc_ranges)); | |
727 | s->ioc_ranges[(0x360 - 0x300) / 8] = LMMIO_DIST_BASE_ADDR | 0x01; /* LMMIO_DIST_BASE (SBA) */ | |
728 | s->ioc_ranges[(0x368 - 0x300) / 8] = 0xfc000000; /* LMMIO_DIST_MASK */ | |
729 | s->ioc_ranges[(0x370 - 0x300) / 8] = 0; /* LMMIO_DIST_ROUTE */ | |
730 | s->ioc_ranges[(0x390 - 0x300) / 8] = IOS_DIST_BASE_ADDR | 0x01; /* IOS_DIST_BASE */ | |
731 | s->ioc_ranges[(0x398 - 0x300) / 8] = 0xffffff0000; /* IOS_DIST_MASK */ | |
732 | s->ioc_ranges[(0x3a0 - 0x300) / 8] = 0x3400000000000000ULL; /* IOS_DIST_ROUTE */ | |
733 | s->ioc_ranges[(0x3c0 - 0x300) / 8] = 0xfffee00000; /* IOS_DIRECT_BASE */ | |
734 | s->ioc_ranges[(0x3c8 - 0x300) / 8] = 0xffffff0000; /* IOS_DIRECT_MASK */ | |
735 | s->ioc_ranges[(0x3d0 - 0x300) / 8] = 0x0; /* IOS_DIRECT_ROUTE */ | |
736 | ||
737 | s->tlb_ibase = 0; | |
738 | s->tlb_imask = 0; | |
739 | s->tlb_pcom = 0; | |
740 | s->tlb_tcnfg = 0; | |
741 | s->tlb_pdir_base = 0; | |
742 | ||
743 | for (i = 0; i < ELROY_NUM; i++) { | |
744 | elroy_reset(DEVICE(s->elroy[i])); | |
745 | } | |
746 | } | |
747 | ||
748 | static void astro_init(Object *obj) | |
749 | { | |
750 | } | |
751 | ||
752 | static void astro_realize(DeviceState *obj, Error **errp) | |
753 | { | |
754 | AstroState *s = ASTRO_CHIP(obj); | |
755 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); | |
756 | int i; | |
757 | ||
758 | memory_region_init_io(&s->this_mem, OBJECT(s), &astro_chip_ops, | |
759 | s, "astro", 0x40000); | |
760 | sysbus_init_mmio(sbd, &s->this_mem); | |
761 | ||
762 | /* Host memory as seen from Elroys PCI side, via the IOMMU. */ | |
763 | memory_region_init_iommu(&s->iommu, sizeof(s->iommu), | |
764 | TYPE_ASTRO_IOMMU_MEMORY_REGION, OBJECT(s), | |
765 | "iommu-astro", UINT64_MAX); | |
766 | address_space_init(&s->iommu_as, MEMORY_REGION(&s->iommu), | |
767 | "bm-pci"); | |
768 | ||
769 | /* Create Elroys (PCI host bus chips). */ | |
770 | for (i = 0; i < ELROY_NUM; i++) { | |
771 | static const int elroy_hpa_offsets[ELROY_NUM] = { | |
772 | 0x30000, 0x32000, 0x38000, 0x3c000 }; | |
773 | static const char elroy_rope_nr[ELROY_NUM] = { | |
774 | 0, 1, 4, 6 }; /* busnum path, e.g. [10:6] */ | |
775 | int addr_offset; | |
776 | ElroyState *elroy; | |
777 | hwaddr map_addr; | |
778 | uint64_t map_size; | |
779 | int rope; | |
780 | ||
781 | addr_offset = elroy_hpa_offsets[i]; | |
782 | rope = elroy_rope_nr[i]; | |
783 | ||
784 | elroy = elroy_init(i); | |
785 | s->elroy[i] = elroy; | |
786 | elroy->hpa = ASTRO_HPA + addr_offset; | |
787 | elroy->pci_bus_num = i; | |
788 | elroy->astro = s; | |
789 | ||
790 | /* | |
791 | * NOTE: we only allow PCI devices on first Elroy for now. | |
792 | * SeaBIOS will not find devices on the other busses. | |
793 | */ | |
794 | if (i > 0) { | |
795 | qbus_mark_full(&PCI_HOST_BRIDGE(elroy)->bus->qbus); | |
796 | } | |
797 | ||
798 | /* map elroy config addresses into Astro space */ | |
799 | memory_region_add_subregion(&s->this_mem, addr_offset, | |
800 | &elroy->this_mem); | |
801 | ||
802 | /* LMMIO */ | |
803 | elroy->mmio_base[(0x0200 - 0x200) / 8] = 0xf0000001; | |
804 | elroy->mmio_base[(0x0208 - 0x200) / 8] = 0xf8000000; | |
805 | /* GMMIO */ | |
806 | elroy->mmio_base[(0x0210 - 0x200) / 8] = 0x000000f800000001; | |
807 | elroy->mmio_base[(0x0218 - 0x200) / 8] = 0x000000ff80000000; | |
808 | /* WLMMIO */ | |
809 | elroy->mmio_base[(0x0220 - 0x200) / 8] = 0xf0000001; | |
810 | elroy->mmio_base[(0x0228 - 0x200) / 8] = 0xf0000000; | |
811 | /* WGMMIO */ | |
812 | elroy->mmio_base[(0x0230 - 0x200) / 8] = 0x000000f800000001; | |
813 | elroy->mmio_base[(0x0238 - 0x200) / 8] = 0x000000fc00000000; | |
814 | /* IOS_BASE */ | |
815 | map_size = IOS_DIST_BASE_SIZE / ROPES_PER_IOC; | |
816 | elroy->mmio_base[(0x0240 - 0x200) / 8] = rope * map_size | 0x01; | |
817 | elroy->mmio_base[(0x0248 - 0x200) / 8] = 0x0000e000; | |
818 | ||
819 | /* map elroys mmio */ | |
820 | map_size = LMMIO_DIST_BASE_SIZE / ROPES_PER_IOC; | |
fd842b2f | 821 | map_addr = F_EXTEND(LMMIO_DIST_BASE_ADDR + rope * map_size); |
e029bb00 HD |
822 | memory_region_init_alias(&elroy->pci_mmio_alias, OBJECT(elroy), |
823 | "pci-mmio-alias", | |
fd842b2f | 824 | &elroy->pci_mmio, (uint32_t) map_addr, map_size); |
e029bb00 HD |
825 | memory_region_add_subregion(get_system_memory(), map_addr, |
826 | &elroy->pci_mmio_alias); | |
827 | ||
fd842b2f | 828 | /* map elroys io */ |
e029bb00 | 829 | map_size = IOS_DIST_BASE_SIZE / ROPES_PER_IOC; |
fd842b2f | 830 | map_addr = F_EXTEND(IOS_DIST_BASE_ADDR + rope * map_size); |
e029bb00 HD |
831 | memory_region_add_subregion(get_system_memory(), map_addr, |
832 | &elroy->pci_io); | |
833 | ||
834 | /* Host memory as seen from the PCI side, via the IOMMU. */ | |
ba7d12eb | 835 | pci_setup_iommu(PCI_HOST_BRIDGE(elroy)->bus, &elroy_pcihost_iommu_ops, |
e029bb00 HD |
836 | elroy); |
837 | } | |
838 | } | |
839 | ||
840 | static void astro_class_init(ObjectClass *klass, void *data) | |
841 | { | |
842 | DeviceClass *dc = DEVICE_CLASS(klass); | |
843 | ||
844 | dc->reset = astro_reset; | |
845 | dc->vmsd = &vmstate_astro; | |
846 | dc->realize = astro_realize; | |
847 | /* | |
848 | * astro with elroys are hard part of the newer PA2.0 machines and can not | |
849 | * be created without that hardware | |
850 | */ | |
851 | dc->user_creatable = false; | |
852 | } | |
853 | ||
854 | static const TypeInfo astro_chip_info = { | |
855 | .name = TYPE_ASTRO_CHIP, | |
856 | .parent = TYPE_SYS_BUS_DEVICE, | |
857 | .instance_init = astro_init, | |
858 | .instance_size = sizeof(AstroState), | |
859 | .class_init = astro_class_init, | |
860 | }; | |
861 | ||
862 | static void astro_iommu_memory_region_class_init(ObjectClass *klass, | |
863 | void *data) | |
864 | { | |
865 | IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass); | |
866 | ||
867 | imrc->translate = astro_translate_iommu; | |
868 | } | |
869 | ||
870 | static const TypeInfo astro_iommu_memory_region_info = { | |
871 | .parent = TYPE_IOMMU_MEMORY_REGION, | |
872 | .name = TYPE_ASTRO_IOMMU_MEMORY_REGION, | |
873 | .class_init = astro_iommu_memory_region_class_init, | |
874 | }; | |
875 | ||
876 | ||
877 | static void astro_register_types(void) | |
878 | { | |
879 | type_register_static(&astro_chip_info); | |
880 | type_register_static(&astro_iommu_memory_region_info); | |
881 | } | |
882 | ||
883 | type_init(astro_register_types) |