freebsd-skq/sys/dev/mlx/mlx.c
Mike Smith 4b006d7bb7 Move some hardware-related items to the hardware-related header.
Shift to using the same queueing strategy that the amr driver uses.
Some simple tests indicate that we use about 2% of the CPU at around
500tps with the controller completely saturated with I/O.
1999-10-26 23:20:43 +00:00

2412 lines
69 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/buf.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/devicestat.h>
#include <sys/disk.h>
#include <machine/resource.h>
#include <machine/bus.h>
#include <machine/clock.h>
#include <sys/rman.h>
#include <dev/mlx/mlxio.h>
#include <dev/mlx/mlxvar.h>
#include <dev/mlx/mlxreg.h>
#if 0
#define debug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args)
#else
#define debug(fmt, args...)
#endif
#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,
/* bmaj */ -1
};
static int cdev_registered = 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_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);
/*
* 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_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 char *mlx_name_controller(u_int32_t hwid);
/*
* 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");
/* 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, SYS_RES_MEMORY,
(sc->mlx_iftype == MLX_IFTYPE_3) ? MLX_CFG_BASE1 : MLX_CFG_BASE0, sc->mlx_mem);
}
/********************************************************************************
* 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");
/* 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;
debug("called");
/* 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.
*/
segsize = sizeof(struct mlx_sgentry) * MLX_NSEG * sc->mlx_maxiop;
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 *me;
struct mlx_enquiry2 *me2;
int rid, error;
debug("called");
/*
* Initialise per-controller queues.
*/
TAILQ_INIT(&sc->mlx_work);
TAILQ_INIT(&sc->mlx_freecmds);
bufq_init(&sc->mlx_bufq);
/*
* Select accessor methods based on controller interface type.
*/
switch(sc->mlx_iftype) {
case MLX_IFTYPE_3:
sc->mlx_tryqueue = mlx_v3_tryqueue;
sc->mlx_findcomplete = mlx_v3_findcomplete;
sc->mlx_intaction = mlx_v3_intaction;
break;
case MLX_IFTYPE_4:
sc->mlx_tryqueue = mlx_v4_tryqueue;
sc->mlx_findcomplete = mlx_v4_findcomplete;
sc->mlx_intaction = mlx_v4_intaction;
break;
default:
device_printf(sc->mlx_dev, "attaching unsupported interface version %d\n", sc->mlx_iftype);
return(ENXIO); /* should never happen */
}
/* disable interrupts before we start talking to the controller */
sc->mlx_intaction(sc, MLX_INTACTION_DISABLE);
/*
* 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, "couldn't allocate interrupt\n");
mlx_free(sc);
return(ENXIO);
}
error = bus_setup_intr(sc->mlx_dev, sc->mlx_irq, INTR_TYPE_BIO, mlx_intr, sc, &sc->mlx_intr);
if (error) {
device_printf(sc->mlx_dev, "couldn'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");
return(ENOMEM);
}
/*
* Create an initial set of s/g mappings.
*/
sc->mlx_maxiop = 2;
error = mlx_sglist_map(sc);
if (error != 0) {
device_printf(sc->mlx_dev, "couldn't make initial s/g list mapping\n");
return(error);
}
/*
* Probe the controller for more information.
*/
/* send an ENQUIRY to the controller */
if ((me = mlx_enquire(sc, MLX_CMD_ENQUIRY, sizeof(*me), NULL)) == NULL) {
device_printf(sc->mlx_dev, "ENQUIRY failed\n");
return(ENXIO);
}
/* pull information out of the ENQUIRY result */
sc->mlx_fwminor = me->me_fwminor;
sc->mlx_fwmajor = me->me_fwmajor;
sc->mlx_maxiop = me->me_max_commands;
sc->mlx_lastevent = sc->mlx_currevent = me->me_event_log_seq_num;
free(me, M_DEVBUF);
/* send an ENQUIRY2 to the controller */
if ((me2 = mlx_enquire(sc, MLX_CMD_ENQUIRY2, sizeof(*me2), NULL)) == NULL) {
device_printf(sc->mlx_dev, "ENQUIRY2 failed\n");
return(ENXIO);
}
/* pull information out of the ENQUIRY2 result */
sc->mlx_nchan = me2->me_configured_channels;
sc->mlx_maxiosize = me2->me_maxblk;
sc->mlx_maxtarg = me2->me_max_targets;
sc->mlx_maxtags = me2->me_max_tags;
sc->mlx_scsicap = me2->me_scsi_cap;
sc->mlx_hwid = me2->me_hardware_id;
/* print a little information about the controller and ourselves */
device_printf(sc->mlx_dev, "Mylex %s, firmware %d.%02d, %dMB RAM\n",
mlx_name_controller(sc->mlx_hwid), sc->mlx_fwmajor, sc->mlx_fwminor,
me2->me_mem_size / (1024 * 1024));
free(me2, M_DEVBUF);
/*
* Do quirk/feature related things.
*/
switch(sc->mlx_iftype) {
case MLX_IFTYPE_3:
/* XXX certify 3.52? */
if (sc->mlx_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 (sc->mlx_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;
default:
device_printf(sc->mlx_dev, "interface version corrupted to %d\n", sc->mlx_iftype);
return(ENXIO); /* should never happen */
}
/*
* Create the final set of s/g mappings now that we know how many commands
* the controller actually supports.
*/
error = mlx_sglist_map(sc);
if (error != 0) {
device_printf(sc->mlx_dev, "couldn't make initial s/g list mapping\n");
return(error);
}
/*
* No rebuild or check is in progress.
*/
sc->mlx_rebuild = -1;
sc->mlx_check = -1;
/*
* Register the control device on first attach.
*/
if (cdev_registered++ == 0)
cdevsw_add(&mlx_cdevsw);
/*
* Start the timeout routine.
*/
sc->mlx_timeout = timeout(mlx_periodic, sc, hz);
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");
/*
* 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");
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, dr);
if (dr->ms_disk == 0)
device_printf(sc->mlx_dev, "device_add_child failed\n");
}
}
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);
int error;
debug("called");
if (sc->mlx_state & MLX_STATE_OPEN)
return(EBUSY);
if ((error = mlx_shutdown(dev)))
return(error);
mlx_free(sc);
/*
* Deregister the control device on last detach.
*/
if (--cdev_registered == 0)
cdevsw_remove(&mlx_cdevsw);
return(0);
}
/********************************************************************************
* 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 bufq 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);
struct mlxd_softc *mlxd;
int i, s, error;
debug("called");
s = splbio();
error = 0;
/* assume we're going to shut down */
sc->mlx_state |= MLX_STATE_SHUTDOWN;
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 shutdown */
sc->mlx_state &= ~MLX_STATE_SHUTDOWN;
device_printf(sc->mlx_sysdrive[i].ms_disk, "still open, can't shutdown\n");
error = EBUSY;
goto out;
}
}
}
/* 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;
}
}
bus_generic_detach(sc->mlx_dev);
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");
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");
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;
int worked;
debug("called");
/* spin collecting finished commands */
worked = 0;
while (mlx_done(sc))
worked = 1;
/* did we do anything? */
if (worked)
mlx_complete(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, struct buf *bp)
{
int s;
debug("called");
s = splbio();
bufq_insert_tail(&sc->mlx_bufq, 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 proc *p)
{
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 proc *p)
{
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 proc *p)
{
int unit = minor(dev);
struct mlx_softc *sc = devclass_get_softc(mlx_devclass, unit);
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[i].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;
bus_generic_detach(sc->mlx_dev);
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_nchan) -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));
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 proc *p)
{
struct mlxd_rebuild *mr = (struct mlxd_rebuild *)addr;
struct mlxd_rebuild_status *mp = (struct mlxd_rebuild_status *)addr;
int *arg = (int *)addr;
int error;
switch(cmd) {
/*
* Return the current status of this drive.
*/
case MLXD_STATUS:
*arg = drive->ms_state;
return(0);
/*
* Start a background rebuild on this drive.
*/
case MLXD_REBUILDASYNC:
/* XXX lock? */
if (sc->mlx_rebuild >= 0)
return(EBUSY);
sc->mlx_rebuild = drive - &sc->mlx_sysdrive[0];
switch (mlx_rebuild(sc, mr->rb_channel, mr->rb_target)) {
case 0:
drive->ms_state = MLX_SYSD_REBUILD;
error = 0;
break;
case 0x10000:
error = ENOMEM; /* couldn't set up the command */
break;
case 0x0002:
case 0x0106:
error = EBUSY;
break;
case 0x0004:
error = EIO;
break;
case 0x0105:
error = ERANGE;
break;
default:
error = EINVAL;
break;
}
if (error != 0)
sc->mlx_rebuild = -1;
return(error);
/*
* Start a background consistency check on this drive.
*/
case MLXD_CHECKASYNC: /* start a background consistency check */
/* XXX implement */
break;
/*
* Get the status of the current rebuild or consistency check.
*/
case MLXD_REBUILDSTAT:
if (sc->mlx_rebuild >= 0) { /* may be a second or so out of date */
mp->rs_drive = sc->mlx_rebuild;
mp->rs_size = sc->mlx_sysdrive[sc->mlx_rebuild].ms_size;
mp->rs_remaining = sc->mlx_rebuildstat;
return(0);
} else if (sc->mlx_check >= 0) {
/* XXX implement */
} else {
/* XXX should return status of last completed operation? */
return(EINVAL);
}
}
return(ENOIOCTL);
}
/********************************************************************************
********************************************************************************
Status Monitoring
********************************************************************************
********************************************************************************/
#define MLX_PERIODIC_ISBUSY(sc) (sc->mlx_polling <= 0)
#define MLX_PERIODIC_BUSY(sc) atomic_add_int(&sc->mlx_polling, 1);
#define MLX_PERIODIC_UNBUSY(sc) atomic_subtract_int(&sc->mlx_polling, 1);
/********************************************************************************
* 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");
/*
* 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 (MLX_PERIODIC_ISBUSY(sc)) {
/* time to perform a periodic status poll? XXX tuneable interval? */
if (time_second > (sc->mlx_lastpoll + 10)) {
sc->mlx_lastpoll = time_second;
/* for caution's sake */
if (sc->mlx_polling < 0) {
device_printf(sc->mlx_dev, "mlx_polling < 0\n");
atomic_set_int(&sc->mlx_polling, 0);
}
/*
* Check controller status.
*/
MLX_PERIODIC_BUSY(sc);
mlx_enquire(sc, MLX_CMD_ENQUIRY, sizeof(struct mlx_enquiry), 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_PERIODIC_BUSY(sc);
mlx_enquire(sc, MLX_CMD_ENQSYSDRIVE, sizeof(struct mlx_enq_sys_drive) * MLX_MAXDRIVES,
mlx_periodic_enquiry);
}
/*
* Get drive rebuild/check status
*/
if (sc->mlx_rebuild >= 0) {
MLX_PERIODIC_BUSY(sc);
mlx_enquire(sc, MLX_CMD_REBUILDSTAT, sizeof(struct mlx_rebuild_stat), mlx_periodic_rebuild);
}
} else {
/*
* If things are still running from the last poll, complain about it.
*
* XXX If this becomes an issue, we should have some way of telling what
* has become stuck.
*/
device_printf(sc->mlx_dev, "poll still busy (%d)\n", sc->mlx_polling);
}
/* XXX check for wedged/timed out commands? */
/* 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");
/* Command completed OK? */
if (mc->mc_status != 0) {
device_printf(sc->mlx_dev, "periodic enquiry failed\n");
goto out;
}
/* respond to command */
switch(mc->mc_mailbox[0]) {
/*
* 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;
/* New stuff in the event log? */
if (me->me_event_log_seq_num != sc->mlx_lastevent) {
/* record where current events are up to */
sc->mlx_currevent = me->me_event_log_seq_num;
device_printf(sc->mlx_dev, "event log pointer was %d, now %d\n",
sc->mlx_lastevent, sc->mlx_currevent);
/* start poll of event log */
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++) {
/* if disk is being rebuilt, we should not check it */
if (dr->ms_state == MLX_SYSD_REBUILD) {
/* 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:
break;
}
out:
free(mc->mc_data, M_DEVBUF);
mlx_releasecmd(mc);
/* this event is done */
MLX_PERIODIC_UNBUSY(sc);
}
/********************************************************************************
* 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");
/* presume we are going to create another event */
MLX_PERIODIC_BUSY(sc);
/* 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);
/* abort this event */
MLX_PERIODIC_UNBUSY(sc);
}
}
/********************************************************************************
* 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");
if (mc->mc_status == 0) {
sc->mlx_lastevent++; /* got the message OK */
/* 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));
}
/* 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);
/* this event is done */
MLX_PERIODIC_UNBUSY(sc);
}
/********************************************************************************
* Handle the completion of a rebuild operation.
*/
static void
mlx_periodic_rebuild(struct mlx_command *mc)
{
struct mlx_softc *sc = mc->mc_sc;
struct mlx_rebuild_stat *mr = (struct mlx_rebuild_stat *)mc->mc_private;
switch(mc->mc_status) {
case 0: /* all OK, rebuild still running */
sc->mlx_rebuildstat = mr->rb_remaining;
break;
case 0x0105: /* rebuild/check finished */
if (sc->mlx_rebuild >= 0) {
device_printf(sc->mlx_sysdrive[sc->mlx_rebuild].ms_disk, "rebuild completed\n");
sc->mlx_rebuild = -1;
} else if (sc->mlx_check >= 0) {
device_printf(sc->mlx_sysdrive[sc->mlx_check].ms_disk, "consistency check completed\n");
sc->mlx_check = -1;
} else {
device_printf(sc->mlx_dev, "consistency check completed\n");
}
break;
}
free(mc->mc_data, M_DEVBUF);
mlx_releasecmd(mc);
/* this event is done */
MLX_PERIODIC_UNBUSY(sc);
}
/********************************************************************************
********************************************************************************
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_nchan; 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, 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");
/* 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");
/* 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);
/* 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, "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 rebuild on the nominated controller/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");
/* 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 rebuild command */
mlx_make_type2(mc, MLX_CMD_REBUILDASYNC, channel, target, 0, 0, 0, 0, 0, 0);
/* 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, "REBUILD ASYNC failed - %s\n", mlx_diagnose_command(mc));
} else {
device_printf(sc->mlx_sysdrive[sc->mlx_rebuild].ms_disk, "rebuild started");
}
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");
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, "I/O error 0x%x\n", mc->mc_status);
return(EIO);
}
return(0);
}
/********************************************************************************
* Start the command (mc) and busy-wait for it to complete.
*
* Should only be used when interrupts are not available. 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");
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 < 10000));
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, "I/O error 0x%x\n", mc->mc_status);
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 bufq..
*/
static void
mlx_startio(struct mlx_softc *sc)
{
struct mlx_command *mc;
struct mlxd_softc *mlxd;
struct buf *bp;
int blkcount;
int driveno;
int cmd;
int s;
/* spin until something prevents us from doing any work */
s = splbio();
for (;;) {
/* see if there's work to be done */
if ((bp = bufq_first(&sc->mlx_bufq)) == 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 */
bufq_remove(&sc->mlx_bufq, bp);
sc->mlx_waitbufs--;
splx(s);
/* connect the buf to the command */
mc->mc_complete = mlx_completeio;
mc->mc_private = bp;
mc->mc_data = bp->b_data;
mc->mc_length = bp->b_bcount;
if (bp->b_flags & B_READ) {
mc->mc_flags |= MLX_CMD_DATAIN;
cmd = MLX_CMD_READOLDSG;
} else {
mc->mc_flags |= MLX_CMD_DATAOUT;
cmd = MLX_CMD_WRITEOLDSG;
}
/* 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 *)bp->b_dev->si_drv1;
driveno = mlxd->mlxd_drive - &sc->mlx_sysdrive[0];
blkcount = bp->b_bcount / MLX_BLKSIZE;
if ((bp->b_pblkno + blkcount) > sc->mlx_sysdrive[driveno].ms_size)
device_printf(sc->mlx_dev, "I/O beyond end of unit (%u,%d > %u)\n",
bp->b_pblkno, 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.
*/
mlx_make_type5(mc, cmd,
blkcount & 0xff, /* xfer length low byte */
(driveno << 3) | ((blkcount >> 8) & 0x07), /* target and length high 3 bits */
bp->b_pblkno, /* physical block number */
mc->mc_sgphys, /* location of SG list */
mc->mc_nsgent & 0x3f); /* size of SG list (top 2 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);
}
/********************************************************************************
* Handle completion of an I/O command.
*/
static void
mlx_completeio(struct mlx_command *mc)
{
struct mlx_softc *sc = mc->mc_sc;
struct buf *bp = (struct buf *)mc->mc_private;
struct mlxd_softc *mlxd = (struct mlxd_softc *)bp->b_dev->si_drv1;
if (mc->mc_status != MLX_STATUS_OK) { /* could be more verbose here? */
bp->b_error = EIO;
bp->b_flags |= B_ERROR;
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",
bp->b_bcount, bp->b_bcount / MLX_BLKSIZE, bp->b_pblkno);
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.
*/
static int
mlx_user_command(struct mlx_softc *sc, struct mlx_usercommand *mu)
{
struct mlx_command *mc;
void *kbuf;
int error;
kbuf = NULL;
mc = NULL;
error = ENOMEM;
/* get a kernel buffer for the transfer */
if (mu->mu_datasize > 0) {
if ((kbuf = malloc(mu->mu_datasize, M_DEVBUF, M_WAITOK)) == NULL)
goto out;
if ((mu->mu_bufptr < 0) || (mu->mu_bufptr > (sizeof(mu->mu_command) < sizeof(u_int32_t)))) {
error = EINVAL;
goto out;
}
}
/* get ourselves a command buffer */
if ((mc = mlx_alloccmd(sc)) == NULL)
goto out;
/* copy the command and data */
bcopy(mu->mu_command, mc->mc_mailbox, sizeof(mc->mc_mailbox));
if ((mu->mu_datasize > 0) && ((error = copyin(mu->mu_buf, kbuf, mu->mu_datasize))))
goto out;
/* get a command slot */
if (mlx_getslot(mc))
goto out;
/* map the command so the controller can see it */
mc->mc_data = kbuf;
mc->mc_length = mu->mu_datasize;
mlx_mapcmd(mc);
/* if there's a data buffer, fix up the command */
if (mu->mu_datasize > 0) {
mc->mc_mailbox[mu->mu_bufptr ] = mc->mc_length & 0xff;
mc->mc_mailbox[mu->mu_bufptr + 1] = (mc->mc_length >> 8) & 0xff;
mc->mc_mailbox[mu->mu_bufptr + 2] = (mc->mc_length >> 16) & 0xff;
mc->mc_mailbox[mu->mu_bufptr + 3] = (mc->mc_length >> 24) & 0xff;
}
/* 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 mc %p sc %p", mc, sc);
/* enforce slot-usage limit */
limit = (mc->mc_flags & MLX_CMD_PRIORITY) ? sc->mlx_maxiop : sc->mlx_maxiop - 4;
if (sc->mlx_busycmds > limit)
return(EBUSY);
/*
* Allocate an outstanding command slot
*
* XXX linear search is slow
*/
s = splbio();
for (slot = 0; slot < sc->mlx_maxiop; slot++) {
debug("try slot %d", slot);
if (sc->mlx_busycmd[slot] == NULL)
break;
}
if (slot < sc->mlx_maxiop) {
sc->mlx_busycmd[slot] = mc;
sc->mlx_busycmds++;
}
splx(s);
/* out of slots? */
if (slot >= sc->mlx_maxiop)
return(EBUSY);
debug("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");
/* 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");
/* 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");
/* 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. Take care of any completed commands
* that we encounter while doing so.
*
* 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, worked;
debug("called");
/* 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;
/* assume we don't collect any completed commands */
worked = 0;
/* 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);
/* check for command completion while we're at it */
if (mlx_done(sc))
worked = 1;
}
/* check to see if we picked up any completed commands */
if (worked)
mlx_complete(sc);
/* 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;
return(EIO);
}
/********************************************************************************
* Look at the controller (sc) and see if a command has been completed.
* If so, move the command buffer to the done queue for later collection
* and free the slot for immediate reuse.
*
* Returns nonzero if anything was added to the done queue.
*/
static int
mlx_done(struct mlx_softc *sc)
{
struct mlx_command *mc;
int s;
u_int8_t slot;
u_int16_t status;
debug("called");
mc = NULL;
slot = 0;
/* poll for a completed command's identifier and status */
s = splbio();
if (sc->mlx_findcomplete(sc, &slot, &status)) {
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);
mc = NULL;
}
} else {
device_printf(sc->mlx_dev, "done event for nonbusy slot %d\n", slot);
}
}
splx(s);
if (mc != NULL) {
/* unmap the command's data buffer */
mlx_unmapcmd(mc);
return(1);
}
return(0);
}
/********************************************************************************
* Handle completion for all commands on (sc)'s done queue.
*/
static void
mlx_complete(struct mlx_softc *sc)
{
struct mlx_command *mc, *nc;
int s, count;
debug("called");
s = splbio();
count = 0;
/* scan the list of done commands */
mc = TAILQ_FIRST(&sc->mlx_work);
while (mc != NULL) {
nc = TAILQ_NEXT(mc, mc_link);
/* XXX this is slightly bogus */
if (count++ > (sc->mlx_maxiop * 2))
panic("mlx_work list corrupt!");
/* Skip commands that are still busy */
if (mc->mc_status != MLX_STATUS_BUSY) {
/*
* 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);
/* queue some more work if there is any */
mlx_startio(sc);
}
/********************************************************************************
********************************************************************************
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");
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);
if (mc != NULL) {
bzero(mc, sizeof(*mc));
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");
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");
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");
/* 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");
/* 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");
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;
}
}
/********************************************************************************
********************************************************************************
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");
/* 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]);
/* 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");
/* 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");
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;
}
}
/********************************************************************************
********************************************************************************
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 */
};
static struct
{
int command;
u_int16_t status;
int msg;
} mlx_messages[] = {
{MLX_CMD_READOLDSG, 0x0001, 1},
{MLX_CMD_READOLDSG, 0x0002, 1},
{MLX_CMD_READOLDSG, 0x0105, 3},
{MLX_CMD_READOLDSG, 0x010c, 4},
{MLX_CMD_WRITEOLDSG, 0x0001, 1},
{MLX_CMD_WRITEOLDSG, 0x0002, 1},
{MLX_CMD_WRITEOLDSG, 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_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},
{-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) &&
(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);
}
/*******************************************************************************
* Return a string describing the controller (hwid)
*/
static char *
mlx_name_controller(u_int32_t hwid)
{
static char buf[80];
char smbuf[16];
char *submodel;
int nchn;
switch(hwid & 0xff) {
case 0x01:
submodel = "P/PD";
break;
case 0x02:
submodel = "PL";
break;
case 0x10:
submodel = "PG";
break;
case 0x11:
submodel = "PJ";
break;
case 0x16:
submodel = "PTL";
break;
default:
sprintf(smbuf, " model 0x%x", hwid & 0xff);
submodel = smbuf;
break;
}
nchn = (hwid >> 8) & 0xff;
sprintf(buf, "DAC960%s, %d channel%s", submodel, nchn, nchn > 1 ? "s" : "");
return(buf);
}
/********************************************************************************
********************************************************************************
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);
}