7fd9a6a23a
also don't use ANSI string concatenation.
2931 lines
88 KiB
C
2931 lines
88 KiB
C
/*-
|
|
* Copyright (c) 1999 Michael Smith
|
|
* 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
/*
|
|
* Driver for the Mylex DAC960 family of RAID controllers.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/kernel.h>
|
|
|
|
#include <sys/bus.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/devicestat.h>
|
|
#include <sys/disk.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <machine/resource.h>
|
|
#include <machine/bus_memio.h>
|
|
#include <machine/bus_pio.h>
|
|
#include <machine/bus.h>
|
|
#include <machine/clock.h>
|
|
#include <sys/rman.h>
|
|
|
|
#include <dev/mlx/mlx_compat.h>
|
|
#include <dev/mlx/mlxio.h>
|
|
#include <dev/mlx/mlxvar.h>
|
|
#include <dev/mlx/mlxreg.h>
|
|
|
|
#define MLX_CDEV_MAJOR 130
|
|
|
|
static struct cdevsw mlx_cdevsw = {
|
|
/* open */ mlx_open,
|
|
/* close */ mlx_close,
|
|
/* read */ noread,
|
|
/* write */ nowrite,
|
|
/* ioctl */ mlx_ioctl,
|
|
/* poll */ nopoll,
|
|
/* mmap */ nommap,
|
|
/* strategy */ nostrategy,
|
|
/* name */ "mlx",
|
|
/* maj */ MLX_CDEV_MAJOR,
|
|
/* dump */ nodump,
|
|
/* psize */ nopsize,
|
|
/* flags */ 0,
|
|
};
|
|
|
|
devclass_t mlx_devclass;
|
|
|
|
/*
|
|
* Per-interface accessor methods
|
|
*/
|
|
static int mlx_v3_tryqueue(struct mlx_softc *sc, struct mlx_command *mc);
|
|
static int mlx_v3_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status);
|
|
static void mlx_v3_intaction(struct mlx_softc *sc, int action);
|
|
static int mlx_v3_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2);
|
|
|
|
static int mlx_v4_tryqueue(struct mlx_softc *sc, struct mlx_command *mc);
|
|
static int mlx_v4_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status);
|
|
static void mlx_v4_intaction(struct mlx_softc *sc, int action);
|
|
static int mlx_v4_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2);
|
|
|
|
static int mlx_v5_tryqueue(struct mlx_softc *sc, struct mlx_command *mc);
|
|
static int mlx_v5_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status);
|
|
static void mlx_v5_intaction(struct mlx_softc *sc, int action);
|
|
static int mlx_v5_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2);
|
|
|
|
/*
|
|
* Status monitoring
|
|
*/
|
|
static void mlx_periodic(void *data);
|
|
static void mlx_periodic_enquiry(struct mlx_command *mc);
|
|
static void mlx_periodic_eventlog_poll(struct mlx_softc *sc);
|
|
static void mlx_periodic_eventlog_respond(struct mlx_command *mc);
|
|
static void mlx_periodic_rebuild(struct mlx_command *mc);
|
|
|
|
/*
|
|
* Channel Pause
|
|
*/
|
|
static void mlx_pause_action(struct mlx_softc *sc);
|
|
static void mlx_pause_done(struct mlx_command *mc);
|
|
|
|
/*
|
|
* Command submission.
|
|
*/
|
|
static void *mlx_enquire(struct mlx_softc *sc, int command, size_t bufsize,
|
|
void (*complete)(struct mlx_command *mc));
|
|
static int mlx_flush(struct mlx_softc *sc);
|
|
static int mlx_check(struct mlx_softc *sc, int drive);
|
|
static int mlx_rebuild(struct mlx_softc *sc, int channel, int target);
|
|
static int mlx_wait_command(struct mlx_command *mc);
|
|
static int mlx_poll_command(struct mlx_command *mc);
|
|
static void mlx_startio(struct mlx_softc *sc);
|
|
static void mlx_completeio(struct mlx_command *mc);
|
|
static int mlx_user_command(struct mlx_softc *sc, struct mlx_usercommand *mu);
|
|
|
|
/*
|
|
* Command buffer allocation.
|
|
*/
|
|
static struct mlx_command *mlx_alloccmd(struct mlx_softc *sc);
|
|
static void mlx_releasecmd(struct mlx_command *mc);
|
|
static void mlx_freecmd(struct mlx_command *mc);
|
|
|
|
/*
|
|
* Command management.
|
|
*/
|
|
static int mlx_getslot(struct mlx_command *mc);
|
|
static void mlx_mapcmd(struct mlx_command *mc);
|
|
static void mlx_unmapcmd(struct mlx_command *mc);
|
|
static int mlx_start(struct mlx_command *mc);
|
|
static int mlx_done(struct mlx_softc *sc);
|
|
static void mlx_complete(struct mlx_softc *sc);
|
|
|
|
/*
|
|
* Debugging.
|
|
*/
|
|
static char *mlx_diagnose_command(struct mlx_command *mc);
|
|
static void mlx_describe_controller(struct mlx_softc *sc);
|
|
static int mlx_fw_message(struct mlx_softc *sc, int status, int param1, int param2);
|
|
|
|
/*
|
|
* Utility functions.
|
|
*/
|
|
static struct mlx_sysdrive *mlx_findunit(struct mlx_softc *sc, int unit);
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Public Interfaces
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Free all of the resources associated with (sc)
|
|
*
|
|
* Should not be called if the controller is active.
|
|
*/
|
|
void
|
|
mlx_free(struct mlx_softc *sc)
|
|
{
|
|
struct mlx_command *mc;
|
|
|
|
debug_called(1);
|
|
|
|
/* cancel status timeout */
|
|
untimeout(mlx_periodic, sc, sc->mlx_timeout);
|
|
|
|
/* throw away any command buffers */
|
|
while ((mc = TAILQ_FIRST(&sc->mlx_freecmds)) != NULL) {
|
|
TAILQ_REMOVE(&sc->mlx_freecmds, mc, mc_link);
|
|
mlx_freecmd(mc);
|
|
}
|
|
|
|
/* destroy data-transfer DMA tag */
|
|
if (sc->mlx_buffer_dmat)
|
|
bus_dma_tag_destroy(sc->mlx_buffer_dmat);
|
|
|
|
/* free and destroy DMA memory and tag for s/g lists */
|
|
if (sc->mlx_sgtable)
|
|
bus_dmamem_free(sc->mlx_sg_dmat, sc->mlx_sgtable, sc->mlx_sg_dmamap);
|
|
if (sc->mlx_sg_dmat)
|
|
bus_dma_tag_destroy(sc->mlx_sg_dmat);
|
|
|
|
/* disconnect the interrupt handler */
|
|
if (sc->mlx_intr)
|
|
bus_teardown_intr(sc->mlx_dev, sc->mlx_irq, sc->mlx_intr);
|
|
if (sc->mlx_irq != NULL)
|
|
bus_release_resource(sc->mlx_dev, SYS_RES_IRQ, 0, sc->mlx_irq);
|
|
|
|
/* destroy the parent DMA tag */
|
|
if (sc->mlx_parent_dmat)
|
|
bus_dma_tag_destroy(sc->mlx_parent_dmat);
|
|
|
|
/* release the register window mapping */
|
|
if (sc->mlx_mem != NULL)
|
|
bus_release_resource(sc->mlx_dev, sc->mlx_mem_type, sc->mlx_mem_rid, sc->mlx_mem);
|
|
|
|
/* free controller enquiry data */
|
|
if (sc->mlx_enq2 != NULL)
|
|
free(sc->mlx_enq2, M_DEVBUF);
|
|
|
|
/* destroy control device */
|
|
if (sc->mlx_dev_t != (dev_t)NULL)
|
|
destroy_dev(sc->mlx_dev_t);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Map the scatter/gather table into bus space
|
|
*/
|
|
static void
|
|
mlx_dma_map_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error)
|
|
{
|
|
struct mlx_softc *sc = (struct mlx_softc *)arg;
|
|
|
|
debug_called(1);
|
|
|
|
/* save base of s/g table's address in bus space */
|
|
sc->mlx_sgbusaddr = segs->ds_addr;
|
|
}
|
|
|
|
static int
|
|
mlx_sglist_map(struct mlx_softc *sc)
|
|
{
|
|
size_t segsize;
|
|
int error, ncmd;
|
|
|
|
debug_called(1);
|
|
|
|
/* destroy any existing mappings */
|
|
if (sc->mlx_sgtable)
|
|
bus_dmamem_free(sc->mlx_sg_dmat, sc->mlx_sgtable, sc->mlx_sg_dmamap);
|
|
if (sc->mlx_sg_dmat)
|
|
bus_dma_tag_destroy(sc->mlx_sg_dmat);
|
|
|
|
/*
|
|
* Create a single tag describing a region large enough to hold all of
|
|
* the s/g lists we will need. If we're called early on, we don't know how
|
|
* many commands we're going to be asked to support, so only allocate enough
|
|
* for a couple.
|
|
*/
|
|
if (sc->mlx_enq2 == NULL) {
|
|
ncmd = 2;
|
|
} else {
|
|
ncmd = sc->mlx_enq2->me_max_commands;
|
|
}
|
|
segsize = sizeof(struct mlx_sgentry) * MLX_NSEG * ncmd;
|
|
error = bus_dma_tag_create(sc->mlx_parent_dmat, /* parent */
|
|
1, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filter, filterarg */
|
|
segsize, 1, /* maxsize, nsegments */
|
|
BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */
|
|
0, /* flags */
|
|
&sc->mlx_sg_dmat);
|
|
if (error != 0) {
|
|
device_printf(sc->mlx_dev, "can't allocate scatter/gather DMA tag\n");
|
|
return(ENOMEM);
|
|
}
|
|
|
|
/*
|
|
* Allocate enough s/g maps for all commands and permanently map them into
|
|
* controller-visible space.
|
|
*
|
|
* XXX this assumes we can get enough space for all the s/g maps in one
|
|
* contiguous slab. We may need to switch to a more complex arrangement where
|
|
* we allocate in smaller chunks and keep a lookup table from slot to bus address.
|
|
*/
|
|
error = bus_dmamem_alloc(sc->mlx_sg_dmat, (void **)&sc->mlx_sgtable, BUS_DMA_NOWAIT, &sc->mlx_sg_dmamap);
|
|
if (error) {
|
|
device_printf(sc->mlx_dev, "can't allocate s/g table\n");
|
|
return(ENOMEM);
|
|
}
|
|
bus_dmamap_load(sc->mlx_sg_dmat, sc->mlx_sg_dmamap, sc->mlx_sgtable, segsize, mlx_dma_map_sg, sc, 0);
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Initialise the controller and softc
|
|
*/
|
|
int
|
|
mlx_attach(struct mlx_softc *sc)
|
|
{
|
|
struct mlx_enquiry_old *meo;
|
|
int rid, error, fwminor, hscode, hserror, hsparam1, hsparam2, hsmsg;
|
|
|
|
debug_called(1);
|
|
|
|
/*
|
|
* Initialise per-controller queues.
|
|
*/
|
|
TAILQ_INIT(&sc->mlx_work);
|
|
TAILQ_INIT(&sc->mlx_freecmds);
|
|
MLX_BIO_QINIT(sc->mlx_bioq);
|
|
|
|
/*
|
|
* Select accessor methods based on controller interface type.
|
|
*/
|
|
switch(sc->mlx_iftype) {
|
|
case MLX_IFTYPE_2:
|
|
case MLX_IFTYPE_3:
|
|
sc->mlx_tryqueue = mlx_v3_tryqueue;
|
|
sc->mlx_findcomplete = mlx_v3_findcomplete;
|
|
sc->mlx_intaction = mlx_v3_intaction;
|
|
sc->mlx_fw_handshake = mlx_v3_fw_handshake;
|
|
break;
|
|
case MLX_IFTYPE_4:
|
|
sc->mlx_tryqueue = mlx_v4_tryqueue;
|
|
sc->mlx_findcomplete = mlx_v4_findcomplete;
|
|
sc->mlx_intaction = mlx_v4_intaction;
|
|
sc->mlx_fw_handshake = mlx_v4_fw_handshake;
|
|
break;
|
|
case MLX_IFTYPE_5:
|
|
sc->mlx_tryqueue = mlx_v5_tryqueue;
|
|
sc->mlx_findcomplete = mlx_v5_findcomplete;
|
|
sc->mlx_intaction = mlx_v5_intaction;
|
|
sc->mlx_fw_handshake = mlx_v5_fw_handshake;
|
|
break;
|
|
default:
|
|
mlx_free(sc);
|
|
return(ENXIO); /* should never happen */
|
|
}
|
|
|
|
/* disable interrupts before we start talking to the controller */
|
|
sc->mlx_intaction(sc, MLX_INTACTION_DISABLE);
|
|
|
|
/*
|
|
* Wait for the controller to come ready, handshake with the firmware if required.
|
|
* This is typically only necessary on platforms where the controller BIOS does not
|
|
* run.
|
|
*/
|
|
hsmsg = 0;
|
|
DELAY(1000);
|
|
while ((hscode = sc->mlx_fw_handshake(sc, &hserror, &hsparam1, &hsparam2)) != 0) {
|
|
/* report first time around... */
|
|
if (hsmsg == 0) {
|
|
device_printf(sc->mlx_dev, "controller initialisation in progress...\n");
|
|
hsmsg = 1;
|
|
}
|
|
/* did we get a real message? */
|
|
if (hscode == 2) {
|
|
hscode = mlx_fw_message(sc, hserror, hsparam1, hsparam2);
|
|
/* fatal initialisation error? */
|
|
if (hscode != 0) {
|
|
mlx_free(sc);
|
|
return(ENXIO);
|
|
}
|
|
}
|
|
}
|
|
if (hsmsg == 1)
|
|
device_printf(sc->mlx_dev, "initialisation complete.\n");
|
|
|
|
/*
|
|
* Allocate and connect our interrupt.
|
|
*/
|
|
rid = 0;
|
|
sc->mlx_irq = bus_alloc_resource(sc->mlx_dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE);
|
|
if (sc->mlx_irq == NULL) {
|
|
device_printf(sc->mlx_dev, "can't allocate interrupt\n");
|
|
mlx_free(sc);
|
|
return(ENXIO);
|
|
}
|
|
error = bus_setup_intr(sc->mlx_dev, sc->mlx_irq, INTR_TYPE_BIO | INTR_ENTROPY, mlx_intr, sc, &sc->mlx_intr);
|
|
if (error) {
|
|
device_printf(sc->mlx_dev, "can't set up interrupt\n");
|
|
mlx_free(sc);
|
|
return(ENXIO);
|
|
}
|
|
|
|
/*
|
|
* Create DMA tag for mapping buffers into controller-addressable space.
|
|
*/
|
|
error = bus_dma_tag_create(sc->mlx_parent_dmat, /* parent */
|
|
1, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filter, filterarg */
|
|
MAXBSIZE, MLX_NSEG, /* maxsize, nsegments */
|
|
BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */
|
|
0, /* flags */
|
|
&sc->mlx_buffer_dmat);
|
|
if (error != 0) {
|
|
device_printf(sc->mlx_dev, "can't allocate buffer DMA tag\n");
|
|
mlx_free(sc);
|
|
return(ENOMEM);
|
|
}
|
|
|
|
/*
|
|
* Create some initial scatter/gather mappings so we can run the probe commands.
|
|
*/
|
|
error = mlx_sglist_map(sc);
|
|
if (error != 0) {
|
|
device_printf(sc->mlx_dev, "can't make initial s/g list mapping\n");
|
|
mlx_free(sc);
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* We don't (yet) know where the event log is up to.
|
|
*/
|
|
sc->mlx_currevent = -1;
|
|
|
|
/*
|
|
* Obtain controller feature information
|
|
*/
|
|
if ((sc->mlx_enq2 = mlx_enquire(sc, MLX_CMD_ENQUIRY2, sizeof(struct mlx_enquiry2), NULL)) == NULL) {
|
|
device_printf(sc->mlx_dev, "ENQUIRY2 failed\n");
|
|
mlx_free(sc);
|
|
return(ENXIO);
|
|
}
|
|
|
|
/*
|
|
* Do quirk/feature related things.
|
|
*/
|
|
fwminor = (sc->mlx_enq2->me_firmware_id >> 8) & 0xff;
|
|
switch(sc->mlx_iftype) {
|
|
case MLX_IFTYPE_2:
|
|
/* These controllers don't report the firmware version in the ENQUIRY2 response */
|
|
if ((meo = mlx_enquire(sc, MLX_CMD_ENQUIRY_OLD, sizeof(struct mlx_enquiry_old), NULL)) == NULL) {
|
|
device_printf(sc->mlx_dev, "ENQUIRY_OLD failed\n");
|
|
mlx_free(sc);
|
|
return(ENXIO);
|
|
}
|
|
sc->mlx_enq2->me_firmware_id = ('0' << 24) | (0 << 16) | (meo->me_fwminor << 8) | meo->me_fwmajor;
|
|
free(meo, M_DEVBUF);
|
|
|
|
/* XXX require 2.42 or better (PCI) or 2.14 or better (EISA) */
|
|
if (meo->me_fwminor < 42) {
|
|
device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n");
|
|
device_printf(sc->mlx_dev, " *** WARNING *** Use revision 2.42 or later\n");
|
|
}
|
|
break;
|
|
case MLX_IFTYPE_3:
|
|
/* XXX certify 3.52? */
|
|
if (fwminor < 51) {
|
|
device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n");
|
|
device_printf(sc->mlx_dev, " *** WARNING *** Use revision 3.51 or later\n");
|
|
}
|
|
break;
|
|
case MLX_IFTYPE_4:
|
|
/* XXX certify firmware versions? */
|
|
if (fwminor < 6) {
|
|
device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n");
|
|
device_printf(sc->mlx_dev, " *** WARNING *** Use revision 4.06 or later\n");
|
|
}
|
|
break;
|
|
case MLX_IFTYPE_5:
|
|
if (fwminor < 7) {
|
|
device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n");
|
|
device_printf(sc->mlx_dev, " *** WARNING *** Use revision 5.07 or later\n");
|
|
}
|
|
break;
|
|
default:
|
|
mlx_free(sc);
|
|
return(ENXIO); /* should never happen */
|
|
}
|
|
|
|
/*
|
|
* Create the final scatter/gather mappings now that we have characterised the controller.
|
|
*/
|
|
error = mlx_sglist_map(sc);
|
|
if (error != 0) {
|
|
device_printf(sc->mlx_dev, "can't make final s/g list mapping\n");
|
|
mlx_free(sc);
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* No user-requested background operation is in progress.
|
|
*/
|
|
sc->mlx_background = 0;
|
|
sc->mlx_rebuildstat.rs_code = MLX_REBUILDSTAT_IDLE;
|
|
|
|
/*
|
|
* Create the control device.
|
|
*/
|
|
sc->mlx_dev_t = make_dev(&mlx_cdevsw, device_get_unit(sc->mlx_dev), UID_ROOT, GID_OPERATOR,
|
|
S_IRUSR | S_IWUSR, "mlx%d", device_get_unit(sc->mlx_dev));
|
|
|
|
/*
|
|
* Start the timeout routine.
|
|
*/
|
|
sc->mlx_timeout = timeout(mlx_periodic, sc, hz);
|
|
|
|
/* print a little information about the controller */
|
|
mlx_describe_controller(sc);
|
|
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Locate disk resources and attach children to them.
|
|
*/
|
|
void
|
|
mlx_startup(struct mlx_softc *sc)
|
|
{
|
|
struct mlx_enq_sys_drive *mes;
|
|
struct mlx_sysdrive *dr;
|
|
int i, error;
|
|
|
|
debug_called(1);
|
|
|
|
/*
|
|
* Scan all the system drives and attach children for those that
|
|
* don't currently have them.
|
|
*/
|
|
mes = mlx_enquire(sc, MLX_CMD_ENQSYSDRIVE, sizeof(*mes) * MLX_MAXDRIVES, NULL);
|
|
if (mes == NULL) {
|
|
device_printf(sc->mlx_dev, "error fetching drive status\n");
|
|
return;
|
|
}
|
|
|
|
/* iterate over drives returned */
|
|
for (i = 0, dr = &sc->mlx_sysdrive[0];
|
|
(i < MLX_MAXDRIVES) && (mes[i].sd_size != 0xffffffff);
|
|
i++, dr++) {
|
|
/* are we already attached to this drive? */
|
|
if (dr->ms_disk == 0) {
|
|
/* pick up drive information */
|
|
dr->ms_size = mes[i].sd_size;
|
|
dr->ms_raidlevel = mes[i].sd_raidlevel & 0xf;
|
|
dr->ms_state = mes[i].sd_state;
|
|
|
|
/* generate geometry information */
|
|
if (sc->mlx_geom == MLX_GEOM_128_32) {
|
|
dr->ms_heads = 128;
|
|
dr->ms_sectors = 32;
|
|
dr->ms_cylinders = dr->ms_size / (128 * 32);
|
|
} else { /* MLX_GEOM_255/63 */
|
|
dr->ms_heads = 255;
|
|
dr->ms_sectors = 63;
|
|
dr->ms_cylinders = dr->ms_size / (255 * 63);
|
|
}
|
|
dr->ms_disk = device_add_child(sc->mlx_dev, /*"mlxd"*/NULL, -1);
|
|
if (dr->ms_disk == 0)
|
|
device_printf(sc->mlx_dev, "device_add_child failed\n");
|
|
device_set_ivars(dr->ms_disk, dr);
|
|
}
|
|
}
|
|
free(mes, M_DEVBUF);
|
|
if ((error = bus_generic_attach(sc->mlx_dev)) != 0)
|
|
device_printf(sc->mlx_dev, "bus_generic_attach returned %d", error);
|
|
|
|
/* mark controller back up */
|
|
sc->mlx_state &= ~MLX_STATE_SHUTDOWN;
|
|
|
|
/* enable interrupts */
|
|
sc->mlx_intaction(sc, MLX_INTACTION_ENABLE);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Disconnect from the controller completely, in preparation for unload.
|
|
*/
|
|
int
|
|
mlx_detach(device_t dev)
|
|
{
|
|
struct mlx_softc *sc = device_get_softc(dev);
|
|
struct mlxd_softc *mlxd;
|
|
int i, s, error;
|
|
|
|
debug_called(1);
|
|
|
|
error = EBUSY;
|
|
s = splbio();
|
|
if (sc->mlx_state & MLX_STATE_OPEN)
|
|
goto out;
|
|
|
|
for (i = 0; i < MLX_MAXDRIVES; i++) {
|
|
if (sc->mlx_sysdrive[i].ms_disk != 0) {
|
|
mlxd = device_get_softc(sc->mlx_sysdrive[i].ms_disk);
|
|
if (mlxd->mlxd_flags & MLXD_OPEN) { /* drive is mounted, abort detach */
|
|
device_printf(sc->mlx_sysdrive[i].ms_disk, "still open, can't detach\n");
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
if ((error = mlx_shutdown(dev)))
|
|
goto out;
|
|
|
|
mlx_free(sc);
|
|
|
|
error = 0;
|
|
out:
|
|
splx(s);
|
|
return(error);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Bring the controller down to a dormant state and detach all child devices.
|
|
*
|
|
* This function is called before detach, system shutdown, or before performing
|
|
* an operation which may add or delete system disks. (Call mlx_startup to
|
|
* resume normal operation.)
|
|
*
|
|
* Note that we can assume that the bioq on the controller is empty, as we won't
|
|
* allow shutdown if any device is open.
|
|
*/
|
|
int
|
|
mlx_shutdown(device_t dev)
|
|
{
|
|
struct mlx_softc *sc = device_get_softc(dev);
|
|
int i, s, error;
|
|
|
|
debug_called(1);
|
|
|
|
s = splbio();
|
|
error = 0;
|
|
|
|
sc->mlx_state |= MLX_STATE_SHUTDOWN;
|
|
sc->mlx_intaction(sc, MLX_INTACTION_DISABLE);
|
|
|
|
/* flush controller */
|
|
device_printf(sc->mlx_dev, "flushing cache...");
|
|
if (mlx_flush(sc)) {
|
|
printf("failed\n");
|
|
} else {
|
|
printf("done\n");
|
|
}
|
|
|
|
/* delete all our child devices */
|
|
for (i = 0; i < MLX_MAXDRIVES; i++) {
|
|
if (sc->mlx_sysdrive[i].ms_disk != 0) {
|
|
if ((error = device_delete_child(sc->mlx_dev, sc->mlx_sysdrive[i].ms_disk)) != 0)
|
|
goto out;
|
|
sc->mlx_sysdrive[i].ms_disk = 0;
|
|
}
|
|
}
|
|
|
|
out:
|
|
splx(s);
|
|
return(error);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Bring the controller to a quiescent state, ready for system suspend.
|
|
*/
|
|
int
|
|
mlx_suspend(device_t dev)
|
|
{
|
|
struct mlx_softc *sc = device_get_softc(dev);
|
|
int s;
|
|
|
|
debug_called(1);
|
|
|
|
s = splbio();
|
|
sc->mlx_state |= MLX_STATE_SUSPEND;
|
|
|
|
/* flush controller */
|
|
device_printf(sc->mlx_dev, "flushing cache...");
|
|
printf("%s\n", mlx_flush(sc) ? "failed" : "done");
|
|
|
|
sc->mlx_intaction(sc, MLX_INTACTION_DISABLE);
|
|
splx(s);
|
|
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Bring the controller back to a state ready for operation.
|
|
*/
|
|
int
|
|
mlx_resume(device_t dev)
|
|
{
|
|
struct mlx_softc *sc = device_get_softc(dev);
|
|
|
|
debug_called(1);
|
|
|
|
sc->mlx_state &= ~MLX_STATE_SUSPEND;
|
|
sc->mlx_intaction(sc, MLX_INTACTION_ENABLE);
|
|
|
|
return(0);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Take an interrupt, or be poked by other code to look for interrupt-worthy
|
|
* status.
|
|
*/
|
|
void
|
|
mlx_intr(void *arg)
|
|
{
|
|
struct mlx_softc *sc = (struct mlx_softc *)arg;
|
|
|
|
debug_called(1);
|
|
|
|
/* collect finished commands, queue anything waiting */
|
|
mlx_done(sc);
|
|
};
|
|
|
|
/*******************************************************************************
|
|
* Receive a buf structure from a child device and queue it on a particular
|
|
* disk resource, then poke the disk resource to start as much work as it can.
|
|
*/
|
|
int
|
|
mlx_submit_buf(struct mlx_softc *sc, mlx_bio *bp)
|
|
{
|
|
int s;
|
|
|
|
debug_called(1);
|
|
|
|
s = splbio();
|
|
MLX_BIO_QINSERT(sc->mlx_bioq, bp);
|
|
sc->mlx_waitbufs++;
|
|
splx(s);
|
|
mlx_startio(sc);
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Accept an open operation on the control device.
|
|
*/
|
|
int
|
|
mlx_open(dev_t dev, int flags, int fmt, struct thread *td)
|
|
{
|
|
int unit = minor(dev);
|
|
struct mlx_softc *sc = devclass_get_softc(mlx_devclass, unit);
|
|
|
|
sc->mlx_state |= MLX_STATE_OPEN;
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Accept the last close on the control device.
|
|
*/
|
|
int
|
|
mlx_close(dev_t dev, int flags, int fmt, struct thread *td)
|
|
{
|
|
int unit = minor(dev);
|
|
struct mlx_softc *sc = devclass_get_softc(mlx_devclass, unit);
|
|
|
|
sc->mlx_state &= ~MLX_STATE_OPEN;
|
|
return (0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Handle controller-specific control operations.
|
|
*/
|
|
int
|
|
mlx_ioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct thread *td)
|
|
{
|
|
int unit = minor(dev);
|
|
struct mlx_softc *sc = devclass_get_softc(mlx_devclass, unit);
|
|
struct mlx_rebuild_request *rb = (struct mlx_rebuild_request *)addr;
|
|
struct mlx_rebuild_status *rs = (struct mlx_rebuild_status *)addr;
|
|
int *arg = (int *)addr;
|
|
struct mlx_pause *mp;
|
|
struct mlx_sysdrive *dr;
|
|
struct mlxd_softc *mlxd;
|
|
int i, error;
|
|
|
|
switch(cmd) {
|
|
/*
|
|
* Enumerate connected system drives; returns the first system drive's
|
|
* unit number if *arg is -1, or the next unit after *arg if it's
|
|
* a valid unit on this controller.
|
|
*/
|
|
case MLX_NEXT_CHILD:
|
|
/* search system drives */
|
|
for (i = 0; i < MLX_MAXDRIVES; i++) {
|
|
/* is this one attached? */
|
|
if (sc->mlx_sysdrive[i].ms_disk != 0) {
|
|
/* looking for the next one we come across? */
|
|
if (*arg == -1) {
|
|
*arg = device_get_unit(sc->mlx_sysdrive[0].ms_disk);
|
|
return(0);
|
|
}
|
|
/* we want the one after this one */
|
|
if (*arg == device_get_unit(sc->mlx_sysdrive[i].ms_disk))
|
|
*arg = -1;
|
|
}
|
|
}
|
|
return(ENOENT);
|
|
|
|
/*
|
|
* Scan the controller to see whether new drives have appeared.
|
|
*/
|
|
case MLX_RESCAN_DRIVES:
|
|
mlx_startup(sc);
|
|
return(0);
|
|
|
|
/*
|
|
* Disconnect from the specified drive; it may be about to go
|
|
* away.
|
|
*/
|
|
case MLX_DETACH_DRIVE: /* detach one drive */
|
|
|
|
if (((dr = mlx_findunit(sc, *arg)) == NULL) ||
|
|
((mlxd = device_get_softc(dr->ms_disk)) == NULL))
|
|
return(ENOENT);
|
|
|
|
device_printf(dr->ms_disk, "detaching...");
|
|
error = 0;
|
|
if (mlxd->mlxd_flags & MLXD_OPEN) {
|
|
error = EBUSY;
|
|
goto detach_out;
|
|
}
|
|
|
|
/* flush controller */
|
|
if (mlx_flush(sc)) {
|
|
error = EBUSY;
|
|
goto detach_out;
|
|
}
|
|
|
|
/* nuke drive */
|
|
if ((error = device_delete_child(sc->mlx_dev, dr->ms_disk)) != 0)
|
|
goto detach_out;
|
|
dr->ms_disk = 0;
|
|
|
|
detach_out:
|
|
if (error) {
|
|
printf("failed\n");
|
|
} else {
|
|
printf("done\n");
|
|
}
|
|
return(error);
|
|
|
|
/*
|
|
* Pause one or more SCSI channels for a period of time, to assist
|
|
* in the process of hot-swapping devices.
|
|
*
|
|
* Note that at least the 3.51 firmware on the DAC960PL doesn't seem
|
|
* to do this right.
|
|
*/
|
|
case MLX_PAUSE_CHANNEL: /* schedule a channel pause */
|
|
/* Does this command work on this firmware? */
|
|
if (!(sc->mlx_feature & MLX_FEAT_PAUSEWORKS))
|
|
return(EOPNOTSUPP);
|
|
|
|
mp = (struct mlx_pause *)addr;
|
|
if ((mp->mp_which == MLX_PAUSE_CANCEL) && (sc->mlx_pause.mp_when != 0)) {
|
|
/* cancel a pending pause operation */
|
|
sc->mlx_pause.mp_which = 0;
|
|
} else {
|
|
/* fix for legal channels */
|
|
mp->mp_which &= ((1 << sc->mlx_enq2->me_actual_channels) -1);
|
|
/* check time values */
|
|
if ((mp->mp_when < 0) || (mp->mp_when > 3600))
|
|
return(EINVAL);
|
|
if ((mp->mp_howlong < 1) || (mp->mp_howlong > (0xf * 30)))
|
|
return(EINVAL);
|
|
|
|
/* check for a pause currently running */
|
|
if ((sc->mlx_pause.mp_which != 0) && (sc->mlx_pause.mp_when == 0))
|
|
return(EBUSY);
|
|
|
|
/* looks ok, go with it */
|
|
sc->mlx_pause.mp_which = mp->mp_which;
|
|
sc->mlx_pause.mp_when = time_second + mp->mp_when;
|
|
sc->mlx_pause.mp_howlong = sc->mlx_pause.mp_when + mp->mp_howlong;
|
|
}
|
|
return(0);
|
|
|
|
/*
|
|
* Accept a command passthrough-style.
|
|
*/
|
|
case MLX_COMMAND:
|
|
return(mlx_user_command(sc, (struct mlx_usercommand *)addr));
|
|
|
|
/*
|
|
* Start a rebuild on a given SCSI disk
|
|
*/
|
|
case MLX_REBUILDASYNC:
|
|
if (sc->mlx_background != 0) {
|
|
rb->rr_status = 0x0106;
|
|
return(EBUSY);
|
|
}
|
|
rb->rr_status = mlx_rebuild(sc, rb->rr_channel, rb->rr_target);
|
|
switch (rb->rr_status) {
|
|
case 0:
|
|
error = 0;
|
|
break;
|
|
case 0x10000:
|
|
error = ENOMEM; /* couldn't set up the command */
|
|
break;
|
|
case 0x0002:
|
|
error = EBUSY;
|
|
break;
|
|
case 0x0104:
|
|
error = EIO;
|
|
break;
|
|
case 0x0105:
|
|
error = ERANGE;
|
|
break;
|
|
case 0x0106:
|
|
error = EBUSY;
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
if (error == 0)
|
|
sc->mlx_background = MLX_BACKGROUND_REBUILD;
|
|
return(error);
|
|
|
|
/*
|
|
* Get the status of the current rebuild or consistency check.
|
|
*/
|
|
case MLX_REBUILDSTAT:
|
|
*rs = sc->mlx_rebuildstat;
|
|
return(0);
|
|
|
|
/*
|
|
* Return the per-controller system drive number matching the
|
|
* disk device number in (arg), if it happens to belong to us.
|
|
*/
|
|
case MLX_GET_SYSDRIVE:
|
|
error = ENOENT;
|
|
mlxd = (struct mlxd_softc *)devclass_get_softc(mlxd_devclass, *arg);
|
|
if ((mlxd != NULL) && (mlxd->mlxd_drive >= sc->mlx_sysdrive) &&
|
|
(mlxd->mlxd_drive < (sc->mlx_sysdrive + MLX_MAXDRIVES))) {
|
|
error = 0;
|
|
*arg = mlxd->mlxd_drive - sc->mlx_sysdrive;
|
|
}
|
|
return(error);
|
|
|
|
default:
|
|
return(ENOTTY);
|
|
}
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Handle operations requested by a System Drive connected to this controller.
|
|
*/
|
|
int
|
|
mlx_submit_ioctl(struct mlx_softc *sc, struct mlx_sysdrive *drive, u_long cmd,
|
|
caddr_t addr, int32_t flag, struct thread *td)
|
|
{
|
|
int *arg = (int *)addr;
|
|
int error, result;
|
|
|
|
switch(cmd) {
|
|
/*
|
|
* Return the current status of this drive.
|
|
*/
|
|
case MLXD_STATUS:
|
|
*arg = drive->ms_state;
|
|
return(0);
|
|
|
|
/*
|
|
* Start a background consistency check on this drive.
|
|
*/
|
|
case MLXD_CHECKASYNC: /* start a background consistency check */
|
|
if (sc->mlx_background != 0) {
|
|
*arg = 0x0106;
|
|
return(EBUSY);
|
|
}
|
|
result = mlx_check(sc, drive - &sc->mlx_sysdrive[0]);
|
|
switch (result) {
|
|
case 0:
|
|
error = 0;
|
|
break;
|
|
case 0x10000:
|
|
error = ENOMEM; /* couldn't set up the command */
|
|
break;
|
|
case 0x0002:
|
|
error = EIO;
|
|
break;
|
|
case 0x0105:
|
|
error = ERANGE;
|
|
break;
|
|
case 0x0106:
|
|
error = EBUSY;
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
if (error == 0)
|
|
sc->mlx_background = MLX_BACKGROUND_CHECK;
|
|
*arg = result;
|
|
return(error);
|
|
|
|
}
|
|
return(ENOIOCTL);
|
|
}
|
|
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Status Monitoring
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Fire off commands to periodically check the status of connected drives.
|
|
*/
|
|
static void
|
|
mlx_periodic(void *data)
|
|
{
|
|
struct mlx_softc *sc = (struct mlx_softc *)data;
|
|
|
|
debug_called(1);
|
|
|
|
/*
|
|
* Run a bus pause?
|
|
*/
|
|
if ((sc->mlx_pause.mp_which != 0) &&
|
|
(sc->mlx_pause.mp_when > 0) &&
|
|
(time_second >= sc->mlx_pause.mp_when)){
|
|
|
|
mlx_pause_action(sc); /* pause is running */
|
|
sc->mlx_pause.mp_when = 0;
|
|
sysbeep(500, hz);
|
|
|
|
/*
|
|
* Bus pause still running?
|
|
*/
|
|
} else if ((sc->mlx_pause.mp_which != 0) &&
|
|
(sc->mlx_pause.mp_when == 0)) {
|
|
|
|
/* time to stop bus pause? */
|
|
if (time_second >= sc->mlx_pause.mp_howlong) {
|
|
mlx_pause_action(sc);
|
|
sc->mlx_pause.mp_which = 0; /* pause is complete */
|
|
sysbeep(500, hz);
|
|
} else {
|
|
sysbeep((time_second % 5) * 100 + 500, hz/8);
|
|
}
|
|
|
|
/*
|
|
* Run normal periodic activities?
|
|
*/
|
|
} else if (time_second > (sc->mlx_lastpoll + 10)) {
|
|
sc->mlx_lastpoll = time_second;
|
|
|
|
/*
|
|
* Check controller status.
|
|
*
|
|
* XXX Note that this may not actually launch a command in situations of high load.
|
|
*/
|
|
mlx_enquire(sc, (sc->mlx_iftype == MLX_IFTYPE_2) ? MLX_CMD_ENQUIRY_OLD : MLX_CMD_ENQUIRY,
|
|
imax(sizeof(struct mlx_enquiry), sizeof(struct mlx_enquiry_old)), mlx_periodic_enquiry);
|
|
|
|
/*
|
|
* Check system drive status.
|
|
*
|
|
* XXX This might be better left to event-driven detection, eg. I/O to an offline
|
|
* drive will detect it's offline, rebuilds etc. should detect the drive is back
|
|
* online.
|
|
*/
|
|
mlx_enquire(sc, MLX_CMD_ENQSYSDRIVE, sizeof(struct mlx_enq_sys_drive) * MLX_MAXDRIVES,
|
|
mlx_periodic_enquiry);
|
|
|
|
}
|
|
|
|
/* get drive rebuild/check status */
|
|
/* XXX should check sc->mlx_background if this is only valid while in progress */
|
|
mlx_enquire(sc, MLX_CMD_REBUILDSTAT, sizeof(struct mlx_rebuild_stat), mlx_periodic_rebuild);
|
|
|
|
/* deal with possibly-missed interrupts and timed-out commands */
|
|
mlx_done(sc);
|
|
|
|
/* reschedule another poll next second or so */
|
|
sc->mlx_timeout = timeout(mlx_periodic, sc, hz);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Handle the result of an ENQUIRY command instigated by periodic status polling.
|
|
*/
|
|
static void
|
|
mlx_periodic_enquiry(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
|
|
debug_called(1);
|
|
|
|
/* Command completed OK? */
|
|
if (mc->mc_status != 0) {
|
|
device_printf(sc->mlx_dev, "periodic enquiry failed - %s\n", mlx_diagnose_command(mc));
|
|
goto out;
|
|
}
|
|
|
|
/* respond to command */
|
|
switch(mc->mc_mailbox[0]) {
|
|
/*
|
|
* This is currently a bit fruitless, as we don't know how to extract the eventlog
|
|
* pointer yet.
|
|
*/
|
|
case MLX_CMD_ENQUIRY_OLD:
|
|
{
|
|
struct mlx_enquiry *me = (struct mlx_enquiry *)mc->mc_data;
|
|
struct mlx_enquiry_old *meo = (struct mlx_enquiry_old *)mc->mc_data;
|
|
int i;
|
|
|
|
/* convert data in-place to new format */
|
|
for (i = (sizeof(me->me_dead) / sizeof(me->me_dead[0])) - 1; i >= 0; i--) {
|
|
me->me_dead[i].dd_chan = meo->me_dead[i].dd_chan;
|
|
me->me_dead[i].dd_targ = meo->me_dead[i].dd_targ;
|
|
}
|
|
me->me_misc_flags = 0;
|
|
me->me_rebuild_count = meo->me_rebuild_count;
|
|
me->me_dead_count = meo->me_dead_count;
|
|
me->me_critical_sd_count = meo->me_critical_sd_count;
|
|
me->me_event_log_seq_num = 0;
|
|
me->me_offline_sd_count = meo->me_offline_sd_count;
|
|
me->me_max_commands = meo->me_max_commands;
|
|
me->me_rebuild_flag = meo->me_rebuild_flag;
|
|
me->me_fwmajor = meo->me_fwmajor;
|
|
me->me_fwminor = meo->me_fwminor;
|
|
me->me_status_flags = meo->me_status_flags;
|
|
me->me_flash_age = meo->me_flash_age;
|
|
for (i = (sizeof(me->me_drvsize) / sizeof(me->me_drvsize[0])) - 1; i >= 0; i--) {
|
|
if (i > ((sizeof(meo->me_drvsize) / sizeof(meo->me_drvsize[0])) - 1)) {
|
|
me->me_drvsize[i] = 0; /* drive beyond supported range */
|
|
} else {
|
|
me->me_drvsize[i] = meo->me_drvsize[i];
|
|
}
|
|
}
|
|
me->me_num_sys_drvs = meo->me_num_sys_drvs;
|
|
}
|
|
/* FALLTHROUGH */
|
|
|
|
/*
|
|
* Generic controller status update. We could do more with this than just
|
|
* checking the event log.
|
|
*/
|
|
case MLX_CMD_ENQUIRY:
|
|
{
|
|
struct mlx_enquiry *me = (struct mlx_enquiry *)mc->mc_data;
|
|
|
|
if (sc->mlx_currevent == -1) {
|
|
/* initialise our view of the event log */
|
|
sc->mlx_currevent = sc->mlx_lastevent = me->me_event_log_seq_num;
|
|
} else if ((me->me_event_log_seq_num != sc->mlx_lastevent) && !(sc->mlx_flags & MLX_EVENTLOG_BUSY)) {
|
|
/* record where current events are up to */
|
|
sc->mlx_currevent = me->me_event_log_seq_num;
|
|
debug(1, "event log pointer was %d, now %d\n", sc->mlx_lastevent, sc->mlx_currevent);
|
|
|
|
/* mark the event log as busy */
|
|
atomic_set_int(&sc->mlx_flags, MLX_EVENTLOG_BUSY);
|
|
|
|
/* drain new eventlog entries */
|
|
mlx_periodic_eventlog_poll(sc);
|
|
}
|
|
break;
|
|
}
|
|
case MLX_CMD_ENQSYSDRIVE:
|
|
{
|
|
struct mlx_enq_sys_drive *mes = (struct mlx_enq_sys_drive *)mc->mc_data;
|
|
struct mlx_sysdrive *dr;
|
|
int i;
|
|
|
|
for (i = 0, dr = &sc->mlx_sysdrive[0];
|
|
(i < MLX_MAXDRIVES) && (mes[i].sd_size != 0xffffffff);
|
|
i++) {
|
|
|
|
/* has state been changed by controller? */
|
|
if (dr->ms_state != mes[i].sd_state) {
|
|
switch(mes[i].sd_state) {
|
|
case MLX_SYSD_OFFLINE:
|
|
device_printf(dr->ms_disk, "drive offline\n");
|
|
break;
|
|
case MLX_SYSD_ONLINE:
|
|
device_printf(dr->ms_disk, "drive online\n");
|
|
break;
|
|
case MLX_SYSD_CRITICAL:
|
|
device_printf(dr->ms_disk, "drive critical\n");
|
|
break;
|
|
}
|
|
/* save new state */
|
|
dr->ms_state = mes[i].sd_state;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
device_printf(sc->mlx_dev, "%s: unknown command 0x%x", __func__, mc->mc_mailbox[0]);
|
|
break;
|
|
}
|
|
|
|
out:
|
|
free(mc->mc_data, M_DEVBUF);
|
|
mlx_releasecmd(mc);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Instigate a poll for one event log message on (sc).
|
|
* We only poll for one message at a time, to keep our command usage down.
|
|
*/
|
|
static void
|
|
mlx_periodic_eventlog_poll(struct mlx_softc *sc)
|
|
{
|
|
struct mlx_command *mc;
|
|
void *result = NULL;
|
|
int error;
|
|
|
|
debug_called(1);
|
|
|
|
/* get ourselves a command buffer */
|
|
error = 1;
|
|
if ((mc = mlx_alloccmd(sc)) == NULL)
|
|
goto out;
|
|
/* allocate the response structure */
|
|
if ((result = malloc(/*sizeof(struct mlx_eventlog_entry)*/1024, M_DEVBUF, M_NOWAIT)) == NULL)
|
|
goto out;
|
|
/* get a command slot */
|
|
if (mlx_getslot(mc))
|
|
goto out;
|
|
|
|
/* map the command so the controller can see it */
|
|
mc->mc_data = result;
|
|
mc->mc_length = /*sizeof(struct mlx_eventlog_entry)*/1024;
|
|
mlx_mapcmd(mc);
|
|
|
|
/* build the command to get one entry */
|
|
mlx_make_type3(mc, MLX_CMD_LOGOP, MLX_LOGOP_GET, 1, sc->mlx_lastevent, 0, 0, mc->mc_dataphys, 0);
|
|
mc->mc_complete = mlx_periodic_eventlog_respond;
|
|
mc->mc_private = mc;
|
|
|
|
/* start the command */
|
|
if ((error = mlx_start(mc)) != 0)
|
|
goto out;
|
|
|
|
error = 0; /* success */
|
|
out:
|
|
if (error != 0) {
|
|
if (mc != NULL)
|
|
mlx_releasecmd(mc);
|
|
if (result != NULL)
|
|
free(result, M_DEVBUF);
|
|
}
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Handle the result of polling for a log message, generate diagnostic output.
|
|
* If this wasn't the last message waiting for us, we'll go collect another.
|
|
*/
|
|
static char *mlx_sense_messages[] = {
|
|
"because write recovery failed",
|
|
"because of SCSI bus reset failure",
|
|
"because of double check condition",
|
|
"because it was removed",
|
|
"because of gross error on SCSI chip",
|
|
"because of bad tag returned from drive",
|
|
"because of timeout on SCSI command",
|
|
"because of reset SCSI command issued from system",
|
|
"because busy or parity error count exceeded limit",
|
|
"because of 'kill drive' command from system",
|
|
"because of selection timeout",
|
|
"due to SCSI phase sequence error",
|
|
"due to unknown status"
|
|
};
|
|
|
|
static void
|
|
mlx_periodic_eventlog_respond(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
struct mlx_eventlog_entry *el = (struct mlx_eventlog_entry *)mc->mc_data;
|
|
char *reason;
|
|
|
|
debug_called(1);
|
|
|
|
sc->mlx_lastevent++; /* next message... */
|
|
if (mc->mc_status == 0) {
|
|
|
|
/* handle event log message */
|
|
switch(el->el_type) {
|
|
/*
|
|
* This is the only sort of message we understand at the moment.
|
|
* The tests here are probably incomplete.
|
|
*/
|
|
case MLX_LOGMSG_SENSE: /* sense data */
|
|
/* Mylex vendor-specific message indicating a drive was killed? */
|
|
if ((el->el_sensekey == 9) &&
|
|
(el->el_asc == 0x80)) {
|
|
if (el->el_asq < (sizeof(mlx_sense_messages) / sizeof(mlx_sense_messages[0]))) {
|
|
reason = mlx_sense_messages[el->el_asq];
|
|
} else {
|
|
reason = "for unknown reason";
|
|
}
|
|
device_printf(sc->mlx_dev, "physical drive %d:%d killed %s\n",
|
|
el->el_channel, el->el_target, reason);
|
|
}
|
|
/* SCSI drive was reset? */
|
|
if ((el->el_sensekey == 6) && (el->el_asc == 0x29)) {
|
|
device_printf(sc->mlx_dev, "physical drive %d:%d reset\n",
|
|
el->el_channel, el->el_target);
|
|
}
|
|
/* SCSI drive error? */
|
|
if (!((el->el_sensekey == 0) ||
|
|
((el->el_sensekey == 2) &&
|
|
(el->el_asc == 0x04) &&
|
|
((el->el_asq == 0x01) ||
|
|
(el->el_asq == 0x02))))) {
|
|
device_printf(sc->mlx_dev, "physical drive %d:%d error log: sense = %d asc = %x asq = %x\n",
|
|
el->el_channel, el->el_target, el->el_sensekey, el->el_asc, el->el_asq);
|
|
device_printf(sc->mlx_dev, " info %4D csi %4D\n", el->el_information, ":", el->el_csi, ":");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
device_printf(sc->mlx_dev, "unknown log message type 0x%x\n", el->el_type);
|
|
break;
|
|
}
|
|
} else {
|
|
device_printf(sc->mlx_dev, "error reading message log - %s\n", mlx_diagnose_command(mc));
|
|
/* give up on all the outstanding messages, as we may have come unsynched */
|
|
sc->mlx_lastevent = sc->mlx_currevent;
|
|
}
|
|
|
|
/* dispose of command and data */
|
|
free(mc->mc_data, M_DEVBUF);
|
|
mlx_releasecmd(mc);
|
|
|
|
/* is there another message to obtain? */
|
|
if (sc->mlx_lastevent != sc->mlx_currevent) {
|
|
mlx_periodic_eventlog_poll(sc);
|
|
} else {
|
|
/* clear log-busy status */
|
|
atomic_clear_int(&sc->mlx_flags, MLX_EVENTLOG_BUSY);
|
|
}
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Handle check/rebuild operations in progress.
|
|
*/
|
|
static void
|
|
mlx_periodic_rebuild(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
struct mlx_rebuild_status *mr = (struct mlx_rebuild_status *)mc->mc_data;
|
|
|
|
switch(mc->mc_status) {
|
|
case 0: /* operation running, update stats */
|
|
sc->mlx_rebuildstat = *mr;
|
|
|
|
/* spontaneous rebuild/check? */
|
|
if (sc->mlx_background == 0) {
|
|
sc->mlx_background = MLX_BACKGROUND_SPONTANEOUS;
|
|
device_printf(sc->mlx_dev, "background check/rebuild operation started\n");
|
|
}
|
|
break;
|
|
|
|
case 0x0105: /* nothing running, finalise stats and report */
|
|
switch(sc->mlx_background) {
|
|
case MLX_BACKGROUND_CHECK:
|
|
device_printf(sc->mlx_dev, "consistency check completed\n"); /* XXX print drive? */
|
|
break;
|
|
case MLX_BACKGROUND_REBUILD:
|
|
device_printf(sc->mlx_dev, "drive rebuild completed\n"); /* XXX print channel/target? */
|
|
break;
|
|
case MLX_BACKGROUND_SPONTANEOUS:
|
|
default:
|
|
/* if we have previously been non-idle, report the transition */
|
|
if (sc->mlx_rebuildstat.rs_code != MLX_REBUILDSTAT_IDLE) {
|
|
device_printf(sc->mlx_dev, "background check/rebuild operation completed\n");
|
|
}
|
|
}
|
|
sc->mlx_background = 0;
|
|
sc->mlx_rebuildstat.rs_code = MLX_REBUILDSTAT_IDLE;
|
|
break;
|
|
}
|
|
free(mc->mc_data, M_DEVBUF);
|
|
mlx_releasecmd(mc);
|
|
}
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Channel Pause
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* It's time to perform a channel pause action for (sc), either start or stop
|
|
* the pause.
|
|
*/
|
|
static void
|
|
mlx_pause_action(struct mlx_softc *sc)
|
|
{
|
|
struct mlx_command *mc;
|
|
int failsafe, i, command;
|
|
|
|
/* What are we doing here? */
|
|
if (sc->mlx_pause.mp_when == 0) {
|
|
command = MLX_CMD_STARTCHANNEL;
|
|
failsafe = 0;
|
|
|
|
} else {
|
|
command = MLX_CMD_STOPCHANNEL;
|
|
|
|
/*
|
|
* Channels will always start again after the failsafe period,
|
|
* which is specified in multiples of 30 seconds.
|
|
* This constrains us to a maximum pause of 450 seconds.
|
|
*/
|
|
failsafe = ((sc->mlx_pause.mp_howlong - time_second) + 5) / 30;
|
|
if (failsafe > 0xf) {
|
|
failsafe = 0xf;
|
|
sc->mlx_pause.mp_howlong = time_second + (0xf * 30) - 5;
|
|
}
|
|
}
|
|
|
|
/* build commands for every channel requested */
|
|
for (i = 0; i < sc->mlx_enq2->me_actual_channels; i++) {
|
|
if ((1 << i) & sc->mlx_pause.mp_which) {
|
|
|
|
/* get ourselves a command buffer */
|
|
if ((mc = mlx_alloccmd(sc)) == NULL)
|
|
goto fail;
|
|
/* get a command slot */
|
|
mc->mc_flags |= MLX_CMD_PRIORITY;
|
|
if (mlx_getslot(mc))
|
|
goto fail;
|
|
|
|
/* build the command */
|
|
mlx_make_type2(mc, command, (failsafe << 4) | i, 0, 0, 0, 0, 0, 0, 0);
|
|
mc->mc_complete = mlx_pause_done;
|
|
mc->mc_private = sc; /* XXX not needed */
|
|
if (mlx_start(mc))
|
|
goto fail;
|
|
/* command submitted OK */
|
|
return;
|
|
|
|
fail:
|
|
device_printf(sc->mlx_dev, "%s failed for channel %d\n",
|
|
command == MLX_CMD_STOPCHANNEL ? "pause" : "resume", i);
|
|
if (mc != NULL)
|
|
mlx_releasecmd(mc);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
mlx_pause_done(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
int command = mc->mc_mailbox[0];
|
|
int channel = mc->mc_mailbox[2] & 0xf;
|
|
|
|
if (mc->mc_status != 0) {
|
|
device_printf(sc->mlx_dev, "%s command failed - %s\n",
|
|
command == MLX_CMD_STOPCHANNEL ? "pause" : "resume", mlx_diagnose_command(mc));
|
|
} else if (command == MLX_CMD_STOPCHANNEL) {
|
|
device_printf(sc->mlx_dev, "channel %d pausing for %ld seconds\n",
|
|
channel, (long)(sc->mlx_pause.mp_howlong - time_second));
|
|
} else {
|
|
device_printf(sc->mlx_dev, "channel %d resuming\n", channel);
|
|
}
|
|
mlx_releasecmd(mc);
|
|
}
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Command Submission
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Perform an Enquiry command using a type-3 command buffer and a return a single
|
|
* linear result buffer. If the completion function is specified, it will
|
|
* be called with the completed command (and the result response will not be
|
|
* valid until that point). Otherwise, the command will either be busy-waited
|
|
* for (interrupts not enabled), or slept for.
|
|
*/
|
|
static void *
|
|
mlx_enquire(struct mlx_softc *sc, int command, size_t bufsize, void (* complete)(struct mlx_command *mc))
|
|
{
|
|
struct mlx_command *mc;
|
|
void *result;
|
|
int error;
|
|
|
|
debug_called(1);
|
|
|
|
/* get ourselves a command buffer */
|
|
error = 1;
|
|
result = NULL;
|
|
if ((mc = mlx_alloccmd(sc)) == NULL)
|
|
goto out;
|
|
/* allocate the response structure */
|
|
if ((result = malloc(bufsize, M_DEVBUF, M_NOWAIT)) == NULL)
|
|
goto out;
|
|
/* get a command slot */
|
|
mc->mc_flags |= MLX_CMD_PRIORITY | MLX_CMD_DATAOUT;
|
|
if (mlx_getslot(mc))
|
|
goto out;
|
|
|
|
/* map the command so the controller can see it */
|
|
mc->mc_data = result;
|
|
mc->mc_length = bufsize;
|
|
mlx_mapcmd(mc);
|
|
|
|
/* build an enquiry command */
|
|
mlx_make_type2(mc, command, 0, 0, 0, 0, 0, 0, mc->mc_dataphys, 0);
|
|
|
|
/* do we want a completion callback? */
|
|
if (complete != NULL) {
|
|
mc->mc_complete = complete;
|
|
mc->mc_private = mc;
|
|
if ((error = mlx_start(mc)) != 0)
|
|
goto out;
|
|
} else {
|
|
/* run the command in either polled or wait mode */
|
|
if ((sc->mlx_state & MLX_STATE_INTEN) ? mlx_wait_command(mc) : mlx_poll_command(mc))
|
|
goto out;
|
|
|
|
/* command completed OK? */
|
|
if (mc->mc_status != 0) {
|
|
device_printf(sc->mlx_dev, "ENQUIRY failed - %s\n", mlx_diagnose_command(mc));
|
|
goto out;
|
|
}
|
|
}
|
|
error = 0; /* success */
|
|
out:
|
|
/* we got a command, but nobody else will free it */
|
|
if ((complete == NULL) && (mc != NULL))
|
|
mlx_releasecmd(mc);
|
|
/* we got an error, and we allocated a result */
|
|
if ((error != 0) && (result != NULL)) {
|
|
free(result, M_DEVBUF);
|
|
result = NULL;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
|
|
/********************************************************************************
|
|
* Perform a Flush command on the nominated controller.
|
|
*
|
|
* May be called with interrupts enabled or disabled; will not return until
|
|
* the flush operation completes or fails.
|
|
*/
|
|
static int
|
|
mlx_flush(struct mlx_softc *sc)
|
|
{
|
|
struct mlx_command *mc;
|
|
int error;
|
|
|
|
debug_called(1);
|
|
|
|
/* get ourselves a command buffer */
|
|
error = 1;
|
|
if ((mc = mlx_alloccmd(sc)) == NULL)
|
|
goto out;
|
|
/* get a command slot */
|
|
if (mlx_getslot(mc))
|
|
goto out;
|
|
|
|
/* build a flush command */
|
|
mlx_make_type2(mc, MLX_CMD_FLUSH, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
|
|
/* can't assume that interrupts are going to work here, so play it safe */
|
|
if (mlx_poll_command(mc))
|
|
goto out;
|
|
|
|
/* command completed OK? */
|
|
if (mc->mc_status != 0) {
|
|
device_printf(sc->mlx_dev, "FLUSH failed - %s\n", mlx_diagnose_command(mc));
|
|
goto out;
|
|
}
|
|
|
|
error = 0; /* success */
|
|
out:
|
|
if (mc != NULL)
|
|
mlx_releasecmd(mc);
|
|
return(error);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Start a background consistency check on (drive).
|
|
*
|
|
* May be called with interrupts enabled or disabled; will return as soon as the
|
|
* operation has started or been refused.
|
|
*/
|
|
static int
|
|
mlx_check(struct mlx_softc *sc, int drive)
|
|
{
|
|
struct mlx_command *mc;
|
|
int error;
|
|
|
|
debug_called(1);
|
|
|
|
/* get ourselves a command buffer */
|
|
error = 0x10000;
|
|
if ((mc = mlx_alloccmd(sc)) == NULL)
|
|
goto out;
|
|
/* get a command slot */
|
|
if (mlx_getslot(mc))
|
|
goto out;
|
|
|
|
/* build a checkasync command, set the "fix it" flag */
|
|
mlx_make_type2(mc, MLX_CMD_CHECKASYNC, 0, 0, 0, 0, 0, drive | 0x80, 0, 0);
|
|
|
|
/* start the command and wait for it to be returned */
|
|
if (mlx_wait_command(mc))
|
|
goto out;
|
|
|
|
/* command completed OK? */
|
|
if (mc->mc_status != 0) {
|
|
device_printf(sc->mlx_dev, "CHECK ASYNC failed - %s\n", mlx_diagnose_command(mc));
|
|
} else {
|
|
device_printf(sc->mlx_sysdrive[drive].ms_disk, "consistency check started");
|
|
}
|
|
error = mc->mc_status;
|
|
|
|
out:
|
|
if (mc != NULL)
|
|
mlx_releasecmd(mc);
|
|
return(error);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Start a background rebuild of the physical drive at (channel),(target).
|
|
*
|
|
* May be called with interrupts enabled or disabled; will return as soon as the
|
|
* operation has started or been refused.
|
|
*/
|
|
static int
|
|
mlx_rebuild(struct mlx_softc *sc, int channel, int target)
|
|
{
|
|
struct mlx_command *mc;
|
|
int error;
|
|
|
|
debug_called(1);
|
|
|
|
/* get ourselves a command buffer */
|
|
error = 0x10000;
|
|
if ((mc = mlx_alloccmd(sc)) == NULL)
|
|
goto out;
|
|
/* get a command slot */
|
|
if (mlx_getslot(mc))
|
|
goto out;
|
|
|
|
/* build a checkasync command, set the "fix it" flag */
|
|
mlx_make_type2(mc, MLX_CMD_REBUILDASYNC, channel, target, 0, 0, 0, 0, 0, 0);
|
|
|
|
/* start the command and wait for it to be returned */
|
|
if (mlx_wait_command(mc))
|
|
goto out;
|
|
|
|
/* command completed OK? */
|
|
if (mc->mc_status != 0) {
|
|
device_printf(sc->mlx_dev, "REBUILD ASYNC failed - %s\n", mlx_diagnose_command(mc));
|
|
} else {
|
|
device_printf(sc->mlx_dev, "drive rebuild started for %d:%d\n", channel, target);
|
|
}
|
|
error = mc->mc_status;
|
|
|
|
out:
|
|
if (mc != NULL)
|
|
mlx_releasecmd(mc);
|
|
return(error);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Run the command (mc) and return when it completes.
|
|
*
|
|
* Interrupts need to be enabled; returns nonzero on error.
|
|
*/
|
|
static int
|
|
mlx_wait_command(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
int error, count;
|
|
|
|
debug_called(1);
|
|
|
|
mc->mc_complete = NULL;
|
|
mc->mc_private = mc; /* wake us when you're done */
|
|
if ((error = mlx_start(mc)) != 0)
|
|
return(error);
|
|
|
|
count = 0;
|
|
/* XXX better timeout? */
|
|
while ((mc->mc_status == MLX_STATUS_BUSY) && (count < 30)) {
|
|
tsleep(mc->mc_private, PRIBIO | PCATCH, "mlxwcmd", hz);
|
|
}
|
|
|
|
if (mc->mc_status != 0) {
|
|
device_printf(sc->mlx_dev, "command failed - %s\n", mlx_diagnose_command(mc));
|
|
return(EIO);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
|
|
/********************************************************************************
|
|
* Start the command (mc) and busy-wait for it to complete.
|
|
*
|
|
* Should only be used when interrupts can't be relied upon. Returns 0 on
|
|
* success, nonzero on error.
|
|
* Successfully completed commands are dequeued.
|
|
*/
|
|
static int
|
|
mlx_poll_command(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
int error, count, s;
|
|
|
|
debug_called(1);
|
|
|
|
mc->mc_complete = NULL;
|
|
mc->mc_private = NULL; /* we will poll for it */
|
|
if ((error = mlx_start(mc)) != 0)
|
|
return(error);
|
|
|
|
count = 0;
|
|
do {
|
|
/* poll for completion */
|
|
mlx_done(mc->mc_sc);
|
|
|
|
} while ((mc->mc_status == MLX_STATUS_BUSY) && (count++ < 15000000));
|
|
if (mc->mc_status != MLX_STATUS_BUSY) {
|
|
s = splbio();
|
|
TAILQ_REMOVE(&sc->mlx_work, mc, mc_link);
|
|
splx(s);
|
|
return(0);
|
|
}
|
|
device_printf(sc->mlx_dev, "command failed - %s\n", mlx_diagnose_command(mc));
|
|
return(EIO);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Pull as much work off the softc's work queue as possible and give it to the
|
|
* controller. Leave a couple of slots free for emergencies.
|
|
*
|
|
* Must be called at splbio or in an equivalent fashion that prevents
|
|
* reentry or activity on the bioq.
|
|
*/
|
|
static void
|
|
mlx_startio(struct mlx_softc *sc)
|
|
{
|
|
struct mlx_command *mc;
|
|
struct mlxd_softc *mlxd;
|
|
mlx_bio *bp;
|
|
int blkcount;
|
|
int driveno;
|
|
int cmd;
|
|
int s;
|
|
|
|
/* avoid reentrancy */
|
|
if (mlx_lock_tas(sc, MLX_LOCK_STARTING))
|
|
return;
|
|
|
|
/* spin until something prevents us from doing any work */
|
|
s = splbio();
|
|
for (;;) {
|
|
|
|
/* see if there's work to be done */
|
|
if ((bp = MLX_BIO_QFIRST(sc->mlx_bioq)) == NULL)
|
|
break;
|
|
/* get a command */
|
|
if ((mc = mlx_alloccmd(sc)) == NULL)
|
|
break;
|
|
/* get a slot for the command */
|
|
if (mlx_getslot(mc) != 0) {
|
|
mlx_releasecmd(mc);
|
|
break;
|
|
}
|
|
/* get the buf containing our work */
|
|
MLX_BIO_QREMOVE(sc->mlx_bioq, bp);
|
|
sc->mlx_waitbufs--;
|
|
splx(s);
|
|
|
|
/* connect the buf to the command */
|
|
mc->mc_complete = mlx_completeio;
|
|
mc->mc_private = bp;
|
|
mc->mc_data = MLX_BIO_DATA(bp);
|
|
mc->mc_length = MLX_BIO_LENGTH(bp);
|
|
if (MLX_BIO_IS_READ(bp)) {
|
|
mc->mc_flags |= MLX_CMD_DATAIN;
|
|
cmd = MLX_CMD_READSG;
|
|
} else {
|
|
mc->mc_flags |= MLX_CMD_DATAOUT;
|
|
cmd = MLX_CMD_WRITESG;
|
|
}
|
|
|
|
/* map the command so the controller can work with it */
|
|
mlx_mapcmd(mc);
|
|
|
|
/* build a suitable I/O command (assumes 512-byte rounded transfers) */
|
|
mlxd = (struct mlxd_softc *)MLX_BIO_SOFTC(bp);
|
|
driveno = mlxd->mlxd_drive - sc->mlx_sysdrive;
|
|
blkcount = (MLX_BIO_LENGTH(bp) + MLX_BLKSIZE - 1) / MLX_BLKSIZE;
|
|
|
|
if ((MLX_BIO_LBA(bp) + blkcount) > sc->mlx_sysdrive[driveno].ms_size)
|
|
device_printf(sc->mlx_dev, "I/O beyond end of unit (%u,%d > %u)\n",
|
|
MLX_BIO_LBA(bp), blkcount, sc->mlx_sysdrive[driveno].ms_size);
|
|
|
|
/*
|
|
* Build the I/O command. Note that the SG list type bits are set to zero,
|
|
* denoting the format of SG list that we are using.
|
|
*/
|
|
if (sc->mlx_iftype == MLX_IFTYPE_2) {
|
|
mlx_make_type1(mc, (cmd == MLX_CMD_WRITESG) ? MLX_CMD_WRITESG_OLD : MLX_CMD_READSG_OLD,
|
|
blkcount & 0xff, /* xfer length low byte */
|
|
MLX_BIO_LBA(bp), /* physical block number */
|
|
driveno, /* target drive number */
|
|
mc->mc_sgphys, /* location of SG list */
|
|
mc->mc_nsgent & 0x3f); /* size of SG list (top 3 bits clear) */
|
|
} else {
|
|
mlx_make_type5(mc, cmd,
|
|
blkcount & 0xff, /* xfer length low byte */
|
|
(driveno << 3) | ((blkcount >> 8) & 0x07), /* target and length high 3 bits */
|
|
MLX_BIO_LBA(bp), /* physical block number */
|
|
mc->mc_sgphys, /* location of SG list */
|
|
mc->mc_nsgent & 0x3f); /* size of SG list (top 3 bits clear) */
|
|
}
|
|
|
|
/* try to give command to controller */
|
|
if (mlx_start(mc) != 0) {
|
|
/* fail the command */
|
|
mc->mc_status = MLX_STATUS_WEDGED;
|
|
mlx_completeio(mc);
|
|
}
|
|
s = splbio();
|
|
}
|
|
splx(s);
|
|
mlx_lock_clr(sc, MLX_LOCK_STARTING);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Handle completion of an I/O command.
|
|
*/
|
|
static void
|
|
mlx_completeio(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
mlx_bio *bp = (mlx_bio *)mc->mc_private;
|
|
struct mlxd_softc *mlxd = (struct mlxd_softc *)MLX_BIO_SOFTC(bp);
|
|
|
|
if (mc->mc_status != MLX_STATUS_OK) { /* could be more verbose here? */
|
|
MLX_BIO_SET_ERROR(bp, EIO);
|
|
|
|
switch(mc->mc_status) {
|
|
case MLX_STATUS_RDWROFFLINE: /* system drive has gone offline */
|
|
device_printf(mlxd->mlxd_dev, "drive offline\n");
|
|
/* should signal this with a return code */
|
|
mlxd->mlxd_drive->ms_state = MLX_SYSD_OFFLINE;
|
|
break;
|
|
|
|
default: /* other I/O error */
|
|
device_printf(sc->mlx_dev, "I/O error - %s\n", mlx_diagnose_command(mc));
|
|
#if 0
|
|
device_printf(sc->mlx_dev, " b_bcount %ld blkcount %ld b_pblkno %d\n",
|
|
MLX_BIO_LENGTH(bp), MLX_BIO_LENGTH(bp) / MLX_BLKSIZE, MLX_BIO_LBA(bp));
|
|
device_printf(sc->mlx_dev, " %13D\n", mc->mc_mailbox, " ");
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
mlx_releasecmd(mc);
|
|
mlxd_intr(bp);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Take a command from user-space and try to run it.
|
|
*
|
|
* XXX Note that this can't perform very much in the way of error checking, and
|
|
* as such, applications _must_ be considered trustworthy.
|
|
* XXX Commands using S/G for data are not supported.
|
|
*/
|
|
static int
|
|
mlx_user_command(struct mlx_softc *sc, struct mlx_usercommand *mu)
|
|
{
|
|
struct mlx_command *mc;
|
|
struct mlx_dcdb *dcdb;
|
|
void *kbuf;
|
|
int error;
|
|
|
|
debug_called(0);
|
|
|
|
kbuf = NULL;
|
|
mc = NULL;
|
|
dcdb = NULL;
|
|
error = ENOMEM;
|
|
|
|
/* get ourselves a command and copy in from user space */
|
|
if ((mc = mlx_alloccmd(sc)) == NULL)
|
|
goto out;
|
|
bcopy(mu->mu_command, mc->mc_mailbox, sizeof(mc->mc_mailbox));
|
|
debug(0, "got command buffer");
|
|
|
|
/* if we need a buffer for data transfer, allocate one and copy in its initial contents */
|
|
if (mu->mu_datasize > 0) {
|
|
if (mu->mu_datasize > MAXPHYS)
|
|
return (EINVAL);
|
|
if (((kbuf = malloc(mu->mu_datasize, M_DEVBUF, M_WAITOK)) == NULL) ||
|
|
(error = copyin(mu->mu_buf, kbuf, mu->mu_datasize)))
|
|
goto out;
|
|
debug(0, "got kernel buffer");
|
|
}
|
|
|
|
/* get a command slot */
|
|
if (mlx_getslot(mc))
|
|
goto out;
|
|
debug(0, "got a slot");
|
|
|
|
/* map the command so the controller can see it */
|
|
mc->mc_data = kbuf;
|
|
mc->mc_length = mu->mu_datasize;
|
|
mlx_mapcmd(mc);
|
|
debug(0, "mapped");
|
|
|
|
/*
|
|
* If this is a passthrough SCSI command, the DCDB is packed at the
|
|
* beginning of the data area. Fix up the DCDB to point to the correct physical
|
|
* address and override any bufptr supplied by the caller since we know
|
|
* what it's meant to be.
|
|
*/
|
|
if (mc->mc_mailbox[0] == MLX_CMD_DIRECT_CDB) {
|
|
dcdb = (struct mlx_dcdb *)kbuf;
|
|
dcdb->dcdb_physaddr = mc->mc_dataphys + sizeof(*dcdb);
|
|
mu->mu_bufptr = 8;
|
|
}
|
|
|
|
/*
|
|
* If there's a data buffer, fix up the command's buffer pointer.
|
|
*/
|
|
if (mu->mu_datasize > 0) {
|
|
|
|
/* range check the pointer to physical buffer address */
|
|
if ((mu->mu_bufptr < 0) || (mu->mu_bufptr > (sizeof(mu->mu_command) - sizeof(u_int32_t)))) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
mc->mc_mailbox[mu->mu_bufptr ] = mc->mc_dataphys & 0xff;
|
|
mc->mc_mailbox[mu->mu_bufptr + 1] = (mc->mc_dataphys >> 8) & 0xff;
|
|
mc->mc_mailbox[mu->mu_bufptr + 2] = (mc->mc_dataphys >> 16) & 0xff;
|
|
mc->mc_mailbox[mu->mu_bufptr + 3] = (mc->mc_dataphys >> 24) & 0xff;
|
|
}
|
|
debug(0, "command fixup");
|
|
|
|
/* submit the command and wait */
|
|
if ((error = mlx_wait_command(mc)) != 0)
|
|
goto out;
|
|
|
|
/* copy out status and data */
|
|
mu->mu_status = mc->mc_status;
|
|
if ((mu->mu_datasize > 0) && ((error = copyout(kbuf, mu->mu_buf, mu->mu_datasize))))
|
|
goto out;
|
|
error = 0;
|
|
|
|
out:
|
|
mlx_releasecmd(mc);
|
|
if (kbuf != NULL)
|
|
free(kbuf, M_DEVBUF);
|
|
return(error);
|
|
}
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Command I/O to Controller
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Find a free command slot for (mc).
|
|
*
|
|
* Don't hand out a slot to a normal-priority command unless there are at least
|
|
* 4 slots free for priority commands.
|
|
*/
|
|
static int
|
|
mlx_getslot(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
int s, slot, limit;
|
|
|
|
debug_called(1);
|
|
|
|
/*
|
|
* Enforce slot-usage limit, if we have the required information.
|
|
*/
|
|
if (sc->mlx_enq2 != NULL) {
|
|
limit = sc->mlx_enq2->me_max_commands;
|
|
} else {
|
|
limit = 2;
|
|
}
|
|
if (sc->mlx_busycmds >= ((mc->mc_flags & MLX_CMD_PRIORITY) ? limit : limit - 4))
|
|
return(EBUSY);
|
|
|
|
/*
|
|
* Allocate an outstanding command slot
|
|
*
|
|
* XXX linear search is slow
|
|
*/
|
|
s = splbio();
|
|
for (slot = 0; slot < limit; slot++) {
|
|
debug(2, "try slot %d", slot);
|
|
if (sc->mlx_busycmd[slot] == NULL)
|
|
break;
|
|
}
|
|
if (slot < limit) {
|
|
sc->mlx_busycmd[slot] = mc;
|
|
sc->mlx_busycmds++;
|
|
}
|
|
splx(s);
|
|
|
|
/* out of slots? */
|
|
if (slot >= limit)
|
|
return(EBUSY);
|
|
|
|
debug(2, "got slot %d", slot);
|
|
mc->mc_slot = slot;
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Map/unmap (mc)'s data in the controller's addressable space.
|
|
*/
|
|
static void
|
|
mlx_setup_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error)
|
|
{
|
|
struct mlx_command *mc = (struct mlx_command *)arg;
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
struct mlx_sgentry *sg;
|
|
int i;
|
|
|
|
debug_called(1);
|
|
|
|
/* XXX should be unnecessary */
|
|
if (sc->mlx_enq2 && (nsegments > sc->mlx_enq2->me_max_sg))
|
|
panic("MLX: too many s/g segments (%d, max %d)", nsegments, sc->mlx_enq2->me_max_sg);
|
|
|
|
/* get base address of s/g table */
|
|
sg = sc->mlx_sgtable + (mc->mc_slot * MLX_NSEG);
|
|
|
|
/* save s/g table information in command */
|
|
mc->mc_nsgent = nsegments;
|
|
mc->mc_sgphys = sc->mlx_sgbusaddr + (mc->mc_slot * MLX_NSEG * sizeof(struct mlx_sgentry));
|
|
mc->mc_dataphys = segs[0].ds_addr;
|
|
|
|
/* populate s/g table */
|
|
for (i = 0; i < nsegments; i++, sg++) {
|
|
sg->sg_addr = segs[i].ds_addr;
|
|
sg->sg_count = segs[i].ds_len;
|
|
}
|
|
}
|
|
|
|
static void
|
|
mlx_mapcmd(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
|
|
debug_called(1);
|
|
|
|
/* if the command involves data at all */
|
|
if (mc->mc_data != NULL) {
|
|
|
|
/* map the data buffer into bus space and build the s/g list */
|
|
bus_dmamap_load(sc->mlx_buffer_dmat, mc->mc_dmamap, mc->mc_data, mc->mc_length,
|
|
mlx_setup_dmamap, mc, 0);
|
|
if (mc->mc_flags & MLX_CMD_DATAIN)
|
|
bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_PREREAD);
|
|
if (mc->mc_flags & MLX_CMD_DATAOUT)
|
|
bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_PREWRITE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mlx_unmapcmd(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
|
|
debug_called(1);
|
|
|
|
/* if the command involved data at all */
|
|
if (mc->mc_data != NULL) {
|
|
|
|
if (mc->mc_flags & MLX_CMD_DATAIN)
|
|
bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_POSTREAD);
|
|
if (mc->mc_flags & MLX_CMD_DATAOUT)
|
|
bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_POSTWRITE);
|
|
|
|
bus_dmamap_unload(sc->mlx_buffer_dmat, mc->mc_dmamap);
|
|
}
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Try to deliver (mc) to the controller.
|
|
*
|
|
* Can be called at any interrupt level, with or without interrupts enabled.
|
|
*/
|
|
static int
|
|
mlx_start(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
int i, s, done;
|
|
|
|
debug_called(1);
|
|
|
|
/* save the slot number as ident so we can handle this command when complete */
|
|
mc->mc_mailbox[0x1] = mc->mc_slot;
|
|
|
|
/* mark the command as currently being processed */
|
|
mc->mc_status = MLX_STATUS_BUSY;
|
|
|
|
/* set a default 60-second timeout XXX tunable? XXX not currently used */
|
|
mc->mc_timeout = time_second + 60;
|
|
|
|
/* spin waiting for the mailbox */
|
|
for (i = 100000, done = 0; (i > 0) && !done; i--) {
|
|
s = splbio();
|
|
if (sc->mlx_tryqueue(sc, mc)) {
|
|
done = 1;
|
|
/* move command to work queue */
|
|
TAILQ_INSERT_TAIL(&sc->mlx_work, mc, mc_link);
|
|
}
|
|
splx(s); /* drop spl to allow completion interrupts */
|
|
}
|
|
|
|
/* command is enqueued */
|
|
if (done)
|
|
return(0);
|
|
|
|
/*
|
|
* We couldn't get the controller to take the command. Revoke the slot
|
|
* that the command was given and return it with a bad status.
|
|
*/
|
|
sc->mlx_busycmd[mc->mc_slot] = NULL;
|
|
device_printf(sc->mlx_dev, "controller wedged (not taking commands)\n");
|
|
mc->mc_status = MLX_STATUS_WEDGED;
|
|
mlx_complete(sc);
|
|
return(EIO);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Poll the controller (sc) for completed commands.
|
|
* Update command status and free slots for reuse. If any slots were freed,
|
|
* new commands may be posted.
|
|
*
|
|
* Returns nonzero if one or more commands were completed.
|
|
*/
|
|
static int
|
|
mlx_done(struct mlx_softc *sc)
|
|
{
|
|
struct mlx_command *mc;
|
|
int s, result;
|
|
u_int8_t slot;
|
|
u_int16_t status;
|
|
|
|
debug_called(2);
|
|
|
|
result = 0;
|
|
|
|
/* loop collecting completed commands */
|
|
s = splbio();
|
|
for (;;) {
|
|
/* poll for a completed command's identifier and status */
|
|
if (sc->mlx_findcomplete(sc, &slot, &status)) {
|
|
result = 1;
|
|
mc = sc->mlx_busycmd[slot]; /* find command */
|
|
if (mc != NULL) { /* paranoia */
|
|
if (mc->mc_status == MLX_STATUS_BUSY) {
|
|
mc->mc_status = status; /* save status */
|
|
|
|
/* free slot for reuse */
|
|
sc->mlx_busycmd[slot] = NULL;
|
|
sc->mlx_busycmds--;
|
|
} else {
|
|
device_printf(sc->mlx_dev, "duplicate done event for slot %d\n", slot);
|
|
}
|
|
} else {
|
|
device_printf(sc->mlx_dev, "done event for nonbusy slot %d\n", slot);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
splx(s);
|
|
|
|
/* if we've completed any commands, try posting some more */
|
|
if (result)
|
|
mlx_startio(sc);
|
|
|
|
/* handle completion and timeouts */
|
|
mlx_complete(sc);
|
|
|
|
return(result);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Perform post-completion processing for commands on (sc).
|
|
*/
|
|
static void
|
|
mlx_complete(struct mlx_softc *sc)
|
|
{
|
|
struct mlx_command *mc, *nc;
|
|
int s, count;
|
|
|
|
debug_called(2);
|
|
|
|
/* avoid reentrancy XXX might want to signal and request a restart */
|
|
if (mlx_lock_tas(sc, MLX_LOCK_COMPLETING))
|
|
return;
|
|
|
|
s = splbio();
|
|
count = 0;
|
|
|
|
/* scan the list of busy/done commands */
|
|
mc = TAILQ_FIRST(&sc->mlx_work);
|
|
while (mc != NULL) {
|
|
nc = TAILQ_NEXT(mc, mc_link);
|
|
|
|
/* Command has been completed in some fashion */
|
|
if (mc->mc_status != MLX_STATUS_BUSY) {
|
|
|
|
/* unmap the command's data buffer */
|
|
mlx_unmapcmd(mc);
|
|
/*
|
|
* Does the command have a completion handler?
|
|
*/
|
|
if (mc->mc_complete != NULL) {
|
|
/* remove from list and give to handler */
|
|
TAILQ_REMOVE(&sc->mlx_work, mc, mc_link);
|
|
mc->mc_complete(mc);
|
|
|
|
/*
|
|
* Is there a sleeper waiting on this command?
|
|
*/
|
|
} else if (mc->mc_private != NULL) { /* sleeping caller wants to know about it */
|
|
|
|
/* remove from list and wake up sleeper */
|
|
TAILQ_REMOVE(&sc->mlx_work, mc, mc_link);
|
|
wakeup_one(mc->mc_private);
|
|
|
|
/*
|
|
* Leave the command for a caller that's polling for it.
|
|
*/
|
|
} else {
|
|
}
|
|
}
|
|
mc = nc;
|
|
}
|
|
splx(s);
|
|
|
|
mlx_lock_clr(sc, MLX_LOCK_COMPLETING);
|
|
}
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Command Buffer Management
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Get a new command buffer.
|
|
*
|
|
* This may return NULL in low-memory cases.
|
|
*
|
|
* Note that using malloc() is expensive (the command buffer is << 1 page) but
|
|
* necessary if we are to be a loadable module before the zone allocator is fixed.
|
|
*
|
|
* If possible, we recycle a command buffer that's been used before.
|
|
*
|
|
* XXX Note that command buffers are not cleaned out - it is the caller's
|
|
* responsibility to ensure that all required fields are filled in before
|
|
* using a buffer.
|
|
*/
|
|
static struct mlx_command *
|
|
mlx_alloccmd(struct mlx_softc *sc)
|
|
{
|
|
struct mlx_command *mc;
|
|
int error;
|
|
int s;
|
|
|
|
debug_called(1);
|
|
|
|
s = splbio();
|
|
if ((mc = TAILQ_FIRST(&sc->mlx_freecmds)) != NULL)
|
|
TAILQ_REMOVE(&sc->mlx_freecmds, mc, mc_link);
|
|
splx(s);
|
|
|
|
/* allocate a new command buffer? */
|
|
if (mc == NULL) {
|
|
mc = (struct mlx_command *)malloc(sizeof(*mc), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
if (mc != NULL) {
|
|
mc->mc_sc = sc;
|
|
error = bus_dmamap_create(sc->mlx_buffer_dmat, 0, &mc->mc_dmamap);
|
|
if (error) {
|
|
free(mc, M_DEVBUF);
|
|
return(NULL);
|
|
}
|
|
}
|
|
}
|
|
return(mc);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Release a command buffer for recycling.
|
|
*
|
|
* XXX It might be a good idea to limit the number of commands we save for reuse
|
|
* if it's shown that this list bloats out massively.
|
|
*/
|
|
static void
|
|
mlx_releasecmd(struct mlx_command *mc)
|
|
{
|
|
int s;
|
|
|
|
debug_called(1);
|
|
|
|
s = splbio();
|
|
TAILQ_INSERT_HEAD(&mc->mc_sc->mlx_freecmds, mc, mc_link);
|
|
splx(s);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Permanently discard a command buffer.
|
|
*/
|
|
static void
|
|
mlx_freecmd(struct mlx_command *mc)
|
|
{
|
|
struct mlx_softc *sc = mc->mc_sc;
|
|
|
|
debug_called(1);
|
|
bus_dmamap_destroy(sc->mlx_buffer_dmat, mc->mc_dmamap);
|
|
free(mc, M_DEVBUF);
|
|
}
|
|
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Type 3 interface accessor methods
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Try to give (mc) to the controller. Returns 1 if successful, 0 on failure
|
|
* (the controller is not ready to take a command).
|
|
*
|
|
* Must be called at splbio or in a fashion that prevents reentry.
|
|
*/
|
|
static int
|
|
mlx_v3_tryqueue(struct mlx_softc *sc, struct mlx_command *mc)
|
|
{
|
|
int i;
|
|
|
|
debug_called(2);
|
|
|
|
/* ready for our command? */
|
|
if (!(MLX_V3_GET_IDBR(sc) & MLX_V3_IDB_FULL)) {
|
|
/* copy mailbox data to window */
|
|
for (i = 0; i < 13; i++)
|
|
MLX_V3_PUT_MAILBOX(sc, i, mc->mc_mailbox[i]);
|
|
|
|
/* post command */
|
|
MLX_V3_PUT_IDBR(sc, MLX_V3_IDB_FULL);
|
|
return(1);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* See if a command has been completed, if so acknowledge its completion
|
|
* and recover the slot number and status code.
|
|
*
|
|
* Must be called at splbio or in a fashion that prevents reentry.
|
|
*/
|
|
static int
|
|
mlx_v3_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status)
|
|
{
|
|
|
|
debug_called(2);
|
|
|
|
/* status available? */
|
|
if (MLX_V3_GET_ODBR(sc) & MLX_V3_ODB_SAVAIL) {
|
|
*slot = MLX_V3_GET_STATUS_IDENT(sc); /* get command identifier */
|
|
*status = MLX_V3_GET_STATUS(sc); /* get status */
|
|
|
|
/* acknowledge completion */
|
|
MLX_V3_PUT_ODBR(sc, MLX_V3_ODB_SAVAIL);
|
|
MLX_V3_PUT_IDBR(sc, MLX_V3_IDB_SACK);
|
|
return(1);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Enable/disable interrupts as requested. (No acknowledge required)
|
|
*
|
|
* Must be called at splbio or in a fashion that prevents reentry.
|
|
*/
|
|
static void
|
|
mlx_v3_intaction(struct mlx_softc *sc, int action)
|
|
{
|
|
debug_called(1);
|
|
|
|
switch(action) {
|
|
case MLX_INTACTION_DISABLE:
|
|
MLX_V3_PUT_IER(sc, 0);
|
|
sc->mlx_state &= ~MLX_STATE_INTEN;
|
|
break;
|
|
case MLX_INTACTION_ENABLE:
|
|
MLX_V3_PUT_IER(sc, 1);
|
|
sc->mlx_state |= MLX_STATE_INTEN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Poll for firmware error codes during controller initialisation.
|
|
* Returns 0 if initialisation is complete, 1 if still in progress but no
|
|
* error has been fetched, 2 if an error has been retrieved.
|
|
*/
|
|
static int
|
|
mlx_v3_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2)
|
|
{
|
|
u_int8_t fwerror;
|
|
static int initted = 0;
|
|
|
|
debug_called(2);
|
|
|
|
/* first time around, clear any hardware completion status */
|
|
if (!initted) {
|
|
MLX_V3_PUT_IDBR(sc, MLX_V3_IDB_SACK);
|
|
DELAY(1000);
|
|
initted = 1;
|
|
}
|
|
|
|
/* init in progress? */
|
|
if (!(MLX_V3_GET_IDBR(sc) & MLX_V3_IDB_INIT_BUSY))
|
|
return(0);
|
|
|
|
/* test error value */
|
|
fwerror = MLX_V3_GET_FWERROR(sc);
|
|
if (!(fwerror & MLX_V3_FWERROR_PEND))
|
|
return(1);
|
|
|
|
/* mask status pending bit, fetch status */
|
|
*error = fwerror & ~MLX_V3_FWERROR_PEND;
|
|
*param1 = MLX_V3_GET_FWERROR_PARAM1(sc);
|
|
*param2 = MLX_V3_GET_FWERROR_PARAM2(sc);
|
|
|
|
/* acknowledge */
|
|
MLX_V3_PUT_FWERROR(sc, 0);
|
|
|
|
return(2);
|
|
}
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Type 4 interface accessor methods
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Try to give (mc) to the controller. Returns 1 if successful, 0 on failure
|
|
* (the controller is not ready to take a command).
|
|
*
|
|
* Must be called at splbio or in a fashion that prevents reentry.
|
|
*/
|
|
static int
|
|
mlx_v4_tryqueue(struct mlx_softc *sc, struct mlx_command *mc)
|
|
{
|
|
int i;
|
|
|
|
debug_called(2);
|
|
|
|
/* ready for our command? */
|
|
if (!(MLX_V4_GET_IDBR(sc) & MLX_V4_IDB_FULL)) {
|
|
/* copy mailbox data to window */
|
|
for (i = 0; i < 13; i++)
|
|
MLX_V4_PUT_MAILBOX(sc, i, mc->mc_mailbox[i]);
|
|
|
|
/* memory-mapped controller, so issue a write barrier to ensure the mailbox is filled */
|
|
bus_space_barrier(sc->mlx_btag, sc->mlx_bhandle, MLX_V4_MAILBOX, MLX_V4_MAILBOX_LENGTH,
|
|
BUS_SPACE_BARRIER_WRITE);
|
|
|
|
/* post command */
|
|
MLX_V4_PUT_IDBR(sc, MLX_V4_IDB_HWMBOX_CMD);
|
|
return(1);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* See if a command has been completed, if so acknowledge its completion
|
|
* and recover the slot number and status code.
|
|
*
|
|
* Must be called at splbio or in a fashion that prevents reentry.
|
|
*/
|
|
static int
|
|
mlx_v4_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status)
|
|
{
|
|
|
|
debug_called(2);
|
|
|
|
/* status available? */
|
|
if (MLX_V4_GET_ODBR(sc) & MLX_V4_ODB_HWSAVAIL) {
|
|
*slot = MLX_V4_GET_STATUS_IDENT(sc); /* get command identifier */
|
|
*status = MLX_V4_GET_STATUS(sc); /* get status */
|
|
|
|
/* acknowledge completion */
|
|
MLX_V4_PUT_ODBR(sc, MLX_V4_ODB_HWMBOX_ACK);
|
|
MLX_V4_PUT_IDBR(sc, MLX_V4_IDB_SACK);
|
|
return(1);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Enable/disable interrupts as requested.
|
|
*
|
|
* Must be called at splbio or in a fashion that prevents reentry.
|
|
*/
|
|
static void
|
|
mlx_v4_intaction(struct mlx_softc *sc, int action)
|
|
{
|
|
debug_called(1);
|
|
|
|
switch(action) {
|
|
case MLX_INTACTION_DISABLE:
|
|
MLX_V4_PUT_IER(sc, MLX_V4_IER_MASK | MLX_V4_IER_DISINT);
|
|
sc->mlx_state &= ~MLX_STATE_INTEN;
|
|
break;
|
|
case MLX_INTACTION_ENABLE:
|
|
MLX_V4_PUT_IER(sc, MLX_V4_IER_MASK & ~MLX_V4_IER_DISINT);
|
|
sc->mlx_state |= MLX_STATE_INTEN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Poll for firmware error codes during controller initialisation.
|
|
* Returns 0 if initialisation is complete, 1 if still in progress but no
|
|
* error has been fetched, 2 if an error has been retrieved.
|
|
*/
|
|
static int
|
|
mlx_v4_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2)
|
|
{
|
|
u_int8_t fwerror;
|
|
static int initted = 0;
|
|
|
|
debug_called(2);
|
|
|
|
/* first time around, clear any hardware completion status */
|
|
if (!initted) {
|
|
MLX_V4_PUT_IDBR(sc, MLX_V4_IDB_SACK);
|
|
DELAY(1000);
|
|
initted = 1;
|
|
}
|
|
|
|
/* init in progress? */
|
|
if (!(MLX_V4_GET_IDBR(sc) & MLX_V4_IDB_INIT_BUSY))
|
|
return(0);
|
|
|
|
/* test error value */
|
|
fwerror = MLX_V4_GET_FWERROR(sc);
|
|
if (!(fwerror & MLX_V4_FWERROR_PEND))
|
|
return(1);
|
|
|
|
/* mask status pending bit, fetch status */
|
|
*error = fwerror & ~MLX_V4_FWERROR_PEND;
|
|
*param1 = MLX_V4_GET_FWERROR_PARAM1(sc);
|
|
*param2 = MLX_V4_GET_FWERROR_PARAM2(sc);
|
|
|
|
/* acknowledge */
|
|
MLX_V4_PUT_FWERROR(sc, 0);
|
|
|
|
return(2);
|
|
}
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Type 5 interface accessor methods
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Try to give (mc) to the controller. Returns 1 if successful, 0 on failure
|
|
* (the controller is not ready to take a command).
|
|
*
|
|
* Must be called at splbio or in a fashion that prevents reentry.
|
|
*/
|
|
static int
|
|
mlx_v5_tryqueue(struct mlx_softc *sc, struct mlx_command *mc)
|
|
{
|
|
int i;
|
|
|
|
debug_called(2);
|
|
|
|
/* ready for our command? */
|
|
if (MLX_V5_GET_IDBR(sc) & MLX_V5_IDB_EMPTY) {
|
|
/* copy mailbox data to window */
|
|
for (i = 0; i < 13; i++)
|
|
MLX_V5_PUT_MAILBOX(sc, i, mc->mc_mailbox[i]);
|
|
|
|
/* post command */
|
|
MLX_V5_PUT_IDBR(sc, MLX_V5_IDB_HWMBOX_CMD);
|
|
return(1);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* See if a command has been completed, if so acknowledge its completion
|
|
* and recover the slot number and status code.
|
|
*
|
|
* Must be called at splbio or in a fashion that prevents reentry.
|
|
*/
|
|
static int
|
|
mlx_v5_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status)
|
|
{
|
|
|
|
debug_called(2);
|
|
|
|
/* status available? */
|
|
if (MLX_V5_GET_ODBR(sc) & MLX_V5_ODB_HWSAVAIL) {
|
|
*slot = MLX_V5_GET_STATUS_IDENT(sc); /* get command identifier */
|
|
*status = MLX_V5_GET_STATUS(sc); /* get status */
|
|
|
|
/* acknowledge completion */
|
|
MLX_V5_PUT_ODBR(sc, MLX_V5_ODB_HWMBOX_ACK);
|
|
MLX_V5_PUT_IDBR(sc, MLX_V5_IDB_SACK);
|
|
return(1);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Enable/disable interrupts as requested.
|
|
*
|
|
* Must be called at splbio or in a fashion that prevents reentry.
|
|
*/
|
|
static void
|
|
mlx_v5_intaction(struct mlx_softc *sc, int action)
|
|
{
|
|
debug_called(1);
|
|
|
|
switch(action) {
|
|
case MLX_INTACTION_DISABLE:
|
|
MLX_V5_PUT_IER(sc, 0xff & MLX_V5_IER_DISINT);
|
|
sc->mlx_state &= ~MLX_STATE_INTEN;
|
|
break;
|
|
case MLX_INTACTION_ENABLE:
|
|
MLX_V5_PUT_IER(sc, 0xff & ~MLX_V5_IER_DISINT);
|
|
sc->mlx_state |= MLX_STATE_INTEN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Poll for firmware error codes during controller initialisation.
|
|
* Returns 0 if initialisation is complete, 1 if still in progress but no
|
|
* error has been fetched, 2 if an error has been retrieved.
|
|
*/
|
|
static int
|
|
mlx_v5_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2)
|
|
{
|
|
u_int8_t fwerror;
|
|
static int initted = 0;
|
|
|
|
debug_called(2);
|
|
|
|
/* first time around, clear any hardware completion status */
|
|
if (!initted) {
|
|
MLX_V5_PUT_IDBR(sc, MLX_V5_IDB_SACK);
|
|
DELAY(1000);
|
|
initted = 1;
|
|
}
|
|
|
|
/* init in progress? */
|
|
if (MLX_V5_GET_IDBR(sc) & MLX_V5_IDB_INIT_DONE)
|
|
return(0);
|
|
|
|
/* test for error value */
|
|
fwerror = MLX_V5_GET_FWERROR(sc);
|
|
if (!(fwerror & MLX_V5_FWERROR_PEND))
|
|
return(1);
|
|
|
|
/* mask status pending bit, fetch status */
|
|
*error = fwerror & ~MLX_V5_FWERROR_PEND;
|
|
*param1 = MLX_V5_GET_FWERROR_PARAM1(sc);
|
|
*param2 = MLX_V5_GET_FWERROR_PARAM2(sc);
|
|
|
|
/* acknowledge */
|
|
MLX_V5_PUT_FWERROR(sc, 0xff);
|
|
|
|
return(2);
|
|
}
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Debugging
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Return a status message describing (mc)
|
|
*/
|
|
static char *mlx_status_messages[] = {
|
|
"normal completion", /* 00 */
|
|
"irrecoverable data error", /* 01 */
|
|
"drive does not exist, or is offline", /* 02 */
|
|
"attempt to write beyond end of drive", /* 03 */
|
|
"bad data encountered", /* 04 */
|
|
"invalid log entry request", /* 05 */
|
|
"attempt to rebuild online drive", /* 06 */
|
|
"new disk failed during rebuild", /* 07 */
|
|
"invalid channel/target", /* 08 */
|
|
"rebuild/check already in progress", /* 09 */
|
|
"one or more disks are dead", /* 10 */
|
|
"invalid or non-redundant drive", /* 11 */
|
|
"channel is busy", /* 12 */
|
|
"channel is not stopped", /* 13 */
|
|
"rebuild successfully terminated", /* 14 */
|
|
"unsupported command", /* 15 */
|
|
"check condition received", /* 16 */
|
|
"device is busy", /* 17 */
|
|
"selection or command timeout", /* 18 */
|
|
"command terminated abnormally", /* 19 */
|
|
""
|
|
};
|
|
|
|
static struct
|
|
{
|
|
int command;
|
|
u_int16_t status;
|
|
int msg;
|
|
} mlx_messages[] = {
|
|
{MLX_CMD_READSG, 0x0001, 1},
|
|
{MLX_CMD_READSG, 0x0002, 1},
|
|
{MLX_CMD_READSG, 0x0105, 3},
|
|
{MLX_CMD_READSG, 0x010c, 4},
|
|
{MLX_CMD_WRITESG, 0x0001, 1},
|
|
{MLX_CMD_WRITESG, 0x0002, 1},
|
|
{MLX_CMD_WRITESG, 0x0105, 3},
|
|
{MLX_CMD_READSG_OLD, 0x0001, 1},
|
|
{MLX_CMD_READSG_OLD, 0x0002, 1},
|
|
{MLX_CMD_READSG_OLD, 0x0105, 3},
|
|
{MLX_CMD_WRITESG_OLD, 0x0001, 1},
|
|
{MLX_CMD_WRITESG_OLD, 0x0002, 1},
|
|
{MLX_CMD_WRITESG_OLD, 0x0105, 3},
|
|
{MLX_CMD_LOGOP, 0x0105, 5},
|
|
{MLX_CMD_REBUILDASYNC, 0x0002, 6},
|
|
{MLX_CMD_REBUILDASYNC, 0x0004, 7},
|
|
{MLX_CMD_REBUILDASYNC, 0x0105, 8},
|
|
{MLX_CMD_REBUILDASYNC, 0x0106, 9},
|
|
{MLX_CMD_REBUILDASYNC, 0x0107, 14},
|
|
{MLX_CMD_CHECKASYNC, 0x0002, 10},
|
|
{MLX_CMD_CHECKASYNC, 0x0105, 11},
|
|
{MLX_CMD_CHECKASYNC, 0x0106, 9},
|
|
{MLX_CMD_STOPCHANNEL, 0x0106, 12},
|
|
{MLX_CMD_STOPCHANNEL, 0x0105, 8},
|
|
{MLX_CMD_STARTCHANNEL, 0x0005, 13},
|
|
{MLX_CMD_STARTCHANNEL, 0x0105, 8},
|
|
{MLX_CMD_DIRECT_CDB, 0x0002, 16},
|
|
{MLX_CMD_DIRECT_CDB, 0x0008, 17},
|
|
{MLX_CMD_DIRECT_CDB, 0x000e, 18},
|
|
{MLX_CMD_DIRECT_CDB, 0x000f, 19},
|
|
{MLX_CMD_DIRECT_CDB, 0x0105, 8},
|
|
|
|
{0, 0x0104, 14},
|
|
{-1, 0, 0}
|
|
};
|
|
|
|
static char *
|
|
mlx_diagnose_command(struct mlx_command *mc)
|
|
{
|
|
static char unkmsg[80];
|
|
int i;
|
|
|
|
/* look up message in table */
|
|
for (i = 0; mlx_messages[i].command != -1; i++)
|
|
if (((mc->mc_mailbox[0] == mlx_messages[i].command) || (mlx_messages[i].command == 0)) &&
|
|
(mc->mc_status == mlx_messages[i].status))
|
|
return(mlx_status_messages[mlx_messages[i].msg]);
|
|
|
|
sprintf(unkmsg, "unknown response 0x%x for command 0x%x", (int)mc->mc_status, (int)mc->mc_mailbox[0]);
|
|
return(unkmsg);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Print a string describing the controller (sc)
|
|
*/
|
|
static struct
|
|
{
|
|
int hwid;
|
|
char *name;
|
|
} mlx_controller_names[] = {
|
|
{0x01, "960P/PD"},
|
|
{0x02, "960PL"},
|
|
{0x10, "960PG"},
|
|
{0x11, "960PJ"},
|
|
{0x12, "960PR"},
|
|
{0x13, "960PT"},
|
|
{0x14, "960PTL0"},
|
|
{0x15, "960PRL"},
|
|
{0x16, "960PTL1"},
|
|
{0x20, "1164PVX"},
|
|
{-1, NULL}
|
|
};
|
|
|
|
static void
|
|
mlx_describe_controller(struct mlx_softc *sc)
|
|
{
|
|
static char buf[80];
|
|
char *model;
|
|
int i;
|
|
|
|
for (i = 0, model = NULL; mlx_controller_names[i].name != NULL; i++) {
|
|
if ((sc->mlx_enq2->me_hardware_id & 0xff) == mlx_controller_names[i].hwid) {
|
|
model = mlx_controller_names[i].name;
|
|
break;
|
|
}
|
|
}
|
|
if (model == NULL) {
|
|
sprintf(buf, " model 0x%x", sc->mlx_enq2->me_hardware_id & 0xff);
|
|
model = buf;
|
|
}
|
|
device_printf(sc->mlx_dev, "DAC%s, %d channel%s, firmware %d.%02d-%c-%02d, %dMB RAM\n",
|
|
model,
|
|
sc->mlx_enq2->me_actual_channels,
|
|
sc->mlx_enq2->me_actual_channels > 1 ? "s" : "",
|
|
sc->mlx_enq2->me_firmware_id & 0xff,
|
|
(sc->mlx_enq2->me_firmware_id >> 8) & 0xff,
|
|
(sc->mlx_enq2->me_firmware_id >> 24) & 0xff,
|
|
(sc->mlx_enq2->me_firmware_id >> 16) & 0xff,
|
|
sc->mlx_enq2->me_mem_size / (1024 * 1024));
|
|
|
|
if (bootverbose) {
|
|
device_printf(sc->mlx_dev, " Hardware ID 0x%08x\n", sc->mlx_enq2->me_hardware_id);
|
|
device_printf(sc->mlx_dev, " Firmware ID 0x%08x\n", sc->mlx_enq2->me_firmware_id);
|
|
device_printf(sc->mlx_dev, " Configured/Actual channels %d/%d\n", sc->mlx_enq2->me_configured_channels,
|
|
sc->mlx_enq2->me_actual_channels);
|
|
device_printf(sc->mlx_dev, " Max Targets %d\n", sc->mlx_enq2->me_max_targets);
|
|
device_printf(sc->mlx_dev, " Max Tags %d\n", sc->mlx_enq2->me_max_tags);
|
|
device_printf(sc->mlx_dev, " Max System Drives %d\n", sc->mlx_enq2->me_max_sys_drives);
|
|
device_printf(sc->mlx_dev, " Max Arms %d\n", sc->mlx_enq2->me_max_arms);
|
|
device_printf(sc->mlx_dev, " Max Spans %d\n", sc->mlx_enq2->me_max_spans);
|
|
device_printf(sc->mlx_dev, " DRAM/cache/flash/NVRAM size %d/%d/%d/%d\n", sc->mlx_enq2->me_mem_size,
|
|
sc->mlx_enq2->me_cache_size, sc->mlx_enq2->me_flash_size, sc->mlx_enq2->me_nvram_size);
|
|
device_printf(sc->mlx_dev, " DRAM type %d\n", sc->mlx_enq2->me_mem_type);
|
|
device_printf(sc->mlx_dev, " Clock Speed %dns\n", sc->mlx_enq2->me_clock_speed);
|
|
device_printf(sc->mlx_dev, " Hardware Speed %dns\n", sc->mlx_enq2->me_hardware_speed);
|
|
device_printf(sc->mlx_dev, " Max Commands %d\n", sc->mlx_enq2->me_max_commands);
|
|
device_printf(sc->mlx_dev, " Max SG Entries %d\n", sc->mlx_enq2->me_max_sg);
|
|
device_printf(sc->mlx_dev, " Max DP %d\n", sc->mlx_enq2->me_max_dp);
|
|
device_printf(sc->mlx_dev, " Max IOD %d\n", sc->mlx_enq2->me_max_iod);
|
|
device_printf(sc->mlx_dev, " Max Comb %d\n", sc->mlx_enq2->me_max_comb);
|
|
device_printf(sc->mlx_dev, " Latency %ds\n", sc->mlx_enq2->me_latency);
|
|
device_printf(sc->mlx_dev, " SCSI Timeout %ds\n", sc->mlx_enq2->me_scsi_timeout);
|
|
device_printf(sc->mlx_dev, " Min Free Lines %d\n", sc->mlx_enq2->me_min_freelines);
|
|
device_printf(sc->mlx_dev, " Rate Constant %d\n", sc->mlx_enq2->me_rate_const);
|
|
device_printf(sc->mlx_dev, " MAXBLK %d\n", sc->mlx_enq2->me_maxblk);
|
|
device_printf(sc->mlx_dev, " Blocking Factor %d sectors\n", sc->mlx_enq2->me_blocking_factor);
|
|
device_printf(sc->mlx_dev, " Cache Line Size %d blocks\n", sc->mlx_enq2->me_cacheline);
|
|
device_printf(sc->mlx_dev, " SCSI Capability %s%dMHz, %d bit\n",
|
|
sc->mlx_enq2->me_scsi_cap & (1<<4) ? "differential " : "",
|
|
(1 << ((sc->mlx_enq2->me_scsi_cap >> 2) & 3)) * 10,
|
|
8 << (sc->mlx_enq2->me_scsi_cap & 0x3));
|
|
device_printf(sc->mlx_dev, " Firmware Build Number %d\n", sc->mlx_enq2->me_firmware_build);
|
|
device_printf(sc->mlx_dev, " Fault Management Type %d\n", sc->mlx_enq2->me_fault_mgmt_type);
|
|
device_printf(sc->mlx_dev, " Features %b\n", sc->mlx_enq2->me_firmware_features,
|
|
"\20\4Background Init\3Read Ahead\2MORE\1Cluster\n");
|
|
|
|
}
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Emit a string describing the firmware handshake status code, and return a flag
|
|
* indicating whether the code represents a fatal error.
|
|
*
|
|
* Error code interpretations are from the Linux driver, and don't directly match
|
|
* the messages printed by Mylex's BIOS. This may change if documentation on the
|
|
* codes is forthcoming.
|
|
*/
|
|
static int
|
|
mlx_fw_message(struct mlx_softc *sc, int error, int param1, int param2)
|
|
{
|
|
switch(error) {
|
|
case 0x00:
|
|
device_printf(sc->mlx_dev, "physical drive %d:%d not responding\n", param2, param1);
|
|
break;
|
|
case 0x08:
|
|
/* we could be neater about this and give some indication when we receive more of them */
|
|
if (!(sc->mlx_flags & MLX_SPINUP_REPORTED)) {
|
|
device_printf(sc->mlx_dev, "spinning up drives...\n");
|
|
sc->mlx_flags |= MLX_SPINUP_REPORTED;
|
|
}
|
|
break;
|
|
case 0x30:
|
|
device_printf(sc->mlx_dev, "configuration checksum error\n");
|
|
break;
|
|
case 0x60:
|
|
device_printf(sc->mlx_dev, "mirror race recovery failed\n");
|
|
break;
|
|
case 0x70:
|
|
device_printf(sc->mlx_dev, "mirror race recovery in progress\n");
|
|
break;
|
|
case 0x90:
|
|
device_printf(sc->mlx_dev, "physical drive %d:%d COD mismatch\n", param2, param1);
|
|
break;
|
|
case 0xa0:
|
|
device_printf(sc->mlx_dev, "logical drive installation aborted\n");
|
|
break;
|
|
case 0xb0:
|
|
device_printf(sc->mlx_dev, "mirror race on a critical system drive\n");
|
|
break;
|
|
case 0xd0:
|
|
device_printf(sc->mlx_dev, "new controller configuration found\n");
|
|
break;
|
|
case 0xf0:
|
|
device_printf(sc->mlx_dev, "FATAL MEMORY PARITY ERROR\n");
|
|
return(1);
|
|
default:
|
|
device_printf(sc->mlx_dev, "unknown firmware initialisation error %02x:%02x:%02x\n", error, param1, param2);
|
|
break;
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/********************************************************************************
|
|
********************************************************************************
|
|
Utility Functions
|
|
********************************************************************************
|
|
********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Find the disk whose unit number is (unit) on this controller
|
|
*/
|
|
static struct mlx_sysdrive *
|
|
mlx_findunit(struct mlx_softc *sc, int unit)
|
|
{
|
|
int i;
|
|
|
|
/* search system drives */
|
|
for (i = 0; i < MLX_MAXDRIVES; i++) {
|
|
/* is this one attached? */
|
|
if (sc->mlx_sysdrive[i].ms_disk != 0) {
|
|
/* is this the one? */
|
|
if (unit == device_get_unit(sc->mlx_sysdrive[i].ms_disk))
|
|
return(&sc->mlx_sysdrive[i]);
|
|
}
|
|
}
|
|
return(NULL);
|
|
}
|