|
|
GNU Mach 1.0
/*
* 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 [email protected]
* 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
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.