/*-
* Copyright (c) 2001 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Andrew Doran.
*
* 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/
/*-
* 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.
*
* from FreeBSD: mlx.c,v 1.14.2.3 2000/08/04 06:52:50 msmith Exp
*/
/*
* Driver for the Mylex DAC960 family of RAID controllers.
*
* TODO:
*
* o Test and enable channel pause.
* o SCSI pass-through.
*/
static const char * const mlx_sense_msgs[] = {
"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 const char * const mlx_status_msgs[] = {
"normal completion", /* 0 */
"irrecoverable data error", /* 1 */
"drive does not exist, or is offline", /* 2 */
"attempt to write beyond end of drive", /* 3 */
"bad data encountered", /* 4 */
"invalid log entry request", /* 5 */
"attempt to rebuild online drive", /* 6 */
"new disk failed during rebuild", /* 7 */
"invalid channel/target", /* 8 */
"rebuild/check already in progress", /* 9 */
"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 */
"controller wedged", /* 20 */
"software timeout", /* 21 */
"command busy (?)", /* 22 */
};
/*
* Allocate and initialize the CCBs.
*/
mc = malloc(sizeof(*mc) * MLX_MAX_QUEUECNT, M_DEVBUF, M_WAITOK);
mlx->mlx_ccbs = mc;
for (i = 0; i < MLX_MAX_QUEUECNT; i++, mc++) {
mc->mc_ident = i;
rv = bus_dmamap_create(mlx->mlx_dmat, MLX_MAX_XFER,
MLX_MAX_SEGS, MLX_MAX_XFER, 0,
BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
&mc->mc_xfer_map);
if (rv != 0)
break;
mlx->mlx_nccbs++;
mlx_ccb_free(mlx, mc);
}
if (mlx->mlx_nccbs != MLX_MAX_QUEUECNT)
printf("%s: %d/%d CCBs usable\n", device_xname(mlx->mlx_dv),
mlx->mlx_nccbs, MLX_MAX_QUEUECNT);
/* Disable interrupts before we start talking to the controller */
(*mlx->mlx_intaction)(mlx, 0);
/* If we've got a reset routine, then reset the controller now. */
if (mlx->mlx_reset != NULL) {
printf("%s: resetting controller...\n",
device_xname(mlx->mlx_dv));
if ((*mlx->mlx_reset)(mlx) != 0) {
aprint_error_dev(mlx->mlx_dv, "reset failed\n");
return;
}
}
/*
* Wait for the controller to come ready, handshaking with the
* firmware if required. This is typically only necessary on
* platforms where the controller BIOS does not run.
*/
hsmsg = 0;
for (;;) {
hscode = (*mlx->mlx_fw_handshake)(mlx, &hserr, &hsparam1,
&hsparam2);
if (hscode == 0) {
if (hsmsg != 0)
printf("%s: initialization complete\n",
device_xname(mlx->mlx_dv));
break;
}
/* Report first time around... */
if (hsmsg == 0) {
printf("%s: initializing (may take some time)...\n",
device_xname(mlx->mlx_dv));
hsmsg = 1;
}
/* Did we get a real message? */
if (hscode == 2) {
hscode = mlx_fw_message(mlx, hserr, hsparam1, hsparam2);
if (ci->ci_iftype <= 2) {
/*
* These controllers may not report the firmware version in
* the ENQUIRY2 response, or may not even support it.
*/
meo = mlx_enquire(mlx, MLX_CMD_ENQUIRY_OLD,
sizeof(struct mlx_enquiry_old), NULL, 0);
if (meo == NULL) {
aprint_error_dev(mlx->mlx_dv, "ENQUIRY_OLD failed\n");
return;
}
ci->ci_firmware_id[0] = meo->me_fwmajor;
ci->ci_firmware_id[1] = meo->me_fwminor;
ci->ci_firmware_id[2] = 0;
ci->ci_firmware_id[3] = '0';
switch (ci->ci_firmware_id[0]) {
case 2:
if (ci->ci_iftype == 1) {
if (fwminor < 14)
wantfwstr = "2.14";
} else if (fwminor < 42)
wantfwstr = "2.42";
break;
case 3:
if (fwminor < 51)
wantfwstr = "3.51";
break;
case 4:
if (fwminor < 6)
wantfwstr = "4.06";
break;
case 5:
if (fwminor < 7)
wantfwstr = "5.07";
break;
}
/* Print a little information about the controller. */
mlx_describe(mlx);
if (wantfwstr != NULL) {
printf("%s: WARNING: this f/w revision is not recommended\n",
device_xname(mlx->mlx_dv));
printf("%s: WARNING: use revision %s or later\n",
device_xname(mlx->mlx_dv), wantfwstr);
}
/* We don't (yet) know where the event log is up to. */
mlx->mlx_currevent = -1;
/* No user-requested background operation is in progress. */
mlx->mlx_bg = 0;
mlx->mlx_rebuildstat.rs_code = MLX_REBUILDSTAT_IDLE;
/* Set maximum number of queued commands for `regular' operations. */
mlx->mlx_max_queuecnt =
uimin(ci->ci_max_commands, MLX_MAX_QUEUECNT) -
MLX_NCCBS_CONTROL;
#ifdef DIAGNOSTIC
if (mlx->mlx_max_queuecnt < MLX_NCCBS_CONTROL + MLX_MAX_DRIVES)
printf("%s: WARNING: few CCBs available\n",
device_xname(mlx->mlx_dv));
if (ci->ci_max_sg < MLX_MAX_SEGS) {
aprint_error_dev(mlx->mlx_dv,
"oops, not enough S/G segments\n");
return;
}
#endif
/*
* Locate disk resources and attach children to them.
*/
int
mlx_configure(struct mlx_softc *mlx, int waitok)
{
struct mlx_enquiry *me;
struct mlx_enquiry_old *meo;
struct mlx_enq_sys_drive *mes;
struct mlx_sysdrive *ms;
struct mlx_attach_args mlxa;
int i, nunits;
u_int size;
int locs[MLXCF_NLOCS];
/* Allow 1 queued command per unit while re-configuring. */
mlx_adjqparam(mlx, 1, 0);
ms = &mlx->mlx_sysdrive[0];
nunits = 0;
for (i = 0; i < MLX_MAX_DRIVES; i++, ms++) {
size = le32toh(mes[i].sd_size);
ms->ms_state = mes[i].sd_state;
/*
* If an existing device has changed in some way (e.g. no
* longer present) then detach it.
*/
if (ms->ms_dv != NULL && (size != ms->ms_size ||
(mes[i].sd_raidlevel & 0xf) != ms->ms_raidlevel))
config_detach(ms->ms_dv, DETACH_FORCE);
switch (cmd) {
case MLX_RESCAN_DRIVES:
/*
* Scan the controller to see whether new drives have
* appeared, or old ones disappeared.
*/
mlx_configure(mlx, 1);
return (0);
case MLX_PAUSE_CHANNEL:
/*
* 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.
*/
if ((mlx->mlx_flags & MLXF_PAUSEWORKS) == 0)
return (EOPNOTSUPP);
case MLX_REBUILDASYNC:
/*
* Start a rebuild on a given SCSI disk
*/
if (mlx->mlx_bg != 0) {
rb->rr_status = 0x0106;
rv = EBUSY;
break;
}
rb->rr_status = mlx_rebuild(mlx, rb->rr_channel, rb->rr_target);
switch (rb->rr_status) {
case 0:
rv = 0;
break;
case 0x10000:
rv = ENOMEM; /* Couldn't set up the command. */
break;
case 0x0002:
rv = EBUSY;
break;
case 0x0104:
rv = EIO;
break;
case 0x0105:
rv = ERANGE;
break;
case 0x0106:
rv = EBUSY;
break;
default:
rv = EINVAL;
break;
}
if (rv == 0)
mlx->mlx_bg = MLX_BG_REBUILD;
return (0);
case MLX_REBUILDSTAT:
/*
* Get the status of the current rebuild or consistency check.
*/
*rs = mlx->mlx_rebuildstat;
return (0);
case MLX_GET_SYSDRIVE:
/*
* Return the system drive number matching the `ld' device
* unit in (arg), if it happens to belong to us.
*/
for (i = 0; i < MLX_MAX_DRIVES; i++) {
ms = &mlx->mlx_sysdrive[i];
if (ms->ms_dv != NULL)
if (device_xname(ms->ms_dv)[2] == '0' + *arg) {
*arg = i;
return (0);
}
}
return (ENOENT);
switch (cmd) {
case MLXD_DETACH:
case MLXD_STATUS:
case MLXD_CHECKASYNC:
if ((u_int)*arg >= MLX_MAX_DRIVES)
return (EINVAL);
ms = &mlx->mlx_sysdrive[*arg];
if (*arg > MLX_MAX_DRIVES || ms->ms_dv == NULL)
return (ENOENT);
break;
default:
return (ENOTTY);
}
switch (cmd) {
case MLXD_DETACH:
/*
* Disconnect from the specified drive; it may be about to go
* away.
*/
return (config_detach(ms->ms_dv, 0));
case MLXD_STATUS:
/*
* Return the current status of this drive.
*/
*arg = ms->ms_state;
return (0);
case MLXD_CHECKASYNC:
/*
* Start a background consistency check on this drive.
*/
if (mlx->mlx_bg != 0) {
*arg = 0x0106;
return (EBUSY);
}
switch (result = mlx_check(mlx, *arg)) {
case 0:
rv = 0;
break;
case 0x10000:
rv = ENOMEM; /* Couldn't set up the command. */
break;
case 0x0002:
rv = EIO;
break;
case 0x0105:
rv = ERANGE;
break;
case 0x0106:
rv = EBUSY;
break;
default:
rv = EINVAL;
break;
}
static void
mlx_periodic_thread(void *cookie)
{
struct mlx_softc *mlx;
int i;
for (;;) {
for (i = 0; i < mlx_cd.cd_ndevs; i++)
if ((mlx = device_lookup_private(&mlx_cd, i)) != NULL)
if (mlx->mlx_ci.ci_iftype > 1)
mlx_periodic(mlx);
/*
* Time-out busy CCBs.
*/
s = splbio();
for (mc = TAILQ_FIRST(&mlx->mlx_ccb_worklist); mc != NULL; mc = nmc) {
nmc = TAILQ_NEXT(mc, mc_chain.tailq);
if (mc->mc_expiry > time_second) {
/*
* The remaining CCBs will expire after this one, so
* there's no point in going further.
*/
break;
}
TAILQ_REMOVE(&mlx->mlx_ccb_worklist, mc, mc_chain.tailq);
mc->mc_status = MLX_STATUS_LOST;
if (mc->mc_mx.mx_handler != NULL)
(*mc->mc_mx.mx_handler)(mc);
else if ((mc->mc_flags & MC_WAITING) != 0)
wakeup(mc);
}
splx(s);
}
/*
* Handle the result of an ENQUIRY command instigated by periodic status
* polling.
*/
static void
mlx_periodic_enquiry(struct mlx_ccb *mc)
{
struct mlx_softc *mlx;
struct mlx_enquiry *me;
struct mlx_enquiry_old *meo;
struct mlx_enq_sys_drive *mes;
struct mlx_sysdrive *dr;
const char *statestr;
int i, j;
u_int lsn;
/*
* Respond to command.
*/
switch (mc->mc_mbox[0]) {
case MLX_CMD_ENQUIRY_OLD:
/*
* This is currently a bit fruitless, as we don't know how
* to extract the eventlog pointer yet.
*/
me = (struct mlx_enquiry *)mc->mc_mx.mx_context;
meo = (struct mlx_enquiry_old *)mc->mc_mx.mx_context;
/* Convert data in-place to new format */
i = sizeof(me->me_dead) / sizeof(me->me_dead[0]);
while (--i >= 0) {
me->me_dead[i].dd_chan = meo->me_dead[i].dd_chan;
me->me_dead[i].dd_targ = meo->me_dead[i].dd_targ;
}
i = sizeof(me->me_drvsize) / sizeof(me->me_drvsize[0]);
j = sizeof(meo->me_drvsize) / sizeof(meo->me_drvsize[0]);
while (--i >= 0) {
if (i >= j)
me->me_drvsize[i] = 0;
else
me->me_drvsize[i] = meo->me_drvsize[i];
}
me->me_num_sys_drvs = meo->me_num_sys_drvs;
/* FALLTHROUGH */
case MLX_CMD_ENQUIRY:
/*
* Generic controller status update. We could do more with
* this than just checking the event log.
*/
me = (struct mlx_enquiry *)mc->mc_mx.mx_context;
lsn = le16toh(me->me_event_log_seq_num);
if (mlx->mlx_currevent == -1) {
/* Initialise our view of the event log. */
mlx->mlx_currevent = lsn;
mlx->mlx_lastevent = lsn;
} else if (lsn != mlx->mlx_lastevent &&
(mlx->mlx_flags & MLXF_EVENTLOG_BUSY) == 0) {
/* Record where current events are up to */
mlx->mlx_currevent = lsn;
/* Mark the event log as busy. */
mlx->mlx_flags |= MLXF_EVENTLOG_BUSY;
/* Drain new eventlog entries. */
mlx_periodic_eventlog_poll(mlx);
}
break;
case MLX_CMD_ENQSYSDRIVE:
/*
* Perform drive status comparison to see if something
* has failed. Don't perform the comparison if we're
* reconfiguring, since the system drive table will be
* changing.
*/
if ((mlx->mlx_flags & MLXF_RESCANNING) != 0)
break;
mes = (struct mlx_enq_sys_drive *)mc->mc_mx.mx_context;
dr = &mlx->mlx_sysdrive[0];
for (i = 0; i < mlx->mlx_numsysdrives; i++, dr++) {
/* Has state been changed by controller? */
if (dr->ms_state != mes[i].sd_state) {
switch (mes[i].sd_state) {
case MLX_SYSD_OFFLINE:
statestr = "offline";
break;
case MLX_SYSD_ONLINE:
statestr = "online";
break;
case MLX_SYSD_CRITICAL:
statestr = "critical";
break;
default:
statestr = "unknown";
break;
}
printf("%s: unit %d %s\n",
device_xname(mlx->mlx_dv), i, statestr);
/* Save new state. */
dr->ms_state = mes[i].sd_state;
}
}
break;
/*
* Instigate a poll for one event log message on (mlx). We only poll for
* one message at a time, to keep our command usage down.
*/
static void
mlx_periodic_eventlog_poll(struct mlx_softc *mlx)
{
struct mlx_ccb *mc;
void *result;
int rv;
result = NULL;
if ((rv = mlx_ccb_alloc(mlx, &mc, 1)) != 0)
goto out;
if ((result = malloc(1024, M_DEVBUF, M_WAITOK)) == NULL) {
rv = ENOMEM;
goto out;
}
if ((rv = mlx_ccb_map(mlx, mc, result, 1024, MC_XFER_IN)) != 0)
goto out;
if (mc->mc_nsgent != 1) {
mlx_ccb_unmap(mlx, mc);
printf("mlx_periodic_eventlog_poll: too many segs\n");
goto out;
}
/* Build the command to get one log entry. */
mlx_make_type3(mc, MLX_CMD_LOGOP, MLX_LOGOP_GET, 1,
mlx->mlx_lastevent, 0, 0, mc->mc_xfer_phys, 0);
/* Start the command. */
mlx_ccb_enqueue(mlx, mc);
out:
if (rv != 0) {
if (mc != NULL)
mlx_ccb_free(mlx, 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 void
mlx_periodic_eventlog_respond(struct mlx_ccb *mc)
{
struct mlx_softc *mlx;
struct mlx_eventlog_entry *el;
const char *reason;
u_int8_t sensekey, chan, targ;
mlx = device_private(mc->mc_mx.mx_dv);
el = mc->mc_mx.mx_context;
mlx_ccb_unmap(mlx, mc);
mlx->mlx_lastevent++;
if (mc->mc_status == 0) {
switch (el->el_type) {
case MLX_LOGMSG_SENSE: /* sense data */
sensekey = el->el_sense & 0x0f;
chan = (el->el_target >> 4) & 0x0f;
targ = el->el_target & 0x0f;
/*
* This is the only sort of message we understand at
* the moment. The tests here are probably
* incomplete.
*/
/*
* Mylex vendor-specific message indicating a drive
* was killed?
*/
if (sensekey == 9 && el->el_asc == 0x80) {
if (el->el_asq < sizeof(mlx_sense_msgs) /
sizeof(mlx_sense_msgs[0]))
reason = mlx_sense_msgs[el->el_asq];
else
reason = "for unknown reason";
/*
* Is there another message to obtain?
*/
if (mlx->mlx_lastevent != mlx->mlx_currevent)
mlx_periodic_eventlog_poll(mlx);
else
mlx->mlx_flags &= ~MLXF_EVENTLOG_BUSY;
}
case MLX_BG_SPONTANEOUS:
default:
/*
* If we have previously been non-idle, report the
* transition
*/
if (mlx->mlx_rebuildstat.rs_code !=
MLX_REBUILDSTAT_IDLE)
opstr = "background check/rebuild";
else
opstr = NULL;
}
if (opstr != NULL)
printf("%s: %s completed\n", device_xname(mlx->mlx_dv),
opstr);
/*
* It's time to perform a channel pause action for (mlx), either start or
* stop the pause.
*/
static void
mlx_pause_action(struct mlx_softc *mlx)
{
struct mlx_ccb *mc;
int failsafe, i, cmd;
/* What are we doing here? */
if (mlx->mlx_pause.mp_when == 0) {
cmd = MLX_CMD_STARTCHANNEL;
failsafe = 0;
} else {
cmd = 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 = ((mlx->mlx_pause.mp_howlong - time_second) + 5) / 30;
/*
* 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 must be blocked), or slept for.
*/
static void *
mlx_enquire(struct mlx_softc *mlx, int command, size_t bufsize,
void (*handler)(struct mlx_ccb *mc), int waitok)
{
struct mlx_ccb *mc;
void *result;
int rv, mapped;
result = NULL;
mapped = 0;
if ((rv = mlx_ccb_alloc(mlx, &mc, 1)) != 0)
goto out;
result = malloc(bufsize, M_DEVBUF, waitok ? M_WAITOK : M_NOWAIT);
if (result == NULL) {
printf("mlx_enquire: malloc() failed\n");
goto out;
}
if ((rv = mlx_ccb_map(mlx, mc, result, bufsize, MC_XFER_IN)) != 0)
goto out;
mapped = 1;
if (mc->mc_nsgent != 1) {
printf("mlx_enquire: too many segs\n");
goto out;
}
/* Do we want a completion callback? */
if (handler != NULL) {
mc->mc_mx.mx_context = result;
mc->mc_mx.mx_dv = mlx->mlx_dv;
mc->mc_mx.mx_handler = handler;
mlx_ccb_enqueue(mlx, mc);
} else {
/* Run the command in either polled or wait mode. */
if (waitok)
rv = mlx_ccb_wait(mlx, mc);
else
rv = mlx_ccb_poll(mlx, mc, 5000);
}
out:
/* We got a command, but nobody else will free it. */
if (handler == NULL && mc != NULL) {
if (mapped)
mlx_ccb_unmap(mlx, mc);
mlx_ccb_free(mlx, mc);
}
/* We got an error, and we allocated a result. */
if (rv != 0 && result != NULL) {
if (mc != NULL)
mlx_ccb_free(mlx, mc);
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.
*/
int
mlx_flush(struct mlx_softc *mlx, int async)
{
struct mlx_ccb *mc;
int rv;
if ((rv = mlx_ccb_alloc(mlx, &mc, 1)) != 0)
goto out;
/* Build a flush command and fire it off. */
mlx_make_type2(mc, MLX_CMD_FLUSH, 0, 0, 0, 0, 0, 0, 0, 0);
if (async)
rv = mlx_ccb_wait(mlx, mc);
else
rv = mlx_ccb_poll(mlx, mc, MLX_TIMEOUT * 1000);
if (rv != 0)
goto out;
rv = mc->mc_status;
out:
if (mc != NULL)
mlx_ccb_free(mlx, mc);
return (rv);
}
/*
* 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 *mlx, int channel, int target)
{
struct mlx_ccb *mc;
int error;
/* Build a rebuildasync 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_ccb_wait(mlx, mc) != 0)
goto out;
/* Command completed OK? */
if (mc->mc_status != 0)
aprint_normal_dev(mlx->mlx_dv, "REBUILD ASYNC failed - %s\n",
mlx_ccb_diagnose(mc));
else
aprint_normal_dev(mlx->mlx_dv, "rebuild started for %d:%d\n",
channel, target);
error = mc->mc_status;
out:
if (mc != NULL)
mlx_ccb_free(mlx, mc);
return (error);
}
/*
* 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,
* XXX 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 *mlx, struct mlx_usercommand *mu)
{
struct mlx_ccb *mc;
struct mlx_dcdb *dcdb;
void *kbuf;
int rv, mapped;
if ((mu->mu_bufdir & ~MU_XFER_MASK) != 0)
return (EINVAL);
kbuf = NULL;
dcdb = NULL;
mapped = 0;
/* Get ourselves a command and copy in from user space. */
if ((rv = mlx_ccb_alloc(mlx, &mc, 1)) != 0) {
DPRINTF(("mlx_user_command: mlx_ccb_alloc = %d\n", rv));
goto out;
}
/*
* 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);
/* Map the buffer so the controller can see it. */
rv = mlx_ccb_map(mlx, mc, kbuf, mu->mu_datasize, mu->mu_bufdir);
if (rv != 0) {
DPRINTF(("mlx_user_command: mlx_ccb_map = %d\n", rv));
goto out;
}
if (mc->mc_nsgent > 1) {
DPRINTF(("mlx_user_command: too many s/g entries\n"));
rv = EFBIG;
goto out;
}
mapped = 1;
/*
* 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_mbox[0] == MLX_CMD_DIRECT_CDB) {
dcdb = (struct mlx_dcdb *)kbuf;
dcdb->dcdb_physaddr = mc->mc_xfer_phys + 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) - 4) {
DPRINTF(("mlx_user_command: bufptr botch\n"));
rv = EINVAL;
goto out;
}
/*
* Allocate and initialise a CCB.
*/
int
mlx_ccb_alloc(struct mlx_softc *mlx, struct mlx_ccb **mcp, int control)
{
struct mlx_ccb *mc;
int s;
s = splbio();
mc = SLIST_FIRST(&mlx->mlx_ccb_freelist);
if (control) {
if (mlx->mlx_nccbs_ctrl >= MLX_NCCBS_CONTROL) {
splx(s);
*mcp = NULL;
return (EAGAIN);
}
mc->mc_flags |= MC_CONTROL;
mlx->mlx_nccbs_ctrl++;
}
SLIST_REMOVE_HEAD(&mlx->mlx_ccb_freelist, mc_chain.slist);
splx(s);
*mcp = mc;
return (0);
}
/*
* Free a CCB.
*/
void
mlx_ccb_free(struct mlx_softc *mlx, struct mlx_ccb *mc)
{
int s;
s = splbio();
if ((mc->mc_flags & MC_CONTROL) != 0)
mlx->mlx_nccbs_ctrl--;
mc->mc_flags = 0;
SLIST_INSERT_HEAD(&mlx->mlx_ccb_freelist, mc, mc_chain.slist);
splx(s);
}
/*
* If a CCB is specified, enqueue it. Pull CCBs off the software queue in
* the order that they were enqueued and try to submit their mailboxes to
* the controller for execution.
*/
void
mlx_ccb_enqueue(struct mlx_softc *mlx, struct mlx_ccb *mc)
{
int s;
s = splbio();
if (mc != NULL)
SIMPLEQ_INSERT_TAIL(&mlx->mlx_ccb_queue, mc, mc_chain.simpleq);
while ((mc = SIMPLEQ_FIRST(&mlx->mlx_ccb_queue)) != NULL) {
if (mlx_ccb_submit(mlx, mc) != 0)
break;
SIMPLEQ_REMOVE_HEAD(&mlx->mlx_ccb_queue, mc_chain.simpleq);
TAILQ_INSERT_TAIL(&mlx->mlx_ccb_worklist, mc, mc_chain.tailq);
}
splx(s);
}
/*
* Map the specified CCB's data buffer onto the bus, and fill the
* scatter-gather list.
*/
int
mlx_ccb_map(struct mlx_softc *mlx, struct mlx_ccb *mc, void *data, int size,
int dir)
{
struct mlx_sgentry *sge;
int nsegs, i, rv, sgloff;
bus_dmamap_t xfer;
/*
* Submit the CCB, and busy-wait for it to complete. Return non-zero on
* timeout or submission error. Must be called with interrupts blocked.
*/
int
mlx_ccb_poll(struct mlx_softc *mlx, struct mlx_ccb *mc, int timo)
{
int rv;
/*
* Enqueue the CCB, and sleep until it completes. Return non-zero on
* timeout or error.
*/
int
mlx_ccb_wait(struct mlx_softc *mlx, struct mlx_ccb *mc)
{
int s;
/*
* Try to submit a CCB's mailbox to the controller for execution. Return
* non-zero on timeout or error. Must be called with interrupts blocked.
*/
static int
mlx_ccb_submit(struct mlx_softc *mlx, struct mlx_ccb *mc)
{
int i, s, r;
/* Save the ident so we can handle this command when complete. */
mc->mc_mbox[1] = (u_int8_t)(mc->mc_ident + 1);
/* Mark the command as currently being processed. */
mc->mc_status = MLX_STATUS_BUSY;
mc->mc_expiry = time_second + MLX_TIMEOUT;
/* Spin waiting for the mailbox. */
for (i = 100; i != 0; i--) {
s = splbio();
r = (*mlx->mlx_submit)(mlx, mc);
splx(s);
if (r != 0)
break;
DELAY(100);
}
if (i != 0)
return (0);
/*
* Return a string that describes why a command has failed.
*/
const char *
mlx_ccb_diagnose(struct mlx_ccb *mc)
{
static char tbuf[80];
int i;
for (i = 0; i < sizeof(mlx_msgs) / sizeof(mlx_msgs[0]); i++)
if ((mc->mc_mbox[0] == mlx_msgs[i].command ||
mlx_msgs[i].command == 0) &&
mc->mc_status == mlx_msgs[i].status) {
snprintf(tbuf, sizeof(tbuf), "%s (0x%x)",
mlx_status_msgs[mlx_msgs[i].msg], mc->mc_status);
return (tbuf);
}
snprintf(tbuf, sizeof(tbuf), "unknown response 0x%x for command 0x%x",
(int)mc->mc_status, (int)mc->mc_mbox[0]);
return (tbuf);
}
/*
* Poll the controller for completed commands. Returns non-zero if one or
* more commands were completed. Must be called with interrupts blocked.
*/
int
mlx_intr(void *cookie)
{
struct mlx_softc *mlx;
struct mlx_ccb *mc;
int result;
u_int ident, status;
mlx = cookie;
result = 0;
while ((*mlx->mlx_findcomplete)(mlx, &ident, &status) != 0) {
result = 1;
ident--;
/* Record status and notify the initiator, if requested. */
mc->mc_status = status;
if (mc->mc_mx.mx_handler != NULL)
(*mc->mc_mx.mx_handler)(mc);
else if ((mc->mc_flags & MC_WAITING) != 0)
wakeup(mc);
}
/* If we've completed any commands, try posting some more. */
if (result)
mlx_ccb_enqueue(mlx, NULL);
return (result);
}
/*
* 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 *mlx, int error, int param1, int param2)
{
const char *fmt;
switch (error) {
case 0x00:
fmt = "physical drive %d:%d not responding";
break;
case 0x08:
/*
* We could be neater about this and give some indication
* when we receive more of them.
*/
if ((mlx->mlx_flags & MLXF_SPINUP_REPORTED) == 0) {
printf("%s: spinning up drives...\n",
device_xname(mlx->mlx_dv));
mlx->mlx_flags |= MLXF_SPINUP_REPORTED;
}
return (0);
case 0x30:
fmt = "configuration checksum error";
break;
case 0x60:
fmt = "mirror race recovery failed";
break;
case 0x70:
fmt = "mirror race recovery in progress";
break;
case 0x90:
fmt = "physical drive %d:%d COD mismatch";
break;
case 0xa0:
fmt = "logical drive installation aborted";
break;
case 0xb0:
fmt = "mirror race on a critical system drive";
break;
case 0xd0:
fmt = "new controller configuration found";
break;
case 0xf0:
aprint_error_dev(mlx->mlx_dv, "FATAL MEMORY PARITY ERROR\n");
return (1);