/* 3b2_scsi.c: AT&T 3B2 SCSI (CM195W) feature card Copyright (c) 2020, Seth J. Morabito Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of the author shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the author. */ #include "3b2_scsi.h" #include "sim_scsi.h" #include "sim_tape.h" #include "3b2_cpu.h" #include "3b2_io.h" #include "3b2_mem.h" #define PUMP_CRC 0x201b3617 static void ha_cmd(uint8 op, uint8 subdev, uint32 addr, int32 len, t_bool express); static void ha_build_req(uint8 subdev, t_bool express); static void ha_ctrl(); static void ha_write(); static void ha_write_ext(); static void ha_read_ext(); static void ha_read_capacity(); static void dump_rep(); static void dump_req(); static void dump_edt(); /* Do not initiate host IRQ if ivec is <= this value */ #define SCSI_MIN_IVEC 1 #define HA_SCSI_ID 0 #define HA_MAXFR (1u << 16) HA_STATE ha_state; SCSI_BUS ha_bus; uint8 *ha_buf; int8 ha_subdev_tab[8]; /* Map of subdevice to SCSI target */ uint8 ha_subdev_cnt; uint32 ha_crc = 0; uint32 cq_offset = 0; static struct scsi_dev_t ha_tab[] = { HA_DISK(SD327), HA_TAPE(ST120) }; #define SCSI_U_FLAGS (UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ \ UNIT_ROABLE+(SD327_DTYPE << UNIT_V_DTYPE)) UNIT ha_unit[] = { { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD327)) }, /* SCSI ID 0 */ { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD327)) }, /* SCSI ID 1 */ { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD327)) }, /* SCSI ID 2 */ { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD327)) }, /* SCSI ID 3 */ { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD327)) }, /* SCSI ID 4 */ { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD327)) }, /* SCSI ID 5 */ { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD327)) }, /* SCSI ID 6 */ { UDATA (&ha_svc, SCSI_U_FLAGS, HA_SIZE(SD327)) }, /* SCSI ID 7 */ { UDATA (&ha_svc, UNIT_DIS, 0) }, /* CIO unit */ }; static UNIT *cio_unit = &ha_unit[8]; MTAB ha_mod[] = { { SCSI_WLK, 0, NULL, "WRITEENABLED", &scsi_set_wlk, NULL, NULL, "Write enable disk drive" }, { SCSI_WLK, SCSI_WLK, NULL, "LOCKED", &scsi_set_wlk, NULL, NULL, "Write lock disk drive" }, { MTAB_XTD|MTAB_VUN, 0, "WRITE", NULL, NULL, &scsi_show_wlk, NULL, "Display drive writelock status" }, { MTAB_XTD|MTAB_VUN, SD327_DTYPE, NULL, "SD327", &ha_set_type, NULL, NULL, "Set CDC 327MB Disk Type" }, { MTAB_XTD|MTAB_VUN, ST120_DTYPE, NULL, "ST120", &ha_set_type, NULL, NULL, "Set Wangtek 120MB Tape Type" }, { MTAB_XTD|MTAB_VUN, 0, "TYPE", NULL, NULL, &ha_show_type, NULL, "Display device type" }, { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT", &scsi_set_fmt, &scsi_show_fmt, NULL, "Set/Display unit format" }, { 0 } }; #define HA_TRACE 1 static DEBTAB ha_debug[] = { { "TRACE", HA_TRACE, "Call Trace" }, { "SCMD", SCSI_DBG_CMD, "SCSI commands"}, { "SBUS", SCSI_DBG_BUS, "SCSI bus activity" }, { "SMSG", SCSI_DBG_MSG, "SCSI messages" }, { "SDSK", SCSI_DBG_DSK, "SCSI disk activity" }, { NULL } }; DEVICE ha_dev = { "SCSI", /* name */ ha_unit, /* units */ NULL, /* registers */ ha_mod, /* modifiers */ 9, /* #units */ 16, /* address radix */ 32, /* address width */ 1, /* address incr. */ 16, /* data radix */ 8, /* data width */ NULL, /* examine routine */ NULL, /* deposit routine */ &ha_reset, /* reset routine */ NULL, /* boot routine */ &ha_attach, /* attach routine */ &ha_detach, /* detach routine */ NULL, /* context */ DEV_DEBUG|DEV_DISK|DEV_SECTORS, /* flags */ 0, /* debug control flags */ ha_debug, /* debug flag names */ NULL, /* memory size change */ NULL, /* logical name */ NULL, /* help routine */ NULL, NULL, NULL, }; void ha_cio_reset(uint8 cid) { sim_debug(HA_TRACE, &ha_dev, "[%08x] Handling CIO reset\n", R[NUM_PC]); ha_state.pump_state = PUMP_NONE; ha_crc = 0; } t_stat ha_reset(DEVICE *dptr) { uint8 cid; uint32 t, dtyp; UNIT *uptr; t_stat r; sim_debug(HA_TRACE, &ha_dev, "[%08x] [ha_reset] Resetting SCSI device\n", R[NUM_PC]); ha_state.pump_state = PUMP_NONE; if (ha_buf == NULL) { ha_buf = (uint8 *)calloc(HA_MAXFR, sizeof(uint8)); } r = scsi_init(&ha_bus, HA_MAXFR); if (r != SCPE_OK) { return r; } ha_bus.dptr = dptr; scsi_reset(&ha_bus); for (t = 0; t < 8; t++) { uptr = dptr->units + t; if (t == HA_SCSI_ID) { uptr->flags = UNIT_DIS; } scsi_add_unit(&ha_bus, t, uptr); dtyp = GET_DTYPE(uptr->flags); scsi_set_unit(&ha_bus, uptr, &ha_tab[dtyp]); scsi_reset_unit(uptr); } if (dptr->flags & DEV_DIS) { sim_debug(HA_TRACE, &ha_dev, "[ha_reset] REMOVING CARD\n"); for (cid = 0; cid < CIO_SLOTS; cid++) { if (cio[cid].id == HA_ID) { break; } } if (cid == CIO_SLOTS) { /* No card was ever attached */ return SCPE_OK; } cio[cid].id = 0; cio[cid].ipl = 0; cio[cid].ivec = 0; cio[cid].exp_handler = NULL; cio[cid].full_handler = NULL; cio[cid].sysgen = NULL; ha_state.initialized = FALSE; } else if (!ha_state.initialized) { sim_debug(HA_TRACE, &ha_dev, "[ha_reset] Attaching SCSI Card\n"); /* Find the first available slot */ for (cid = 0; cid < CIO_SLOTS; cid++) { if (cio[cid].id == 0) { break; } } if (cid == CIO_SLOTS) { return SCPE_NXM; } cio[cid].id = HA_ID; cio[cid].ipl = HA_IPL; cio[cid].exp_handler = &ha_express; cio[cid].full_handler = &ha_full; cio[cid].reset_handler = &ha_cio_reset; cio[cid].sysgen = &ha_sysgen; ha_state.initialized = TRUE; ha_state.cid = cid; sim_debug(HA_TRACE, &ha_dev, "[ha_reset] SCSI Card now enabled in IO Slot %d\n", cid); } return SCPE_OK; } static void ha_calc_subdevs() { uint32 target; UNIT *uptr; SCSI_DEV *dev; ha_subdev_cnt = 0; memset(ha_subdev_tab, -1, 8); for (target = 0; target < 8; target++) { uptr = &ha_unit[target]; if (uptr->flags & UNIT_ATT) { dev = (SCSI_DEV *)uptr->up7; ha_subdev_tab[ha_subdev_cnt++] = target; } } } t_stat ha_attach(UNIT *uptr, CONST char *cptr) { t_stat r; r = scsi_attach(uptr, cptr); ha_calc_subdevs(); return r; } t_stat ha_detach(UNIT *uptr) { t_stat r; r = scsi_detach(uptr); ha_calc_subdevs(); return r; } t_stat ha_svc(UNIT *uptr) { cio_entry cqe; uint8 capp_data[CAPP_LEN] = {0}; /* Finish any pending job */ if (ha_state.reply.pending) { ha_state.reply.pending = FALSE; switch(ha_state.reply.type) { case HA_JOB_QUICK: ha_fcm_express(); sim_debug(HA_TRACE, &ha_dev, "[ha_svc] FAST MODE CQ: status=%02x op=%02x subdev=%02x ssb=%02x\n", ha_state.reply.status, ha_state.reply.op, ha_state.reply.subdev, ha_state.reply.ssb); break; case HA_JOB_EXPRESS: case HA_JOB_FULL: cqe.byte_count = ha_state.reply.len; cqe.opcode = ha_state.reply.status; /* Yes, status, not opcode! */ cqe.subdevice = ha_state.reply.subdev; cqe.address = ha_state.reply.addr; sim_debug(HA_TRACE, &ha_dev, "[ha_svc] CQE: byte_count=%04x, opcode=%02x, subdevice=%02x, addr=%08x\n", cqe.byte_count, cqe.opcode, cqe.subdevice, cqe.address); if (ha_state.reply.type == HA_JOB_EXPRESS) { sim_debug(HA_TRACE, &ha_dev, "[ha_svc] EXPRESS MODE CQ: byte_count=%02x op=%02x subdev=%02x address=%08x\n", cqe.byte_count, cqe.opcode, cqe.subdevice, cqe.address); cio_cexpress(ha_state.cid, SCQCESIZE, &cqe, capp_data); } else { sim_debug(HA_TRACE, &ha_dev, "[ha_svc] FULL MODE CQ: status=%02x op=%02x subdev=%02x ssb=%02x\n", ha_state.reply.status, ha_state.reply.op, ha_state.reply.subdev, ha_state.reply.ssb); cio_cqueue(ha_state.cid, 0, SCQCESIZE, &cqe, capp_data); } break; } dump_rep(); } if (cio[ha_state.cid].ivec > SCSI_MIN_IVEC) { sim_debug(HA_TRACE, &ha_dev, "[%08x] [ha_svc] IRQ for board %d (VEC=%d).\n", R[NUM_PC], ha_state.cid, cio[ha_state.cid].ivec); CIO_SET_INT(ha_state.cid); } return SCPE_OK; } void ha_sysgen(uint8 cid) { uint32 sysgen_p; uint32 alert_buf_p; cq_offset = 0; sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] Handling Sysgen.\n"); sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] rqp=%08x\n", cio[cid].rqp); sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] cqp=%08x\n", cio[cid].cqp); sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] rqs=%d\n", cio[cid].rqs); sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] cqs=%d\n", cio[cid].cqs); sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] ivec=%d\n", cio[cid].ivec); sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] no_rque=%d\n", cio[cid].no_rque); sysgen_p = pread_w(SYSGEN_PTR); alert_buf_p = pread_w(sysgen_p + 12); sim_debug(HA_TRACE, &ha_dev, "[ha_sysgen] alert_bfr=%08x\n", alert_buf_p); ha_state.frq = (cio[cid].no_rque == 0); ha_state.reply.type = ha_state.frq ? HA_JOB_QUICK : HA_JOB_EXPRESS; ha_state.reply.addr = 0; ha_state.reply.len = 0; ha_state.reply.status = 3; ha_state.reply.op = 0; ha_state.reply.pending = TRUE; if (ha_crc == PUMP_CRC) { sim_debug(HA_TRACE, &ha_dev, "[%08x] [ha_full] PUMP: NEW STATE = PUMP_SYSGEN\n", R[NUM_PC]); ha_state.pump_state = PUMP_SYSGEN; } else { sim_debug(HA_TRACE, &ha_dev, "[%08x] [ha_full] PUMP: NEW STATE = PUMP_NONE\n", R[NUM_PC]); ha_state.pump_state = PUMP_NONE; } sim_activate_abs(cio_unit, 100000); } void ha_fast_queue_check() { uint8 busy, op, subdev; uint32 timeout, addr, len, rqp; rqp = cio[ha_state.cid].rqp; busy = pread_b(rqp); op = pread_b(rqp + 1); subdev = pread_b(rqp + 2); timeout = pread_w(rqp + 4); addr = pread_w(rqp + 8); len = pread_w(rqp + 12); if (busy == 0xff || ha_state.pump_state != PUMP_COMPLETE) { sim_debug(HA_TRACE, &ha_dev, "[%08x] [ha_fast_queue_check] Job pending (opcode=0x%02x subdev=%02x)\n", R[NUM_PC], op, subdev); pwrite_b(rqp, 0); /* Job has been taken */ ha_cmd(op, subdev, addr, len, FALSE); } } void ha_express(uint8 cid) { cio_entry rqe; uint8 rapp_data[RAPP_LEN] = {0}; sim_debug(HA_TRACE, &ha_dev, "[%08x] [ha_express] Handling Express Request\n", R[NUM_PC]); if (ha_state.frq) { ha_fast_queue_check(); } else { cio_rexpress(cid, SCQRESIZE, &rqe, rapp_data); ha_cmd(rqe.opcode, rqe.subdevice, rqe.address, rqe.byte_count, TRUE); } } void ha_full(uint8 cid) { sim_debug(HA_TRACE, &ha_dev, "[%08x] [ha_full] Handling Full Request (INT3)\n", R[NUM_PC]); if (ha_state.pump_state == PUMP_SYSGEN) { sim_debug(HA_TRACE, &ha_dev, "[%08x] [ha_full] PUMP: NEW STATE = PUMP_COMPLETE\n", R[NUM_PC]); ha_state.pump_state = PUMP_COMPLETE; } if (ha_state.frq) { ha_fast_queue_check(); } else { sim_debug(HA_TRACE, &ha_dev, "[%08x] [ha_full] NON_FRQ NOT HANDLED\n", R[NUM_PC]); } } static void dump_req() { sim_debug(HA_TRACE, &ha_dev, "[REQ] [%08x] %08x\n", cio[ha_state.cid].rqp, pread_w(cio[ha_state.cid].rqp)); sim_debug(HA_TRACE, &ha_dev, "[REQ] [%08x] %08x\n", cio[ha_state.cid].rqp + 4, pread_w(cio[ha_state.cid].rqp + 4)); sim_debug(HA_TRACE, &ha_dev, "[REQ] [%08x] %08x\n", cio[ha_state.cid].rqp + 8, pread_w(cio[ha_state.cid].rqp + 8)); sim_debug(HA_TRACE, &ha_dev, "[REQ] [%08x] %08x\n", cio[ha_state.cid].rqp + 12, pread_w(cio[ha_state.cid].rqp + 12)); sim_debug(HA_TRACE, &ha_dev, "[REQ] [%08x] %08x\n", cio[ha_state.cid].rqp + 16, pread_w(cio[ha_state.cid].rqp + 16)); sim_debug(HA_TRACE, &ha_dev, "[REQ] [%08x] %08x\n", cio[ha_state.cid].rqp + 20, pread_w(cio[ha_state.cid].rqp + 20)); sim_debug(HA_TRACE, &ha_dev, "[REQ] [%08x] %08x\n", cio[ha_state.cid].rqp + 24, pread_w(cio[ha_state.cid].rqp + 24)); } static void dump_rep() { uint32 i; uint32 cqs = cio[ha_state.cid].cqs; for (i = 0; i < cqs; i++) { sim_debug(HA_TRACE, &ha_dev, "[CEQ] [%08x] %08x\n", cio[ha_state.cid].cqp + (i * 4), pread_w(cio[ha_state.cid].cqp + (i * 4))); } } static void ha_boot_disk(UNIT *uptr, uint8 target) { t_seccnt sectsread; t_stat r; uint8 buf[HA_BLKSZ]; uint32 i, boot_loc; /* Read in the Physical Descriptor (PD) block (block 0) */ r = sim_disk_rdsect(uptr, 0, buf, §sread, 1); if (r != SCPE_OK) { sim_debug(HA_TRACE, &ha_dev, "[ha_boot_disk] Could not read LBA 0\n"); HA_STAT(HA_CKCON, CIO_SUCCESS); return; } /* Store the Physical Descriptor (PD) block at well-known address 0x2004400 */ sim_debug(HA_TRACE, &ha_dev, "[ha_boot_disk] Storing PD block at 0x%08x.\n", HA_PDINFO_ADDR); for (i = 0; i < HA_BLKSZ; i++) { pwrite_b(HA_PDINFO_ADDR + i, buf[i]); } /* The PD block points to the logical start of disk */ boot_loc = ATOW(buf, HA_PDLS_OFF); sim_debug(HA_TRACE, &ha_dev, "[ha_boot_disk] Logical Start is at 0x%x\n", boot_loc); r = sim_disk_rdsect(uptr, boot_loc, buf, §sread, 1); sim_debug(HA_TRACE, &ha_dev, "[ha_boot_disk] Storing boot block %d at 0x%08x.\n", boot_loc, HA_BOOT_ADDR); for (i = 0; i < HA_BLKSZ; i++) { pwrite_b(HA_BOOT_ADDR + i, buf[i]); } sim_debug(HA_TRACE, &ha_dev, "[ha_boot_disk] Done storing boot block at 0x%08x\n", HA_BOOT_ADDR); HA_STAT(HA_GOOD, CIO_SUCCESS); ha_state.reply.addr = HA_BOOT_ADDR; ha_state.reply.len = HA_BLKSZ; } static void ha_boot_tape(UNIT *uptr) { t_seccnt sectsread; t_stat r; uint8 buf[HA_BLKSZ]; uint32 i; if (!(uptr->flags & UNIT_ATT)) { sim_debug(HA_TRACE, &ha_dev, "[ha_boot_tape] Target not attached\n"); HA_STAT(HA_CKCON, CIO_SUCCESS); return; } r = sim_tape_rewind(uptr); if (r != SCPE_OK) { sim_debug(HA_TRACE, &ha_dev, "[ha_boot_tape] Could not rewind tape\n"); HA_STAT(HA_CKCON, CIO_SUCCESS); return; } r = sim_tape_rdrecf(uptr, buf, §sread, HA_BLKSZ); /* Read block 0 */ if (r != SCPE_OK) { sim_debug(HA_TRACE, &ha_dev, "[ha_boot_tape] Could not read PD block.\n"); HA_STAT(HA_CKCON, CIO_SUCCESS); return; } for (i = 0; i < HA_BLKSZ; i++) { pwrite_b(HA_BOOT_ADDR + i, buf[i]); } sim_debug(HA_TRACE, &ha_dev, "[ha_boot_tape] Transfered 512 bytes to 0x%08x\n", HA_BOOT_ADDR); r = sim_tape_sprecf(uptr, §sread); /* Skip block 1 */ HA_STAT(HA_GOOD, CIO_SUCCESS); ha_state.reply.addr = HA_BOOT_ADDR; ha_state.reply.len = HA_BLKSZ; } static void ha_read_block_tape(UNIT *uptr, uint32 addr) { t_seccnt sectsread; t_stat r; uint8 buf[HA_BLKSZ]; uint32 i; if (!(uptr->flags & UNIT_ATT)) { sim_debug(HA_TRACE, &ha_dev, "[ha_read_block_tape] Target not attached\n"); HA_STAT(HA_CKCON, CIO_SUCCESS); return; } r = sim_tape_rdrecf(uptr, buf, §sread, HA_BLKSZ); if (r != SCPE_OK) { sim_debug(HA_TRACE, &ha_dev, "[ha_read_block_tape] Could not read next block.\n"); HA_STAT(HA_CKCON, CIO_SUCCESS); return; } for (i = 0; i < HA_BLKSZ; i++) { pwrite_b(addr + i, buf[i]); } sim_debug(HA_TRACE, &ha_dev, "[ha_read_block_tape] Transfered 512 bytes to 0x%08x\n", addr); HA_STAT(HA_GOOD, CIO_SUCCESS); ha_state.reply.addr = addr; ha_state.reply.len = HA_BLKSZ; } static void ha_read_block_disk(UNIT *uptr, uint8 target, uint32 addr, uint32 lba) { t_seccnt sectsread; t_stat r; uint8 buf[HA_BLKSZ]; uint32 i; sim_debug(HA_TRACE, &ha_dev, "[ha_read_block_disk] Block translated from LBA 0x%X to PBA 0x%X\n", lba, lba); r = sim_disk_rdsect(uptr, lba, buf, §sread, 1); if (r != SCPE_OK) { sim_debug(HA_TRACE, &ha_dev, "[ha_read_block_disk] Could not read block %d\n", lba); HA_STAT(HA_CKCON, CIO_SUCCESS); return; } for (i = 0; i < HA_BLKSZ; i++) { pwrite_b(addr + i, buf[i]); } sim_debug(HA_TRACE, &ha_dev, "[ha_read_block_disk] Transferred 512 bytes to 0x%08x\n", addr); HA_STAT(HA_GOOD, CIO_SUCCESS); ha_state.reply.addr = addr; ha_state.reply.len = HA_BLKSZ; } static void ha_build_req(uint8 subdev, t_bool express) { uint32 i, rqp, ptr, dma_lst; uint32 len, addr; cio_entry rqe; uint8 rapp_data[RAPP_LEN] = {0}; /* * There are two possible ways to get the SCSI command we've * been asked to perform. * * 1. If this is a "fast mode" operation, then the SCSI command * is embedded in the Fast Request Queue entry. * * 2. If this is a regular queue operation, then the SCSI command * is embedded in a struct pointed to by the "address" field * of the queue entry. */ for (i = 0; i < HA_MAX_CMD; i++) { ha_state.request.cmd[i] = 0; } if (ha_state.frq) { rqp = cio[ha_state.cid].rqp; subdev = pread_b(rqp + 2); ha_state.request.tc = FC_TC(subdev); ha_state.request.lu = FC_LU(subdev); ha_state.request.timeout = pread_w(rqp + 4); ha_state.request.cmd_len = pread_h(rqp + 18); for (i = 0; i < HA_MAX_CMD; i++) { ha_state.request.cmd[i] = pread_b(rqp + 20 + i); } ha_state.request.op = ha_state.request.cmd[0]; /* Possible list of DMA scatter/gather addresses */ dma_lst = pread_h(rqp + 16) / 8; if (dma_lst) { t_bool link; /* There's a list of address / lengths. Each entry is 8 * bytes long. */ ptr = pread_w(rqp + 8); link = FALSE; sim_debug(HA_TRACE, &ha_dev, "[build_req] Building a list of scatter/gather addresses.\n"); for (i = 0; (i < dma_lst) || link; i++) { addr = pread_w(ptr); len = pread_w(ptr + 4); if (len == 0) { sim_debug(HA_TRACE, &ha_dev, "[build_req] Found length of 0, bailing early.\n"); break; /* Done early */ } if (len > 0x1000) { /* There's a new pointer in town */ ptr = pread_w(ptr); sim_debug(HA_TRACE, &ha_dev, "[build_req] New ptr=%08x\n", ptr); link = TRUE; continue; } sim_debug(HA_TRACE, &ha_dev, "[build_req] daddr[%d]: addr=%08x, len=%d (%x)\n", i, addr, len, len); ha_state.request.daddr[i].addr = addr; ha_state.request.daddr[i].len = len; ptr += 8; } ha_state.request.dlen = i; } else { /* There's only one embedded address / length */ ha_state.request.daddr[0].addr = pread_w(rqp + 8); ha_state.request.daddr[0].len = pread_w(rqp + 12); ha_state.request.dlen = 1; } } else { if (express) { cio_rexpress(ha_state.cid, SCQRESIZE, &rqe, rapp_data); } else { /* TODO: Find correct queue number! */ cio_rqueue(ha_state.cid, 0, SCQRESIZE, &rqe, rapp_data); } ptr = rqe.address; ha_state.request.tc = FC_TC(rqe.subdevice); ha_state.request.lu = FC_LU(rqe.subdevice); ha_state.request.cmd_len = pread_w(ptr + 4); ha_state.request.timeout = pread_w(ptr + 8); ha_state.request.daddr[0].addr = pread_w(ptr + 12); ha_state.request.daddr[0].len = rqe.byte_count; ha_state.request.dlen = 1; sim_debug(HA_TRACE, &ha_dev, "[build_req] [non-fast] Building a list of 1 scatter/gather addresses.\n"); ptr = pread_w(ptr); for (i = 0; (i < ha_state.request.cmd_len) && (i < HA_MAX_CMD); i++) { ha_state.request.cmd[i] = pread_b(ptr + i); } ha_state.request.op = ha_state.request.cmd[0]; } } static void ha_cmd(uint8 op, uint8 subdev, uint32 addr, int32 len, t_bool express) { int32 i, block; UNIT *uptr; SCSI_DEV *dev; int8 target; /* Immediately cancel any pending IRQs */ sim_cancel(cio_unit); ha_state.reply.pending = TRUE; ha_state.reply.op = op; ha_state.reply.subdev = subdev; ha_state.reply.status = CIO_FAILURE; ha_state.reply.ssb = 0; ha_state.reply.len = 0; ha_state.reply.addr = 0; if (ha_state.pump_state == PUMP_COMPLETE) { ha_state.reply.op |= 0x80; } if (ha_state.frq) { ha_state.reply.type = HA_JOB_QUICK; } else if (express) { ha_state.reply.type = HA_JOB_EXPRESS; } else { ha_state.reply.type = HA_JOB_FULL; } sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] --------------------------[START]---------------------------------\n"); sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] op=%02x (%d), subdev=%02x, addr=%08x, len=%d\n", op, op, subdev, addr, len); dump_req(); switch (op) { case CIO_DLM: for (i = 0; i < len; i++) { ha_crc = cio_crc32_shift(ha_crc, pread_b(addr + i)); } sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] SCSI Download Memory: bytecnt=%04x " "addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", len, addr, addr, subdev, ha_crc); ha_state.reply.status = CIO_SUCCESS; sim_activate_abs(cio_unit, 1200); break; case CIO_FCF: sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] SCSI Force Function Call. (CRC=%08x)\n", ha_crc); cio[ha_state.cid].sysgen_s = 0; ha_state.reply.status = CIO_SUCCESS; sim_activate_abs(cio_unit, 1200); break; case CIO_DSD: sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] SCSI DSD - %d CONFIGURED DEVICES (writing to addr %08x).\n", ha_subdev_cnt, addr); pwrite_h(addr, ha_subdev_cnt); for (i = 0; i < ha_subdev_cnt; i++) { addr += 2; target = ha_subdev_tab[i]; if (target < 0) { pwrite_h(addr, 0); continue; } uptr = &ha_unit[target]; dev = (SCSI_DEV *)uptr->up7; sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] [DSD] Probing subdev %d, target %d, devtype %d\n", i, target, dev->devtype); switch(dev->devtype) { case SCSI_DISK: sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] [DSD] Subdev %d is DISK (writing to addr %08x)\n", i, addr); pwrite_h(addr, HA_DSD_DISK); break; case SCSI_TAPE: sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] [DSD] Subdev %d is TAPE (writing to addr %08x)\n", i, addr); pwrite_h(addr, HA_DSD_TAPE); break; default: sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] [DSD] Warning: No device type for subdev %d (Writing to addr %08x)\n", i, addr); pwrite_h(addr, 0); break; } } ha_state.reply.status = CIO_SUCCESS; sim_activate_abs(cio_unit, 5000); break; case HA_BOOT: target = ha_subdev_tab[subdev & 7]; sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] TARGET %d BOOTING.\n", target); if (target < 0) { ha_state.reply.status = CIO_TIMEOUT; sim_activate_abs(cio_unit, 250); return; } uptr = &ha_unit[target]; if (!(uptr->flags & UNIT_ATT)) { sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] TARGET %d NOT ATTACHED.\n", target); ha_state.reply.status = CIO_TIMEOUT; sim_activate_abs(cio_unit, 250); return; } dev = (SCSI_DEV *)uptr->up7; switch(dev->devtype) { case SCSI_DISK: ha_boot_disk(uptr, target); break; case SCSI_TAPE: ha_boot_tape(uptr); break; default: sim_debug(HA_TRACE, &ha_dev, "[HA_BOOT] Cannot boot target %d (not disk or tape).\n", target); ha_state.reply.status = CIO_SUCCESS; break; } sim_activate_abs(cio_unit, 5000); break; case HA_READ_BLK: target = ha_subdev_tab[subdev & 7]; sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] SUBDEV %d TARGET %d READ BLOCK (BLOCK 0x%08x TO ADDR 0x%08x)\n", subdev, target, pread_w(addr), pread_w(addr + 4)); sim_debug(HA_TRACE, &ha_dev, "[boot_next] addr = %08x\n", addr); sim_debug(HA_TRACE, &ha_dev, "[boot_next] %08x = %08x\n", addr, pread_w(addr)); sim_debug(HA_TRACE, &ha_dev, "[boot_next] %08x = %08x\n", addr + 4, pread_w(addr + 4)); sim_debug(HA_TRACE, &ha_dev, "[boot_next] %08x = %08x\n", addr + 8, pread_w(addr + 8)); sim_debug(HA_TRACE, &ha_dev, "[boot_next] %08x = %08x\n", addr + 12, pread_w(addr + 12)); sim_debug(HA_TRACE, &ha_dev, "[boot_next] %08x = %08x\n", addr + 16, pread_w(addr + 16)); if (target < 0) { ha_state.reply.status = CIO_TIMEOUT; sim_activate_abs(cio_unit, 250); return; } uptr = &ha_unit[target]; if (!(uptr->flags & UNIT_ATT)) { ha_state.reply.status = CIO_TIMEOUT; sim_activate_abs(cio_unit, 250); return; } dev = (SCSI_DEV *)uptr->up7; block = pread_w(addr); /* Logical block we've been asked to read */ addr = pread_w(addr + 4); /* Dereference the pointer to the destination */ switch(dev->devtype) { case SCSI_TAPE: ha_read_block_tape(uptr, addr); break; case SCSI_DISK: ha_read_block_disk(uptr, target, addr, block); break; default: sim_debug(HA_TRACE, &ha_dev, "[HA_READ_BLOCK] Cannot read block %d on target %d (not disk or tape)\n", block, target); ha_state.reply.status = CIO_SUCCESS; break; } sim_activate_abs(cio_unit, 5000); break; case HA_CNTRL: sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] SCSI CONTROL (subdev=%02x addr=%08x)\n", subdev, addr); ha_build_req(subdev, express); ha_ctrl(); sim_activate_abs(cio_unit, 1000); break; case HA_VERS: /* * Get Host Adapter Version */ sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] SCSI GET VERSION (addr=%08x len=%08x)\n", addr, len); pwrite_w(addr, HA_VERSION); ha_state.reply.status = CIO_SUCCESS; sim_activate_abs(cio_unit, 5000); break; case HA_DL_EEDT: /* * This is a request to download the Extended Equipped Device * Table from the host adapter to the 3B2 main memory. */ sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] SCSI DOWNLOAD EDT (%d bytes to address %08x)\n", len, addr); for (i = 0; i < len; i++) { pwrite_b(addr + i, ha_state.edt[i]); } ha_state.reply.status = CIO_SUCCESS; sim_activate_abs(cio_unit, 5000); break; case HA_UL_EEDT: /* * This is a request to upload the Extended Equipped Device * Table from the 3B2 main memory to the host adapter */ sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] SCSI UPLOAD EDT (%d bytes from address %08x)\n", len, addr); for (i = 0; i < len; i++) { ha_state.edt[i] = pread_b(addr + i); } ha_state.reply.status = CIO_SUCCESS; sim_activate_abs(cio_unit, 5000); break; case HA_EDSD: /* * This command is used to determine which TCs are attached to * the SCSI bus, and what LUNs they support. A "1" in a slot * means that the target is not a direct block device. Since we * only support direct block devices, we just put "0" in each * slot. */ sim_debug(HA_TRACE, &ha_dev, "[%08x] [ha_cmd] SCSI EXTENDED DSD.\n", R[NUM_PC]); ha_state.reply.status = CIO_SUCCESS; ha_state.reply.addr = addr; ha_state.reply.len = 9; for (i = 0; i < 8; i++) { uptr = &ha_unit[i]; pwrite_b(addr + i, (uptr->flags & UNIT_ATT) ? 1 : 0); } pwrite_b(addr + 8, HA_SCSI_ID); /* ID of the card */ sim_activate_abs(cio_unit, 200); break; case 0x45: scsi_reset(&ha_bus); ha_state.reply.status = CIO_SUCCESS; ha_state.reply.addr = addr; ha_state.reply.len = 0; sim_activate_abs(cio_unit, 2500); break; default: sim_debug(HA_TRACE, &ha_dev, "[%08x] *** SCSI WARNING: UNHANDLED OPCODE 0x%02x\n", R[NUM_PC], op); ha_state.reply.status = CIO_FAILURE; sim_activate_abs(cio_unit, 200); } sim_debug(HA_TRACE, &ha_dev, "[ha_cmd] ---------------------------[END]----------------------------------\n"); } /* * Handle a raw SCSI control message. */ void ha_ctrl() { volatile t_bool txn_done; uint32 i, j; uint32 plen, ha_ptr; uint32 in_len, out_len; uint8 lu, status; uint8 msgi_buf[64]; uint32 msgi_len; uint32 to_read; sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] [HA_REQ] TC=%d LU=%d TIMEOUT=%d DLEN=%d\n", ha_state.request.tc, ha_state.request.lu, ha_state.request.timeout, ha_state.request.dlen); sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] [HA_REQ] CMD_LEN=%d CMD=<%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x>\n", ha_state.request.cmd_len, ha_state.request.cmd[0], ha_state.request.cmd[1], ha_state.request.cmd[2], ha_state.request.cmd[3], ha_state.request.cmd[4], ha_state.request.cmd[5], ha_state.request.cmd[6], ha_state.request.cmd[7], ha_state.request.cmd[8], ha_state.request.cmd[9]); in_len = out_len = 0; /* * These ops need special handling. */ switch(ha_state.request.op) { case HA_TESTRDY: /* Fail early if LU is set */ if (ha_state.request.lu) { HA_STAT(HA_CKCON, CIO_TIMEOUT); return; } break; case HA_FORMAT: /* Not yet handled by sim_scsi library */ case HA_VERIFY: /* Not yet handled by sim_scsi library */ /* Just mimic success */ HA_STAT(HA_GOOD, CIO_SUCCESS); return; } /* Get the bus's attention */ if (!scsi_arbitrate(&ha_bus, HA_SCSI_ID)) { HA_STAT(HA_CKCON, CIO_TIMEOUT); return; } scsi_set_atn(&ha_bus); if (!scsi_select(&ha_bus, ha_state.request.tc)) { HA_STAT(HA_CKCON, CIO_TIMEOUT); scsi_release(&ha_bus); return; } /* Select the correct LU */ lu = 0x80 | ha_state.request.lu; scsi_write(&ha_bus, &lu, 1); txn_done = FALSE; while (!txn_done) { switch(ha_bus.phase) { case SCSI_CMD: plen = scsi_write(&ha_bus, ha_state.request.cmd, ha_state.request.cmd_len); if (plen < ha_state.request.cmd_len) { HA_STAT(HA_CKCON, CIO_SUCCESS); scsi_release(&ha_bus); return; } break; case SCSI_DATI: /* This is a read */ in_len = scsi_read(&ha_bus, ha_buf, HA_MAXFR); sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] SCSI_DATI: Consumed %d (0x%X) bytes to ha_buf in SCSI read.\n", in_len, in_len); /* We need special handling based on the op code */ switch(ha_state.request.op) { case HA_READ: case HA_READEXT: ha_ptr = 0; for (i = 0; i < ha_state.request.dlen; i++) { /* * Consume the lesser of: * - The total bytes we consumed, or: * - The length of the current block */ to_read = MIN(ha_state.request.daddr[i].len, in_len); sim_debug(HA_TRACE, &ha_dev, "[(%02x) TC%d,LU%d] DATI: Processing %d bytes to address %08x...\n", ha_state.request.op, ha_state.request.tc, ha_state.request.lu, to_read, ha_state.request.daddr[i].addr); for (j = 0; j < to_read; j++) { pwrite_b(ha_state.request.daddr[i].addr + j, ha_buf[ha_ptr++]); } if (in_len >= to_read) { in_len -= to_read; } else { /* Nothing left to write */ break; } } break; default: sim_debug(HA_TRACE, &ha_dev, "[(%02x) TC%d,LU%d] DATI: Processing %d bytes to address %08x...\n", ha_state.request.op, ha_state.request.tc, ha_state.request.lu, in_len, ha_state.request.daddr[0].addr); for (i = 0; i < in_len; i++) { sim_debug(HA_TRACE, &ha_dev, "[%04x] [DATI] 0x%02x\n", i, ha_buf[i]); pwrite_b(ha_state.request.daddr[0].addr + i, ha_buf[i]); } break; } break; case SCSI_DATO: /* This is a write */ ha_ptr = 0; out_len = 0; ha_state.reply.len = ha_state.request.dlen; for (i = 0; i < ha_state.request.dlen; i++) { sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] [%d] DATO: Writing %d bytes to ha_buf.\n", i, ha_state.request.daddr[i].len); for (j = 0; j < ha_state.request.daddr[i].len; j++) { ha_buf[ha_ptr++] = pread_b(ha_state.request.daddr[i].addr + j); } out_len += ha_state.request.daddr[i].len; } if (ha_state.request.op == HA_WRITE || ha_state.request.op == HA_WRTEXT) { /* If total len is not on a block boundary, we have to bump it up in order to write the whole block. */ if (out_len % HA_BLKSZ) { out_len = out_len + (HA_BLKSZ - (out_len % HA_BLKSZ)); } } scsi_write(&ha_bus, ha_buf, out_len); sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] SCSI Write of %08x (%d) bytes Complete\n", out_len, out_len); break; case SCSI_STS: scsi_read(&ha_bus, &status, 1); sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] STATUS BYTE: %02x\n", status); break; case SCSI_MSGI: msgi_len = scsi_read(&ha_bus, msgi_buf, 64); sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] MESSAGE IN LENGTH %d\n", msgi_len); for (i = 0; i < msgi_len; i++) { sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] MSGI[%02d] = %02x\n", i, msgi_buf[i]); } txn_done = TRUE; break; } } if (ha_bus.sense_info) { sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] SENSE INFO=%d, CKCON.\n", ha_bus.sense_info); HA_STAT(HA_CKCON, 0x60); } else { sim_debug(HA_TRACE, &ha_dev, "[ha_ctrl] NO SENSE INFO.\n"); HA_STAT(HA_GOOD, CIO_SUCCESS); } /* Release the bus */ scsi_release(&ha_bus); } void ha_fcm_express() { uint32 rqp, cqp, cqs; rqp = cio[ha_state.cid].rqp; cqp = cio[ha_state.cid].cqp; cqs = cio[ha_state.cid].cqs; /* Write the fast completion entry. */ pwrite_b(cqp + cq_offset, ha_state.reply.status); pwrite_b(cqp + cq_offset + 1, ha_state.reply.op); pwrite_b(cqp + cq_offset + 2, ha_state.reply.subdev); pwrite_b(cqp + cq_offset + 3, ha_state.reply.ssb); sim_debug(HA_TRACE, &ha_dev, "[ha_fcm_express] stat=%02x, op=%02x (%d), cq_index=%d target=%d, lun=%d, ssb=%02x\n", ha_state.reply.status, ha_state.reply.op, ha_state.reply.op, (cq_offset / 4), FC_TC(ha_state.reply.subdev), FC_LU(ha_state.reply.subdev), ha_state.reply.ssb); if (ha_state.pump_state == PUMP_COMPLETE && cqs > 0) { cq_offset = (cq_offset + 4) % (cqs * 4); } else { cq_offset = 0; } } t_stat ha_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc) { if (val < 0 || cptr || val > HA_MAX_DTYPE) { return SCPE_ARG; } if (uptr->flags & UNIT_ATT) { return SCPE_ALATT; } uptr->flags = (uptr->flags & ~UNIT_DTYPE) | (val << UNIT_V_DTYPE); uptr->capac = (t_addr) ha_tab[val].lbn; scsi_set_unit(&ha_bus, uptr, &ha_tab[val]); scsi_reset_unit(uptr); return SCPE_OK; } t_stat ha_show_type(FILE *st, UNIT *uptr, int32 val, CONST void *desc) { fprintf(st, "%s", ha_tab[GET_DTYPE(uptr->flags)].name); return SCPE_OK; } /* Used for debugging only */ /* TODO: Remove after testing */ static void dump_edt() { uint8 tc_size, lu_size, num_tc, num_lu, i, j; uint32 offset; char name[11]; sim_debug(HA_TRACE, &ha_dev, "[EDT] Sanity: %08x\n", ATOW(ha_state.edt, 0)); sim_debug(HA_TRACE, &ha_dev, "[EDT] Version: %d\n", ha_state.edt[4]); sim_debug(HA_TRACE, &ha_dev, "[EDT] Slot: %d\n", ha_state.edt[5]); sim_debug(HA_TRACE, &ha_dev, "[EDT] Max TC: %d\n", ha_state.edt[6]); sim_debug(HA_TRACE, &ha_dev, "[EDT] TC Size: %d\n", ha_state.edt[7]); sim_debug(HA_TRACE, &ha_dev, "[EDT] Max LUs: %d\n", ha_state.edt[8]); sim_debug(HA_TRACE, &ha_dev, "[EDT] LU Size: %d\n", ha_state.edt[9]); sim_debug(HA_TRACE, &ha_dev, "[EDT] Equipped TCs: %d\n", ha_state.edt[10]); tc_size = ha_state.edt[7]; lu_size = ha_state.edt[9]; num_tc = ha_state.edt[10] + 1; for (i = 0; i < num_tc; i++) { offset = 12 + (tc_size * i); num_lu = ha_state.edt[offset + 17]; strncpy(name, ((const char *)ha_state.edt + offset + 4), 10); sim_debug(HA_TRACE, &ha_dev, "[EDT] -------------------------\n"); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d] Major Number: %d\n", i, ATOW(ha_state.edt, offset)); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d] Name: %s\n", i, name); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d] Type: %d\n", i, ATOH(ha_state.edt, offset + 14)); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d] Equipped?: %d\n", i, ha_state.edt[offset + 16]); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d] Equipped LUs: %d\n", i, ha_state.edt[offset + 17]); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d] Maximum LUs: %d\n", i, ha_state.edt[offset + 18]); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d] LU Index: %04x\n", i, ATOH(ha_state.edt, offset + 20)); offset = ATOH(ha_state.edt, offset + 20); for (j = 0; j < num_lu; j++) { offset = offset + (j * lu_size); sim_debug(HA_TRACE, &ha_dev, "[EDT] -------------------------\n"); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d,LU%d] LU #: %d\n", i, j, ha_state.edt[offset]); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d,LU%d] PD Type: 0x%02x\n", i, j, ha_state.edt[offset + 1]); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d,LU%d] Dev Type: 0x%02x\n", i, j, ha_state.edt[offset + 2] >> 1); sim_debug(HA_TRACE, &ha_dev, "[EDT] [TC%d,LU%d] Removable?: %d\n", i, j, ha_state.edt[offset + 2] & 1); } } }