/* Simulation of the Control/Status Register (CSR) memory space. */ /* SPDX-License-Identifier: MIT Copyright (c) 2024 Sprite_tm <jeroen@spritesmods.com> */ #include <stdlib.h> #include <stdint.h> #include <stdio.h> #include <assert.h> #include "csr.h" #include "emu.h" #include "log.h" #include "scsi.h" #include "int.h" // Debug logging #define CSR_LOG(msg_level, format_and_args...) \ log_printf(LOG_SRC_CSR, msg_level, format_and_args) #define CSR_LOG_DEBUG(format_and_args...) CSR_LOG(LOG_DEBUG, format_and_args) #define CSR_LOG_INFO(format_and_args...) CSR_LOG(LOG_INFO, format_and_args) #define CSR_LOG_WARN(format_and_args...) CSR_LOG(LOG_WARNING, format_and_args) //csr: usr/include/sys/mtpr.h, note we're Robin #define CSR_O_RSEL 0x00 /* reset selection , see below */ #define CSR_I_PERR1 0x00 /* parity error latch */ #define CSR_I_PERR2 0x02 /* parity error latch */ #define CSR_I_MBERR 0x04 /* latches address on multibus error */ #define CSR_O_SC_C 0x06 /* scsi byte count */ #define CSR_I_SC_C 0x06 /* scsi byte count */ #define CSR_O_SC_P 0x0A /* scsi pointer register */ #define CSR_I_SC_P 0x0A /* scsi pointer register */ #define CSR_O_SC_R 0x0E /* scsi register */ #define CSR_I_SC_R 0x0E /* scsi register */ #define CSR_O_LEDS 0x10 /* led register */ #define CSR_I_LEDS 0x10 /* led register */ #define CSR_I_USRT 0x12 /* usart register */ #define CSR_I_ERR 0x14 /* error reporting */ #define CSR_O_MISC 0x16 /* misc. functions */ #define CSR_I_MISC 0x16 /* misc. functions */ #define CSR_O_KILL 0x18 /* kill job / dma cpu */ #define CSR_I_KILL 0x18 /* kill job / dma cpu */ #define CSR_O_TRCE 0x1A /* rce/ tce for usarts */ #define CSR_I_TRCE 0x1A /* rce/ tce for usarts */ #define CSR_O_INTE 0x1C /* interupt register */ #define CSR_I_INTE 0x1C /* interupt register */ #define CSR_O_MAPID 0x1E /* user id register */ #define CSR_I_USER 0x1E /* user number */ #define MISC_UINTEN 0x1 /* enable ups interrupt */ #define MISC_TINTEN 0x2 /* enable temperature interrupt */ #define MISC_CINTJEN 0x4 /* enable job's clock interrupt */ #define MISC_CINTDEN 0x8 /* enable dma's clock interrupt */ #define MISC_RESMB 0x10 /* reset multibus ACTIVE LOW */ #define MISC_HOLDMBUS 0x20 /* hold multibus */ #define MISC_DIAGUART 0x40 /* disable output to ttys */ #define MISC_TBUSY 0x80 /* READ only */ #define MISC_ENMAP 0x100 /* enable mapping (active low) */ #define MISC_DISMAP 0x100 /* disables map (active hi ) */ #define MISC_DIAGMB 0x200 /* put multibus into diagnostic mode */ #define MISC_DIAGPESC 0x400 /* force parity scsi parity error */ #define MISC_DIAGPL 0x800 /* force parity error low byte */ #define MISC_DIAGPH 0x1000 /* force parity error hi byte */ #define MISC_SCSIDL 0x2000 /* enable diag latch (ACTIVE LOW) */ #define MISC_BOOTJOB 0x4000 /* force job's A23 high (ACTIVE LOW ) */ #define MISC_BOOTDMA 0x8000 /* force dma's A23 high (ACTIVE LOW ) */ #define KILL_KILL_DMA 0x1 #define KILL_NKILL_JOB 0x2 #define KILL_INT_DMA 0x4 #define KILL_INT_JOB 0x8 #define KILL_JKPD 0x40 //job control protection disable #define KILL_CUR_IS_JOB 0x80 #define RESET_MULTERR 0x020 /* reset multibus interface error flag */ #define RESET_SCSI_PFLG 0x040 /* reset scsi parity error flag */ #define RESET_CLR_JOBINT 0x060 /* reset job processor software int */ #define RESET_SET_JOBINT 0x080 /* set job processor software int */ #define RESET_CLR_DMAINT 0x0a0 /* reset dma processor software int */ #define RESET_SET_DMAINT 0x0c0 /* set dma processor int */ #define RESET_CINTJ 0x0e0 /* reset job clock interrupt */ #define RESET_CINTD 0x100 /* reset dma clock int */ #define RESET_JBERR 0x120 /* reset job bus error flag */ #define RESET_DBERR 0x140 /* reset dma bus error flag */ #define RESET_MPERR 0x160 /* reset memory parity err flag SET ON RESET*/ #define RESET_SWINT 0x180 /* reset switch interrupt */ #define RESET_SCSIBERR 0x1a0 /* reset scsi bus error flag */ #define ERR_AS26 0x8000 //deadman timer for all DMA transfers #define ERR_SOOPS 0x4000 //Any DMA access of multibus or map #define ERR_UBE_DMA 0x1000 //User ID mismatch #define ERR_ABE_DMA 0x0800 //Privilege violation #define ERR_EN_BLK 0x0400 #define ERR_EN_DMA 0x0200 #define ERR_EN_JOB 0x0100 #define ERR_AERR_JOB 0x0080 //User in system space #define ERR_DERR_JOB 0x0040 //Generated when job CPU accesses DMA bus #define ERR_MBTO 0x0020 //Multibus timeout #define ERR_UBE_JOB 0x0010 //User ID mismatch #define ERR_ABE_JOB 0x0008 //Privilege violation #define ERR_EN_JOB2 0x0004 //duplicate with 0x100? #define ERR_EN_BLK2 0x0002 //duplicate with 0x200? #define ERR_EN_MBUS 0x0001 //note other defines are in scsi.c #define I_NSCPERR 0x2000 #define I_NSCBERR 0x1000 struct csr_t { uint16_t reg[0x10]; scsi_t *scsi; }; int csr_cpu_is_reset(csr_t *csr, int cpu) { int r=csr->reg[CSR_O_KILL/2] & (1<<cpu); if (cpu==1) r=!r; //job kill is low active (dma kill is high active) return r; } int csr_get_rtc_int_ena(csr_t *csr, int cpu) { if (cpu==0) { return (csr->reg[CSR_O_MISC/2]&MISC_CINTDEN); } else { return (csr->reg[CSR_O_MISC/2]&MISC_CINTJEN); } } int csr_try_mbus_held(csr_t *csr) { if (csr->reg[CSR_O_MISC/2]&MISC_HOLDMBUS) { csr->reg[CSR_O_MISC/2]|=MISC_TBUSY; return 0; } return 1; } void csr_set_access_error(csr_t *csr, int cpu, int type, int addr, int is_write) { int v=0; if (cpu==0) { if (type&ACCESS_ERROR_U) v|=ERR_UBE_DMA; if (type&ACCESS_ERROR_A) v|=ERR_ABE_DMA; } else { if (type&ACCESS_ERROR_U) v|=ERR_UBE_JOB; if (type&ACCESS_ERROR_A) v|=ERR_ABE_JOB; if (type&ACCESS_ERROR_AJOB) v|=ERR_AERR_JOB; } if (type&ACCESS_ERROR_MBTO) { v|=ERR_MBTO; if (emu_get_mb_diag()) { csr->reg[CSR_I_MBERR/2]=(addr>>11)&0xfe; if (!is_write) csr->reg[CSR_I_MBERR/2]|=0x1; } } csr->reg[CSR_I_ERR/2]|=v; } void csr_set_parity_error(csr_t *c, int hl) { c->reg[CSR_I_PERR1/2]&=~((1<<12)|(1<<13)); if (hl&2) c->reg[CSR_I_PERR1/2]|=(1<<12); if (hl&1) c->reg[CSR_I_PERR1/2]|=(1<<13); } static void update_scsi_regs(csr_t *c) { int b=scsi_get_bytecount(c->scsi); c->reg[CSR_O_SC_C/2]=b>>16; c->reg[CSR_O_SC_C/2+1]=b; b=scsi_get_pointer(c->scsi); c->reg[CSR_O_SC_P/2]=b>>16; c->reg[CSR_O_SC_P/2+1]=b; } void csr_write16(void *obj, unsigned int a, unsigned int val) { csr_t *c=(csr_t*)obj; update_scsi_regs(c); if (a==CSR_I_PERR1) return; //ro if (a==CSR_O_RSEL) { CSR_LOG_DEBUG("csr write16 0x%X (reset sel) val 0x%X\n", a, val); } else if (a==CSR_O_SC_C || a==CSR_O_SC_C+2) { c->reg[a/2]=val; scsi_set_bytecount(c->scsi, ((c->reg[CSR_O_SC_C/2]<<16)+c->reg[CSR_O_SC_C/2+1])&0xffffff); } else if (a==CSR_O_SC_P || a==CSR_O_SC_P+2) { c->reg[a/2]=val; scsi_set_pointer(c->scsi, ((c->reg[CSR_O_SC_P/2]<<16)+c->reg[CSR_O_SC_P/2+1])&0xffffff); } else if (a==CSR_O_SC_R) { scsi_set_scsireg(c->scsi, val); } else if (a==CSR_O_MISC) { emu_enable_mapper(!(val&MISC_ENMAP)); if ((val&MISC_HOLDMBUS)==0) { val&=~MISC_TBUSY; } else { val|=MISC_TBUSY; } int v=0; if ((val&MISC_SCSIDL)==0) v|=SCSI_DIAG_LATCH; if ((val&MISC_DIAGPESC)) v|=SCSI_DIAG_PARITY; scsi_set_diag(c->scsi, v); v=0; if (!(val&MISC_BOOTDMA)) v|=1; if (!(val&MISC_BOOTJOB)) v|=2; emu_set_force_a23(v); v=0; if (val&MISC_DIAGPL) v|=1; if (val&MISC_DIAGPH) v|=2; emu_set_force_parity_error(v); emu_set_mb_diag(val&MISC_DIAGMB); } else if (a==CSR_O_KILL) { //kill CSR_LOG_DEBUG("csr write16 0x%X (kill) val 0x%X\n", a, val); assert((val&0x40)==0); //we don't support this bit yet but sw doesn't seem to use it val&=0x43; //rest is set elsewhere } else if (a==CSR_I_ERR) { CSR_LOG_DEBUG("csr write16 0x%X (err) val 0x%X - reg is RO?\n", a, val); val=c->reg[a/2]; } else if (a==CSR_O_MAPID) { emu_set_cur_mapid(val); } else { CSR_LOG_DEBUG("csr write16 0x%X val 0x%X\n", a, val); } c->reg[a/2]=val; } void csr_write32(void *obj, unsigned int a, unsigned int val) { csr_write16(obj, a, val>>16); csr_write16(obj, a+2, val&0xffff); } void csr_write8(void *obj, unsigned int a, unsigned int val) { CSR_LOG_DEBUG("csr write8 %x val %x\n", a, val); //fake with a csr write16 if (a&1) { csr_write16(obj, a-1, val); } else { csr_write16(obj, a, val<<8); } } unsigned int csr_read16(void *obj, unsigned int a) { csr_t *c=(csr_t*)obj; update_scsi_regs(c); unsigned int ret=c->reg[a/2]; if (a==CSR_O_KILL) { //note: return 0x80 if we are the job cpu if (emu_get_cur_cpu()) ret|=0x80; } else if (a==CSR_O_SC_R) { return scsi_get_scsireg(c->scsi); } else { CSR_LOG_DEBUG("csr read16 0x%X -> 0x%X\n", a, ret); } return ret; } unsigned int csr_read32(void *obj, unsigned int a) { return (csr_read16(obj, a)<<16)+csr_read16(obj, a+2); } unsigned int csr_read8(void *obj, unsigned int a) { //fake using read16 if (a&1) { return csr_read16(obj, a-1)&0xff; } else { return csr_read16(obj, a)>>8; } } //Note: Most of these have been checked against the schematic. void csr_write16_mmio(void *obj, unsigned int a, unsigned int val) { csr_t *c=(csr_t*)obj; //note: a has the start of MMIO as base, but RESET_* has the base of CSR, //so we adjust the address here. a=a+0x20; if (a==RESET_MULTERR) { //Note: this doesn't really seem to do anything except allow //the multibus error address register to accept a new address. CSR_LOG_DEBUG("CSR: Reset mbus error\n"); // c->reg[CSR_O_MISC/2]&=~MISC_TBUSY; emu_raise_int(INT_VECT_MB_IF_ERR, 0, 0); emu_raise_int(INT_VECT_MB_IF_ERR, 0, 1); } else if (a==RESET_SCSI_PFLG) { c->reg[CSR_O_SC_R/2] |= I_NSCPERR; emu_raise_int(INT_VECT_SCSI_PARITY, 0, 0); } else if (a==RESET_CLR_JOBINT) { CSR_LOG_DEBUG("CSR: Clear job int\n"); c->reg[CSR_O_KILL/2] &= ~KILL_INT_JOB; emu_raise_int(INT_VECT_JOB, 0, 1); } else if (a==RESET_SET_JOBINT) { CSR_LOG_DEBUG("CSR: Set job int\n"); c->reg[CSR_O_KILL/2] |= KILL_INT_JOB; emu_raise_int(INT_VECT_JOB, INT_LEVEL_JOB, 1); } else if (a==RESET_CLR_DMAINT) { CSR_LOG_DEBUG("CSR: Clear dma int\n"); c->reg[CSR_O_KILL/2] &= ~KILL_INT_DMA; emu_raise_int(INT_VECT_DMA, 0, 0); } else if (a==RESET_SET_DMAINT) { CSR_LOG_DEBUG("CSR: Set dma int\n"); c->reg[CSR_O_KILL/2] |= KILL_INT_DMA; emu_raise_int(INT_VECT_DMA, INT_LEVEL_DMA, 0); } else if (a==RESET_CINTJ) { emu_raise_int(INT_VECT_CLOCK, 0, 1); } else if (a==RESET_CINTD) { emu_raise_int(INT_VECT_CLOCK, 0, 0); } else if (a==RESET_JBERR) { CSR_LOG_DEBUG("CSR: Reset job bus error\n"); c->reg[CSR_I_ERR/2]&=~(ERR_UBE_JOB|ERR_ABE_JOB|ERR_MBTO|ERR_DERR_JOB|ERR_AERR_JOB); } else if (a==RESET_DBERR) { CSR_LOG_DEBUG("CSR: Reset dma bus error\n"); c->reg[CSR_I_ERR/2]&=~(ERR_UBE_DMA|ERR_ABE_DMA|ERR_AS26|ERR_SOOPS); } else if (a==RESET_MPERR) { CSR_LOG_DEBUG("CSR: Reset parity error\n"); emu_raise_int(INT_VECT_PARITY_ERR, 0, 0); emu_raise_int(INT_VECT_PARITY_ERR, 0, 1); // c->reg[CSR_I_PERR1/2]&=~((1<<13)|(1<<12)); } else if (a==RESET_SWINT) { //SWitch INTerrupt //not implemented yet } else if (a==RESET_SCSIBERR) { c->reg[CSR_O_SC_R/2] |= I_NSCBERR; for (int i=0; i<16; i++) { if (i!=4) emu_raise_int(INT_VECT_SCSI_SPURIOUS+i, 0, 0); } } else { CSR_LOG_DEBUG("Unhandled MMIO write 0x%x\n", a); } } unsigned int csr_read16_mmio(void *obj, unsigned int a) { csr_write16_mmio(obj, a, 0); return 0; } csr_t *csr_new(scsi_t *scsi) { csr_t *ret=calloc(sizeof(csr_t), 1); ret->scsi=scsi; return ret; }