summaryrefslogtreecommitdiff
path: root/scsi/scsi.c
diff options
context:
space:
mode:
authorThomas Bushnell <thomas@gnu.org>1997-02-25 21:28:37 +0000
committerThomas Bushnell <thomas@gnu.org>1997-02-25 21:28:37 +0000
commitf07a4c844da9f0ecae5bbee1ab94be56505f26f7 (patch)
tree12b07c7e578fc1a5f53dbfde2632408491ff2a70 /scsi/scsi.c
Initial source
Diffstat (limited to 'scsi/scsi.c')
-rw-r--r--scsi/scsi.c642
1 files changed, 642 insertions, 0 deletions
diff --git a/scsi/scsi.c b/scsi/scsi.c
new file mode 100644
index 0000000..d4aecf6
--- /dev/null
+++ b/scsi/scsi.c
@@ -0,0 +1,642 @@
+/*
+ * Mach Operating System
+ * Copyright (c) 1993-1989 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.c
+ * Author: Alessandro Forin, Carnegie Mellon University
+ * Date: 9/90
+ *
+ * Middle layer of the SCSI driver: chip independent functions
+ * This file contains Controller and Device-independent functions
+ */
+
+#include <scsi.h>
+
+#if NSCSI > 0
+#include <platforms.h>
+
+#include <machine/machspl.h> /* spl definitions */
+
+#include <mach/std_types.h>
+#include <sys/types.h>
+#include <scsi/compat_30.h>
+
+#include <chips/busses.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi2.h>
+#include <scsi/scsi_defs.h>
+#include <machine/machspl.h>
+
+
+
+#ifdef VAXSTATION
+/* We run some of this code on the interrupt stack */
+#undef spl0
+#define spl0() spl1()
+#endif /*VAXSTATION*/
+
+/*
+ * Overall driver state
+ */
+
+target_info_t scsi_target_data[NSCSI*8]; /* per target state */
+scsi_softc_t scsi_softc_data[NSCSI]; /* per HBA state */
+scsi_softc_t *scsi_softc[NSCSI]; /* quick access&checking */
+
+/*
+ * If a specific target should NOT be asked to go synchronous
+ * then its bit in this bitmap should be set. Each SCSI controller
+ * (Host Bus Adapter) can hold at most 8 targets --> use one
+ * byte per controller. A bit set to one means NO synchronous.
+ * Patch with adb if necessary.
+ */
+unsigned char scsi_no_synchronous_xfer[NSCSI];
+
+/*
+ * For certain targets it is wise to use the long form of the
+ * read/write commands even if their capacity would not necessitate
+ * it. Same as above for usage.
+ */
+unsigned char scsi_use_long_form[NSCSI];
+
+
+/*
+ * Control over disconnect-reconnect mode.
+ */
+unsigned char scsi_might_disconnect[NSCSI] = /* do it if deemed appropriate */
+ { 0xff, 0xff, 0xff, 0xff, 0xff};/* Fix by hand viz NSCSI */
+unsigned char scsi_should_disconnect[NSCSI] = /* just do it */
+ { 0,};
+unsigned char scsi_initiator_id[NSCSI] = /* our id on the bus(ses) */
+ { 7, 7, 7, 7, 7};
+
+/*
+ * Miscellaneus config
+ */
+boolean_t scsi_exabyte_filemarks = FALSE; /* use short filemarks */
+int scsi_watchdog_period = 10; /* but exabyte needs >=30 for bspace */
+int scsi_delay_after_reset = 1000000;/* microseconds */
+boolean_t scsi_no_automatic_bbr = FALSE; /* revector bad blocks automatically */
+
+#ifdef MACH_KERNEL
+#else
+/* This covers Exabyte's max record size */
+unsigned int scsi_per_target_virtual = 256*1024;
+#endif MACH_KERNEL
+
+
+/*
+ * Device-specific operations are switched off this table
+ */
+
+extern char
+ *scdisk_name(), *sctape_name(), *scprt_name(),
+ *sccpu_name(), *scworm_name(), *sccdrom_name(),
+ *scscn_name(), *scmem_name(), *scjb_name(), *sccomm_name();
+extern void
+ sctape_optimize();
+extern scsi_ret_t
+ scdisk_open(), sctape_open(), sctape_close(),
+ sccomm_open(), sccomm_close();
+extern int
+ scdisk_strategy(), sctape_strategy(), sccpu_strategy(),
+ sccomm_strategy();
+extern void
+ scdisk_start(), sctape_start(), sccpu_start(), sccomm_start();
+
+extern io_return_t
+ scdisk_set_status(), scdisk_get_status(),
+ sctape_set_status(), sctape_get_status(),
+ sccomm_set_status(), sccomm_get_status();
+
+scsi_devsw_t scsi_devsw[] = {
+
+/* SCSI_DISK */ { scdisk_name, SCSI_OPTIMIZE_NULL,
+ scdisk_open, SCSI_CLOSE_NULL,
+ scdisk_strategy, scdisk_start,
+ scdisk_get_status, scdisk_set_status },
+
+/* SCSI_TAPE */ { sctape_name, sctape_optimize,
+ sctape_open, sctape_close,
+ sctape_strategy, sctape_start,
+ sctape_get_status, sctape_set_status },
+
+/* SCSI_PRINTER */ { scprt_name, SCSI_OPTIMIZE_NULL, /*XXX*/},
+
+/* SCSI_CPU */ { sccpu_name, SCSI_OPTIMIZE_NULL,
+ SCSI_OPEN_NULL, SCSI_CLOSE_NULL,
+ sccpu_strategy, sccpu_start,},
+
+/* SCSI_WORM */ { scworm_name, SCSI_OPTIMIZE_NULL,
+ scdisk_open, SCSI_CLOSE_NULL,
+ scdisk_strategy, scdisk_start,
+ scdisk_get_status, scdisk_set_status },
+
+/* SCSI_CDROM */ { sccdrom_name, SCSI_OPTIMIZE_NULL,
+ scdisk_open, SCSI_CLOSE_NULL,
+ scdisk_strategy, scdisk_start,
+ scdisk_get_status, scdisk_set_status },
+/* scsi2 */
+/* SCSI_SCANNER */ { scscn_name, SCSI_OPTIMIZE_NULL, /*XXX*/ },
+
+/* SCSI_MEMORY */ { scmem_name, SCSI_OPTIMIZE_NULL,
+ scdisk_open, SCSI_CLOSE_NULL,
+ scdisk_strategy, scdisk_start,
+ scdisk_get_status, scdisk_set_status },
+
+/* SCSI_J_BOX */ { scjb_name, SCSI_OPTIMIZE_NULL, /*XXX*/ },
+
+/* SCSI_COMM */ { sccomm_name, SCSI_OPTIMIZE_NULL,
+#if (NCENDATA>0)
+ sccomm_open, sccomm_close,
+ sccomm_strategy, sccomm_start,
+ sccomm_get_status, sccomm_set_status
+#endif
+ },
+ 0
+};
+
+/*
+ * Allocation routines for state structures
+ */
+scsi_softc_t *
+scsi_master_alloc(unit, hw)
+ unsigned unit;
+ char *hw;
+{
+ scsi_softc_t *sc;
+
+ if (unit < NSCSI) {
+ sc = &scsi_softc_data[unit];
+ scsi_softc[unit] = sc;
+ sc->masterno = unit;
+ sc->hw_state = hw;
+ return sc;
+ }
+ return 0;
+}
+
+target_info_t *
+scsi_slave_alloc(unit, slave, hw)
+ unsigned unit, slave;
+ char *hw;
+{
+ target_info_t *tgt;
+
+ tgt = &scsi_target_data[(unit<<3) + slave];
+ tgt->hw_state = hw;
+ tgt->dev_ops = 0; /* later */
+ tgt->target_id = slave;
+ tgt->masterno = unit;
+ tgt->block_size = 1; /* default */
+ tgt->flags = TGT_ALIVE;
+ tgt->sync_period = 0;
+ tgt->sync_offset = 0;
+ simple_lock_init(&tgt->target_lock);
+
+ scsi_softc[unit]->target[slave] = tgt;
+ return tgt;
+}
+
+void
+zero_ior(
+ io_req_t ior )
+{
+ ior->io_next = ior->io_prev = 0;
+ ior->io_count = 0;
+ ior->io_op = IO_INTERNAL;
+ ior->io_error = 0;
+}
+
+/*
+ * Slave routine:
+ * See if the slave description (controller, unit, ..)
+ * matches one of the slaves found during probe
+ *
+ * Implementation:
+ * Send out an INQUIRY command to see what sort of device
+ * the slave is.
+ * Notes:
+ * At this time the driver is fully functional and works
+ * off interrupts.
+ * TODO:
+ * The SCSI2 spec says what exactly must happen: see F.2.3
+ */
+int scsi_slave( ui, reg)
+ struct bus_device *ui;
+ unsigned reg;
+{
+ scsi_softc_t *sc = scsi_softc[(unsigned char)ui->ctlr];
+ target_info_t *tgt = sc->target[(unsigned char)ui->slave];
+ scsi2_inquiry_data_t *inq;
+ int scsi_std;
+ int ptype, s;
+
+ if (!tgt || !(tgt->flags & TGT_ALIVE))
+ return 0;
+
+ /* Might have scanned already */
+ if (tgt->dev_ops)
+ goto out;
+
+#ifdef SCSI2
+ This is what should happen:
+ - for all LUNs
+ INQUIRY
+ scsi_verify_state (see)
+ scsi_initialize (see)
+#endif SCSI2
+
+ tgt->unit_no = ui->slave; /* incorrect, but needed early */
+
+ s = spl0(); /* we need interrupts */
+
+ if (BGET(scsi_no_synchronous_xfer,(unsigned char)sc->masterno,tgt->target_id))
+ tgt->flags |= TGT_DID_SYNCH;
+
+ /*
+ * Ok, it is time to see what type of device this is,
+ * send an INQUIRY cmd and wait till done.
+ * Possibly do the synch negotiation here.
+ */
+ scsi_inquiry(tgt, SCSI_INQ_STD_DATA);
+
+ inq = (scsi2_inquiry_data_t*)tgt->cmd_ptr;
+ ptype = inq->periph_type;
+
+ switch (ptype) {
+ case SCSI_CDROM :
+ tgt->flags |= TGT_READONLY;
+ /* fall through */
+ case SCSI_DISK :
+ case SCSI_TAPE :
+ case SCSI_PRINTER :
+ case SCSI_CPU :
+ case SCSI_WORM :
+ case SCSI_SCANNER :
+ case SCSI_MEMORY :
+ case SCSI_J_BOX :
+ case SCSI_COMM :
+/* case SCSI_PREPRESS1 : reserved, really
+ case SCSI_PREPRESS2 : */
+ tgt->dev_ops = &scsi_devsw[ptype];
+ break;
+ default:
+ printf("scsi%d: %s %d (x%x). ", ui->ctlr,
+ "Unsupported device type at SCSI id", ui->slave,
+ inq->periph_type);
+ scsi_print_inquiry((scsi2_inquiry_data_t*)inq,
+ SCSI_INQ_STD_DATA, 0);
+ tgt->flags = 0;
+ splx(s);
+ return 0;
+ }
+
+ if (inq->rmb)
+ tgt->flags |= TGT_REMOVABLE_MEDIA;
+
+ /*
+ * Tell the user we know this target, then see if we
+ * can be a bit smart about it.
+ */
+ scsi_print_inquiry((scsi2_inquiry_data_t*)inq,
+ SCSI_INQ_STD_DATA, tgt->tgt_name);
+ if (scsi_debug)
+ scsi_print_inquiry((scsi2_inquiry_data_t*)inq,
+ SCSI_INQ_STD_DATA, 0);
+
+ /*
+ * The above says if it currently behaves as a scsi2,
+ * however scsi1 might just be the default setting.
+ * The spec say that even if in scsi1 mode the target
+ * should answer to the full scsi2 inquiry spec.
+ */
+ scsi_std = (inq->ansi == 2 || inq->response_fmt == 2) ? 2 : 1;
+#if nosey
+ if (scsi_std == 2) {
+ unsigned char supp_pages[256], i;
+ scsi2_impl_opdef_page_t *impl;
+
+ scsi_inquiry(tgt, SCSI_INQ_SUPP_PAGES);
+ impl = (scsi2_impl_opdef_page_t *)inq;
+ npages = impl->page_len - 2;
+ bcopy(impl->supp_opdef, supp_pages, npages);
+
+ for (i = 0; i < npages; i++) {
+ scsi_inquiry(tgt, supp_pages[i]);
+ scsi_print_inquiry(inq, supp_pages[i], 0);
+ }
+ }
+
+ if (scsi_std == 2) {
+ scsi2_impl_opdef_page_t *impl;
+ int i;
+
+ scsi_inquiry(tgt, SCSI_INQ_IMPL_OPDEF);
+ impl = (scsi2_impl_opdef_page_t *)inq;
+ for (i = 0; i < impl->page_len - 2; i++)
+ if (impl->supp_opdef[i] == SCSI2_OPDEF) {
+ scsi_change_definition(tgt, SCSI2_OPDEF);
+ /* if success .. */
+ tgt->flags |= TGT_SCSI_2_MODE;
+ break;
+ }
+ }
+#endif nosey
+
+ splx(s);
+out:
+ return (strcmp(ui->name, (*tgt->dev_ops->driver_name)(TRUE)) == 0);
+}
+
+#ifdef SCSI2
+scsi_verify_state(...)
+{
+verify_state: send test_unit_ready up to 3 times, each time it fails
+(with check condition) send a requeste_sense. It is ok to get UNIT ATTENTION
+the first time only, NOT READY the second, only GOOD the last time.
+If you get BUSY or RESERVATION CONFLICT retry.
+}
+
+scsi_initialize(...)
+{
+
+initialize: send start_unit with immed=0 (->disconnect), if fails
+with check condition send requeste_sense and if "illegal request"
+proceed anyways. Retry on BUSY.
+Do a verify_state, then
+disks:
+ - mode_sense (current) if ANSI2 or needed by vendor (!!!!)
+ and if check-condition&illegal-request goto capacity
+ - mode_sense (changeable)
+ - if needed do a mode_select (yes, 512)
+ - read_capacity
+tapes:
+
+}
+#endif SCSI2
+
+/*
+ * Attach routine:
+ * Fill in all the relevant per-slave data and make
+ * the slave operational.
+ *
+ * Implementation:
+ * Get target's status, start the unit and then
+ * switch off to device-specific functions to gather
+ * as much info as possible about the slave.
+ */
+void scsi_attach(ui)
+ register struct bus_device *ui;
+{
+ scsi_softc_t *sc = scsi_softc[ui->mi->unit];
+ target_info_t *tgt = sc->target[(unsigned char)ui->slave];
+ int i;
+ spl_t s;
+
+ printf(" (%s %s) ", (*tgt->dev_ops->driver_name)(FALSE),tgt->tgt_name);
+
+ if (tgt->flags & TGT_US) {
+ printf(" [this cpu]");
+ return;
+ }
+
+ s = spl0();
+
+ /* sense return from inquiry */
+ scsi_request_sense(tgt, 0, 0);
+
+ /*
+ * Do this twice, certain targets need it
+ */
+ if (tgt->dev_ops != &scsi_devsw[SCSI_CPU]) {
+ (void) scsi_start_unit(tgt, SCSI_CMD_SS_START, 0);
+ i = 0;
+ while (scsi_start_unit(tgt, SCSI_CMD_SS_START, 0) == SCSI_RET_RETRY) {
+ if (i++ == 5)
+ printf(".. not yet online ..");
+ delay(1000000);
+ if (i == 60) {
+ printf(" seems hopeless.");
+ break;
+ }
+ }
+ }
+
+ /*
+ * See if it is up and about
+ */
+ scsi_test_unit_ready(tgt, 0);
+
+ if (tgt->dev_ops->optimize != SCSI_OPTIMIZE_NULL)
+ (*tgt->dev_ops->optimize)(tgt);
+
+ tgt->flags |= TGT_FULLY_PROBED;
+
+ splx(s);
+}
+
+/*
+ * Probe routine:
+ * See if a device answers. Used AFTER autoconf.
+ *
+ * Implementation:
+ * First ask the HBA to see if anyone is there at all, then
+ * call the scsi_slave and scsi_attach routines with a fake ui.
+ */
+boolean_t
+scsi_probe( sc, tgt_ptr, target_id, ior)
+ scsi_softc_t *sc;
+ target_info_t **tgt_ptr;
+ int target_id;
+ io_req_t ior;
+{
+ struct bus_device ui;
+ target_info_t *tgt;
+
+ if (!sc->probe || target_id > 7 || target_id == sc->initiator_id)
+ return FALSE; /* sanity */
+
+ if (sc->target[target_id] == 0)
+ scsi_slave_alloc( sc->masterno, target_id, sc->hw_state);
+ tgt = sc->target[target_id];
+ tgt->flags = 0;/* we donno yet */
+ tgt->dev_ops = 0;
+
+ /* mildly enquire */
+ if (!(sc->probe)(tgt, ior))
+ goto fail;
+
+ /* There is something there, see what it is */
+ bzero(&ui, sizeof(ui));
+ ui.ctlr = sc->masterno;
+ ui.unit =
+ ui.slave = target_id;
+ ui.name = "";
+
+ /* this fails on the name for sure */
+ (void) scsi_slave( &ui, 0 /* brrrr */);
+ if ((tgt->flags & TGT_ALIVE) == 0)
+ goto fail;
+
+ {
+ struct bus_ctlr mi;
+
+ mi.unit = sc->masterno;
+ ui.mi = &mi;
+ printf("%s at slave %d ",
+ (*tgt->dev_ops->driver_name)(TRUE), target_id);
+ scsi_attach(&ui);
+ }
+
+ *tgt_ptr = tgt;
+ return TRUE;
+fail:
+ tgt->flags = 0;
+ return FALSE;
+}
+
+
+/*
+ * Watchdog routine:
+ * Issue a SCSI bus reset if a target holds up the
+ * bus for too long.
+ *
+ * Implementation:
+ * Each HBA that wants to use this should have a
+ * watchdog_t structure at the head of its hardware
+ * descriptor. This variable is set by this periodic
+ * routine and reset on bus activity. If it is not reset on
+ * time (say some ten seconds or so) we reset the
+ * SCSI bus.
+ * NOTE:
+ * An HBA must be ready to accept bus reset interrupts
+ * properly in order to use this.
+ */
+void scsi_watchdog(hw)
+ watchdog_t *hw;
+{
+ spl_t s = splbio();
+
+ switch (hw->watchdog_state) {
+ case SCSI_WD_EXPIRED:
+
+ /* double check first */
+ if (hw->nactive == 0) {
+ hw->watchdog_state = SCSI_WD_INACTIVE;
+ break;
+ }
+ if (scsi_debug)
+ printf("SCSI Watchdog expired\n");
+ hw->watchdog_state = SCSI_WD_INACTIVE;
+ (*hw->reset)(hw);
+ break;
+
+ case SCSI_WD_ACTIVE:
+
+ hw->watchdog_state = SCSI_WD_EXPIRED;
+ break;
+
+ case SCSI_WD_INACTIVE:
+
+ break;
+ }
+
+ /* do this here, fends against powered down devices */
+ if (scsi_watchdog_period != 0)
+ timeout((int(*)())scsi_watchdog, (char*)hw, scsi_watchdog_period * hz);
+
+ splx(s);
+}
+
+
+/*
+ * BusReset Notification:
+ * Called when the HBA sees a BusReset interrupt
+ *
+ * Implementation:
+ * Go through the list of targets, redo the synch
+ * negotiation, and restart whatever operation was
+ * in progress for that target.
+ */
+void scsi_bus_was_reset(sc)
+ scsi_softc_t *sc;
+{
+ register target_info_t *tgt;
+ int i;
+ /*
+ * Redo the synch negotiation
+ */
+ for (i = 0; i < 8; i++) {
+ io_req_t ior;
+ spl_t s;
+
+ if (i == sc->initiator_id)
+ continue;
+ tgt = sc->target[i];
+ if (!tgt || !(tgt->flags & TGT_ALIVE))
+ continue;
+
+ tgt->flags &= ~(TGT_DID_SYNCH|TGT_DISCONNECTED);
+#if 0
+ /* the standard does *not* imply this gets reset too */
+ tgt->sync_period = 0;
+ tgt->sync_offset = 0;
+#endif
+
+ /*
+ * retry the synch negotiation
+ */
+ ior = tgt->ior;
+ tgt->ior = 0;
+ printf(".. tgt %d ", tgt->target_id);
+ if (BGET(scsi_no_synchronous_xfer,(unsigned char)sc->masterno,tgt->target_id))
+ tgt->flags |= TGT_DID_SYNCH;
+ else {
+ s = spl0();
+ scsi_test_unit_ready(tgt, 0);
+ splx(s);
+ }
+ tgt->ior = ior;
+ }
+
+ /*
+ * Notify each target of the accident
+ */
+ for (i = 0; i < 8; i++) {
+ if (i == sc->initiator_id)
+ continue;
+ tgt = sc->target[i];
+ if (!tgt)
+ continue;
+ tgt->done = SCSI_RET_ABORTED|SCSI_RET_RETRY;
+ if (tgt->ior)
+ (*tgt->dev_ops->restart)( tgt, TRUE);
+ }
+
+ printf("%s", " reset complete\n");
+}
+
+#endif NSCSI > 0