/* $NetBSD: iris_scsi.c,v 1.2 2024/02/09 22:08:33 andvar Exp $ */ /* * Copyright (c) 2018 Naruaki Etomi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Silicon Graphics "IRIS" series MIPS processors machine bootloader. * WD33C93 SCSI bus driver for standalone programs. */ #include #include #ifndef INDIGO_R3K_MODE #include #endif #include "iris_machdep.h" #include "iris_scsivar.h" #include "iris_scsireg.h" #include "iris_scsicmd.h" #include #define SBIC_WAIT(regs, until, timeo) wd33c93_wait(regs, until, timeo) /* * Timeouts */ int wd33c93_cmd_wait = SBIC_CMD_WAIT; int wd33c93_data_wait = SBIC_DATA_WAIT; int wd33c93_init_wait = SBIC_INIT_WAIT; #define STATUS_UNKNOWN 0xff void wd33c93_reset(struct wd33c93_softc *); int wd33c93_wait(struct wd33c93_softc *, uint8_t, int); uint8_t wd33c93_selectbus(struct wd33c93_softc *, uint8_t *, size_t, uint8_t *, size_t *); void wd33c93_setsync(struct wd33c93_softc *); int wd33c93_nextstate(struct wd33c93_softc *, uint8_t *, size_t, uint8_t *, size_t *, uint8_t, uint8_t); size_t wd33c93_xfout(struct wd33c93_softc *, void *, size_t *); int wd33c93_intr(struct wd33c93_softc *, uint8_t *, size_t, uint8_t *, size_t *); size_t wd33c93_xfin(struct wd33c93_softc *, void *, size_t *); void wd33c93_xferdone(struct wd33c93_softc *); int wd33c93_abort(struct wd33c93_softc *, uint8_t *, size_t, uint8_t *, size_t *); int wd33c93_poll(struct wd33c93_softc *, uint8_t *, size_t, uint8_t *, size_t *); void wd33c93_timeout(struct wd33c93_softc *, uint8_t *, size_t, uint8_t *, size_t *); int wd33c93_msgin_phase(struct wd33c93_softc *); void wd33c93_scsistart(struct wd33c93_softc *); void wd33c93_scsidone(struct wd33c93_softc *); void wd33c93_error(struct wd33c93_softc *); /* * Initialize SPC & Data Structure */ void wd33c93_init(void *aaddr, void *daddr) { struct wd33c93_softc *sc; sc = &wd33c93_softc[scsi_ctlr]; sc->sc_asr_regh = aaddr; sc->sc_data_regh = daddr; sc->sc_target = scsi_id; sc->sc_flags = 0; sc->sc_state = SBIC_IDLE; wd33c93_reset(sc); } void wd33c93_reset(struct wd33c93_softc *sc) { u_int my_id; uint8_t csr; SET_SBIC_cmd(sc, SBIC_CMD_ABORT); WAIT_CIP(sc); my_id = sc->sc_target & SBIC_ID_MASK; /* Set Clock == 20.0 MHz */ my_id |= SBIC_ID_FS_8_10; sc->sc_syncperiods = 2 * 2 * 1250 / SCSI_CLKFREQ; SET_SBIC_myid(sc, my_id); /* Reset the chip */ SET_SBIC_cmd(sc, SBIC_CMD_RESET); DELAY(25); SBIC_WAIT(sc, SBIC_ASR_INT, 0); /* PIO mode */ SBIC_TC_PUT(sc, 0); SET_SBIC_control(sc, SBIC_CTL_EDI | SBIC_CTL_IDI); /* clears interrupt also */ GET_SBIC_csr(sc, csr); __USE(csr); SET_SBIC_rselid(sc, SBIC_RID_ER); SET_SBIC_syn(sc, 0); sc->sc_flags = 0; sc->sc_state = SBIC_IDLE; } int wd33c93_wait(struct wd33c93_softc *sc, uint8_t until, int timeo) { uint8_t val; if (timeo == 0) /* some large value.. */ timeo = 1000000; GET_SBIC_asr(sc, val); while ((val & until) == 0) { if (timeo-- == 0) { return val; break; } DELAY(1); GET_SBIC_asr(sc, val); } return val; } /* SCSI command entry point */ int wd33c93_go(struct wd33c93_softc *sc, uint8_t *cbuf, size_t clen, uint8_t *buf, size_t *lenp) { int i; uint8_t csr, asr; wd33c93_scsistart(sc); sc->sc_status = STATUS_UNKNOWN; sc->sc_flags = 0; /* select the SCSI bus (it's an error if bus isn't free) */ if ((csr = wd33c93_selectbus(sc, cbuf, clen, buf, lenp)) == 0) /* Not done: needs to be rescheduled */ return 1; /* * Lets cycle a while then let the interrupt handler take over. */ GET_SBIC_asr(sc, asr); do { /* Handle the new phase */ i = wd33c93_nextstate(sc, cbuf, clen, buf, lenp, csr, asr); WAIT_CIP(sc); /* XXX */ if (sc->sc_state == SBIC_CONNECTED) { GET_SBIC_asr(sc, asr); if ((asr & SBIC_ASR_LCI) != 0) DELAY(5000); if ((asr & SBIC_ASR_INT) != 0) GET_SBIC_csr(sc, csr); } } while (sc->sc_state == SBIC_CONNECTED && (asr & (SBIC_ASR_INT|SBIC_ASR_LCI)) != 0); if (i == SBIC_STATE_DONE) { if (sc->sc_status == STATUS_UNKNOWN) { printf("wd33c93_go: stat == UNKNOWN\n"); return 1; } } if (wd33c93_poll(sc, cbuf, clen, buf, lenp)) { wd33c93_timeout(sc, cbuf, clen, buf, lenp); if (wd33c93_poll(sc, cbuf, clen, buf, lenp)) { wd33c93_timeout(sc, cbuf, clen, buf, lenp); } } return 0; } /* * select the bus, return when selected or error. * * Returns the current CSR following selection and optionally MSG out phase. * i.e. the returned CSR *should* indicate CMD phase... * If the return value is 0, some error happened. */ uint8_t wd33c93_selectbus(struct wd33c93_softc *sc, uint8_t *cbuf, size_t clen, uint8_t *buf, size_t *lenp) { uint8_t asr, csr, id, lun, target; static int i = 0; sc->sc_state = SBIC_SELECTING; target = sc->sc_target; lun = SCSI_LUN; /* * issue select */ SBIC_TC_PUT(sc, 0); SET_SBIC_selid(sc, target); SET_SBIC_timeo(sc, SBIC_TIMEOUT(250, SCSI_CLKFREQ)); GET_SBIC_asr(sc, asr); if ((asr & (SBIC_ASR_INT|SBIC_ASR_BSY)) != 0) { return 0; } SET_SBIC_cmd(sc, SBIC_CMD_SEL_ATN); WAIT_CIP(sc); /* * wait for select (merged from separate function may need * cleanup) */ do { asr = SBIC_WAIT(sc, SBIC_ASR_INT | SBIC_ASR_LCI, 0); if ((asr & SBIC_ASR_LCI) != 0) { return 0; } /* Clear interrupt */ GET_SBIC_csr(sc, csr); /* Reselected from under our feet? */ if (csr == SBIC_CSR_RSLT_NI || csr == SBIC_CSR_RSLT_IFY) { /* * We need to handle this now so we don't lock up later */ wd33c93_nextstate(sc, cbuf, clen, buf, lenp, csr, asr); return 0; } /* Whoops! */ if (csr == SBIC_CSR_SLT || csr == SBIC_CSR_SLT_ATN) { return 0; } } while (csr != (SBIC_CSR_MIS_2 | MESG_OUT_PHASE) && csr != (SBIC_CSR_MIS_2 | CMD_PHASE) && csr != SBIC_CSR_SEL_TIMEO); /* Anyone at home? */ if (csr == SBIC_CSR_SEL_TIMEO) { return 0; } /* Assume we're now selected */ GET_SBIC_selid(sc, id); if (id != target) { /* Something went wrong - wrong target was select */ printf("wd33c93_selectbus: wrong target selected WANTED %d GOT %d \n", target, id); printf("Boot failed! Halting...\n"); reboot(); } sc->sc_flags |= SBICF_SELECTED; sc->sc_state = SBIC_CONNECTED; /* setup correct sync mode for this target */ wd33c93_setsync(sc); SET_SBIC_rselid(sc, SBIC_RID_ER); /* * We only really need to do anything when the target goes to MSG out * If the device ignored ATN, it's probably old and brain-dead, * but we'll try to support it anyhow. * If it doesn't support message out, it definitely doesn't * support synchronous transfers, so no point in even asking... */ if (csr == (SBIC_CSR_MIS_2 | MESG_OUT_PHASE)) { if (i < 6) { SEND_BYTE(sc, MSG_IDENTIFY(lun, 0)); DELAY(200000); i++; } else { /* * setup scsi message sync message request */ sc->sc_omsg[0] = MSG_IDENTIFY(lun, 0); sc->sc_omsg[1] = MSG_EXTENDED; sc->sc_omsg[2] = MSG_EXT_SDTR_LEN; sc->sc_omsg[3] = MSG_EXT_SDTR; sc->sc_omsg[4] = sc->sc_syncperiods; sc->sc_omsg[5] = SBIC_SYN_93AB_MAX_OFFSET; size_t foo = 6; size_t *bar; bar = &foo; wd33c93_xfout(sc, sc->sc_omsg, bar); sc->sc_flags |= SBICF_SYNCNEGO; } SBIC_WAIT(sc, SBIC_ASR_INT , 0); GET_SBIC_csr(sc, csr); } return csr; } /* * Setup sync mode for given target */ void wd33c93_setsync(struct wd33c93_softc *sc) { uint8_t syncreg; syncreg = SBIC_SYN(0, 0, 0); SET_SBIC_syn(sc, syncreg); } /* * wd33c93_nextstate() * return: * SBIC_STATE_DONE == done * SBIC_STATE_RUNNING == working * SBIC_STATE_DISCONNECT == disconnected * SBIC_STATE_ERROR == error */ int wd33c93_nextstate(struct wd33c93_softc *sc, uint8_t *cbuf, size_t clen, uint8_t *buf, size_t *lenp, uint8_t csr, uint8_t asr) { size_t *clenp; size_t resid; switch (csr) { case SBIC_CSR_XFERRED | CMD_PHASE: case SBIC_CSR_MIS | CMD_PHASE: case SBIC_CSR_MIS_1 | CMD_PHASE: case SBIC_CSR_MIS_2 | CMD_PHASE: clenp = &clen; if (wd33c93_xfout(sc, cbuf, clenp)) goto abort; break; case SBIC_CSR_XFERRED | STATUS_PHASE: case SBIC_CSR_MIS | STATUS_PHASE: case SBIC_CSR_MIS_1 | STATUS_PHASE: case SBIC_CSR_MIS_2 | STATUS_PHASE: SET_SBIC_control(sc, SBIC_CTL_EDI | SBIC_CTL_IDI); /* * this should be the normal i/o completion case. * get the status & cmd complete msg then let the * device driver look at what happened. */ wd33c93_xferdone(sc); wd33c93_scsidone(sc); return SBIC_STATE_DONE; case SBIC_CSR_XFERRED | DATA_IN_PHASE: case SBIC_CSR_MIS | DATA_IN_PHASE: case SBIC_CSR_MIS_1 | DATA_IN_PHASE: case SBIC_CSR_MIS_2 | DATA_IN_PHASE: case SBIC_CSR_XFERRED | DATA_OUT_PHASE: case SBIC_CSR_MIS | DATA_OUT_PHASE: case SBIC_CSR_MIS_1 | DATA_OUT_PHASE: case SBIC_CSR_MIS_2 | DATA_OUT_PHASE: /* * Should we transfer using PIO or DMA ? */ /* Perform transfer using PIO */ if (SBIC_PHASE(csr) == DATA_IN_PHASE){ /* data in */ resid = wd33c93_xfin(sc, buf, lenp); *lenp = resid; wd33c93_intr(sc, cbuf, clen, buf, lenp); } else { /* data out */ resid = wd33c93_xfout(sc, buf, lenp); *lenp = resid; } break; case SBIC_CSR_XFERRED | MESG_IN_PHASE: case SBIC_CSR_MIS | MESG_IN_PHASE: case SBIC_CSR_MIS_1 | MESG_IN_PHASE: case SBIC_CSR_MIS_2 | MESG_IN_PHASE: /* Handle a single message in... */ return wd33c93_msgin_phase(sc); case SBIC_CSR_MSGIN_W_ACK: /* * We should never see this since it's handled in * 'wd33c93_msgin_phase()' but just for the sake of paranoia... */ SET_SBIC_cmd(sc, SBIC_CMD_CLR_ACK); break; case SBIC_CSR_XFERRED | MESG_OUT_PHASE: case SBIC_CSR_MIS | MESG_OUT_PHASE: case SBIC_CSR_MIS_1 | MESG_OUT_PHASE: case SBIC_CSR_MIS_2 | MESG_OUT_PHASE: /* * Message out phase. ATN signal has been asserted */ return SBIC_STATE_RUNNING; case SBIC_CSR_DISC: case SBIC_CSR_DISC_1: sc->sc_state = SBIC_IDLE; sc->sc_flags = 0; return SBIC_STATE_DISCONNECT; case SBIC_CSR_RSLT_NI: case SBIC_CSR_RSLT_IFY: { sc->sc_state = SBIC_RESELECTED; if (csr == SBIC_CSR_RSLT_IFY) SET_SBIC_cmd(sc, SBIC_CMD_CLR_ACK); break; } default: abort: /* Something unexpected happend -- deal with it. */ printf("wd33c93_nextstate:abort\n"); printf("Boot failed! Halting...\n"); reboot(); } return SBIC_STATE_RUNNING; } /* * Information Transfer *to* a SCSI Target. * * Note: Don't expect there to be an interrupt immediately after all * the data is transferred out. The WD spec sheet says that the Transfer- * Info command for non-MSG_IN phases only completes when the target * next asserts 'REQ'. That is, when the SCSI bus changes to a new state. * * This can have a nasty effect on commands which take a relatively long * time to complete, for example a START/STOP unit command may remain in * CMD phase until the disk has spun up. Only then will the target change * to STATUS phase. This is really only a problem for immediate commands * since we don't allow disconnection for them (yet). */ size_t wd33c93_xfout(struct wd33c93_softc *sc, void *bp, size_t *lenp) { int wait = wd33c93_data_wait; uint8_t asr, *buf = bp; size_t len = *lenp; /* * sigh.. WD-PROTO strikes again.. sending the command in one go * causes the chip to lock up if talking to certain (misbehaving?) * targets. Anyway, this procedure should work for all targets, but * it's slightly slower due to the overhead */ SET_SBIC_control(sc, SBIC_CTL_EDI | SBIC_CTL_IDI); SBIC_TC_PUT(sc, (unsigned int)len); WAIT_CIP(sc); SET_SBIC_cmd(sc, SBIC_CMD_XFER_INFO); /* * Loop for each byte transferred */ do { GET_SBIC_asr(sc, asr); if ((asr & SBIC_ASR_DBR) != 0) { if (len != 0) { SET_SBIC_data(sc, *buf); buf++; len--; } else { SET_SBIC_data(sc, 0); } wait = wd33c93_data_wait; } } while (len != 0 && (asr & SBIC_ASR_INT) == 0 && wait-- > 0); /* * Normally, an interrupt will be pending when this routing returns. */ return len; } int wd33c93_intr(struct wd33c93_softc *sc, uint8_t *cbuf, size_t clen, uint8_t *buf, size_t *lenp) { uint8_t asr, csr; /* * pending interrupt? */ GET_SBIC_asr(sc, asr); if ((asr & SBIC_ASR_INT) == 0) return 0; GET_SBIC_csr(sc, csr); do { (void)wd33c93_nextstate(sc, cbuf, clen, buf, lenp, csr, asr); WAIT_CIP(sc); if (sc->sc_state == SBIC_CONNECTED) { GET_SBIC_asr(sc, asr); if ((asr & SBIC_ASR_INT) != 0) GET_SBIC_csr(sc, csr); } } while (sc->sc_state == SBIC_CONNECTED && (asr & (SBIC_ASR_INT|SBIC_ASR_LCI)) != 0); return 1; } /* * Information Transfer *from* a Scsi Target * returns # bytes left to read */ size_t wd33c93_xfin(struct wd33c93_softc *sc, void *bp, size_t *lenp) { size_t len = *lenp; int wait = wd33c93_data_wait; uint8_t *buf = bp; uint8_t asr; SET_SBIC_control(sc, SBIC_CTL_EDI | SBIC_CTL_IDI); SBIC_TC_PUT(sc, (unsigned int)len); WAIT_CIP(sc); SET_SBIC_cmd(sc, SBIC_CMD_XFER_INFO); /* * Loop for each byte transferred */ do { GET_SBIC_asr(sc, asr); if ((asr & SBIC_ASR_DBR) != 0) { if (len != 0) { GET_SBIC_data(sc, *buf); buf++; len--; } else { uint8_t foo; GET_SBIC_data(sc, foo); __USE(foo); } wait = wd33c93_data_wait; } } while ((asr & SBIC_ASR_INT) == 0 && wait-- > 0); SBIC_TC_PUT(sc, 0); /* * this leaves with one csr to be read */ return len; } /* * Finish SCSI xfer command: After the completion interrupt from * a read/write operation, sequence through the final phases in * programmed i/o. */ void wd33c93_xferdone(struct wd33c93_softc *sc) { uint8_t phase, csr; /* * have the wd33c93 complete on its own */ SBIC_TC_PUT(sc, 0); SET_SBIC_cmd_phase(sc, 0x46); SET_SBIC_cmd(sc, SBIC_CMD_SEL_ATN_XFER); do { SBIC_WAIT(sc, SBIC_ASR_INT, 0); GET_SBIC_csr(sc, csr); } while ((csr != SBIC_CSR_DISC) && (csr != SBIC_CSR_DISC_1) && (csr != SBIC_CSR_S_XFERRED)); sc->sc_flags &= ~SBICF_SELECTED; sc->sc_state = SBIC_DISCONNECT; GET_SBIC_cmd_phase(sc, phase); if (phase == 0x60) GET_SBIC_tlun(sc, sc->sc_status); else wd33c93_error(sc); } int wd33c93_abort(struct wd33c93_softc *sc, uint8_t *cbuf, size_t clen, uint8_t *buf, size_t *lenp) { uint8_t csr, asr; GET_SBIC_asr(sc, asr); GET_SBIC_csr(sc, csr); /* * Clean up chip itself */ wd33c93_timeout(sc, cbuf, clen, buf, lenp); while ((asr & SBIC_ASR_DBR) != 0) { /* * wd33c93 is jammed w/data. need to clear it * But we don't know what direction it needs to go */ GET_SBIC_data(sc, asr); GET_SBIC_asr(sc, asr); if ((asr & SBIC_ASR_DBR) != 0) /* Not the read direction */ SET_SBIC_data(sc, asr); GET_SBIC_asr(sc, asr); } WAIT_CIP(sc); SET_SBIC_cmd(sc, SBIC_CMD_ABORT); WAIT_CIP(sc); GET_SBIC_asr(sc, asr); if ((asr & (SBIC_ASR_BSY|SBIC_ASR_LCI)) != 0) { /* * ok, get more drastic.. */ wd33c93_reset(sc); } else { SET_SBIC_cmd(sc, SBIC_CMD_DISC); WAIT_CIP(sc); do { SBIC_WAIT(sc, SBIC_ASR_INT, 0); GET_SBIC_asr(sc, asr); GET_SBIC_csr(sc, csr); } while ((csr != SBIC_CSR_DISC) && (csr != SBIC_CSR_DISC_1) && (csr != SBIC_CSR_CMD_INVALID)); sc->sc_state = SBIC_ERROR; sc->sc_flags = 0; } return SBIC_STATE_ERROR; } void wd33c93_timeout(struct wd33c93_softc *sc, uint8_t *cbuf, size_t clen, uint8_t *buf, size_t *lenp) { uint8_t asr; GET_SBIC_asr(sc, asr); if ((asr & SBIC_ASR_INT) != 0) { /* We need to service a missed IRQ */ wd33c93_intr(sc, cbuf, clen, buf, lenp); } else { wd33c93_abort(sc, cbuf, clen, buf, lenp); } } /* * Complete current command using polled I/O.Used when interrupt driven * I/O is not allowed (ie. during boot and shutdown) * * Polled I/O is very processor intensive */ int wd33c93_poll(struct wd33c93_softc *sc, uint8_t *cbuf, size_t clen, uint8_t *buf, size_t *lenp) { uint8_t asr, csr = 0; int count; SBIC_WAIT(sc, SBIC_ASR_INT, wd33c93_cmd_wait); for (count = SBIC_ABORT_TIMEOUT; count;) { GET_SBIC_asr(sc, asr); if ((asr & SBIC_ASR_LCI) != 0) DELAY(5000); if ((asr & SBIC_ASR_INT) != 0) { GET_SBIC_csr(sc, csr); (void)wd33c93_nextstate(sc, cbuf, clen, buf, lenp, csr, asr); WAIT_CIP(sc); } else { DELAY(5000); count--; } if ((sc->xs_status & XS_STS_DONE) != 0) return 0; } return 1; } static inline int __verify_msg_format(uint8_t *p, int len) { if (len == 1 && MSG_IS1BYTE(p[0])) return 1; if (len == 2 && MSG_IS2BYTE(p[0])) return 1; if (len >= 3 && MSG_ISEXTENDED(p[0]) && len == p[1] + 2) return 1; return 0; } /* * Handle message_in phase */ int wd33c93_msgin_phase(struct wd33c93_softc *sc) { int len; uint8_t asr, csr, *msg; GET_SBIC_asr(sc, asr); __USE(asr); GET_SBIC_selid(sc, csr); SET_SBIC_selid(sc, csr | SBIC_SID_FROM_SCSI); SBIC_TC_PUT(sc, 0); SET_SBIC_control(sc, SBIC_CTL_EDI | SBIC_CTL_IDI); msg = sc->sc_imsg; len = 0; do { /* Fetch the next byte of the message */ RECV_BYTE(sc, *msg++); len++; /* * get the command completion interrupt, or we * can't send a new command (LCI) */ SBIC_WAIT(sc, SBIC_ASR_INT, 0); GET_SBIC_csr(sc, csr); if (__verify_msg_format(sc->sc_imsg, len)) /* Complete message received */ break; /* * Clear ACK, and wait for the interrupt * for the next byte or phase change */ SET_SBIC_cmd(sc, SBIC_CMD_CLR_ACK); SBIC_WAIT(sc, SBIC_ASR_INT, 0); GET_SBIC_csr(sc, csr); } while (len < SBIC_MAX_MSGLEN); /* * Clear ACK, and wait for the interrupt * for the phase change */ SET_SBIC_cmd(sc, SBIC_CMD_CLR_ACK); SBIC_WAIT(sc, SBIC_ASR_INT, 0); /* Should still have one CSR to read */ return SBIC_STATE_RUNNING; } void wd33c93_scsistart(struct wd33c93_softc *sc) { sc->xs_status = 0; } void wd33c93_scsidone(struct wd33c93_softc *sc) { sc->xs_status = XS_STS_DONE; } void wd33c93_error(struct wd33c93_softc *sc) { }