960 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			960 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C
		
	
	
	
| /*
 | |
|  * DEC 21272 (TSUNAMI/TYPHOON) chipset emulation.
 | |
|  *
 | |
|  * Written by Richard Henderson.
 | |
|  *
 | |
|  * This work is licensed under the GNU GPL license version 2 or later.
 | |
|  */
 | |
| 
 | |
| #include "qemu/osdep.h"
 | |
| #include "qapi/error.h"
 | |
| #include "cpu.h"
 | |
| #include "hw/hw.h"
 | |
| #include "hw/devices.h"
 | |
| #include "sysemu/sysemu.h"
 | |
| #include "alpha_sys.h"
 | |
| #include "exec/address-spaces.h"
 | |
| 
 | |
| 
 | |
| #define TYPE_TYPHOON_PCI_HOST_BRIDGE "typhoon-pcihost"
 | |
| 
 | |
| typedef struct TyphoonCchip {
 | |
|     MemoryRegion region;
 | |
|     uint64_t misc;
 | |
|     uint64_t drir;
 | |
|     uint64_t dim[4];
 | |
|     uint32_t iic[4];
 | |
|     AlphaCPU *cpu[4];
 | |
| } TyphoonCchip;
 | |
| 
 | |
| typedef struct TyphoonWindow {
 | |
|     uint64_t wba;
 | |
|     uint64_t wsm;
 | |
|     uint64_t tba;
 | |
| } TyphoonWindow;
 | |
|  
 | |
| typedef struct TyphoonPchip {
 | |
|     MemoryRegion region;
 | |
|     MemoryRegion reg_iack;
 | |
|     MemoryRegion reg_mem;
 | |
|     MemoryRegion reg_io;
 | |
|     MemoryRegion reg_conf;
 | |
| 
 | |
|     AddressSpace iommu_as;
 | |
|     MemoryRegion iommu;
 | |
| 
 | |
|     uint64_t ctl;
 | |
|     TyphoonWindow win[4];
 | |
| } TyphoonPchip;
 | |
| 
 | |
| #define TYPHOON_PCI_HOST_BRIDGE(obj) \
 | |
|     OBJECT_CHECK(TyphoonState, (obj), TYPE_TYPHOON_PCI_HOST_BRIDGE)
 | |
| 
 | |
| typedef struct TyphoonState {
 | |
|     PCIHostState parent_obj;
 | |
| 
 | |
|     TyphoonCchip cchip;
 | |
|     TyphoonPchip pchip;
 | |
|     MemoryRegion dchip_region;
 | |
|     MemoryRegion ram_region;
 | |
| } TyphoonState;
 | |
| 
 | |
| /* Called when one of DRIR or DIM changes.  */
 | |
| static void cpu_irq_change(AlphaCPU *cpu, uint64_t req)
 | |
| {
 | |
|     /* If there are any non-masked interrupts, tell the cpu.  */
 | |
|     if (cpu != NULL) {
 | |
|         CPUState *cs = CPU(cpu);
 | |
|         if (req) {
 | |
|             cpu_interrupt(cs, CPU_INTERRUPT_HARD);
 | |
|         } else {
 | |
|             cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static uint64_t cchip_read(void *opaque, hwaddr addr, unsigned size)
 | |
| {
 | |
|     CPUState *cpu = current_cpu;
 | |
|     TyphoonState *s = opaque;
 | |
|     uint64_t ret = 0;
 | |
| 
 | |
|     switch (addr) {
 | |
|     case 0x0000:
 | |
|         /* CSC: Cchip System Configuration Register.  */
 | |
|         /* All sorts of data here; probably the only thing relevant is
 | |
|            PIP<14> Pchip 1 Present = 0.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0040:
 | |
|         /* MTR: Memory Timing Register.  */
 | |
|         /* All sorts of stuff related to real DRAM.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0080:
 | |
|         /* MISC: Miscellaneous Register.  */
 | |
|         ret = s->cchip.misc | (cpu->cpu_index & 3);
 | |
|         break;
 | |
| 
 | |
|     case 0x00c0:
 | |
|         /* MPD: Memory Presence Detect Register.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0100: /* AAR0 */
 | |
|     case 0x0140: /* AAR1 */
 | |
|     case 0x0180: /* AAR2 */
 | |
|     case 0x01c0: /* AAR3 */
 | |
|         /* AAR: Array Address Register.  */
 | |
|         /* All sorts of information about DRAM.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0200:
 | |
|         /* DIM0: Device Interrupt Mask Register, CPU0.  */
 | |
|         ret = s->cchip.dim[0];
 | |
|         break;
 | |
|     case 0x0240:
 | |
|         /* DIM1: Device Interrupt Mask Register, CPU1.  */
 | |
|         ret = s->cchip.dim[1];
 | |
|         break;
 | |
|     case 0x0280:
 | |
|         /* DIR0: Device Interrupt Request Register, CPU0.  */
 | |
|         ret = s->cchip.dim[0] & s->cchip.drir;
 | |
|         break;
 | |
|     case 0x02c0:
 | |
|         /* DIR1: Device Interrupt Request Register, CPU1.  */
 | |
|         ret = s->cchip.dim[1] & s->cchip.drir;
 | |
|         break;
 | |
|     case 0x0300:
 | |
|         /* DRIR: Device Raw Interrupt Request Register.  */
 | |
|         ret = s->cchip.drir;
 | |
|         break;
 | |
| 
 | |
|     case 0x0340:
 | |
|         /* PRBEN: Probe Enable Register.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0380:
 | |
|         /* IIC0: Interval Ignore Count Register, CPU0.  */
 | |
|         ret = s->cchip.iic[0];
 | |
|         break;
 | |
|     case 0x03c0:
 | |
|         /* IIC1: Interval Ignore Count Register, CPU1.  */
 | |
|         ret = s->cchip.iic[1];
 | |
|         break;
 | |
| 
 | |
|     case 0x0400: /* MPR0 */
 | |
|     case 0x0440: /* MPR1 */
 | |
|     case 0x0480: /* MPR2 */
 | |
|     case 0x04c0: /* MPR3 */
 | |
|         /* MPR: Memory Programming Register.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0580:
 | |
|         /* TTR: TIGbus Timing Register.  */
 | |
|         /* All sorts of stuff related to interrupt delivery timings.  */
 | |
|         break;
 | |
|     case 0x05c0:
 | |
|         /* TDR: TIGbug Device Timing Register.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0600:
 | |
|         /* DIM2: Device Interrupt Mask Register, CPU2.  */
 | |
|         ret = s->cchip.dim[2];
 | |
|         break;
 | |
|     case 0x0640:
 | |
|         /* DIM3: Device Interrupt Mask Register, CPU3.  */
 | |
|         ret = s->cchip.dim[3];
 | |
|         break;
 | |
|     case 0x0680:
 | |
|         /* DIR2: Device Interrupt Request Register, CPU2.  */
 | |
|         ret = s->cchip.dim[2] & s->cchip.drir;
 | |
|         break;
 | |
|     case 0x06c0:
 | |
|         /* DIR3: Device Interrupt Request Register, CPU3.  */
 | |
|         ret = s->cchip.dim[3] & s->cchip.drir;
 | |
|         break;
 | |
| 
 | |
|     case 0x0700:
 | |
|         /* IIC2: Interval Ignore Count Register, CPU2.  */
 | |
|         ret = s->cchip.iic[2];
 | |
|         break;
 | |
|     case 0x0740:
 | |
|         /* IIC3: Interval Ignore Count Register, CPU3.  */
 | |
|         ret = s->cchip.iic[3];
 | |
|         break;
 | |
| 
 | |
|     case 0x0780:
 | |
|         /* PWR: Power Management Control.   */
 | |
|         break;
 | |
|     
 | |
|     case 0x0c00: /* CMONCTLA */
 | |
|     case 0x0c40: /* CMONCTLB */
 | |
|     case 0x0c80: /* CMONCNT01 */
 | |
|     case 0x0cc0: /* CMONCNT23 */
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         cpu_unassigned_access(cpu, addr, false, false, 0, size);
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static uint64_t dchip_read(void *opaque, hwaddr addr, unsigned size)
 | |
| {
 | |
|     /* Skip this.  It's all related to DRAM timing and setup.  */
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static uint64_t pchip_read(void *opaque, hwaddr addr, unsigned size)
 | |
| {
 | |
|     TyphoonState *s = opaque;
 | |
|     uint64_t ret = 0;
 | |
| 
 | |
|     switch (addr) {
 | |
|     case 0x0000:
 | |
|         /* WSBA0: Window Space Base Address Register.  */
 | |
|         ret = s->pchip.win[0].wba;
 | |
|         break;
 | |
|     case 0x0040:
 | |
|         /* WSBA1 */
 | |
|         ret = s->pchip.win[1].wba;
 | |
|         break;
 | |
|     case 0x0080:
 | |
|         /* WSBA2 */
 | |
|         ret = s->pchip.win[2].wba;
 | |
|         break;
 | |
|     case 0x00c0:
 | |
|         /* WSBA3 */
 | |
|         ret = s->pchip.win[3].wba;
 | |
|         break;
 | |
| 
 | |
|     case 0x0100:
 | |
|         /* WSM0: Window Space Mask Register.  */
 | |
|         ret = s->pchip.win[0].wsm;
 | |
|         break;
 | |
|     case 0x0140:
 | |
|         /* WSM1 */
 | |
|         ret = s->pchip.win[1].wsm;
 | |
|         break;
 | |
|     case 0x0180:
 | |
|         /* WSM2 */
 | |
|         ret = s->pchip.win[2].wsm;
 | |
|         break;
 | |
|     case 0x01c0:
 | |
|         /* WSM3 */
 | |
|         ret = s->pchip.win[3].wsm;
 | |
|         break;
 | |
| 
 | |
|     case 0x0200:
 | |
|         /* TBA0: Translated Base Address Register.  */
 | |
|         ret = s->pchip.win[0].tba;
 | |
|         break;
 | |
|     case 0x0240:
 | |
|         /* TBA1 */
 | |
|         ret = s->pchip.win[1].tba;
 | |
|         break;
 | |
|     case 0x0280:
 | |
|         /* TBA2 */
 | |
|         ret = s->pchip.win[2].tba;
 | |
|         break;
 | |
|     case 0x02c0:
 | |
|         /* TBA3 */
 | |
|         ret = s->pchip.win[3].tba;
 | |
|         break;
 | |
| 
 | |
|     case 0x0300:
 | |
|         /* PCTL: Pchip Control Register.  */
 | |
|         ret = s->pchip.ctl;
 | |
|         break;
 | |
|     case 0x0340:
 | |
|         /* PLAT: Pchip Master Latency Register.  */
 | |
|         break;
 | |
|     case 0x03c0:
 | |
|         /* PERROR: Pchip Error Register.  */
 | |
|         break;
 | |
|     case 0x0400:
 | |
|         /* PERRMASK: Pchip Error Mask Register.  */
 | |
|         break;
 | |
|     case 0x0440:
 | |
|         /* PERRSET: Pchip Error Set Register.  */
 | |
|         break;
 | |
|     case 0x0480:
 | |
|         /* TLBIV: Translation Buffer Invalidate Virtual Register (WO).  */
 | |
|         break;
 | |
|     case 0x04c0:
 | |
|         /* TLBIA: Translation Buffer Invalidate All Register (WO).  */
 | |
|         break;
 | |
|     case 0x0500: /* PMONCTL */
 | |
|     case 0x0540: /* PMONCNT */
 | |
|     case 0x0800: /* SPRST */
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         cpu_unassigned_access(current_cpu, addr, false, false, 0, size);
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static void cchip_write(void *opaque, hwaddr addr,
 | |
|                         uint64_t val, unsigned size)
 | |
| {
 | |
|     TyphoonState *s = opaque;
 | |
|     uint64_t oldval, newval;
 | |
| 
 | |
|     switch (addr) {
 | |
|     case 0x0000:
 | |
|         /* CSC: Cchip System Configuration Register.  */
 | |
|         /* All sorts of data here; nothing relevant RW.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0040:
 | |
|         /* MTR: Memory Timing Register.  */
 | |
|         /* All sorts of stuff related to real DRAM.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0080:
 | |
|         /* MISC: Miscellaneous Register.  */
 | |
|         newval = oldval = s->cchip.misc;
 | |
|         newval &= ~(val & 0x10000ff0);     /* W1C fields */
 | |
|         if (val & 0x100000) {
 | |
|             newval &= ~0xff0000ull;        /* ACL clears ABT and ABW */
 | |
|         } else {
 | |
|             newval |= val & 0x00f00000;    /* ABT field is W1S */
 | |
|             if ((newval & 0xf0000) == 0) {
 | |
|                 newval |= val & 0xf0000;   /* ABW field is W1S iff zero */
 | |
|             }
 | |
|         }
 | |
|         newval |= (val & 0xf000) >> 4;     /* IPREQ field sets IPINTR.  */
 | |
| 
 | |
|         newval &= ~0xf0000000000ull;       /* WO and RW fields */
 | |
|         newval |= val & 0xf0000000000ull;
 | |
|         s->cchip.misc = newval;
 | |
| 
 | |
|         /* Pass on changes to IPI and ITI state.  */
 | |
|         if ((newval ^ oldval) & 0xff0) {
 | |
|             int i;
 | |
|             for (i = 0; i < 4; ++i) {
 | |
|                 AlphaCPU *cpu = s->cchip.cpu[i];
 | |
|                 if (cpu != NULL) {
 | |
|                     CPUState *cs = CPU(cpu);
 | |
|                     /* IPI can be either cleared or set by the write.  */
 | |
|                     if (newval & (1 << (i + 8))) {
 | |
|                         cpu_interrupt(cs, CPU_INTERRUPT_SMP);
 | |
|                     } else {
 | |
|                         cpu_reset_interrupt(cs, CPU_INTERRUPT_SMP);
 | |
|                     }
 | |
| 
 | |
|                     /* ITI can only be cleared by the write.  */
 | |
|                     if ((newval & (1 << (i + 4))) == 0) {
 | |
|                         cpu_reset_interrupt(cs, CPU_INTERRUPT_TIMER);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case 0x00c0:
 | |
|         /* MPD: Memory Presence Detect Register.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0100: /* AAR0 */
 | |
|     case 0x0140: /* AAR1 */
 | |
|     case 0x0180: /* AAR2 */
 | |
|     case 0x01c0: /* AAR3 */
 | |
|         /* AAR: Array Address Register.  */
 | |
|         /* All sorts of information about DRAM.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0200: /* DIM0 */
 | |
|         /* DIM: Device Interrupt Mask Register, CPU0.  */
 | |
|         s->cchip.dim[0] = val;
 | |
|         cpu_irq_change(s->cchip.cpu[0], val & s->cchip.drir);
 | |
|         break;
 | |
|     case 0x0240: /* DIM1 */
 | |
|         /* DIM: Device Interrupt Mask Register, CPU1.  */
 | |
|         s->cchip.dim[1] = val;
 | |
|         cpu_irq_change(s->cchip.cpu[1], val & s->cchip.drir);
 | |
|         break;
 | |
| 
 | |
|     case 0x0280: /* DIR0 (RO) */
 | |
|     case 0x02c0: /* DIR1 (RO) */
 | |
|     case 0x0300: /* DRIR (RO) */
 | |
|         break;
 | |
| 
 | |
|     case 0x0340:
 | |
|         /* PRBEN: Probe Enable Register.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0380: /* IIC0 */
 | |
|         s->cchip.iic[0] = val & 0xffffff;
 | |
|         break;
 | |
|     case 0x03c0: /* IIC1 */
 | |
|         s->cchip.iic[1] = val & 0xffffff;
 | |
|         break;
 | |
| 
 | |
|     case 0x0400: /* MPR0 */
 | |
|     case 0x0440: /* MPR1 */
 | |
|     case 0x0480: /* MPR2 */
 | |
|     case 0x04c0: /* MPR3 */
 | |
|         /* MPR: Memory Programming Register.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0580:
 | |
|         /* TTR: TIGbus Timing Register.  */
 | |
|         /* All sorts of stuff related to interrupt delivery timings.  */
 | |
|         break;
 | |
|     case 0x05c0:
 | |
|         /* TDR: TIGbug Device Timing Register.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0600:
 | |
|         /* DIM2: Device Interrupt Mask Register, CPU2.  */
 | |
|         s->cchip.dim[2] = val;
 | |
|         cpu_irq_change(s->cchip.cpu[2], val & s->cchip.drir);
 | |
|         break;
 | |
|     case 0x0640:
 | |
|         /* DIM3: Device Interrupt Mask Register, CPU3.  */
 | |
|         s->cchip.dim[3] = val;
 | |
|         cpu_irq_change(s->cchip.cpu[3], val & s->cchip.drir);
 | |
|         break;
 | |
| 
 | |
|     case 0x0680: /* DIR2 (RO) */
 | |
|     case 0x06c0: /* DIR3 (RO) */
 | |
|         break;
 | |
| 
 | |
|     case 0x0700: /* IIC2 */
 | |
|         s->cchip.iic[2] = val & 0xffffff;
 | |
|         break;
 | |
|     case 0x0740: /* IIC3 */
 | |
|         s->cchip.iic[3] = val & 0xffffff;
 | |
|         break;
 | |
| 
 | |
|     case 0x0780:
 | |
|         /* PWR: Power Management Control.   */
 | |
|         break;
 | |
|     
 | |
|     case 0x0c00: /* CMONCTLA */
 | |
|     case 0x0c40: /* CMONCTLB */
 | |
|     case 0x0c80: /* CMONCNT01 */
 | |
|     case 0x0cc0: /* CMONCNT23 */
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         cpu_unassigned_access(current_cpu, addr, true, false, 0, size);
 | |
|         return;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void dchip_write(void *opaque, hwaddr addr,
 | |
|                         uint64_t val, unsigned size)
 | |
| {
 | |
|     /* Skip this.  It's all related to DRAM timing and setup.  */
 | |
| }
 | |
| 
 | |
| static void pchip_write(void *opaque, hwaddr addr,
 | |
|                         uint64_t val, unsigned size)
 | |
| {
 | |
|     TyphoonState *s = opaque;
 | |
|     uint64_t oldval;
 | |
| 
 | |
|     switch (addr) {
 | |
|     case 0x0000:
 | |
|         /* WSBA0: Window Space Base Address Register.  */
 | |
|         s->pchip.win[0].wba = val & 0xfff00003u;
 | |
|         break;
 | |
|     case 0x0040:
 | |
|         /* WSBA1 */
 | |
|         s->pchip.win[1].wba = val & 0xfff00003u;
 | |
|         break;
 | |
|     case 0x0080:
 | |
|         /* WSBA2 */
 | |
|         s->pchip.win[2].wba = val & 0xfff00003u;
 | |
|         break;
 | |
|     case 0x00c0:
 | |
|         /* WSBA3 */
 | |
|         s->pchip.win[3].wba = (val & 0x80fff00001ull) | 2;
 | |
|         break;
 | |
| 
 | |
|     case 0x0100:
 | |
|         /* WSM0: Window Space Mask Register.  */
 | |
|         s->pchip.win[0].wsm = val & 0xfff00000u;
 | |
|         break;
 | |
|     case 0x0140:
 | |
|         /* WSM1 */
 | |
|         s->pchip.win[1].wsm = val & 0xfff00000u;
 | |
|         break;
 | |
|     case 0x0180:
 | |
|         /* WSM2 */
 | |
|         s->pchip.win[2].wsm = val & 0xfff00000u;
 | |
|         break;
 | |
|     case 0x01c0:
 | |
|         /* WSM3 */
 | |
|         s->pchip.win[3].wsm = val & 0xfff00000u;
 | |
|         break;
 | |
| 
 | |
|     case 0x0200:
 | |
|         /* TBA0: Translated Base Address Register.  */
 | |
|         s->pchip.win[0].tba = val & 0x7fffffc00ull;
 | |
|         break;
 | |
|     case 0x0240:
 | |
|         /* TBA1 */
 | |
|         s->pchip.win[1].tba = val & 0x7fffffc00ull;
 | |
|         break;
 | |
|     case 0x0280:
 | |
|         /* TBA2 */
 | |
|         s->pchip.win[2].tba = val & 0x7fffffc00ull;
 | |
|         break;
 | |
|     case 0x02c0:
 | |
|         /* TBA3 */
 | |
|         s->pchip.win[3].tba = val & 0x7fffffc00ull;
 | |
|         break;
 | |
| 
 | |
|     case 0x0300:
 | |
|         /* PCTL: Pchip Control Register.  */
 | |
|         oldval = s->pchip.ctl;
 | |
|         oldval &= ~0x00001cff0fc7ffull;       /* RW fields */
 | |
|         oldval |= val & 0x00001cff0fc7ffull;
 | |
|         s->pchip.ctl = oldval;
 | |
|         break;
 | |
| 
 | |
|     case 0x0340:
 | |
|         /* PLAT: Pchip Master Latency Register.  */
 | |
|         break;
 | |
|     case 0x03c0:
 | |
|         /* PERROR: Pchip Error Register.  */
 | |
|         break;
 | |
|     case 0x0400:
 | |
|         /* PERRMASK: Pchip Error Mask Register.  */
 | |
|         break;
 | |
|     case 0x0440:
 | |
|         /* PERRSET: Pchip Error Set Register.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0480:
 | |
|         /* TLBIV: Translation Buffer Invalidate Virtual Register.  */
 | |
|         break;
 | |
| 
 | |
|     case 0x04c0:
 | |
|         /* TLBIA: Translation Buffer Invalidate All Register (WO).  */
 | |
|         break;
 | |
| 
 | |
|     case 0x0500:
 | |
|         /* PMONCTL */
 | |
|     case 0x0540:
 | |
|         /* PMONCNT */
 | |
|     case 0x0800:
 | |
|         /* SPRST */
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         cpu_unassigned_access(current_cpu, addr, true, false, 0, size);
 | |
|         return;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static const MemoryRegionOps cchip_ops = {
 | |
|     .read = cchip_read,
 | |
|     .write = cchip_write,
 | |
|     .endianness = DEVICE_LITTLE_ENDIAN,
 | |
|     .valid = {
 | |
|         .min_access_size = 8,
 | |
|         .max_access_size = 8,
 | |
|     },
 | |
|     .impl = {
 | |
|         .min_access_size = 8,
 | |
|         .max_access_size = 8,
 | |
|     },
 | |
| };
 | |
| 
 | |
| static const MemoryRegionOps dchip_ops = {
 | |
|     .read = dchip_read,
 | |
|     .write = dchip_write,
 | |
|     .endianness = DEVICE_LITTLE_ENDIAN,
 | |
|     .valid = {
 | |
|         .min_access_size = 8,
 | |
|         .max_access_size = 8,
 | |
|     },
 | |
|     .impl = {
 | |
|         .min_access_size = 8,
 | |
|         .max_access_size = 8,
 | |
|     },
 | |
| };
 | |
| 
 | |
| static const MemoryRegionOps pchip_ops = {
 | |
|     .read = pchip_read,
 | |
|     .write = pchip_write,
 | |
|     .endianness = DEVICE_LITTLE_ENDIAN,
 | |
|     .valid = {
 | |
|         .min_access_size = 8,
 | |
|         .max_access_size = 8,
 | |
|     },
 | |
|     .impl = {
 | |
|         .min_access_size = 8,
 | |
|         .max_access_size = 8,
 | |
|     },
 | |
| };
 | |
| 
 | |
| /* A subroutine of typhoon_translate_iommu that builds an IOMMUTLBEntry
 | |
|    using the given translated address and mask.  */
 | |
| static bool make_iommu_tlbe(hwaddr taddr, hwaddr mask, IOMMUTLBEntry *ret)
 | |
| {
 | |
|     *ret = (IOMMUTLBEntry) {
 | |
|         .target_as = &address_space_memory,
 | |
|         .translated_addr = taddr,
 | |
|         .addr_mask = mask,
 | |
|         .perm = IOMMU_RW,
 | |
|     };
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /* A subroutine of typhoon_translate_iommu that handles scatter-gather
 | |
|    translation, given the address of the PTE.  */
 | |
| static bool pte_translate(hwaddr pte_addr, IOMMUTLBEntry *ret)
 | |
| {
 | |
|     uint64_t pte = address_space_ldq(&address_space_memory, pte_addr,
 | |
|                                      MEMTXATTRS_UNSPECIFIED, NULL);
 | |
| 
 | |
|     /* Check valid bit.  */
 | |
|     if ((pte & 1) == 0) {
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     return make_iommu_tlbe((pte & 0x3ffffe) << 12, 0x1fff, ret);
 | |
| }
 | |
| 
 | |
| /* A subroutine of typhoon_translate_iommu that handles one of the
 | |
|    four single-address-cycle translation windows.  */
 | |
| static bool window_translate(TyphoonWindow *win, hwaddr addr,
 | |
|                              IOMMUTLBEntry *ret)
 | |
| {
 | |
|     uint32_t wba = win->wba;
 | |
|     uint64_t wsm = win->wsm;
 | |
|     uint64_t tba = win->tba;
 | |
|     uint64_t wsm_ext = wsm | 0xfffff;
 | |
| 
 | |
|     /* Check for window disabled.  */
 | |
|     if ((wba & 1) == 0) {
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /* Check for window hit.  */
 | |
|     if ((addr & ~wsm_ext) != (wba & 0xfff00000u)) {
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     if (wba & 2) {
 | |
|         /* Scatter-gather translation.  */
 | |
|         hwaddr pte_addr;
 | |
| 
 | |
|         /* See table 10-6, Generating PTE address for PCI DMA Address.  */
 | |
|         pte_addr  = tba & ~(wsm >> 10);
 | |
|         pte_addr |= (addr & (wsm | 0xfe000)) >> 10;
 | |
|         return pte_translate(pte_addr, ret);
 | |
|     } else {
 | |
| 	/* Direct-mapped translation.  */
 | |
| 	return make_iommu_tlbe(tba & ~wsm_ext, wsm_ext, ret);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Handle PCI-to-system address translation.  */
 | |
| /* TODO: A translation failure here ought to set PCI error codes on the
 | |
|    Pchip and generate a machine check interrupt.  */
 | |
| static IOMMUTLBEntry typhoon_translate_iommu(MemoryRegion *iommu, hwaddr addr,
 | |
|                                              bool is_write)
 | |
| {
 | |
|     TyphoonPchip *pchip = container_of(iommu, TyphoonPchip, iommu);
 | |
|     IOMMUTLBEntry ret;
 | |
|     int i;
 | |
| 
 | |
|     if (addr <= 0xffffffffu) {
 | |
|         /* Single-address cycle.  */
 | |
| 
 | |
|         /* Check for the Window Hole, inhibiting matching.  */
 | |
|         if ((pchip->ctl & 0x20)
 | |
|             && addr >= 0x80000
 | |
|             && addr <= 0xfffff) {
 | |
|             goto failure;
 | |
|         }
 | |
| 
 | |
|         /* Check the first three windows.  */
 | |
|         for (i = 0; i < 3; ++i) {
 | |
|             if (window_translate(&pchip->win[i], addr, &ret)) {
 | |
|                 goto success;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /* Check the fourth window for DAC disable.  */
 | |
|         if ((pchip->win[3].wba & 0x80000000000ull) == 0
 | |
| 	    && window_translate(&pchip->win[3], addr, &ret)) {
 | |
|             goto success;
 | |
|         }
 | |
|     } else {
 | |
|         /* Double-address cycle.  */
 | |
| 
 | |
|         if (addr >= 0x10000000000ull && addr < 0x20000000000ull) {
 | |
|             /* Check for the DMA monster window.  */
 | |
|             if (pchip->ctl & 0x40) {
 | |
|                 /* See 10.1.4.4; in particular <39:35> is ignored.  */
 | |
|                 make_iommu_tlbe(0, 0x007ffffffffull, &ret);
 | |
| 		goto success;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (addr >= 0x80000000000ull && addr <= 0xfffffffffffull) {
 | |
|             /* Check the fourth window for DAC enable and window enable.  */
 | |
|             if ((pchip->win[3].wba & 0x80000000001ull) == 0x80000000001ull) {
 | |
|                 uint64_t pte_addr;
 | |
| 
 | |
|                 pte_addr  = pchip->win[3].tba & 0x7ffc00000ull;
 | |
|                 pte_addr |= (addr & 0xffffe000u) >> 10;
 | |
|                 if (pte_translate(pte_addr, &ret)) {
 | |
| 			goto success;
 | |
| 		}
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|  failure:
 | |
|     ret = (IOMMUTLBEntry) { .perm = IOMMU_NONE };
 | |
|  success:
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static const MemoryRegionIOMMUOps typhoon_iommu_ops = {
 | |
|     .translate = typhoon_translate_iommu,
 | |
| };
 | |
| 
 | |
| static AddressSpace *typhoon_pci_dma_iommu(PCIBus *bus, void *opaque, int devfn)
 | |
| {
 | |
|     TyphoonState *s = opaque;
 | |
|     return &s->pchip.iommu_as;
 | |
| }
 | |
| 
 | |
| static void typhoon_set_irq(void *opaque, int irq, int level)
 | |
| {
 | |
|     TyphoonState *s = opaque;
 | |
|     uint64_t drir;
 | |
|     int i;
 | |
| 
 | |
|     /* Set/Reset the bit in CCHIP.DRIR based on IRQ+LEVEL.  */
 | |
|     drir = s->cchip.drir;
 | |
|     if (level) {
 | |
|         drir |= 1ull << irq;
 | |
|     } else {
 | |
|         drir &= ~(1ull << irq);
 | |
|     }
 | |
|     s->cchip.drir = drir;
 | |
| 
 | |
|     for (i = 0; i < 4; ++i) {
 | |
|         cpu_irq_change(s->cchip.cpu[i], s->cchip.dim[i] & drir);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void typhoon_set_isa_irq(void *opaque, int irq, int level)
 | |
| {
 | |
|     typhoon_set_irq(opaque, 55, level);
 | |
| }
 | |
| 
 | |
| static void typhoon_set_timer_irq(void *opaque, int irq, int level)
 | |
| {
 | |
|     TyphoonState *s = opaque;
 | |
|     int i;
 | |
| 
 | |
|     /* Thankfully, the mc146818rtc code doesn't track the IRQ state,
 | |
|        and so we don't have to worry about missing interrupts just
 | |
|        because we never actually ACK the interrupt.  Just ignore any
 | |
|        case of the interrupt level going low.  */
 | |
|     if (level == 0) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* Deliver the interrupt to each CPU, considering each CPU's IIC.  */
 | |
|     for (i = 0; i < 4; ++i) {
 | |
|         AlphaCPU *cpu = s->cchip.cpu[i];
 | |
|         if (cpu != NULL) {
 | |
|             uint32_t iic = s->cchip.iic[i];
 | |
| 
 | |
|             /* ??? The verbage in Section 10.2.2.10 isn't 100% clear.
 | |
|                Bit 24 is the OverFlow bit, RO, and set when the count
 | |
|                decrements past 0.  When is OF cleared?  My guess is that
 | |
|                OF is actually cleared when the IIC is written, and that
 | |
|                the ICNT field always decrements.  At least, that's an
 | |
|                interpretation that makes sense, and "allows the CPU to
 | |
|                determine exactly how mant interval timer ticks were
 | |
|                skipped".  At least within the next 4M ticks...  */
 | |
| 
 | |
|             iic = ((iic - 1) & 0x1ffffff) | (iic & 0x1000000);
 | |
|             s->cchip.iic[i] = iic;
 | |
| 
 | |
|             if (iic & 0x1000000) {
 | |
|                 /* Set the ITI bit for this cpu.  */
 | |
|                 s->cchip.misc |= 1 << (i + 4);
 | |
|                 /* And signal the interrupt.  */
 | |
|                 cpu_interrupt(CPU(cpu), CPU_INTERRUPT_TIMER);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void typhoon_alarm_timer(void *opaque)
 | |
| {
 | |
|     TyphoonState *s = (TyphoonState *)((uintptr_t)opaque & ~3);
 | |
|     int cpu = (uintptr_t)opaque & 3;
 | |
| 
 | |
|     /* Set the ITI bit for this cpu.  */
 | |
|     s->cchip.misc |= 1 << (cpu + 4);
 | |
|     cpu_interrupt(CPU(s->cchip.cpu[cpu]), CPU_INTERRUPT_TIMER);
 | |
| }
 | |
| 
 | |
| PCIBus *typhoon_init(ram_addr_t ram_size, ISABus **isa_bus,
 | |
|                      qemu_irq *p_rtc_irq,
 | |
|                      AlphaCPU *cpus[4], pci_map_irq_fn sys_map_irq)
 | |
| {
 | |
|     const uint64_t MB = 1024 * 1024;
 | |
|     const uint64_t GB = 1024 * MB;
 | |
|     MemoryRegion *addr_space = get_system_memory();
 | |
|     DeviceState *dev;
 | |
|     TyphoonState *s;
 | |
|     PCIHostState *phb;
 | |
|     PCIBus *b;
 | |
|     int i;
 | |
| 
 | |
|     dev = qdev_create(NULL, TYPE_TYPHOON_PCI_HOST_BRIDGE);
 | |
| 
 | |
|     s = TYPHOON_PCI_HOST_BRIDGE(dev);
 | |
|     phb = PCI_HOST_BRIDGE(dev);
 | |
| 
 | |
|     s->cchip.misc = 0x800000000ull; /* Revision: Typhoon.  */
 | |
|     s->pchip.win[3].wba = 2;        /* Window 3 SG always enabled. */
 | |
| 
 | |
|     /* Remember the CPUs so that we can deliver interrupts to them.  */
 | |
|     for (i = 0; i < 4; i++) {
 | |
|         AlphaCPU *cpu = cpus[i];
 | |
|         s->cchip.cpu[i] = cpu;
 | |
|         if (cpu != NULL) {
 | |
|             cpu->alarm_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
 | |
|                                                  typhoon_alarm_timer,
 | |
|                                                  (void *)((uintptr_t)s + i));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     *p_rtc_irq = qemu_allocate_irq(typhoon_set_timer_irq, s, 0);
 | |
| 
 | |
|     /* Main memory region, 0x00.0000.0000.  Real hardware supports 32GB,
 | |
|        but the address space hole reserved at this point is 8TB.  */
 | |
|     memory_region_allocate_system_memory(&s->ram_region, OBJECT(s), "ram",
 | |
|                                          ram_size);
 | |
|     memory_region_add_subregion(addr_space, 0, &s->ram_region);
 | |
| 
 | |
|     /* TIGbus, 0x801.0000.0000, 1GB.  */
 | |
|     /* ??? The TIGbus is used for delivering interrupts, and access to
 | |
|        the flash ROM.  I'm not sure that we need to implement it at all.  */
 | |
| 
 | |
|     /* Pchip0 CSRs, 0x801.8000.0000, 256MB.  */
 | |
|     memory_region_init_io(&s->pchip.region, OBJECT(s), &pchip_ops, s, "pchip0",
 | |
|                           256*MB);
 | |
|     memory_region_add_subregion(addr_space, 0x80180000000ULL,
 | |
|                                 &s->pchip.region);
 | |
| 
 | |
|     /* Cchip CSRs, 0x801.A000.0000, 256MB.  */
 | |
|     memory_region_init_io(&s->cchip.region, OBJECT(s), &cchip_ops, s, "cchip0",
 | |
|                           256*MB);
 | |
|     memory_region_add_subregion(addr_space, 0x801a0000000ULL,
 | |
|                                 &s->cchip.region);
 | |
| 
 | |
|     /* Dchip CSRs, 0x801.B000.0000, 256MB.  */
 | |
|     memory_region_init_io(&s->dchip_region, OBJECT(s), &dchip_ops, s, "dchip0",
 | |
|                           256*MB);
 | |
|     memory_region_add_subregion(addr_space, 0x801b0000000ULL,
 | |
|                                 &s->dchip_region);
 | |
| 
 | |
|     /* Pchip0 PCI memory, 0x800.0000.0000, 4GB.  */
 | |
|     memory_region_init(&s->pchip.reg_mem, OBJECT(s), "pci0-mem", 4*GB);
 | |
|     memory_region_add_subregion(addr_space, 0x80000000000ULL,
 | |
|                                 &s->pchip.reg_mem);
 | |
| 
 | |
|     /* Pchip0 PCI I/O, 0x801.FC00.0000, 32MB.  */
 | |
|     memory_region_init_io(&s->pchip.reg_io, OBJECT(s), &alpha_pci_ignore_ops,
 | |
|                           NULL, "pci0-io", 32*MB);
 | |
|     memory_region_add_subregion(addr_space, 0x801fc000000ULL,
 | |
|                                 &s->pchip.reg_io);
 | |
| 
 | |
|     b = pci_register_bus(dev, "pci",
 | |
|                          typhoon_set_irq, sys_map_irq, s,
 | |
|                          &s->pchip.reg_mem, &s->pchip.reg_io,
 | |
|                          0, 64, TYPE_PCI_BUS);
 | |
|     phb->bus = b;
 | |
|     qdev_init_nofail(dev);
 | |
| 
 | |
|     /* Host memory as seen from the PCI side, via the IOMMU.  */
 | |
|     memory_region_init_iommu(&s->pchip.iommu, OBJECT(s), &typhoon_iommu_ops,
 | |
|                              "iommu-typhoon", UINT64_MAX);
 | |
|     address_space_init(&s->pchip.iommu_as, &s->pchip.iommu, "pchip0-pci");
 | |
|     pci_setup_iommu(b, typhoon_pci_dma_iommu, s);
 | |
| 
 | |
|     /* Pchip0 PCI special/interrupt acknowledge, 0x801.F800.0000, 64MB.  */
 | |
|     memory_region_init_io(&s->pchip.reg_iack, OBJECT(s), &alpha_pci_iack_ops,
 | |
|                           b, "pci0-iack", 64*MB);
 | |
|     memory_region_add_subregion(addr_space, 0x801f8000000ULL,
 | |
|                                 &s->pchip.reg_iack);
 | |
| 
 | |
|     /* Pchip0 PCI configuration, 0x801.FE00.0000, 16MB.  */
 | |
|     memory_region_init_io(&s->pchip.reg_conf, OBJECT(s), &alpha_pci_conf1_ops,
 | |
|                           b, "pci0-conf", 16*MB);
 | |
|     memory_region_add_subregion(addr_space, 0x801fe000000ULL,
 | |
|                                 &s->pchip.reg_conf);
 | |
| 
 | |
|     /* For the record, these are the mappings for the second PCI bus.
 | |
|        We can get away with not implementing them because we indicate
 | |
|        via the Cchip.CSC<PIP> bit that Pchip1 is not present.  */
 | |
|     /* Pchip1 PCI memory, 0x802.0000.0000, 4GB.  */
 | |
|     /* Pchip1 CSRs, 0x802.8000.0000, 256MB.  */
 | |
|     /* Pchip1 PCI special/interrupt acknowledge, 0x802.F800.0000, 64MB.  */
 | |
|     /* Pchip1 PCI I/O, 0x802.FC00.0000, 32MB.  */
 | |
|     /* Pchip1 PCI configuration, 0x802.FE00.0000, 16MB.  */
 | |
| 
 | |
|     /* Init the ISA bus.  */
 | |
|     /* ??? Technically there should be a cy82c693ub pci-isa bridge.  */
 | |
|     {
 | |
|         qemu_irq *isa_irqs;
 | |
| 
 | |
|         *isa_bus = isa_bus_new(NULL, get_system_memory(), &s->pchip.reg_io,
 | |
|                                &error_abort);
 | |
|         isa_irqs = i8259_init(*isa_bus,
 | |
|                               qemu_allocate_irq(typhoon_set_isa_irq, s, 0));
 | |
|         isa_bus_irqs(*isa_bus, isa_irqs);
 | |
|     }
 | |
| 
 | |
|     return b;
 | |
| }
 | |
| 
 | |
| static int typhoon_pcihost_init(SysBusDevice *dev)
 | |
| {
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static void typhoon_pcihost_class_init(ObjectClass *klass, void *data)
 | |
| {
 | |
|     SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
 | |
| 
 | |
|     k->init = typhoon_pcihost_init;
 | |
| }
 | |
| 
 | |
| static const TypeInfo typhoon_pcihost_info = {
 | |
|     .name          = TYPE_TYPHOON_PCI_HOST_BRIDGE,
 | |
|     .parent        = TYPE_PCI_HOST_BRIDGE,
 | |
|     .instance_size = sizeof(TyphoonState),
 | |
|     .class_init    = typhoon_pcihost_class_init,
 | |
| };
 | |
| 
 | |
| static void typhoon_register_types(void)
 | |
| {
 | |
|     type_register_static(&typhoon_pcihost_info);
 | |
| }
 | |
| 
 | |
| type_init(typhoon_register_types)
 |