/* $NetBSD: siop.c,v 1.5 2014/12/12 15:57:30 phx Exp $ */ /* * Copyright (c) 2010 KIYOHARA Takashi * 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. */ #include #include #include #include "boot.h" #include "sdvar.h" #ifdef DEBUG #define DPRINTF(x) printf x #else #define DPRINTF(x) #endif #define ALLOC(T, A) \ (T *)(((uint32_t)alloc(sizeof(T) + (A)) + (A)) & ~((A) - 1)) #define VTOPHYS(va) (uint32_t)(va) #define DEVTOV(pa) (uint32_t)(pa) #define wbinv(adr, siz) _wbinv(VTOPHYS(adr), (uint32_t)(siz)) #define inv(adr, siz) _inv(VTOPHYS(adr), (uint32_t)(siz)) /* 53c810 supports little endian */ #define htoc32(x) htole32(x) #define ctoh32(x) le32toh(x) static void siop_pci_reset(int); static void siop_setuptables(struct siop_adapter *, struct siop_xfer *, struct scsi_xfer *); static void siop_ma(struct siop_adapter *, struct scsi_xfer *); static void siop_sdp(struct siop_adapter *, struct siop_xfer *, struct scsi_xfer *, int); static void siop_update_resid(struct siop_adapter *, struct siop_xfer *, struct scsi_xfer *, int); static int siop_intr(struct siop_adapter *); static void siop_scsicmd_end(struct siop_adapter *, struct scsi_xfer *); static int siop_scsi_request(struct siop_adapter *, struct scsi_xfer *); static void siop_start(struct siop_adapter *, struct scsi_xfer *); static void siop_xfer_setup(struct siop_xfer *, void *); static int siop_add_reselsw(struct siop_adapter *, int, int); static void siop_update_scntl3(struct siop_adapter *, int, int); static int _scsi_inquire(struct siop_adapter *, int, int, int, char *); static void scsi_request_sense(struct siop_adapter *, struct scsi_xfer *); static int scsi_interpret_sense(struct siop_adapter *, struct scsi_xfer *); static int scsi_probe(struct siop_adapter *); static struct siop_adapter adapt; static void siop_pci_reset(int addr) { int dmode, ctest5; const int maxburst = 4; /* 53c810 */ dmode = readb(addr + SIOP_DMODE); ctest5 = readb(addr + SIOP_CTEST5); writeb(addr + SIOP_CTEST4, readb(addr + SIOP_CTEST4) & ~CTEST4_BDIS); ctest5 &= ~CTEST5_BBCK; ctest5 |= (maxburst - 1) & CTEST5_BBCK; writeb(addr + SIOP_CTEST5, ctest5); dmode |= DMODE_ERL; dmode &= ~DMODE_BL_MASK; dmode |= ((maxburst - 1) << DMODE_BL_SHIFT) & DMODE_BL_MASK; writeb(addr + SIOP_DMODE, dmode); } static void siop_setuptables(struct siop_adapter *adp, struct siop_xfer *xfer, struct scsi_xfer *xs) { int msgoffset = 1; xfer->siop_tables.id = htoc32((adp->clock_div << 24) | (xs->target << 16)); memset(xfer->siop_tables.msg_out, 0, sizeof(xfer->siop_tables.msg_out)); /* request sense doesn't disconnect */ if (xs->cmd->opcode == SCSI_REQUEST_SENSE) xfer->siop_tables.msg_out[0] = MSG_IDENTIFY(xs->lun, 0); else xfer->siop_tables.msg_out[0] = MSG_IDENTIFY(xs->lun, 1); xfer->siop_tables.t_msgout.count = htoc32(msgoffset); xfer->siop_tables.status = htoc32(SCSI_SIOP_NOSTATUS); /* set invalid status */ xfer->siop_tables.cmd.count = htoc32(xs->cmdlen); xfer->siop_tables.cmd.addr = htoc32(local_to_PCI((u_long)xs->cmd)); if (xs->datalen != 0) { xfer->siop_tables.data[0].count = htoc32(xs->datalen); xfer->siop_tables.data[0].addr = htoc32(local_to_PCI((u_long)xs->data)); } } static void siop_ma(struct siop_adapter *adp, struct scsi_xfer *xs) { int offset, dbc; /* * compute how much of the current table didn't get handled when * a phase mismatch occurs */ if (xs->datalen == 0) return; /* no valid data transfer */ offset = readb(adp->addr + SIOP_SCRATCHA + 1); if (offset >= SIOP_NSG) { printf("bad offset in siop_sdp (%d)\n", offset); return; } dbc = readl(adp->addr + SIOP_DBC) & 0x00ffffff; xs->resid = dbc; } static void siop_clearfifo(struct siop_adapter *adp) { int timo = 0; uint8_t ctest3 = readb(adp->addr + SIOP_CTEST3); DPRINTF(("DMA FIFO not empty!\n")); writeb(adp->addr + SIOP_CTEST3, ctest3 | CTEST3_CLF); while ((readb(adp->addr + SIOP_CTEST3) & CTEST3_CLF) != 0) { delay(1); if (++timo > 1000) { printf("Clear FIFO failed!\n"); writeb(adp->addr + SIOP_CTEST3, readb(adp->addr + SIOP_CTEST3) & ~CTEST3_CLF); return; } } } static void siop_sdp(struct siop_adapter *adp, struct siop_xfer *xfer, struct scsi_xfer *xs, int offset) { if (xs->datalen == 0) return; /* no data pointers to save */ /* * offset == SIOP_NSG may be a valid condition if we get a Save data * pointer when the xfer is done. Just ignore the Save data pointer * in this case */ if (offset == SIOP_NSG) return; /* * Save data pointer. We do this by adjusting the tables to point * at the begginning of the data not yet transfered. * offset points to the first table with untransfered data. */ /* * before doing that we decrease resid from the ammount of data which * has been transfered. */ siop_update_resid(adp, xfer, xs, offset); #if 0 /* * First let see if we have a resid from a phase mismatch. If so, * we have to adjst the table at offset to remove transfered data. */ if (siop_cmd->flags & CMDFL_RESID) { scr_table_t *table; siop_cmd->flags &= ~CMDFL_RESID; table = &xfer->siop_tables.data[offset]; /* "cut" already transfered data from this table */ table->addr = htoc32(ctoh32(table->addr) + ctoh32(table->count) - siop_cmd->resid); table->count = htoc32(siop_cmd->resid); } #endif /* * now we can remove entries which have been transfered. * We just move the entries with data left at the beggining of the * tables */ memmove(xfer->siop_tables.data, &xfer->siop_tables.data[offset], (SIOP_NSG - offset) * sizeof(scr_table_t)); } static void siop_update_resid(struct siop_adapter *adp, struct siop_xfer *xfer, struct scsi_xfer *xs, int offset) { int i; if (xs->datalen == 0) return; /* no data to transfer */ /* * update resid. First account for the table entries which have * been fully completed. */ for (i = 0; i < offset; i++) xs->resid -= ctoh32(xfer->siop_tables.data[i].count); #if 0 /* * if CMDFL_RESID is set, the last table (pointed by offset) is a * partial transfers. If not, offset points to the entry folloing * the last full transfer. */ if (siop_cmd->flags & CMDFL_RESID) { scr_table_t *table = &xfer->siop_tables.data[offset]; xs->resid -= ctoh32(table->count) - xs->resid; } #endif } #define CALL_SCRIPT(ent) writel(adp->addr + SIOP_DSP, scriptaddr + ent); static int siop_intr(struct siop_adapter *adp) { struct siop_xfer *siop_xfer = NULL; struct scsi_xfer *xs = NULL; u_long scriptaddr = local_to_PCI((u_long)adp->script); int offset, target, lun, tag, restart = 0, need_reset = 0; uint32_t dsa, irqcode; uint16_t sist; uint8_t dstat, sstat1, istat; istat = readb(adp->addr + SIOP_ISTAT); if ((istat & (ISTAT_INTF | ISTAT_DIP | ISTAT_SIP)) == 0) return 0; if (istat & ISTAT_INTF) { printf("INTRF\n"); writeb(adp->addr + SIOP_ISTAT, ISTAT_INTF); } if ((istat & (ISTAT_DIP | ISTAT_SIP | ISTAT_ABRT)) == (ISTAT_DIP | ISTAT_ABRT)) /* clear abort */ writeb(adp->addr + SIOP_ISTAT, 0); /* use DSA to find the current siop_cmd */ dsa = readl(adp->addr + SIOP_DSA); if (dsa >= local_to_PCI((u_long)adp->xfer) && dsa < local_to_PCI((u_long)adp->xfer) + SIOP_TABLE_SIZE) { dsa -= local_to_PCI((u_long)adp->xfer); siop_xfer = adp->xfer; _inv((u_long)siop_xfer, sizeof(*siop_xfer)); xs = adp->xs; } if (istat & ISTAT_DIP) dstat = readb(adp->addr + SIOP_DSTAT); if (istat & ISTAT_SIP) { if (istat & ISTAT_DIP) delay(10); /* * Can't read sist0 & sist1 independently, or we have to * insert delay */ sist = readw(adp->addr + SIOP_SIST0); sstat1 = readb(adp->addr + SIOP_SSTAT1); if ((sist & SIST0_MA) && need_reset == 0) { if (siop_xfer) { int scratcha0; dstat = readb(adp->addr + SIOP_DSTAT); /* * first restore DSA, in case we were in a S/G * operation. */ writel(adp->addr + SIOP_DSA, local_to_PCI((u_long)siop_xfer)); scratcha0 = readb(adp->addr + SIOP_SCRATCHA); switch (sstat1 & SSTAT1_PHASE_MASK) { case SSTAT1_PHASE_STATUS: /* * previous phase may be aborted for any reason * ( for example, the target has less data to * transfer than requested). Compute resid and * just go to status, the command should * terminate. */ if (scratcha0 & A_flag_data) siop_ma(adp, xs); else if ((dstat & DSTAT_DFE) == 0) siop_clearfifo(adp); CALL_SCRIPT(Ent_status); return 1; case SSTAT1_PHASE_MSGIN: /* * target may be ready to disconnect * Compute resid which would be used later * if a save data pointer is needed. */ if (scratcha0 & A_flag_data) siop_ma(adp, xs); else if ((dstat & DSTAT_DFE) == 0) siop_clearfifo(adp); writeb(adp->addr + SIOP_SCRATCHA, scratcha0 & ~A_flag_data); CALL_SCRIPT(Ent_msgin); return 1; } printf("unexpected phase mismatch %d\n", sstat1 & SSTAT1_PHASE_MASK); } else printf("phase mismatch without command\n"); need_reset = 1; } if (sist & (SIST1_STO << 8)) { /* selection time out, assume there's no device here */ if (siop_xfer) { xs->error = XS_SELTIMEOUT; goto end; } else printf("selection timeout without command\n"); } /* Else it's an unhandled exception (for now). */ printf("unhandled scsi interrupt," " sist=0x%x sstat1=0x%x DSA=0x%x DSP=0x%lx\n", sist, sstat1, dsa, readl(adp->addr + SIOP_DSP) - scriptaddr); if (siop_xfer) { xs->error = XS_SELTIMEOUT; goto end; } need_reset = 1; } if (need_reset) { reset: printf("XXXXX: fatal error, need reset the bus...\n"); return 1; } //scintr: if ((istat & ISTAT_DIP) && (dstat & DSTAT_SIR)) { /* script interrupt */ irqcode = readl(adp->addr + SIOP_DSPS); /* * no command, or an inactive command is only valid for a * reselect interrupt */ if ((irqcode & 0x80) == 0) { if (siop_xfer == NULL) { printf( "script interrupt 0x%x with invalid DSA\n", irqcode); goto reset; } } switch(irqcode) { case A_int_err: printf("error, DSP=0x%lx\n", readl(adp->addr + SIOP_DSP) - scriptaddr); if (xs) { xs->error = XS_SELTIMEOUT; goto end; } else { goto reset; } case A_int_reseltarg: printf("reselect with invalid target\n"); goto reset; case A_int_resellun: target = readb(adp->addr + SIOP_SCRATCHA) & 0xf; lun = readb(adp->addr + SIOP_SCRATCHA + 1); tag = readb(adp->addr + SIOP_SCRATCHA + 2); if (target != adp->xs->target || lun != adp->xs->lun || tag != 0) { printf("unknwon resellun:" " target %d lun %d tag %d\n", target, lun, tag); goto reset; } siop_xfer = adp->xfer; dsa = local_to_PCI((u_long)siop_xfer); writel(adp->addr + SIOP_DSP, dsa + sizeof(struct siop_common_xfer) + Ent_ldsa_reload_dsa); _wbinv((u_long)siop_xfer, sizeof(*siop_xfer)); return 1; case A_int_reseltag: printf("reselect with invalid tag\n"); goto reset; case A_int_disc: offset = readb(adp->addr + SIOP_SCRATCHA + 1); siop_sdp(adp, siop_xfer, xs, offset); #if 0 /* we start again with no offset */ siop_cmd->saved_offset = SIOP_NOOFFSET; #endif _wbinv((u_long)siop_xfer, sizeof(*siop_xfer)); CALL_SCRIPT(Ent_script_sched); return 1; case A_int_resfail: printf("reselect failed\n"); return 1; case A_int_done: if (xs == NULL) { printf("done without command, DSA=0x%lx\n", local_to_PCI((u_long)adp->xfer)); return 1; } /* update resid. */ offset = readb(adp->addr + SIOP_SCRATCHA + 1); #if 0 /* * if we got a disconnect between the last data phase * and the status phase, offset will be 0. In this * case, siop_cmd->saved_offset will have the proper * value if it got updated by the controller */ if (offset == 0 && siop_cmd->saved_offset != SIOP_NOOFFSET) offset = siop_cmd->saved_offset; #endif siop_update_resid(adp, siop_xfer, xs, offset); goto end; default: printf("unknown irqcode %x\n", irqcode); if (xs) { xs->error = XS_SELTIMEOUT; goto end; } goto reset; } return 1; } /* We just should't get there */ panic("siop_intr: I shouldn't be there !"); return 1; end: /* * restart the script now if command completed properly * Otherwise wait for siop_scsicmd_end(), we may need to cleanup the * queue */ xs->status = ctoh32(siop_xfer->siop_tables.status); if (xs->status == SCSI_OK) writel(adp->addr + SIOP_DSP, scriptaddr + Ent_script_sched); else restart = 1; siop_scsicmd_end(adp, xs); if (restart) writel(adp->addr + SIOP_DSP, scriptaddr + Ent_script_sched); return 1; } static void siop_scsicmd_end(struct siop_adapter *adp, struct scsi_xfer *xs) { switch(xs->status) { case SCSI_OK: xs->error = XS_NOERROR; break; case SCSI_BUSY: case SCSI_CHECK: case SCSI_QUEUE_FULL: xs->error = XS_BUSY; break; case SCSI_SIOP_NOCHECK: /* * don't check status, xs->error is already valid */ break; case SCSI_SIOP_NOSTATUS: /* * the status byte was not updated, cmd was * aborted */ xs->error = XS_SELTIMEOUT; break; default: printf("invalid status code %d\n", xs->status); xs->error = XS_DRIVER_STUFFUP; } _inv((u_long)xs->cmd, xs->cmdlen); if (xs->datalen != 0) _inv((u_long)xs->data, xs->datalen); xs->xs_status = XS_STS_DONE; } static int siop_scsi_request(struct siop_adapter *adp, struct scsi_xfer *xs) { void *xfer = adp->xfer; int timo, error; if (adp->sel_t != xs->target) { const int free_lo = __arraycount(siop_script); int i; void *scriptaddr = (void *)local_to_PCI((u_long)adp->script); if (adp->sel_t != -1) adp->script[Ent_resel_targ0 / 4 + adp->sel_t * 2] = htoc32(0x800c00ff); for (i = 0; i < __arraycount(lun_switch); i++) adp->script[free_lo + i] = htoc32(lun_switch[i]); adp->script[free_lo + E_abs_lunsw_return_Used[0]] = htoc32(scriptaddr + Ent_lunsw_return); siop_add_reselsw(adp, xs->target, free_lo); adp->sel_t = xs->target; } restart: siop_setuptables(adp, xfer, xs); /* load the DMA maps */ if (xs->datalen != 0) _inv((u_long)xs->data, xs->datalen); _wbinv((u_long)xs->cmd, xs->cmdlen); _wbinv((u_long)xfer, sizeof(struct siop_xfer)); siop_start(adp, xs); adp->xs = xs; timo = 0; while (!(xs->xs_status & XS_STS_DONE)) { delay(1000); siop_intr(adp); if (timo++ > 3000) { /* XXXX: 3sec */ printf("%s: timeout\n", __func__); return ETIMEDOUT; } } if (xs->error != XS_NOERROR) { if (xs->error == XS_BUSY || xs->status == SCSI_CHECK) scsi_request_sense(adp, xs); switch (xs->error) { case XS_SENSE: case XS_SHORTSENSE: error = scsi_interpret_sense(adp, xs); break; case XS_RESOURCE_SHORTAGE: printf("adapter resource shortage\n"); /* FALLTHROUGH */ case XS_BUSY: error = EBUSY; break; case XS_REQUEUE: printf("XXXX: requeue...\n"); error = ERESTART; break; case XS_SELTIMEOUT: case XS_TIMEOUT: error = EIO; break; case XS_RESET: error = EIO; break; case XS_DRIVER_STUFFUP: printf("generic HBA error\n"); error = EIO; break; default: printf("invalid return code from adapter: %d\n", xs->error); error = EIO; break; } if (error == ERESTART) { xs->error = XS_NOERROR; xs->status = SCSI_OK; xs->xs_status &= ~XS_STS_DONE; goto restart; } return error; } return 0; } static void siop_start(struct siop_adapter *adp, struct scsi_xfer *xs) { struct siop_xfer *siop_xfer = adp->xfer; uint32_t dsa, *script = adp->script; int slot; void *scriptaddr = (void *)local_to_PCI((u_long)script); const int siop_common_xfer_size = sizeof(struct siop_common_xfer); /* * The queue management here is a bit tricky: the script always looks * at the slot from first to last, so if we always use the first * free slot commands can stay at the tail of the queue ~forever. * The algorithm used here is to restart from the head when we know * that the queue is empty, and only add commands after the last one. * When we're at the end of the queue wait for the script to clear it. * The best thing to do here would be to implement a circular queue, * but using only 53c720 features this can be "interesting". * A mid-way solution could be to implement 2 queues and swap orders. */ slot = adp->currschedslot; /* * If the instruction is 0x80000000 (JUMP foo, IF FALSE) the slot is * free. As this is the last used slot, all previous slots are free, * we can restart from 0. */ if (ctoh32(script[(Ent_script_sched_slot0 / 4) + slot * 2]) == 0x80000000) { slot = adp->currschedslot = 0; } else { slot++; } /* * find a free scheduler slot and load it. */ #define SIOP_NSLOTS 0x40 for (; slot < SIOP_NSLOTS; slot++) { /* * If cmd if 0x80000000 the slot is free */ if (ctoh32(script[(Ent_script_sched_slot0 / 4) + slot * 2]) == 0x80000000) break; } if (slot == SIOP_NSLOTS) { /* * no more free slot, no need to continue. freeze the queue * and requeue this command. */ printf("no mode free slot\n"); return; } /* patch scripts with DSA addr */ dsa = local_to_PCI((u_long)siop_xfer); /* CMD script: MOVE MEMORY addr */ siop_xfer->resel[E_ldsa_abs_slot_Used[0]] = htoc32(scriptaddr + Ent_script_sched_slot0 + slot * 8); _wbinv((u_long)siop_xfer, sizeof(*siop_xfer)); /* scheduler slot: JUMP ldsa_select */ script[(Ent_script_sched_slot0 / 4) + slot * 2 + 1] = htoc32(dsa + siop_common_xfer_size + Ent_ldsa_select); /* * Change JUMP cmd so that this slot will be handled */ script[(Ent_script_sched_slot0 / 4) + slot * 2] = htoc32(0x80080000); adp->currschedslot = slot; /* make sure SCRIPT processor will read valid data */ _wbinv((u_long)script, SIOP_SCRIPT_SIZE); /* Signal script it has some work to do */ writeb(adp->addr + SIOP_ISTAT, ISTAT_SIGP); /* and wait for IRQ */ } static void siop_xfer_setup(struct siop_xfer *xfer, void *scriptaddr) { const int off_msg_in = offsetof(struct siop_common_xfer, msg_in); const int off_status = offsetof(struct siop_common_xfer, status); uint32_t dsa, *scr; int i; memset(xfer, 0, sizeof(*xfer)); dsa = local_to_PCI((u_long)xfer); xfer->siop_tables.t_msgout.count = htoc32(1); xfer->siop_tables.t_msgout.addr = htoc32(dsa); xfer->siop_tables.t_msgin.count = htoc32(1); xfer->siop_tables.t_msgin.addr = htoc32(dsa + off_msg_in); xfer->siop_tables.t_extmsgin.count = htoc32(2); xfer->siop_tables.t_extmsgin.addr = htoc32(dsa + off_msg_in + 1); xfer->siop_tables.t_extmsgdata.addr = htoc32(dsa + off_msg_in + 3); xfer->siop_tables.t_status.count = htoc32(1); xfer->siop_tables.t_status.addr = htoc32(dsa + off_status); /* The select/reselect script */ scr = xfer->resel; for (i = 0; i < __arraycount(load_dsa); i++) scr[i] = htoc32(load_dsa[i]); /* * 0x78000000 is a 'move data8 to reg'. data8 is the second * octet, reg offset is the third. */ scr[Ent_rdsa0 / 4] = htoc32(0x78100000 | ((dsa & 0x000000ff) << 8)); scr[Ent_rdsa1 / 4] = htoc32(0x78110000 | ( dsa & 0x0000ff00 )); scr[Ent_rdsa2 / 4] = htoc32(0x78120000 | ((dsa & 0x00ff0000) >> 8)); scr[Ent_rdsa3 / 4] = htoc32(0x78130000 | ((dsa & 0xff000000) >> 16)); scr[E_ldsa_abs_reselected_Used[0]] = htoc32(scriptaddr + Ent_reselected); scr[E_ldsa_abs_reselect_Used[0]] = htoc32(scriptaddr + Ent_reselect); scr[E_ldsa_abs_selected_Used[0]] = htoc32(scriptaddr + Ent_selected); scr[E_ldsa_abs_data_Used[0]] = htoc32(dsa + sizeof(struct siop_common_xfer) + Ent_ldsa_data); /* JUMP foo, IF FALSE - used by MOVE MEMORY to clear the slot */ scr[Ent_ldsa_data / 4] = htoc32(0x80000000); } static int siop_add_reselsw(struct siop_adapter *adp, int target, int lunsw_off) { uint32_t *script = adp->script; int reseloff; void *scriptaddr = (void *)local_to_PCI((u_long)adp->script); /* * add an entry to resel switch */ reseloff = Ent_resel_targ0 / 4 + target * 2; if ((ctoh32(script[reseloff]) & 0xff) != 0xff) { /* it's not free */ printf("siop: resel switch full\n"); return EBUSY; } /* JUMP abs_foo, IF target | 0x80; */ script[reseloff + 0] = htoc32(0x800c0080 | target); script[reseloff + 1] = htoc32(scriptaddr + lunsw_off * 4 + Ent_lun_switch_entry); siop_update_scntl3(adp, target, lunsw_off); return 0; } static void siop_update_scntl3(struct siop_adapter *adp, int target, int lunsw_off) { uint32_t *script = adp->script; /* MOVE target->id >> 24 TO SCNTL3 */ script[lunsw_off + (Ent_restore_scntl3 / 4)] = htoc32(0x78030000 | ((adp->clock_div >> 16) & 0x0000ff00)); /* MOVE target->id >> 8 TO SXFER */ script[lunsw_off + (Ent_restore_scntl3 / 4) + 2] = htoc32(0x78050000 | (0x000000000 & 0x0000ff00)); _wbinv((u_long)script, SIOP_SCRIPT_SIZE); } /* * SCSI functions */ static int _scsi_inquire(struct siop_adapter *adp, int t, int l, int buflen, char *buf) { struct scsipi_inquiry *cmd = (struct scsipi_inquiry *)adp->cmd; struct scsipi_inquiry_data *inqbuf = (struct scsipi_inquiry_data *)adp->data; struct scsi_xfer xs; int error; memset(cmd, 0, sizeof(*cmd)); cmd->opcode = INQUIRY; cmd->length = SCSIPI_INQUIRY_LENGTH_SCSI2; memset(inqbuf, 0, sizeof(*inqbuf)); memset(&xs, 0, sizeof(xs)); xs.target = t; xs.lun = l; xs.cmdlen = sizeof(*cmd); xs.cmd = (void *)cmd; xs.datalen = SCSIPI_INQUIRY_LENGTH_SCSI2; xs.data = (void *)inqbuf; xs.error = XS_NOERROR; xs.resid = xs.datalen; xs.status = SCSI_OK; error = siop_scsi_request(adp, &xs); if (error != 0) return error; memcpy(buf, inqbuf, buflen); return 0; } static void scsi_request_sense(struct siop_adapter *adp, struct scsi_xfer *xs) { struct scsi_request_sense *cmd = adp->sense; struct scsi_sense_data *data = (struct scsi_sense_data *)adp->data; struct scsi_xfer sense; int error; memset(cmd, 0, sizeof(struct scsi_request_sense)); cmd->opcode = SCSI_REQUEST_SENSE; cmd->length = sizeof(struct scsi_sense_data); memset(data, 0, sizeof(struct scsi_sense_data)); memset(&sense, 0, sizeof(sense)); sense.target = xs->target; sense.lun = xs->lun; sense.cmdlen = sizeof(struct scsi_request_sense); sense.cmd = (void *)cmd; sense.datalen = sizeof(struct scsi_sense_data); sense.data = (void *)data; sense.error = XS_NOERROR; sense.resid = sense.datalen; sense.status = SCSI_OK; error = siop_scsi_request(adp, &sense); switch (error) { case 0: /* we have a valid sense */ xs->error = XS_SENSE; return; case EINTR: /* REQUEST_SENSE interrupted by bus reset. */ xs->error = XS_RESET; return; case EIO: /* request sense coudn't be performed */ /* * XXX this isn't quite right but we don't have anything * better for now */ xs->error = XS_DRIVER_STUFFUP; return; default: /* Notify that request sense failed. */ xs->error = XS_DRIVER_STUFFUP; printf("request sense failed with error %d\n", error); return; } } /* * scsi_interpret_sense: * * Look at the returned sense and act on the error, determining * the unix error number to pass back. (0 = report no error) * * NOTE: If we return ERESTART, we are expected to haved * thawed the device! * * THIS IS THE DEFAULT ERROR HANDLER FOR SCSI DEVICES. */ static int scsi_interpret_sense(struct siop_adapter *adp, struct scsi_xfer *xs) { struct scsi_sense_data *sense; u_int8_t key; int error; uint32_t info; static const char *error_mes[] = { "soft error (corrected)", "not ready", "medium error", "non-media hardware failure", "illegal request", "unit attention", "readonly device", "no data found", "vendor unique", "copy aborted", "command aborted", "search returned equal", "volume overflow", "verify miscompare", "unknown error key" }; sense = (struct scsi_sense_data *)xs->data; DPRINTF((" sense debug information:\n")); DPRINTF(("\tcode 0x%x valid %d\n", SSD_RCODE(sense->response_code), sense->response_code & SSD_RCODE_VALID ? 1 : 0)); DPRINTF(("\tseg 0x%x key 0x%x ili 0x%x eom 0x%x fmark 0x%x\n", sense->segment, SSD_SENSE_KEY(sense->flags), sense->flags & SSD_ILI ? 1 : 0, sense->flags & SSD_EOM ? 1 : 0, sense->flags & SSD_FILEMARK ? 1 : 0)); DPRINTF(("\ninfo: 0x%x 0x%x 0x%x 0x%x followed by %d " "extra bytes\n", sense->info[0], sense->info[1], sense->info[2], sense->info[3], sense->extra_len)); switch (SSD_RCODE(sense->response_code)) { /* * Old SCSI-1 and SASI devices respond with * codes other than 70. */ case 0x00: /* no error (command completed OK) */ return 0; case 0x04: /* drive not ready after it was selected */ if (adp->sd->sc_flags & FLAGS_REMOVABLE) adp->sd->sc_flags &= ~FLAGS_MEDIA_LOADED; /* XXX - display some sort of error here? */ return EIO; case 0x20: /* invalid command */ return EINVAL; case 0x25: /* invalid LUN (Adaptec ACB-4000) */ return EACCES; /* * If it's code 70, use the extended stuff and * interpret the key */ case 0x71: /* delayed error */ key = SSD_SENSE_KEY(sense->flags); printf(" DEFERRED ERROR, key = 0x%x\n", key); /* FALLTHROUGH */ case 0x70: if ((sense->response_code & SSD_RCODE_VALID) != 0) info = _4btol(sense->info); else info = 0; key = SSD_SENSE_KEY(sense->flags); switch (key) { case SKEY_NO_SENSE: case SKEY_RECOVERED_ERROR: if (xs->resid == xs->datalen && xs->datalen) { /* * Why is this here? */ xs->resid = 0; /* not short read */ } case SKEY_EQUAL: error = 0; break; case SKEY_NOT_READY: if (adp->sd->sc_flags & FLAGS_REMOVABLE) adp->sd->sc_flags &= ~FLAGS_MEDIA_LOADED; if (sense->asc == 0x3A) { error = ENODEV; /* Medium not present */ } else error = EIO; break; case SKEY_ILLEGAL_REQUEST: error = EINVAL; break; case SKEY_UNIT_ATTENTION: if (sense->asc == 0x29 && sense->ascq == 0x00) { /* device or bus reset */ return ERESTART; } if (adp->sd->sc_flags & FLAGS_REMOVABLE) adp->sd->sc_flags &= ~FLAGS_MEDIA_LOADED; if (!(adp->sd->sc_flags & FLAGS_REMOVABLE)) return ERESTART; error = EIO; break; case SKEY_DATA_PROTECT: error = EROFS; break; case SKEY_BLANK_CHECK: error = 0; break; case SKEY_ABORTED_COMMAND: break; case SKEY_VOLUME_OVERFLOW: error = ENOSPC; break; default: error = EIO; break; } /* Print brief(er) sense information */ printf("%s", error_mes[key - 1]); if ((sense->response_code & SSD_RCODE_VALID) != 0) { switch (key) { case SKEY_NOT_READY: case SKEY_ILLEGAL_REQUEST: case SKEY_UNIT_ATTENTION: case SKEY_DATA_PROTECT: break; case SKEY_BLANK_CHECK: printf(", requested size: %d (decimal)", info); break; case SKEY_ABORTED_COMMAND: printf(", cmd 0x%x, info 0x%x", xs->cmd->opcode, info); break; default: printf(", info = %d (decimal)", info); } } if (sense->extra_len != 0) { int n; printf(", data ="); for (n = 0; n < sense->extra_len; n++) printf(" %x", sense->csi[n]); } printf("\n"); return error; /* * Some other code, just report it */ default: printf("Sense Error Code 0x%x", SSD_RCODE(sense->response_code)); if ((sense->response_code & SSD_RCODE_VALID) != 0) { struct scsi_sense_data_unextended *usense = (struct scsi_sense_data_unextended *)sense; printf(" at block no. %d (decimal)", _3btol(usense->block)); } printf("\n"); return EIO; } } static int scsi_probe(struct siop_adapter *adp) { struct scsipi_inquiry_data *inqbuf; int found, t, l; uint8_t device; char buf[SCSIPI_INQUIRY_LENGTH_SCSI2], product[sizeof(inqbuf->product) + 1]; found = 0; for (t = 0; t < 8; t++) { if (t == adp->id) continue; for (l = 0; l < 8; l++) { if (_scsi_inquire(adp, t, l, sizeof(buf), buf) != 0) continue; inqbuf = (struct scsipi_inquiry_data *)buf; device = inqbuf->device & SID_TYPE; if (device == T_NODEVICE) continue; if (device != T_DIRECT && device != T_OPTICAL && device != T_SIMPLE_DIRECT) continue; memset(product, 0, sizeof(product)); strncpy(product, inqbuf->product, sizeof(product) - 1); printf("/dev/disk/scsi/0%d%d: <%s>\n", t, l, product); found++; } } return found; } int scsi_inquire(struct sd_softc *sd, int buflen, void *buf) { struct siop_adapter *adp; int error; if (sd->sc_bus != 0) return ENOTSUP; if (adapt.addr == 0) return ENOENT; adp = &adapt; adp->sd = sd; error = _scsi_inquire(adp, sd->sc_target, sd->sc_lun, buflen, buf); adp->sd = NULL; return error; } /* * scsi_mode_sense * get a sense page from a device */ int scsi_mode_sense(struct sd_softc *sd, int byte2, int page, struct scsi_mode_parameter_header_6 *data, int len) { struct scsi_mode_sense_6 cmd; memset(&cmd, 0, sizeof(cmd)); cmd.opcode = SCSI_MODE_SENSE_6; cmd.byte2 = byte2; cmd.page = page; cmd.length = len & 0xff; return scsi_command(sd, (void *)&cmd, sizeof(cmd), (void *)data, len); } int scsi_command(struct sd_softc *sd, void *cmd, int cmdlen, void *data, int datalen) { struct siop_adapter *adp; struct scsi_xfer xs; int error; if (sd->sc_bus != 0) return ENOTSUP; if (adapt.addr == 0) return ENOENT; adp = &adapt; memcpy(adp->cmd, cmd, cmdlen); adp->sd = sd; memset(&xs, 0, sizeof(xs)); xs.target = sd->sc_target; xs.lun = sd->sc_lun; xs.cmdlen = cmdlen; xs.cmd = adp->cmd; xs.datalen = datalen; xs.data = adp->data; xs.error = XS_NOERROR; xs.resid = datalen; xs.status = SCSI_OK; error = siop_scsi_request(adp, &xs); adp->sd = NULL; if (error != 0) return error; if (datalen > 0) memcpy(data, adp->data, datalen); return 0; } /* * Initialize the device. */ int siop_init(int bus, int dev, int func) { struct siop_adapter tmp; struct siop_xfer *xfer; struct scsipi_generic *cmd; struct scsi_request_sense *sense; uint32_t reg; u_long addr; uint32_t *script; int slot, id, i; void *scriptaddr; u_char *data; const int clock_div = 3; /* 53c810 */ slot = PCISlotnum(bus, dev, func); if (slot == -1) return ENOENT; addr = PCIAddress(slot, 1, PCI_MAPREG_TYPE_MEM); if (addr == 0xffffffff) return EINVAL; enablePCI(slot, 0, 1, 1); script = ALLOC(uint32_t, SIOP_SCRIPT_SIZE); if (script == NULL) return ENOMEM; scriptaddr = (void *)local_to_PCI((u_long)script); cmd = ALLOC(struct scsipi_generic, SIOP_SCSI_COMMAND_SIZE); if (cmd == NULL) return ENOMEM; sense = ALLOC(struct scsi_request_sense, SIOP_SCSI_COMMAND_SIZE); if (sense == NULL) return ENOMEM; data = ALLOC(u_char, SIOP_SCSI_DATA_SIZE); if (data == NULL) return ENOMEM; xfer = ALLOC(struct siop_xfer, sizeof(struct siop_xfer)); if (xfer == NULL) return ENOMEM; siop_xfer_setup(xfer, scriptaddr); id = readb(addr + SIOP_SCID) & SCID_ENCID_MASK; /* reset bus */ reg = readb(addr + SIOP_SCNTL1); writeb(addr + SIOP_SCNTL1, reg | SCNTL1_RST); delay(100); writeb(addr + SIOP_SCNTL1, reg); /* reset the chip */ writeb(addr + SIOP_ISTAT, ISTAT_SRST); delay(1000); writeb(addr + SIOP_ISTAT, 0); /* init registers */ writeb(addr + SIOP_SCNTL0, SCNTL0_ARB_MASK | SCNTL0_EPC | SCNTL0_AAP); writeb(addr + SIOP_SCNTL1, 0); writeb(addr + SIOP_SCNTL3, clock_div); writeb(addr + SIOP_SXFER, 0); writeb(addr + SIOP_DIEN, 0xff); writeb(addr + SIOP_SIEN0, 0xff & ~(SIEN0_CMP | SIEN0_SEL | SIEN0_RSL)); writeb(addr + SIOP_SIEN1, 0xff & ~(SIEN1_HTH | SIEN1_GEN)); writeb(addr + SIOP_STEST2, 0); writeb(addr + SIOP_STEST3, STEST3_TE); writeb(addr + SIOP_STIME0, (0xb << STIME0_SEL_SHIFT)); writeb(addr + SIOP_SCID, id | SCID_RRE); writeb(addr + SIOP_RESPID0, 1 << id); writeb(addr + SIOP_DCNTL, DCNTL_COM); /* BeBox uses PCIC */ writeb(addr + SIOP_STEST1, STEST1_SCLK); siop_pci_reset(addr); /* copy and patch the script */ for (i = 0; i < __arraycount(siop_script); i++) script[i] = htoc32(siop_script[i]); for (i = 0; i < __arraycount(E_abs_msgin_Used); i++) script[E_abs_msgin_Used[i]] = htoc32(scriptaddr + Ent_msgin_space); /* start script */ _wbinv((u_long)script, SIOP_SCRIPT_SIZE); writel(addr + SIOP_DSP, (int)scriptaddr + Ent_reselect); memset(&tmp, 0, sizeof(tmp)); tmp.id = id; tmp.clock_div = clock_div; tmp.addr = addr; tmp.script = script; tmp.xfer = xfer; tmp.cmd = cmd; tmp.sense = sense; tmp.data = data; tmp.currschedslot = 0; tmp.sel_t = -1; if (scsi_probe(&tmp) == 0) return ENXIO; adapt = tmp; return 0; }