/* * Mach Operating System * Copyright (c) 1993,1992,1991,1990 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ /* * File: scsi_aha15_hdw.c * Author: Alessandro Forin, Carnegie Mellon University * Date: 6/91 * * Bottom layer of the SCSI driver: chip-dependent functions * * This file contains the code that is specific to the Adaptec * AHA-15xx family of Intelligent SCSI Host Adapter boards: * probing, start operation, and interrupt routine. */ /* * Since the board is "Intelligent" we do not need scripts like * other simpler HBAs. Maybe. */ #include #include #include #if NAHA > 0 #include #include #include #include #include /* #include */ #include #include #include #include #ifdef AT386 #define MACHINE_PGBYTES I386_PGBYTES #define MAPPABLE 0 #define gimmeabreak() asm("int3") #include /* inlining of outb and inb */ #endif /*AT386*/ #ifdef CBUS /* For the Corollary machine, physical */ #include #include #define aha_cbus_window transient_state.hba_dep[0] /* must use windows for phys addresses */ /* greater than 16 megs */ #define kvtoAT cbus_kvtoAT #else /* CBUS */ #define kvtoAT kvtophys #endif /* CBUS */ #ifndef MACHINE_PGBYTES /* cross compile check */ #define MACHINE_PGBYTES 0x1000 #define MAPPABLE 1 #define gimmeabreak() Debugger("gimmeabreak"); #endif /* * Data structures: ring, ccbs, a per target buffer */ #define AHA_NMBOXES 2 /* no need for more, I think */ struct aha_mb_ctl { aha_mbox_t omb[AHA_NMBOXES]; aha_mbox_t imb[AHA_NMBOXES]; unsigned char iidx, oidx; /* roving ptrs into */ }; #define next_mbx_idx(i) ((((i)+1)==AHA_NMBOXES)?0:((i)+1)) #define AHA_NCCB 8 /* for now */ struct aha_ccb_raw { target_info_t *active_target; aha_ccb_t ccb; char buffer[256]; /* separate out this ? */ }; #define rccb_to_cmdptr(rccb) ((char*)&((rccb)->ccb.ccb_scsi_cmd)) /* forward decls */ int aha_reset_scsibus(); boolean_t aha_probe_target(); /* * State descriptor for this layer. There is one such structure * per (enabled) board */ struct aha_softc { watchdog_t wd; decl_simple_lock_data(, aha_lock) unsigned int port; /* I/O port */ int ntargets; /* how many alive on this scsibus */ scsi_softc_t *sc; /* HBA-indep info */ struct aha_mb_ctl mb; /* mailbox structures */ /* This chicanery is for mapping back the phys address of a CCB (which we get in an MBI) to its virtual */ /* [we could use phystokv(), but it isn't standard] */ vm_offset_t I_hold_my_phys_address; struct aha_ccb_raw aha_ccbs[AHA_NCCB]; } aha_softc_data[NAHA]; typedef struct aha_softc *aha_softc_t; aha_softc_t aha_softc[NAHA]; struct aha_ccb_raw * mb_to_rccb(aha, mbi) aha_softc_t aha; aha_mbox_t mbi; { vm_offset_t addr; AHA_MB_GET_PTR(&mbi,addr); /* phys address of ccb */ /* make virtual */ addr = ((vm_offset_t)&aha->I_hold_my_phys_address) + (addr - aha->I_hold_my_phys_address); /* adjust by proper offset to get base */ addr -= (vm_offset_t)&(((struct aha_ccb_raw *)0)->ccb); return (struct aha_ccb_raw *)addr; } target_info_t * aha_tgt_alloc(aha, id, sns_len, tgt) aha_softc_t aha; target_info_t *tgt; { struct aha_ccb_raw *rccb; aha->ntargets++; if (tgt == 0) tgt = scsi_slave_alloc(aha - aha_softc_data, id, aha); rccb = &(aha->aha_ccbs[id]); rccb->ccb.ccb_reqsns_len = sns_len; tgt->cmd_ptr = rccb_to_cmdptr(rccb); tgt->dma_ptr = 0; #ifdef CBUS tgt->aha_cbus_window = 0; #endif /* CBUS */ return tgt; } /* * Synch xfer timing conversions */ #define aha_to_scsi_period(a) ((200 + ((a) * 50)) >> 2) #define scsi_period_to_aha(p) ((((p) << 2) - 200) / 50) /* * Definition of the controller for the auto-configuration program. */ /* DOCUMENTATION */ /* base ports can be: 0x334, 0x330 (default), 0x234, 0x230, 0x134, 0x130 possible interrupt channels are: 9, 10, 11 (default), 12, 14, 15 DMA channels can be: 7, 6, 5 (default), 0 /* DOCUMENTATION */ int aha_probe(), scsi_slave(), aha_go(), aha_intr(); void scsi_attach(); vm_offset_t aha_std[NAHA] = { 0 }; struct bus_device *aha_dinfo[NAHA*8]; struct bus_ctlr *aha_minfo[NAHA]; struct bus_driver aha_driver = { aha_probe, scsi_slave, scsi_attach, aha_go, aha_std, "rz", aha_dinfo, "ahac", aha_minfo, BUS_INTR_B4_PROBE}; #define DEBUG 1 #if DEBUG #define PRINT(x) if (scsi_debug) printf x aha_state(port) { register unsigned char st, intr; if (port == 0) port = 0x330; st = inb(AHA_STATUS_PORT(port)); intr = inb(AHA_INTR_PORT(port)); printf("status %x intr %x\n", st, intr); return 0; } aha_target_state(tgt) target_info_t *tgt; { if (tgt == 0) tgt = aha_softc[0]->sc->target[0]; if (tgt == 0) return 0; printf("fl %x dma %X+%x cmd %x@%X id %x per %x off %x ior %X ret %X\n", tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset, tgt->cur_cmd, tgt->cmd_ptr, tgt->target_id, tgt->sync_period, tgt->sync_offset, tgt->ior, tgt->done); return 0; } aha_all_targets(unit) { int i; target_info_t *tgt; for (i = 0; i < 8; i++) { tgt = aha_softc[unit]->sc->target[i]; if (tgt) aha_target_state(tgt); } } #define TRMAX 200 int tr[TRMAX+3]; int trpt, trpthi; #define TR(x) tr[trpt++] = x #define TRWRAP trpthi = trpt; trpt = 0; #define TRCHECK if (trpt > TRMAX) {TRWRAP} #define TRACE #ifdef TRACE #define LOGSIZE 256 #define LOG_KERN 0<<3 /* from syslog.h */ int aha_logpt; char aha_log[LOGSIZE]; #define MAXLOG_VALUE 0x1e struct { char *name; unsigned int count; } logtbl[MAXLOG_VALUE]; static LOG(e,f) char *f; { aha_log[aha_logpt++] = (e); if (aha_logpt == LOGSIZE) aha_logpt = 0; if ((e) < MAXLOG_VALUE) { logtbl[(e)].name = (f); logtbl[(e)].count++; } } aha_print_log(skip) int skip; { register int i, j; register unsigned char c; for (i = 0, j = aha_logpt; i < LOGSIZE; i++) { c = aha_log[j]; if (++j == LOGSIZE) j = 0; if (skip-- > 0) continue; if (c < MAXLOG_VALUE) printf(" %s", logtbl[c].name); else printf("-%x", c & 0x7f); } return 0; } aha_print_stat() { register int i; register char *p; for (i = 0; i < MAXLOG_VALUE; i++) { if (p = logtbl[i].name) printf("%d %s\n", logtbl[i].count, p); } } #else /*TRACE*/ #define LOG(e,f) #define LOGSIZE #endif /*TRACE*/ #else /*DEBUG*/ #define PRINT(x) #define LOG(e,f) #define LOGSIZE #define TRCHECK #define TR(a) #endif /*DEBUG*/ /* Utility functions at end */ /* * Probe/Slave/Attach functions */ int aha_dotarget = 1; /* somehow on some boards this is trouble */ /* * Probe routine: * Should find out (a) if the controller is * present and (b) which/where slaves are present. * * Implementation: * Just ask the board to do it */ aha_probe(port, ui) register port; struct bus_ctlr *ui; { int unit = ui->unit; aha_softc_t aha = &aha_softc_data[unit]; int target_id; scsi_softc_t *sc; spl_t s; boolean_t did_banner = FALSE; struct aha_devs installed; struct aha_conf conf; /* No interrupts yet */ s = splbio(); /* * We should be called with a sensible port, but you never know. * Send an echo command and see that we get it back properly */ { register unsigned char st; st = inb(AHA_STATUS_PORT(port)); /* * There is no board reset in case of reboot with * no power-on/power-off sequence. Test it and do * the reset if necessary. */ if (!(st & AHA_CSR_INIT_REQ)) { outb(AHA_CONTROL_PORT(port), AHA_CTL_SOFT_RESET|AHA_CTL_HARD_RESET); while ((st = inb(AHA_STATUS_PORT(port))) & AHA_CSR_SELF_TEST); } if ((st & AHA_CSR_DATAO_FULL) || !(st & AHA_CSR_INIT_REQ)) goto fail; outb(AHA_COMMAND_PORT(port), AHA_CMD_ECHO); delay(1000);/*?*/ st = inb(AHA_STATUS_PORT(port)); if (st & (AHA_CSR_CMD_ERR|AHA_CSR_DATAO_FULL)) goto fail; outb(AHA_COMMAND_PORT(port), 0x5e); delay(1000); st = inb(AHA_STATUS_PORT(port)); if ((st & AHA_CSR_CMD_ERR) || ((st & AHA_CSR_DATAI_FULL) == 0)) goto fail; st = inb(AHA_DATA_PORT(port)); if (st != 0x5e) { fail: splx(s); return 0; } /* * augment test with check for echoing inverse and with * test for enhanced adapter with standard ports enabled. */ /* Check that 0xa1 echoed as well as 0x5e */ outb(AHA_COMMAND_PORT(port), AHA_CMD_ECHO); delay(1000);/*?*/ st = inb(AHA_STATUS_PORT(port)); if (st & (AHA_CSR_CMD_ERR|AHA_CSR_DATAO_FULL)) goto fail; outb(AHA_COMMAND_PORT(port), 0xa1); delay(1000); st = inb(AHA_STATUS_PORT(port)); if ((st & AHA_CSR_CMD_ERR) || ((st & AHA_CSR_DATAI_FULL) == 0)) goto fail; st = inb(AHA_DATA_PORT(port)); if (st != 0xa1) goto fail ; { /* Check that port isn't 174x in enhanced mode with standard mode ports enabled. This should be ignored because it will be caught and correctly handled by eaha_probe(). See TRM4-11..13. dph */ unsigned z ; static unsigned port_table[] = {0,0,0x130,0x134,0x230,0x234,0x330,0x334}; for (z= 0x1000; z<= 0xF000; z+= 0x1000) if (inb(z+0xC80) == 0x04 && inb(z+0xC81) == 0x90 && inb(z+0xCC0) & 0x80 == 0x80 && port_table [inb(z+0xCC0) & 0x07] == port) goto fail ; } outb(AHA_CONTROL_PORT(port), AHA_CTL_INTR_CLR); } #if MAPPABLE /* Mappable version side */ AHA_probe(port, ui); #endif /*MAPPABLE*/ /* * Initialize hw descriptor, cache some pointers */ aha_softc[unit] = aha; aha->port = port; sc = scsi_master_alloc(unit, aha); aha->sc = sc; simple_lock_init(&aha->aha_lock); sc->go = aha_go; sc->watchdog = scsi_watchdog; sc->probe = aha_probe_target; aha->wd.reset = aha_reset_scsibus; /* Stupid limitation, no way around it */ sc->max_dma_data = (AHA_MAX_SEGLIST-1) * MACHINE_PGBYTES; /* XXX * I'm not sure how much use this bit of code is really. * On the 1542CF we don't really want to try and initialize * the mailboxes before unlocking them in any case, and * resetting the card is done above. */ #if 0 #if 0 /* * Reset board. */ aha_reset(port, TRUE); #else /* * Initialize mailboxes */ aha_init_1(aha); #endif #endif /* * Who are we ? */ { struct aha_inq inq; struct aha_extbios extbios; char *id; aha_command(port, AHA_CMD_INQUIRY, 0, 0, &inq, sizeof(inq), TRUE); switch (inq.board_id) { case AHA_BID_1540_B16: case AHA_BID_1540_B64: id = "1540"; break; case AHA_BID_1540B: id = "1540B/1542B"; break; case AHA_BID_1640: id = "1640"; break; case AHA_BID_1740: id = "1740 Unsupported!!"; break; case AHA_BID_1542C: id = "1542C"; aha_dotarget = 0; break; case AHA_BID_1542CF: id = "1542CF"; break; default: id = 0; break; } printf("Adaptec %s [id %x], rev %c%c, options x%x\n", id ? id : "Board", inq.board_id, inq.frl_1, inq.frl_2, inq.options); /* * If we are a 1542C or 1542CF disable the extended bios * so that the mailbox interface is unlocked. * No need to check the extended bios flags as some of the * extensions that cause us problems are not flagged in * that byte. */ if (inq.board_id == 0x44 || inq.board_id == 0x45) { aha_command(port, AHA_EXT_BIOS, 0, 0, &extbios, sizeof(extbios), TRUE); #ifdef AHADEBUG printf("aha: extended bios flags 0x%x\n", extbios.flags); printf("aha: mailboxlock 0x%x\n", extbios.mblock); #endif /* AHADEBUG */ printf("aha: 1542C/CF detected, unlocking mailbox\n"); /* XXX - This sends the mailboxlock code out to the * controller. We need to output a 0, then the * code...so since we don't care about the flags * anyway, we just zero out that field and re-use * the struct. */ extbios.flags = 0; aha_command(port, AHA_MBX_ENABLE, &extbios, sizeof(extbios), 0, 0, TRUE); } } doconf: /* * Readin conf data */ aha_command(port, AHA_CMD_GET_CONFIG, 0, 0, &conf, sizeof(conf), TRUE); { unsigned char args; /* * Change the bus on/off times to not clash with * other dma users. */ args = 7; aha_command(port, AHA_CMD_SET_BUSON, &args, 1, 0, 0, TRUE); args = 5; aha_command(port, AHA_CMD_SET_BUSOFF, &args, 1, 0, 0, TRUE); } /* XXX - This is _REALLY_ sickening. */ /* * Set up the DMA channel we'll be using. */ { register int d, i; static struct { unsigned char port; unsigned char init_data; } aha_dma_init[8][2] = { {{0x0b,0x0c}, {0x0a,0x00}}, /* channel 0 */ {{0,0},{0,0}}, {{0,0},{0,0}}, {{0,0},{0,0}}, {{0,0},{0,0}}, {{0xd6,0xc1}, {0xd4,0x01}}, /* channel 5 (def) */ {{0xd6,0xc2}, {0xd4,0x02}}, /* channel 6 */ {{0xd6,0xc3}, {0xd4,0x03}} /* channel 7 */ }; for (i = 0; i < 8; i++) if ((1 << i) & conf.intr_ch) break; i += 9; #if there_was_a_way /* * On second unit, avoid clashes with first */ if ((unit > 0) && (ui->sysdep1 != i)) { printf("Reprogramming irq and dma ch..\n"); .... goto doconf; } #endif /* * Initialize the DMA controller viz the channel we'll use */ for (d = 0; d < 8; d++) if ((1 << d) & conf.dma_arbitration) break; outb(aha_dma_init[d][0].port, aha_dma_init[d][0].init_data); outb(aha_dma_init[d][1].port, aha_dma_init[d][1].init_data); /* make mapping phys->virt possible for CCBs */ aha->I_hold_my_phys_address = kvtoAT((vm_offset_t)&aha->I_hold_my_phys_address); /* * Our SCSI ID. (xxx) On some boards this is SW programmable. */ sc->initiator_id = conf.my_scsi_id; printf("%s%d: [dma ch %d intr ch %d] my SCSI id is %d", ui->name, unit, d, i, sc->initiator_id); /* Interrupt vector setup */ ui->sysdep1 = i; take_ctlr_irq(ui); } /* * More initializations */ { register target_info_t *tgt; aha_init(aha); /* allocate a desc for tgt mode role */ tgt = aha_tgt_alloc(aha, sc->initiator_id, 1, 0); sccpu_new_initiator(tgt, tgt); /* self */ } /* Now we could take interrupts, BUT we do not want to be selected as targets by some other host just yet */ /* * For all possible targets, see if there is one and allocate * a descriptor for it if it is there. * This includes ourselves, when acting as target */ aha_command( port, AHA_CMD_FIND_DEVICES, 0, 0, &installed, sizeof(installed), TRUE); for (target_id = 0; target_id < 8; target_id++) { if (target_id == sc->initiator_id) /* done already */ continue; if (installed.tgt_luns[target_id] == 0) continue; printf(",%s%d", did_banner++ ? " " : " target(s) at ", target_id); /* Normally, only LUN 0 */ if (installed.tgt_luns[target_id] != 1) printf("(%x)", installed.tgt_luns[target_id]); /* * Found a target */ (void) aha_tgt_alloc(aha, target_id, 1/*no REQSNS*/, 0); } printf(".\n"); splx(s); return 1; } boolean_t aha_probe_target(tgt, ior) target_info_t *tgt; io_req_t ior; { aha_softc_t aha = aha_softc[tgt->masterno]; boolean_t newlywed; newlywed = (tgt->cmd_ptr == 0); if (newlywed) { /* desc was allocated afresh */ (void) aha_tgt_alloc(aha,tgt->target_id, 1/*no REQSNS*/, tgt); } if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN) return FALSE; tgt->flags = TGT_ALIVE; return TRUE; } aha_reset(port, quick) { register unsigned char st; /* * Reset board and wait till done */ outb(AHA_CONTROL_PORT(port), AHA_CTL_SOFT_RESET); do { delay(25); st = inb(AHA_STATUS_PORT(port)); } while ((st & (AHA_CSR_IDLE|AHA_CSR_INIT_REQ)) == 0); if (quick) return; /* * reset the scsi bus. Does NOT generate an interrupt (bozos) */ outb(AHA_CONTROL_PORT(port), AHA_CTL_SCSI_RST); } aha_init_1(aha) aha_softc_t aha; { struct aha_init a; vm_offset_t phys; bzero(&aha->mb, sizeof(aha->mb)); /* also means all free */ a.mb_count = AHA_NMBOXES; phys = kvtoAT((vm_offset_t)&aha->mb); AHA_ADDRESS_SET(a.mb_ptr, phys); aha_command(aha->port, AHA_CMD_INIT, &a, sizeof(a), 0, 0, TRUE); } aha_init_2(port) { unsigned char disable = AHA_MBO_DISABLE; struct aha_tgt role; /* Disable MBO available interrupt */ aha_command(port, AHA_CMD_MBO_IE, &disable, 1, 0,0, FALSE); if (aha_dotarget) { /* Enable target mode role */ role.enable = 1; role.luns = 1; /* only LUN 0 */ aha_command(port, AHA_CMD_ENB_TGT_MODE, &role, sizeof(role), 0, 0, TRUE); } } aha_init(aha) aha_softc_t aha; { aha_init_1(aha); aha_init_2(aha->port); } /* * Operational functions */ /* * Start a SCSI command on a target */ aha_go(tgt, cmd_count, in_count, cmd_only) target_info_t *tgt; boolean_t cmd_only; { aha_softc_t aha; spl_t s; struct aha_ccb_raw *rccb; int len; vm_offset_t virt, phys; #if CBUS at386_io_lock_state(); #endif LOG(1,"go"); aha = (aha_softc_t)tgt->hw_state; /* XXX delay the handling of the ccb till later */ rccb = &(aha->aha_ccbs[tgt->target_id]); rccb->active_target = tgt; /* * We can do real DMA. */ /* tgt->transient_state.copy_count = 0; unused */ /* tgt->transient_state.dma_offset = 0; unused */ tgt->transient_state.cmd_count = cmd_count; if ((tgt->cur_cmd == SCSI_CMD_WRITE) || (tgt->cur_cmd == SCSI_CMD_LONG_WRITE)){ io_req_t ior = tgt->ior; register int len = ior->io_count; tgt->transient_state.out_count = len; /* How do we avoid leaks here ? Trust the board will do zero-padding, for now. XXX CHECKME */ #if 0 if (len < tgt->block_size) { bzero(to + len, tgt->block_size - len); len = tgt->block_size; tgt->transient_state.out_count = len; } #endif } else { tgt->transient_state.out_count = 0; } /* See above for in_count < block_size */ tgt->transient_state.in_count = in_count; /* * Setup CCB state */ tgt->done = SCSI_RET_IN_PROGRESS; switch (tgt->cur_cmd) { case SCSI_CMD_READ: case SCSI_CMD_LONG_READ: LOG(9,"readop"); virt = (vm_offset_t)tgt->ior->io_data; len = tgt->transient_state.in_count; rccb->ccb.ccb_in = 1; rccb->ccb.ccb_out = 0; break; case SCSI_CMD_WRITE: case SCSI_CMD_LONG_WRITE: LOG(0x1a,"writeop"); virt = (vm_offset_t)tgt->ior->io_data; len = tgt->transient_state.out_count; rccb->ccb.ccb_in = 0; rccb->ccb.ccb_out = 1; break; case SCSI_CMD_INQUIRY: case SCSI_CMD_REQUEST_SENSE: case SCSI_CMD_MODE_SENSE: case SCSI_CMD_RECEIVE_DIAG_RESULTS: case SCSI_CMD_READ_CAPACITY: case SCSI_CMD_READ_BLOCK_LIMITS: case SCSI_CMD_READ_TOC: case SCSI_CMD_READ_SUBCH: case SCSI_CMD_READ_HEADER: case 0xc4: /* despised: SCSI_CMD_DEC_PLAYBACK_STATUS */ case 0xc6: /* despised: SCSI_CMD_TOSHIBA_READ_SUBCH_Q */ case 0xc7: /* despised: SCSI_CMD_TOSHIBA_READ_TOC_ENTRY */ case 0xdd: /* despised: SCSI_CMD_NEC_READ_SUBCH_Q */ case 0xde: /* despised: SCSI_CMD_NEC_READ_TOC */ LOG(0x1c,"cmdop"); LOG(0x80+tgt->cur_cmd,0); virt = (vm_offset_t)tgt->cmd_ptr; len = tgt->transient_state.in_count; rccb->ccb.ccb_in = 1; rccb->ccb.ccb_out = 0; break; case SCSI_CMD_MODE_SELECT: case SCSI_CMD_REASSIGN_BLOCKS: case SCSI_CMD_FORMAT_UNIT: case 0xc9: /* vendor-spec: SCSI_CMD_DEC_PLAYBACK_CONTROL */ { register int cs = sizeof_scsi_command(tgt->cur_cmd); tgt->transient_state.cmd_count = cs; len = tgt->transient_state.out_count = cmd_count - cs; virt = (vm_offset_t)tgt->cmd_ptr + cs; rccb->ccb.ccb_in = 0; rccb->ccb.ccb_out = 1; LOG(0x1c,"cmdop"); LOG(0x80+tgt->cur_cmd,0); } break; default: LOG(0x1c,"cmdop"); LOG(0x80+tgt->cur_cmd,0); virt = 0; len = 0; rccb->ccb.ccb_in = 0; rccb->ccb.ccb_out = 0; } #if CBUS at386_io_lock(MP_DEV_WAIT); #endif aha_prepare_rccb(tgt, rccb, virt, len); rccb->ccb.ccb_lun = tgt->lun; rccb->ccb.ccb_scsi_id = tgt->target_id; /* AHA_LENGTH_SET(rccb->ccb.ccb_linkptr, 0); unused */ /* rccb->ccb.ccb_linkid = 0; unused */ #if !CBUS s = splbio(); #endif LOG(3,"enqueue"); aha_start_scsi(aha, &rccb->ccb); #if CBUS at386_io_unlock(); #else splx(s); #endif } aha_prepare_rccb(tgt, rccb, virt, len) target_info_t *tgt; struct aha_ccb_raw *rccb; vm_offset_t virt; vm_size_t len; { vm_offset_t phys; #ifdef CBUS int cbus_window; #endif /* CBUS */ rccb->ccb.ccb_cmd_len = tgt->transient_state.cmd_count; /* this opcode is refused, grrrr. */ /* rccb->ccb.ccb_code = AHA_CCB_I_CMD_R; /* default common case */ rccb->ccb.ccb_code = AHA_CCB_I_CMD; /* default common case */ AHA_LENGTH_SET(rccb->ccb.ccb_datalen, len);/* default common case */ #ifdef CBUS if (tgt->aha_cbus_window == 0) tgt->aha_cbus_window = cbus_alloc_win(AHA_MAX_SEGLIST+1); cbus_window = tgt->aha_cbus_window; #endif /* CBUS */ if (virt == 0) { /* no xfers */ AHA_ADDRESS_SET(rccb->ccb.ccb_dataptr, 0); } else if (len <= MACHINE_PGBYTES) { /* INCORRECT: what if across two pages :INCORRECT */ /* simple xfer */ #ifdef CBUS phys = cbus_kvtoAT_ww(virt, cbus_window); #else /* CBUS */ phys = kvtophys(virt); #endif /* CBUS */ AHA_ADDRESS_SET(rccb->ccb.ccb_dataptr, phys); } else { /* messy xfer */ aha_seglist_t *seglist; vm_offset_t ph1, off; vm_size_t l1; /* this opcode does not work, grrrrr */ /* rccb->ccb.ccb_code = AHA_CCB_I_CMD_SG_R;*/ rccb->ccb.ccb_code = AHA_CCB_I_CMD_SG; if (tgt->dma_ptr == 0) aha_alloc_segment_list(tgt); seglist = (aha_seglist_t *) tgt->dma_ptr; #ifdef CBUS phys = cbus_kvtoAT_ww(seglist, cbus_window); cbus_window++; #else /* CBUS */ phys = kvtophys((vm_offset_t) seglist); #endif /* CBUS */ AHA_ADDRESS_SET(rccb->ccb.ccb_dataptr, phys); ph1 = /*i386_trunc_page*/ virt & ~(MACHINE_PGBYTES - 1); off = virt & (MACHINE_PGBYTES - 1); #ifdef CBUS ph1 = cbus_kvtoAT_ww(ph1, cbus_window) + off; cbus_window++; #else /* CBUS */ ph1 = kvtophys(ph1) + off; #endif /* CBUS */ l1 = MACHINE_PGBYTES - off; off = 1;/* now #pages */ while (1) { AHA_ADDRESS_SET(seglist->ptr, ph1); AHA_LENGTH_SET(seglist->len, l1); seglist++; if ((len -= l1) <= 0) break; virt += l1; off++; #ifdef CBUS ph1 = cbus_kvtoAT_ww(virt, cbus_window); cbus_window++; #else /* CBUS */ ph1 = kvtophys(virt); #endif /* CBUS */ l1 = (len > MACHINE_PGBYTES) ? MACHINE_PGBYTES : len; } l1 = off * sizeof(*seglist); AHA_LENGTH_SET(rccb->ccb.ccb_datalen, l1); } } aha_start_scsi(aha, ccb) aha_softc_t aha; aha_ccb_t *ccb; { register aha_mbox_t *mb; register idx; vm_offset_t phys; aha_mbox_t mbo; spl_t s; LOG(4,"start"); LOG(0x80+ccb->ccb_scsi_id,0); /* * Get an MBO, spin if necessary (takes little time) */ s = splbio(); phys = kvtoAT((vm_offset_t)ccb); /* might cross pages, but should be ok (kernel is contig) */ AHA_MB_SET_PTR(&mbo,phys); mbo.mb.mb_cmd = AHA_MBO_START; simple_lock(&aha->aha_lock); if (aha->wd.nactive++ == 0) aha->wd.watchdog_state = SCSI_WD_ACTIVE; idx = aha->mb.oidx; aha->mb.oidx = next_mbx_idx(idx); mb = &aha->mb.omb[idx]; while (mb->mb.mb_status != AHA_MBO_FREE) delay(1); mb->bits = mbo.bits; simple_unlock(&aha->aha_lock); /* * Start the board going */ aha_command(aha->port, AHA_CMD_START, 0, 0, 0, 0, FALSE); splx(s); } /* * Interrupt routine * Take interrupts from the board * * Implementation: * TBD */ aha_intr(unit) { register aha_softc_t aha; register port; register csr, intr; #if MAPPABLE extern boolean_t rz_use_mapped_interface; if (rz_use_mapped_interface) return AHA_intr(unit); #endif /*MAPPABLE*/ aha = aha_softc[unit]; port = aha->port; LOG(5,"\n\tintr"); gotintr: /* collect ephemeral information */ csr = inb(AHA_STATUS_PORT(port)); intr = inb(AHA_INTR_PORT(port)); /* * Check for errors */ if (csr & (AHA_CSR_DIAG_FAIL|AHA_CSR_CMD_ERR)) { /* XXX */ gimmeabreak(); } /* drop spurious interrupts */ if ((intr & AHA_INTR_PENDING) == 0) { LOG(2,"SPURIOUS"); return; } outb(AHA_CONTROL_PORT(port), AHA_CTL_INTR_CLR); TR(csr);TR(intr);TRCHECK if (intr & AHA_INTR_RST) return aha_bus_reset(aha); /* we got an interrupt allright */ if (aha->wd.nactive) aha->wd.watchdog_state = SCSI_WD_ACTIVE; if (intr == AHA_INTR_DONE) { /* csr & AHA_CSR_CMD_ERR --> with error */ LOG(6,"done"); return; } /* if (intr & AHA_INTR_MBO_AVAIL) will not happen */ /* Some real work today ? */ if (intr & AHA_INTR_MBI_FULL) { register int idx; register aha_mbox_t *mb; int nscan = 0; aha_mbox_t mbi; rescan: simple_lock(&aha->aha_lock); idx = aha->mb.iidx; aha->mb.iidx = next_mbx_idx(idx); mb = &aha->mb.imb[idx]; mbi.bits = mb->bits; mb->mb.mb_status = AHA_MBI_FREE; simple_unlock(&aha->aha_lock); nscan++; switch (mbi.mb.mb_status) { case AHA_MBI_FREE: if (nscan >= AHA_NMBOXES) return; goto rescan; break; case AHA_MBI_SUCCESS: case AHA_MBI_ERROR: aha_initiator_intr(aha, mbi); break; case AHA_MBI_NEED_CCB: aha_target_intr(aha, mbi); break; /* case AHA_MBI_ABORTED: /* this we wont see */ /* case AHA_MBI_NOT_FOUND: /* this we wont see */ default: log( LOG_KERN, "aha%d: Bogus status (x%x) in MBI\n", unit, mbi.mb.mb_status); break; } /* peek ahead */ if (aha->mb.imb[aha->mb.iidx].mb.mb_status != AHA_MBI_FREE) goto rescan; } /* See if more work ready */ if (inb(AHA_INTR_PORT(port)) & AHA_INTR_PENDING) { LOG(7,"\n\tre-intr"); goto gotintr; } } /* * The interrupt routine turns to one of these two * functions, depending on the incoming mbi's role */ aha_target_intr(aha, mbi) aha_softc_t aha; aha_mbox_t mbi; { target_info_t *initiator; /* this is the caller */ target_info_t *self; /* this is us */ int len; if (mbi.mbt.mb_cmd != AHA_MBI_NEED_CCB) gimmeabreak(); /* If we got here this is not zero .. */ self = aha->sc->target[aha->sc->initiator_id]; initiator = aha->sc->target[mbi.mbt.mb_initiator_id]; /* ..but initiators are not required to answer to our inquiry */ if (initiator == 0) { /* allocate */ initiator = aha_tgt_alloc(aha, mbi.mbt.mb_initiator_id, sizeof(scsi_sense_data_t) + 5, 0); /* We do not know here wether the host was down when we inquired, or it refused the connection. Leave the decision on how we will talk to it to higher level code */ LOG(0xC, "new_initiator"); sccpu_new_initiator(self, initiator); } /* The right thing to do would be build an ior and call the self->dev_ops->strategy routine, but we cannot allocate it at interrupt level. Also note that we are now disconnected from the initiator, no way to do anything else with it but reconnect and do what it wants us to do */ /* obviously, this needs both spl and MP protection */ self->dev_info.cpu.req_pending = TRUE; self->dev_info.cpu.req_id = mbi.mbt.mb_initiator_id; self->dev_info.cpu.req_lun = mbi.mbt.mb_lun; self->dev_info.cpu.req_cmd = mbi.mbt.mb_isa_send ? SCSI_CMD_SEND: SCSI_CMD_RECEIVE; len = (mbi.mbt.mb_data_len_msb << 16) | (mbi.mbt.mb_data_len_mid << 8 ); len += 0x100;/* truncation problem */ self->dev_info.cpu.req_len = len; LOG(0xB,"tgt-mode-restart"); (*self->dev_ops->restart)( self, FALSE); /* The call above has either prepared the data, placing an ior on self, or it handled it some other way */ if (self->ior == 0) return; /* I guess we'll do it later */ { struct aha_ccb_raw *rccb; rccb = &(aha->aha_ccbs[initiator->target_id]); rccb->active_target = initiator; if (self->dev_info.cpu.req_cmd == SCSI_CMD_SEND) { rccb->ccb.ccb_in = 1; rccb->ccb.ccb_out = 0; } else { rccb->ccb.ccb_in = 0; rccb->ccb.ccb_out = 1; } aha_prepare_rccb(initiator, rccb, (vm_offset_t)self->ior->io_data, self->ior->io_count); rccb->ccb.ccb_code = AHA_CCB_T_CMD; rccb->ccb.ccb_lun = initiator->lun; rccb->ccb.ccb_scsi_id = initiator->target_id; simple_lock(&aha->aha_lock); if (aha->wd.nactive++ == 0) aha->wd.watchdog_state = SCSI_WD_ACTIVE; simple_unlock(&aha->aha_lock); aha_start_scsi(aha, &rccb->ccb); } } aha_initiator_intr(aha, mbi) aha_softc_t aha; aha_mbox_t mbi; { struct aha_ccb_raw *rccb; scsi2_status_byte_t status; target_info_t *tgt; rccb = mb_to_rccb(aha,mbi); tgt = rccb->active_target; rccb->active_target = 0; /* shortcut (sic!) */ if (mbi.mb.mb_status == AHA_MBI_SUCCESS) goto allok; switch (rccb->ccb.ccb_hstatus) { case AHA_HST_SUCCESS: allok: status = rccb->ccb.ccb_status; if (status.st.scsi_status_code != SCSI_ST_GOOD) { scsi_error(tgt, SCSI_ERR_STATUS, status.bits, 0); tgt->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ? SCSI_RET_RETRY : SCSI_RET_NEED_SENSE; } else tgt->done = SCSI_RET_SUCCESS; break; case AHA_HST_SEL_TIMEO: if (tgt->flags & TGT_FULLY_PROBED) tgt->flags = 0; /* went offline */ tgt->done = SCSI_RET_DEVICE_DOWN; break; case AHA_HST_DATA_OVRUN: /* BUT we don't know if this is an underrun. It is ok if we get less data than we asked for, in a number of cases. Most boards do not seem to generate this anyways, but some do. */ { register int cmd = tgt->cur_cmd; switch (cmd) { case SCSI_CMD_INQUIRY: case SCSI_CMD_REQUEST_SENSE: break; default: printf("%sx%x\n", "aha: U/OVRUN on scsi command x%x\n", cmd); gimmeabreak(); } } goto allok; case AHA_HST_BAD_DISCONN: printf("aha: bad disconnect\n"); tgt->done = SCSI_RET_ABORTED; break; case AHA_HST_BAD_PHASE_SEQ: /* we'll get an interrupt soon */ printf("aha: bad PHASE sequencing\n"); tgt->done = SCSI_RET_ABORTED; break; case AHA_HST_BAD_OPCODE: /* fall through */ case AHA_HST_BAD_PARAM: printf("aha: BADCCB\n");gimmeabreak(); tgt->done = SCSI_RET_RETRY; break; case AHA_HST_BAD_LINK_LUN: /* these should not happen */ case AHA_HST_INVALID_TDIR: case AHA_HST_DUPLICATED_CCB: printf("aha: bad hstatus (x%x)\n", rccb->ccb.ccb_hstatus); tgt->done = SCSI_RET_ABORTED; break; } LOG(8,"end"); simple_lock(&aha->aha_lock); if (aha->wd.nactive-- == 1) aha->wd.watchdog_state = SCSI_WD_INACTIVE; simple_unlock(&aha->aha_lock); if (tgt->ior) { LOG(0xA,"ops->restart"); (*tgt->dev_ops->restart)( tgt, TRUE); } return FALSE; } /* * The bus was reset */ aha_bus_reset(aha) register aha_softc_t aha; { register port = aha->port; LOG(0x1d,"bus_reset"); /* * Clear bus descriptor */ aha->wd.nactive = 0; aha_reset(port, TRUE); aha_init(aha); printf("aha: (%d) bus reset ", ++aha->wd.reset_count); delay(scsi_delay_after_reset); /* some targets take long to reset */ if (aha->sc == 0) /* sanity */ return; scsi_bus_was_reset(aha->sc); } /* * Watchdog * * We know that some (name withdrawn) disks get * stuck in the middle of dma phases... */ aha_reset_scsibus(aha) register aha_softc_t aha; { register target_info_t *tgt; register port = aha->port; register int i; for (i = 0; i < AHA_NCCB; i++) { tgt = aha->aha_ccbs[i].active_target; if (/*scsi_debug &&*/ tgt) printf("Target %d was active, cmd x%x in x%x out x%x\n", tgt->target_id, tgt->cur_cmd, tgt->transient_state.in_count, tgt->transient_state.out_count); } aha_reset(port, FALSE); delay(35); /* no interrupt will come */ aha_bus_reset(aha); } /* * Utilities */ /* * Send a command to the board along with some * optional parameters, optionally receive the * results at command completion, returns how * many bytes we did NOT get back. */ aha_command(port, cmd, outp, outc, inp, inc, clear_interrupt) unsigned char *outp, *inp; { register unsigned char st; boolean_t failed = TRUE; do { st = inb(AHA_STATUS_PORT(port)); } while (st & AHA_CSR_DATAO_FULL); /* Output command and any data */ outb(AHA_COMMAND_PORT(port), cmd); while (outc--) { do { st = inb(AHA_STATUS_PORT(port)); if (st & AHA_CSR_CMD_ERR) goto out; } while (st & AHA_CSR_DATAO_FULL); outb(AHA_COMMAND_PORT(port), *outp++); } /* get any data */ while (inc--) { do { st = inb(AHA_STATUS_PORT(port)); if (st & AHA_CSR_CMD_ERR) goto out; } while ((st & AHA_CSR_DATAI_FULL) == 0); *inp++ = inb(AHA_DATA_PORT(port)); } ++inc; failed = FALSE; /* wait command complete */ if (clear_interrupt) do { delay(1); st = inb(AHA_INTR_PORT(port)); } while ((st & AHA_INTR_DONE) == 0); out: if (clear_interrupt) outb(AHA_CONTROL_PORT(port), AHA_CTL_INTR_CLR); if (failed) printf("aha_command: error on (%x %x %x %x %x %x), status %x\n", port, cmd, outp, outc, inp, inc, st); return inc; } #include /* * Allocate dynamically segment lists to * targets (for scatter/gather) * Its a max of 17*6=102 bytes per target. */ vm_offset_t aha_seglist_next, aha_seglist_end; aha_alloc_segment_list(tgt) target_info_t *tgt; { #define ALLOC_SIZE (AHA_MAX_SEGLIST * sizeof(aha_seglist_t)) /* XXX locking */ if ((aha_seglist_next + ALLOC_SIZE) > aha_seglist_end) { (void) kmem_alloc_wired(kernel_map, &aha_seglist_next, PAGE_SIZE); aha_seglist_end = aha_seglist_next + PAGE_SIZE; } tgt->dma_ptr = (char *)aha_seglist_next; aha_seglist_next += ALLOC_SIZE; /* XXX locking */ } #endif /* NAHA > 0 */