/*-
* Copyright (c) 2001 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Andrew Doran, Thor Lancelot Simon, and Eric Haszlakiewicz.
*
* 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) 2000, 2001 Michael Smith
* Copyright (c) 2000 BSDi
* 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: mly.c,v 1.8 2001/07/14 00:12:22 msmith Exp
*/
/*
* Driver for the Mylex AcceleRAID and eXtremeRAID family with v6 firmware.
*
* TODO:
*
* o Make mly->mly_btl a hash, then MLY_BTL_RESCAN becomes a SIMPLEQ.
* o Handle FC and multiple LUNs.
* o Fix mmbox usage.
* o Fix transfer speed fudge.
*/
/*
* Disable interrupts before we start talking to the controller.
*/
mly_outb(mly, mly->mly_interrupt_mask, MLY_INTERRUPT_MASK_DISABLE);
/*
* 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.
*/
if (mly_fwhandshake(mly)) {
aprint_error_dev(self, "unable to bring controller online\n");
goto bad;
}
/*
* Allocate initial command buffers, obtain controller feature
* information, and then reallocate command buffers, since we'll
* know how many we want.
*/
if (mly_alloc_ccbs(mly)) {
aprint_error_dev(self, "unable to allocate CCBs\n");
goto bad;
}
state++;
if (mly_get_controllerinfo(mly)) {
aprint_error_dev(self, "unable to retrieve controller info\n");
goto bad;
}
mly_release_ccbs(mly);
if (mly_alloc_ccbs(mly)) {
aprint_error_dev(self, "unable to allocate CCBs\n");
state--;
goto bad;
}
/*
* Get the current event counter for health purposes, populate the
* initial health status buffer.
*/
if (mly_get_eventstatus(mly)) {
aprint_error_dev(self, "unable to retrieve event status\n");
goto bad;
}
/*
* Enable memory-mailbox mode.
*/
if (mly_enable_mmbox(mly)) {
aprint_error_dev(self, "unable to enable memory mailbox\n");
goto bad;
}
/*
* Print a little information about the controller.
*/
mi = mly->mly_controllerinfo;
/*
* Clear any previous BTL information. For each bus that scsipi
* wants to scan, we'll receive the SCBUSIOLLSCAN ioctl and retrieve
* all BTL info at that point.
*/
memset(&mly->mly_btl, 0, sizeof(mly->mly_btl));
bad:
if (state > 2)
mly_release_ccbs(mly);
if (state > 1)
mly_dmamem_free(mly, sizeof(struct mly_mmbox),
mly->mly_mmbox_dmamap, (void *)mly->mly_mmbox,
&mly->mly_mmbox_seg);
if (state > 0)
mly_dmamem_free(mly, MLY_SGL_SIZE * MLY_MAX_CCBS,
mly->mly_sg_dmamap, (void *)mly->mly_sg,
&mly->mly_sg_seg);
}
/*
* Scan all possible devices on the specified channel.
*/
static void
mly_scan_channel(struct mly_softc *mly, int bus)
{
int s, target;
for (target = 0; target < MLY_MAX_TARGETS; target++) {
s = splbio();
if (!mly_scan_btl(mly, bus, target)) {
tsleep(&mly->mly_btl[bus][target], PRIBIO, "mlyscan",
0);
}
splx(s);
}
}
/*
* Shut down all configured `mly' devices.
*/
static void
mly_shutdown(void *cookie)
{
struct mly_softc *mly;
int i;
for (i = 0; i < mly_cd.cd_ndevs; i++) {
if ((mly = device_lookup_private(&mly_cd, i)) == NULL)
continue;
if (mly_flush(mly))
aprint_error_dev(mly->mly_dv, "unable to flush cache\n");
}
}
/*
* Fill in the mly_controllerinfo and mly_controllerparam fields in the
* softc.
*/
static int
mly_get_controllerinfo(struct mly_softc *mly)
{
struct mly_cmd_ioctl mci;
int rv;
/*
* Build the getcontrollerinfo ioctl and send it.
*/
memset(&mci, 0, sizeof(mci));
mci.sub_ioctl = MDACIOCTL_GETCONTROLLERINFO;
rv = mly_ioctl(mly, &mci, (void **)&mly->mly_controllerinfo,
sizeof(*mly->mly_controllerinfo), NULL, NULL);
if (rv != 0)
return (rv);
/*
* Build the getcontrollerparameter ioctl and send it.
*/
memset(&mci, 0, sizeof(mci));
mci.sub_ioctl = MDACIOCTL_GETCONTROLLERPARAMETER;
rv = mly_ioctl(mly, &mci, (void **)&mly->mly_controllerparam,
sizeof(*mly->mly_controllerparam), NULL, NULL);
return (rv);
}
/*
* Rescan a device, possibly as a consequence of getting an event which
* suggests that it may have changed. Must be called with interrupts
* blocked.
*/
static int
mly_scan_btl(struct mly_softc *mly, int bus, int target)
{
struct mly_ccb *mc;
struct mly_cmd_ioctl *mci;
int rv;
/*
* Recover the bus and target from the command. We need these even
* in the case where we don't have a useful response.
*/
mci = (struct mly_cmd_ioctl *)&mc->mc_packet->ioctl;
tmp = _3ltol(mci->addr);
rescan = 0;
if (mci->sub_ioctl == MDACIOCTL_GETLOGDEVINFOVALID) {
bus = MLY_LOGDEV_BUS(mly, MLY_LOGADDR_DEV(tmp));
target = MLY_LOGDEV_TARGET(mly, MLY_LOGADDR_DEV(tmp));
} else {
bus = MLY_PHYADDR_CHANNEL(tmp);
target = MLY_PHYADDR_TARGET(tmp);
}
btlp = &mly->mly_btl[bus][target];
/* The default result is 'no device'. */
memset(&btl, 0, sizeof(btl));
btl.mb_flags = MLY_BTL_PROTECTED;
/* If the rescan completed OK, we have possibly-new BTL data. */
if (mc->mc_status != 0)
goto out;
if (pdi->state != MLY_DEVICE_STATE_UNCONFIGURED)
btl.mb_flags |= MLY_BTL_PROTECTED;
if (pdi->command_tags != 0)
btl.mb_flags |= MLY_BTL_TQING;
} else {
printf("%s: BTL rescan result invalid\n", device_xname(mly->mly_dv));
goto out;
}
/* Decide whether we need to rescan the device. */
if (btl.mb_flags != btlp->mb_flags ||
btl.mb_speed != btlp->mb_speed ||
btl.mb_width != btlp->mb_width)
rescan = 1;
/*
* Get the current health status and set the 'next event' counter to suit.
*/
static int
mly_get_eventstatus(struct mly_softc *mly)
{
struct mly_cmd_ioctl mci;
struct mly_health_status *mh;
int rv;
/* Build the gethealthstatus ioctl and send it. */
memset(&mci, 0, sizeof(mci));
mh = NULL;
mci.sub_ioctl = MDACIOCTL_GETHEALTHSTATUS;
/* Pass it off to the controller */
return (mly_ioctl(mly, &mci, NULL, 0, NULL, NULL));
}
/*
* Perform an ioctl command.
*
* If (data) is not NULL, the command requires data transfer to the
* controller. If (*data) is NULL the command requires data transfer from
* the controller, and we will allocate a buffer for it.
*/
static int
mly_ioctl(struct mly_softc *mly, struct mly_cmd_ioctl *ioctl, void **data,
size_t datasize, void *sense_buffer,
size_t *sense_length)
{
struct mly_ccb *mc;
struct mly_cmd_ioctl *mci;
u_int8_t status;
int rv;
mc = NULL;
if ((rv = mly_ccb_alloc(mly, &mc)) != 0)
goto bad;
/*
* Copy the ioctl structure, but save some important fields and then
* fixup.
*/
mci = &mc->mc_packet->ioctl;
ioctl->sense_buffer_address = htole64(mci->sense_buffer_address);
ioctl->maximum_sense_size = mci->maximum_sense_size;
*mci = *ioctl;
mci->opcode = MDACMD_IOCTL;
mci->timeout = 30 | MLY_TIMEOUT_SECONDS;
/* Handle the data buffer. */
if (data != NULL) {
if (*data == NULL) {
/* Allocate data buffer */
mc->mc_data = malloc(datasize, M_DEVBUF, M_NOWAIT);
mc->mc_flags |= MLY_CCB_DATAIN;
} else {
mc->mc_data = *data;
mc->mc_flags |= MLY_CCB_DATAOUT;
}
mc->mc_length = datasize;
mc->mc_packet->generic.data_size = htole32(datasize);
}
/* Run the command. */
if (datasize > 0)
if ((rv = mly_ccb_map(mly, mc)) != 0)
goto bad;
rv = mly_ccb_poll(mly, mc, 30000);
if (datasize > 0)
mly_ccb_unmap(mly, mc);
if (rv != 0)
goto bad;
/* Clean up and return any data. */
status = mc->mc_status;
if (status != 0)
printf("mly_ioctl: command status %d\n", status);
bad:
if (mc != NULL) {
/* Do we need to free a data buffer we allocated? */
if (rv != 0 && mc->mc_data != NULL &&
(data == NULL || *data == NULL))
free(mc->mc_data, M_DEVBUF);
mly_ccb_free(mly, mc);
}
return (rv);
}
/*
* Check for event(s) outstanding in the controller.
*/
static void
mly_check_event(struct mly_softc *mly)
{
/*
* The controller may have updated the health status information, so
* check for it here. Note that the counters are all in host
* memory, so this check is very cheap. Also note that we depend on
* checking on completion
*/
if (le32toh(mly->mly_mmbox->mmm_health.status.change_counter) !=
mly->mly_event_change) {
mly->mly_event_change =
le32toh(mly->mly_mmbox->mmm_health.status.change_counter);
mly->mly_event_waiting =
le32toh(mly->mly_mmbox->mmm_health.status.next_event);
/* Wake up anyone that might be interested in this. */
wakeup(&mly->mly_event_change);
}
if (mly->mly_event_counter != mly->mly_event_waiting)
mly_fetch_event(mly);
}
/*
* Fetch one event from the controller. If we fail due to resource
* starvation, we'll be retried the next time a command completes.
*/
static void
mly_fetch_event(struct mly_softc *mly)
{
struct mly_ccb *mc;
struct mly_cmd_ioctl *mci;
int s;
u_int32_t event;
/* Get a command. */
if (mly_ccb_alloc(mly, &mc))
return;
/* Set up the data buffer. */
mc->mc_data = malloc(sizeof(struct mly_event), M_DEVBUF,
M_NOWAIT|M_ZERO);
/*
* Get an event number to fetch. It's possible that we've raced
* with another context for the last event, in which case there will
* be no more events.
*/
s = splbio();
if (mly->mly_event_counter == mly->mly_event_waiting) {
splx(s);
free(mc->mc_data, M_DEVBUF);
mly_ccb_free(mly, mc);
return;
}
event = mly->mly_event_counter++;
splx(s);
/*
* Build the ioctl.
*
* At this point we are committed to sending this request, as it
* will be the only one constructed for this particular event
* number.
*/
mci = (struct mly_cmd_ioctl *)&mc->mc_packet->ioctl;
mci->opcode = MDACMD_IOCTL;
mci->data_size = htole32(sizeof(struct mly_event));
_lto3l(MLY_PHYADDR(0, 0, (event >> 16) & 0xff, (event >> 24) & 0xff),
mci->addr);
mci->timeout = 30 | MLY_TIMEOUT_SECONDS;
mci->sub_ioctl = MDACIOCTL_GETEVENT;
mci->param.getevent.sequence_number_low = htole16(event & 0xffff);
/*
* Submit the command.
*/
if (mly_ccb_map(mly, mc) != 0)
goto bad;
mly_ccb_enqueue(mly, mc);
return;
/*
* Handle the completion of an event poll.
*/
static void
mly_complete_event(struct mly_softc *mly, struct mly_ccb *mc)
{
struct mly_event *me;
me = (struct mly_event *)mc->mc_data;
mly_ccb_unmap(mly, mc);
mly_ccb_free(mly, mc);
/* If the event was successfully fetched, process it. */
if (mc->mc_status == SCSI_OK)
mly_process_event(mly, me);
else
aprint_error_dev(mly->mly_dv, "unable to fetch event; status = 0x%x\n",
mc->mc_status);
free(me, M_DEVBUF);
/* Check for another event. */
mly_check_event(mly);
}
/*
* Process a controller event. Called with interrupts blocked (i.e., at
* interrupt time).
*/
static void
mly_process_event(struct mly_softc *mly, struct mly_event *me)
{
struct scsi_sense_data *ssd;
int bus, target, event, class, action;
const char *fp, *tp;
ssd = (struct scsi_sense_data *)&me->sense[0];
/*
* Errors can be reported using vendor-unique sense data. In this
* case, the event code will be 0x1c (Request sense data present),
* the sense key will be 0x09 (vendor specific), the MSB of the ASC
* will be set, and the actual event code will be a 16-bit value
* comprised of the ASCQ (low byte) and low seven bits of the ASC
* (low seven bits of the high byte).
*/
if (le32toh(me->code) == 0x1c &&
SSD_SENSE_KEY(ssd->flags) == SKEY_VENDOR_SPECIFIC &&
(ssd->asc & 0x80) != 0) {
event = ((int)(ssd->asc & ~0x80) << 8) +
ssd->ascq;
} else
event = le32toh(me->code);
/* Look up event, get codes. */
fp = mly_describe_code(mly_table_event, event);
/* Quiet event? */
class = fp[0];
#ifdef notyet
if (isupper(class) && bootverbose)
class = tolower(class);
#endif
/* Get action code, text string. */
action = fp[1];
tp = fp + 3;
/*
* Print some information about the event.
*
* This code uses a table derived from the corresponding portion of
* the Linux driver, and thus the parser is very similar.
*/
switch (class) {
case 'p':
/*
* Error on physical drive.
*/
printf("%s: physical device %d:%d %s\n", device_xname(mly->mly_dv),
me->channel, me->target, tp);
if (action == 'r')
mly->mly_btl[me->channel][me->target].mb_flags |=
MLY_BTL_RESCAN;
break;
case 'l':
case 'm':
/*
* Error on logical unit, or message about logical unit.
*/
bus = MLY_LOGDEV_BUS(mly, me->lun);
target = MLY_LOGDEV_TARGET(mly, me->lun);
printf("%s: logical device %d:%d %s\n", device_xname(mly->mly_dv),
bus, target, tp);
if (action == 'r')
mly->mly_btl[bus][target].mb_flags |= MLY_BTL_RESCAN;
break;
case 's':
/*
* Report of sense data.
*/
if ((SSD_SENSE_KEY(ssd->flags) == SKEY_NO_SENSE ||
SSD_SENSE_KEY(ssd->flags) == SKEY_NOT_READY) &&
ssd->asc == 0x04 &&
(ssd->ascq == 0x01 ||
ssd->ascq == 0x02)) {
/* Ignore NO_SENSE or NOT_READY in one case */
break;
}
/*
* XXX Should translate this if SCSIVERBOSE.
*/
printf("%s: physical device %d:%d %s\n", device_xname(mly->mly_dv),
me->channel, me->target, tp);
printf("%s: sense key %d asc %02x ascq %02x\n",
device_xname(mly->mly_dv), SSD_SENSE_KEY(ssd->flags),
ssd->asc, ssd->ascq);
printf("%s: info %x%x%x%x csi %x%x%x%x\n",
device_xname(mly->mly_dv), ssd->info[0], ssd->info[1],
ssd->info[2], ssd->info[3], ssd->csi[0],
ssd->csi[1], ssd->csi[2],
ssd->csi[3]);
if (action == 'r')
mly->mly_btl[me->channel][me->target].mb_flags |=
MLY_BTL_RESCAN;
break;
case 'e':
printf("%s: ", device_xname(mly->mly_dv));
printf(tp, me->target, me->lun);
break;
case 'c':
printf("%s: controller %s\n", device_xname(mly->mly_dv), tp);
break;
case '?':
printf("%s: %s - %d\n", device_xname(mly->mly_dv), tp, event);
break;
default:
/* Probably a 'noisy' event being ignored. */
break;
}
}
for (;;) {
/* Check for new events. */
mly_check_event(mly);
/* Re-scan up to 1 device. */
s = splbio();
done = 0;
for (bus = 0; bus < mly->mly_nchans && !done; bus++) {
for (target = 0; target < MLY_MAX_TARGETS; target++) {
/* Perform device rescan? */
btl = &mly->mly_btl[bus][target];
if ((btl->mb_flags & MLY_BTL_RESCAN) != 0) {
btl->mb_flags ^= MLY_BTL_RESCAN;
mly_scan_btl(mly, bus, target);
done = 1;
break;
}
}
}
splx(s);
/* Sleep for N seconds. */
tsleep(mly_thread, PWAIT, "mlyzzz",
hz * MLY_PERIODIC_INTERVAL);
}
}
/*
* Submit a command to the controller and poll on completion. Return
* non-zero on timeout.
*/
static int
mly_ccb_poll(struct mly_softc *mly, struct mly_ccb *mc, int timo)
{
int rv;
if ((rv = mly_ccb_submit(mly, mc)) != 0)
return (rv);
for (timo *= 10; timo != 0; timo--) {
if ((mc->mc_flags & MLY_CCB_COMPLETE) != 0)
break;
mly_intr(mly);
DELAY(100);
}
return (timo == 0);
}
/*
* Submit a command to the controller and sleep on completion. Return
* non-zero on timeout.
*/
static int
mly_ccb_wait(struct mly_softc *mly, struct mly_ccb *mc, int timo)
{
int rv, s;
mly_ccb_enqueue(mly, mc);
s = splbio();
if ((mc->mc_flags & MLY_CCB_COMPLETE) != 0) {
splx(s);
return (0);
}
rv = tsleep(mc, PRIBIO, "mlywccb", timo * hz / 1000);
splx(s);
return (rv);
}
/*
* 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 command blocks
* to the controller for execution.
*/
void
mly_ccb_enqueue(struct mly_softc *mly, struct mly_ccb *mc)
{
int s;
s = splbio();
if (mc != NULL)
SIMPLEQ_INSERT_TAIL(&mly->mly_ccb_queue, mc, mc_link.simpleq);
while ((mc = SIMPLEQ_FIRST(&mly->mly_ccb_queue)) != NULL) {
if (mly_ccb_submit(mly, mc))
break;
SIMPLEQ_REMOVE_HEAD(&mly->mly_ccb_queue, mc_link.simpleq);
}
splx(s);
}
/*
* Deliver a command to the controller.
*/
static int
mly_ccb_submit(struct mly_softc *mly, struct mly_ccb *mc)
{
union mly_cmd_packet *pkt;
int s, off;
/*
* Do we have to use the hardware mailbox?
*/
if ((mly->mly_state & MLY_STATE_MMBOX_ACTIVE) == 0) {
/*
* Check to see if the controller is ready for us.
*/
if (mly_idbr_true(mly, MLY_HM_CMDSENT)) {
splx(s);
return (EBUSY);
}
/* Signal controller and update index. */
mly_outb(mly, mly->mly_idbr, MLY_AM_CMDSENT);
mly->mly_mmbox_cmd_idx =
(mly->mly_mmbox_cmd_idx + 1) % MLY_MMBOX_COMMANDS;
}
splx(s);
return (0);
}
/*
* Pick up completed commands from the controller and handle accordingly.
*/
int
mly_intr(void *cookie)
{
struct mly_ccb *mc;
union mly_status_packet *sp;
u_int16_t slot;
int forus, off;
struct mly_softc *mly;
mly = cookie;
forus = 0;
/*
* Pick up hardware-mailbox commands.
*/
if (mly_odbr_true(mly, MLY_HM_STSREADY)) {
slot = mly_inw(mly, mly->mly_status_mailbox);
/*
* Call completion handler or wake up sleeping consumer.
*/
if (complete != NULL)
(*complete)(mly, mc);
else
wakeup(mc);
}
/*
* Allocate a command.
*/
int
mly_ccb_alloc(struct mly_softc *mly, struct mly_ccb **mcp)
{
struct mly_ccb *mc;
int s;
s = splbio();
mc = SLIST_FIRST(&mly->mly_ccb_free);
if (mc != NULL)
SLIST_REMOVE_HEAD(&mly->mly_ccb_free, mc_link.slist);
splx(s);
*mcp = mc;
return (mc == NULL ? EAGAIN : 0);
}
/*
* Release a command back to the freelist.
*/
void
mly_ccb_free(struct mly_softc *mly, struct mly_ccb *mc)
{
int s;
/*
* Fill in parts of the command that may cause confusion if a
* consumer doesn't when we are later allocated.
*/
mc->mc_data = NULL;
mc->mc_flags = 0;
mc->mc_complete = NULL;
mc->mc_private = NULL;
mc->mc_packet->generic.command_control = 0;
/*
* By default, we set up to overwrite the command packet with sense
* information.
*/
mc->mc_packet->generic.sense_buffer_address =
htole64(mc->mc_packetphys);
mc->mc_packet->generic.maximum_sense_size =
sizeof(union mly_cmd_packet);
s = splbio();
SLIST_INSERT_HEAD(&mly->mly_ccb_free, mc, mc_link.slist);
splx(s);
}
/*
* Allocate and initialize command and packet structures.
*
* If the controller supports fewer than MLY_MAX_CCBS commands, limit our
* allocation to that number. If we don't yet know how many commands the
* controller supports, allocate a very small set (suitable for initialization
* purposes only).
*/
static int
mly_alloc_ccbs(struct mly_softc *mly)
{
struct mly_ccb *mc;
int i, rv;
if (mly->mly_controllerinfo == NULL)
mly->mly_ncmds = MLY_CCBS_RESV;
else {
i = le16toh(mly->mly_controllerinfo->maximum_parallel_commands);
mly->mly_ncmds = uimin(MLY_MAX_CCBS, i);
}
/*
* Allocate enough space for all the command packets in one chunk
* and map them permanently into controller-visible space.
*/
rv = mly_dmamem_alloc(mly,
mly->mly_ncmds * sizeof(union mly_cmd_packet),
&mly->mly_pkt_dmamap, (void **)&mly->mly_pkt,
&mly->mly_pkt_busaddr, &mly->mly_pkt_seg);
if (rv)
return (rv);
/*
* Free all the storage held by commands.
*
* Must be called with all commands on the free list.
*/
static void
mly_release_ccbs(struct mly_softc *mly)
{
struct mly_ccb *mc;
/* Build the packet for the controller. */
ss = &mc->mc_packet->scsi_small;
ss->opcode = MDACMD_SCSI;
#ifdef notdef
/*
* XXX FreeBSD does this, but it doesn't fix anything,
* XXX and appears potentially harmful.
*/
ss->command_control |= MLY_CMDCTL_DISABLE_DISCONNECT;
#endif
/*
* Give the command to the controller.
*/
if ((xs->xs_control & XS_CTL_POLL) != 0) {
if (mly_ccb_poll(mly, mc, xs->timeout + 5000)) {
xs->error = XS_REQUEUE;
if (mc->mc_length != 0)
mly_ccb_unmap(mly, mc);
mly_ccb_free(mly, mc);
scsipi_done(xs);
}
} else
mly_ccb_enqueue(mly, mc);
break;
case ADAPTER_REQ_GROW_RESOURCES:
/*
* Not supported.
*/
break;
case ADAPTER_REQ_SET_XFER_MODE:
/*
* We can't change the transfer mode, but at least let
* scsipi know what the adapter has negotiated.
*/
mly_get_xfer_mode(mly, chan->chan_channel, arg);
break;
}
}
/*
* XXX The `resid' value as returned by the controller appears to be
* bogus, so we always set it to zero. Is it perhaps the transfer
* count?
*/
xs->resid = 0; /* mc->mc_resid; */
if (mc->mc_length != 0)
mly_ccb_unmap(mly, mc);
switch (mc->mc_status) {
case SCSI_OK:
/*
* In order to report logical device type and status, we
* overwrite the result of the INQUIRY command to logical
* devices.
*/
if (xs->cmd->opcode == INQUIRY) {
chan = xs->xs_periph->periph_channel;
target = xs->xs_periph->periph_target;
btl = &mly->mly_btl[chan->chan_channel][target];
s = splbio();
if ((btl->mb_flags & MLY_BTL_LOGICAL) != 0) {
inq = (struct scsipi_inquiry_data *)xs->data;
mly_padstr(inq->vendor, "MYLEX", 8);
p = mly_describe_code(mly_table_device_type,
btl->mb_type);
mly_padstr(inq->product, p, 16);
p = mly_describe_code(mly_table_device_state,
btl->mb_state);
mly_padstr(inq->revision, p, 4);
}
splx(s);
}
xs->error = XS_NOERROR;
break;
case SCSI_CHECK:
sl = mc->mc_sense;
if (sl > sizeof(xs->sense.scsi_sense))
sl = sizeof(xs->sense.scsi_sense);
memcpy(&xs->sense.scsi_sense, mc->mc_packet, sl);
xs->error = XS_SENSE;
break;
case SCSI_BUSY:
case SCSI_QUEUE_FULL:
xs->error = XS_BUSY;
break;
/*
* ioctl hook; used here only to initiate low-level rescans.
*/
static int
mly_scsipi_ioctl(struct scsipi_channel *chan, u_long cmd, void *data,
int flag, struct proc *p)
{
struct mly_softc *mly;
int rv;
/*
* Handshake with the firmware while the card is being initialized.
*/
static int
mly_fwhandshake(struct mly_softc *mly)
{
u_int8_t error;
int spinup;
spinup = 0;
/* Set HM_STSACK and let the firmware initialize. */
mly_outb(mly, mly->mly_idbr, MLY_HM_STSACK);
DELAY(1000); /* too short? */
/* If HM_STSACK is still true, the controller is initializing. */
if (!mly_idbr_true(mly, MLY_HM_STSACK))
return (0);
/*
* Spin waiting for initialization to finish, or for a message to be
* delivered.
*/
while (mly_idbr_true(mly, MLY_HM_STSACK)) {
/* Check for a message */
if (!mly_error_valid(mly))
continue;
switch (error) {
case MLY_MSG_SPINUP:
if (!spinup) {
printf("%s: drive spinup in progress\n",
device_xname(mly->mly_dv));
spinup = 1;
}
break;
case MLY_MSG_RACE_RECOVERY_FAIL:
printf("%s: mirror race recovery failed - \n",
device_xname(mly->mly_dv));
printf("%s: one or more drives offline\n",
device_xname(mly->mly_dv));
break;
case MLY_MSG_RACE_IN_PROGRESS:
printf("%s: mirror race recovery in progress\n",
device_xname(mly->mly_dv));
break;
case MLY_MSG_RACE_ON_CRITICAL:
printf("%s: mirror race recovery on critical drive\n",
device_xname(mly->mly_dv));
break;
case MLY_MSG_PARITY_ERROR:
printf("%s: FATAL MEMORY PARITY ERROR\n",
device_xname(mly->mly_dv));
return (ENXIO);
/*
* Execute a command passed in from userspace.
*
* The control structure contains the actual command for the controller, as
* well as the user-space data pointer and data size, and an optional sense
* buffer size/pointer. On completion, the data size is adjusted to the
* command residual, and the sense buffer size to the size of the returned
* sense data.
*/
static int
mly_user_command(struct mly_softc *mly, struct mly_user_command *uc)
{
struct mly_ccb *mc;
int rv, mapped;
if ((rv = mly_ccb_alloc(mly, &mc)) != 0)
return (rv);
mapped = 0;
mc->mc_data = NULL;
/*
* Handle data size/direction.
*/
if ((mc->mc_length = abs(uc->DataTransferLength)) != 0) {
if (mc->mc_length > MAXPHYS) {
rv = EINVAL;
goto out;
}
/* Copy in the command and execute it. */
memcpy(mc->mc_packet, &uc->CommandMailbox, sizeof(uc->CommandMailbox));
if ((rv = mly_ccb_wait(mly, mc, 60000)) != 0)
goto out;
/* Return the data to userspace. */
if (uc->DataTransferLength > 0) {
rv = copyout(mc->mc_data, uc->DataTransferBuffer,
mc->mc_length);
if (rv != 0)
goto out;
}
/* Return the sense buffer to userspace. */
if (uc->RequestSenseLength > 0 && mc->mc_sense > 0) {
rv = copyout(mc->mc_packet, uc->RequestSenseBuffer,
uimin(uc->RequestSenseLength, mc->mc_sense));
if (rv != 0)
goto out;
}
out:
if (mapped)
mly_ccb_unmap(mly, mc);
if (mc->mc_data != NULL)
free(mc->mc_data, M_DEVBUF);
mly_ccb_free(mly, mc);
return (rv);
}
/*
* Return health status to userspace. If the health change index in the
* user structure does not match that currently exported by the controller,
* we return the current status immediately. Otherwise, we block until
* either interrupted or new status is delivered.
*/
static int
mly_user_health(struct mly_softc *mly, struct mly_user_health *uh)
{
struct mly_health_status mh;
int rv, s;
/* Fetch the current health status from userspace. */
rv = copyin(uh->HealthStatusBuffer, &mh, sizeof(mh));
if (rv != 0)
return (rv);
/* spin waiting for a status update */
s = splbio();
if (mly->mly_event_change == mh.change_counter)
rv = tsleep(&mly->mly_event_change, PRIBIO | PCATCH,
"mlyhealth", 0);
splx(s);
if (rv == 0) {
/*
* Copy the controller's health status buffer out (there is
* a race here if it changes again).
*/
rv = copyout(&mly->mly_mmbox->mmm_health.status,
uh->HealthStatusBuffer, sizeof(uh->HealthStatusBuffer));
}