static void lsi_execute_script(LSIState *s) { uint32_t insn; uint32_t addr, addr_high; int opcode; int insn_processed = 0; s->istat1 |= LSI_ISTAT1_SRUN; again: insn_processed++; insn = read_dword(s, s->dsp); addr = read_dword(s, s->dsp + 4); addr_high = 0; DPRINTF("SCRIPTS dsp=%08x opcode %08x arg %08x\n", s->dsp, insn, addr); s->dsps = addr; s->dcmd = insn >> 24; s->dsp += 8; switch (insn >> 30) { case 0: /* Block move. */ if (s->sist1 & LSI_SIST1_STO) { DPRINTF("Delayed select timeout\n"); lsi_stop_script(s); break; } s->dbc = insn & 0xffffff; s->rbc = s->dbc; if (insn & (1 << 29)) { /* Indirect addressing. */ addr = read_dword(s, addr); } else if (insn & (1 << 28)) { uint32_t buf[2]; int32_t offset; /* Table indirect addressing. */ offset = sxt24(addr); cpu_physical_memory_read(s->dsa + offset, (uint8_t *)buf, 8); /* byte count is stored in bits 0:23 only */ s->dbc = cpu_to_le32(buf[0]) & 0xffffff; s->rbc = s->dbc; addr = cpu_to_le32(buf[1]); /* 40-bit DMA, upper addr bits [39:32] stored in first DWORD of * table, bits [31:24] */ if (lsi_dma_40bit(s)) addr_high = cpu_to_le32(buf[0]) >> 24; } if ((s->sstat1 & PHASE_MASK) != ((insn >> 24) & 7)) { DPRINTF("Wrong phase got %d expected %d\n", s->sstat1 & PHASE_MASK, (insn >> 24) & 7); lsi_script_scsi_interrupt(s, LSI_SIST0_MA, 0); break; } s->dnad = addr; s->dnad64 = addr_high; /* ??? Set ESA. */ s->ia = s->dsp - 8; switch (s->sstat1 & 0x7) { case PHASE_DO: s->waiting = 2; lsi_do_dma(s, 1); if (s->waiting) s->waiting = 3; break; case PHASE_DI: s->waiting = 2; s->current_dma_len = s->dbc; lsi_do_dma(s, 0); if (s->waiting) s->waiting = 3; break; case PHASE_CMD: lsi_do_command(s); break; case PHASE_ST: lsi_do_status(s); break; case PHASE_MO: lsi_do_msgout(s); break; case PHASE_MI: lsi_do_msgin(s); break; default: BADF("Unimplemented phase %d\n", s->sstat1 & PHASE_MASK); exit(1); } s->dfifo = s->dbc & 0xff; s->ctest5 = (s->ctest5 & 0xfc) | ((s->dbc >> 8) & 3); s->sbc = s->dbc; s->rbc -= s->dbc; s->ua = addr + s->dbc; break; case 1: /* IO or Read/Write instruction. */ opcode = (insn >> 27) & 7; if (opcode < 5) { uint32_t id; if (insn & (1 << 25)) { id = read_dword(s, s->dsa + sxt24(insn)); } else { id = addr; } id = (id >> 16) & 0xf; if (insn & (1 << 26)) { addr = s->dsp + sxt24(addr); } s->dnad = addr; switch (opcode) { case 0: /* Select */ s->sdid = id; if (s->current_dma_len && (s->ssid & 0xf) == id) { DPRINTF("Already reselected by target %d\n", id); break; } s->sstat0 |= LSI_SSTAT0_WOA; s->scntl1 &= ~LSI_SCNTL1_IARB; if (id >= LSI_MAX_DEVS || !s->scsi_dev[id]) { DPRINTF("Selected absent target %d\n", id); lsi_script_scsi_interrupt(s, 0, LSI_SIST1_STO); lsi_disconnect(s); break; } DPRINTF("Selected target %d%s\n", id, insn & (1 << 3) ? " ATN" : ""); /* ??? Linux drivers compain when this is set. Maybe it only applies in low-level mode (unimplemented). lsi_script_scsi_interrupt(s, LSI_SIST0_CMP, 0); */ s->current_dev = s->scsi_dev[id]; s->current_tag = id << 8; s->scntl1 |= LSI_SCNTL1_CON; if (insn & (1 << 3)) { s->socl |= LSI_SOCL_ATN; } lsi_set_phase(s, PHASE_MO); break; case 1: /* Disconnect */ DPRINTF("Wait Disconect\n"); s->scntl1 &= ~LSI_SCNTL1_CON; break; case 2: /* Wait Reselect */ lsi_wait_reselect(s); break; case 3: /* Set */ DPRINTF("Set%s%s%s%s\n", insn & (1 << 3) ? " ATN" : "", insn & (1 << 6) ? " ACK" : "", insn & (1 << 9) ? " TM" : "", insn & (1 << 10) ? " CC" : ""); if (insn & (1 << 3)) { s->socl |= LSI_SOCL_ATN; lsi_set_phase(s, PHASE_MO); } if (insn & (1 << 9)) { BADF("Target mode not implemented\n"); exit(1); } if (insn & (1 << 10)) s->carry = 1; break; case 4: /* Clear */ DPRINTF("Clear%s%s%s%s\n", insn & (1 << 3) ? " ATN" : "", insn & (1 << 6) ? " ACK" : "", insn & (1 << 9) ? " TM" : "", insn & (1 << 10) ? " CC" : ""); if (insn & (1 << 3)) { s->socl &= ~LSI_SOCL_ATN; } if (insn & (1 << 10)) s->carry = 0; break; } } else { uint8_t op0; uint8_t op1; uint8_t data8; int reg; int operator; #ifdef DEBUG_LSI static const char *opcode_names[3] = {"Write", "Read", "Read-Modify-Write"}; static const char *operator_names[8] = {"MOV", "SHL", "OR", "XOR", "AND", "SHR", "ADD", "ADC"}; #endif reg = ((insn >> 16) & 0x7f) | (insn & 0x80); data8 = (insn >> 8) & 0xff; opcode = (insn >> 27) & 7; operator = (insn >> 24) & 7; DPRINTF("%s reg 0x%x %s data8=0x%02x sfbr=0x%02x%s\n", opcode_names[opcode - 5], reg, operator_names[operator], data8, s->sfbr, (insn & (1 << 23)) ? " SFBR" : ""); op0 = op1 = 0; switch (opcode) { case 5: /* From SFBR */ op0 = s->sfbr; op1 = data8; break; case 6: /* To SFBR */ if (operator) op0 = lsi_reg_readb(s, reg); op1 = data8; break; case 7: /* Read-modify-write */ if (operator) op0 = lsi_reg_readb(s, reg); if (insn & (1 << 23)) { op1 = s->sfbr; } else { op1 = data8; } break; } switch (operator) { case 0: /* move */ op0 = op1; break; case 1: /* Shift left */ op1 = op0 >> 7; op0 = (op0 << 1) | s->carry; s->carry = op1; break; case 2: /* OR */ op0 |= op1; break; case 3: /* XOR */ op0 ^= op1; break; case 4: /* AND */ op0 &= op1; break; case 5: /* SHR */ op1 = op0 & 1; op0 = (op0 >> 1) | (s->carry << 7); s->carry = op1; break; case 6: /* ADD */ op0 += op1; s->carry = op0 < op1; break; case 7: /* ADC */ op0 += op1 + s->carry; if (s->carry) s->carry = op0 <= op1; else s->carry = op0 < op1; break; } switch (opcode) { case 5: /* From SFBR */ case 7: /* Read-modify-write */ lsi_reg_writeb(s, reg, op0); break; case 6: /* To SFBR */ s->sfbr = op0; break; } } break; case 2: /* Transfer Control. */ { int cond; int jmp; if ((insn & 0x002e0000) == 0) { DPRINTF("NOP\n"); break; } if (s->sist1 & LSI_SIST1_STO) { DPRINTF("Delayed select timeout\n"); lsi_stop_script(s); break; } cond = jmp = (insn & (1 << 19)) != 0; if (cond == jmp && (insn & (1 << 21))) { DPRINTF("Compare carry %d\n", s->carry == jmp); cond = s->carry != 0; } if (cond == jmp && (insn & (1 << 17))) { DPRINTF("Compare phase %d %c= %d\n", (s->sstat1 & PHASE_MASK), jmp ? '=' : '!', ((insn >> 24) & 7)); cond = (s->sstat1 & PHASE_MASK) == ((insn >> 24) & 7); } if (cond == jmp && (insn & (1 << 18))) { uint8_t mask; mask = (~insn >> 8) & 0xff; DPRINTF("Compare data 0x%x & 0x%x %c= 0x%x\n", s->sfbr, mask, jmp ? '=' : '!', insn & mask); cond = (s->sfbr & mask) == (insn & mask); } if (cond == jmp) { if (insn & (1 << 23)) { /* Relative address. */ addr = s->dsp + sxt24(addr); } switch ((insn >> 27) & 7) { case 0: /* Jump */ DPRINTF("Jump to 0x%08x\n", addr); s->dsp = addr; break; case 1: /* Call */ DPRINTF("Call 0x%08x\n", addr); s->temp = s->dsp; s->dsp = addr; break; case 2: /* Return */ DPRINTF("Return to 0x%08x\n", s->temp); s->dsp = s->temp; break; case 3: /* Interrupt */ DPRINTF("Interrupt 0x%08x\n", s->dsps); if ((insn & (1 << 20)) != 0) { s->istat0 |= LSI_ISTAT0_INTF; lsi_update_irq(s); } else { lsi_script_dma_interrupt(s, LSI_DSTAT_SIR); } break; default: DPRINTF("Illegal transfer control\n"); lsi_script_dma_interrupt(s, LSI_DSTAT_IID); break; } } else { DPRINTF("Control condition failed\n"); } } break; case 3: if ((insn & (1 << 29)) == 0) { /* Memory move. */ uint32_t dest; /* ??? The docs imply the destination address is loaded into the TEMP register. However the Linux drivers rely on the value being presrved. */ dest = read_dword(s, s->dsp); s->dsp += 4; lsi_memcpy(s, dest, addr, insn & 0xffffff); } else { uint8_t data[7]; int reg; int n; int i; if (insn & (1 << 28)) { addr = s->dsa + sxt24(addr); } n = (insn & 7); reg = (insn >> 16) & 0xff; if (insn & (1 << 24)) { cpu_physical_memory_read(addr, data, n); DPRINTF("Load reg 0x%x size %d addr 0x%08x = %08x\n", reg, n, addr, *(int *)data); for (i = 0; i < n; i++) { lsi_reg_writeb(s, reg + i, data[i]); } } else { DPRINTF("Store reg 0x%x size %d addr 0x%08x\n", reg, n, addr); for (i = 0; i < n; i++) { data[i] = lsi_reg_readb(s, reg + i); } cpu_physical_memory_write(addr, data, n); } } } if (insn_processed > 10000 && !s->waiting) { /* Some windows drivers make the device spin waiting for a memory location to change. If we have been executed a lot of code then assume this is the case and force an unexpected device disconnect. This is apparently sufficient to beat the drivers into submission. */ if (!(s->sien0 & LSI_SIST0_UDC)) fprintf(stderr, "inf. loop with UDC masked\n"); lsi_script_scsi_interrupt(s, LSI_SIST0_UDC, 0); lsi_disconnect(s); } else if (s->istat1 & LSI_ISTAT1_SRUN && !s->waiting) { if (s->dcntl & LSI_DCNTL_SSM) { lsi_script_dma_interrupt(s, LSI_DSTAT_SSI); } else { goto again; } } DPRINTF("SCRIPTS execution stopped\n"); }