/*-
* Copyright (c) 2000, 2001, 2002, 2003, 2004 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Andrew Doran; and by Jason R. Thorpe of Wasabi Systems, Inc.
*
* 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 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: twe.c,v 1.1 2000/05/24 23:35:23 msmith Exp
*/
/*
* Driver for the 3ware Escalade family of RAID controllers.
*/
/* Save the first CCB for AEN retrieval. */
if (i != 0)
SLIST_INSERT_HEAD(&sc->sc_ccb_freelist, ccb,
ccb_chain.slist);
}
/* Wait for the controller to become ready. */
if (twe_status_wait(sc, TWE_STS_MICROCONTROLLER_READY, 6)) {
aprint_error_dev(self, "microcontroller not ready\n");
return;
}
twe_outl(sc, TWE_REG_CTL, TWE_CTL_DISABLE_INTRS);
/* Reset the controller. */
s = splbio();
rv = twe_reset(sc);
splx(s);
if (rv) {
aprint_error_dev(self, "reset failed\n");
return;
}
/* Initialise connection with controller. */
twe_init_connection(sc);
/* For each detected unit, collect size and store in an array. */
td = &sc->sc_units[unit];
/* Unit present? */
if ((dtp->tp_data[unit] & TWE_PARAM_UNITSTATUS_Online) == 0) {
/*
* XXX Should we check to see if a device has been
* XXX attached at this index and detach it if it
* XXX has? ("rescan" semantics)
*/
rv = 0;
goto out;
}
rv = twe_param_get_2(sc, TWE_PARAM_UNITINFO + unit,
TWE_PARAM_UNITINFO_DescriptorSize, &dsize);
if (rv != 0) {
aprint_error_dev(sc->sc_dev,
"error %d fetching descriptor size for unit %d\n",
rv, unit);
goto out;
}
rv = twe_param_get(sc, TWE_PARAM_UNITINFO + unit,
TWE_PARAM_UNITINFO_Descriptor, dsize - 3, NULL, &atp);
if (rv != 0) {
aprint_error_dev(sc->sc_dev,
"error %d fetching array descriptor for unit %d\n",
rv, unit);
goto out;
}
rv = twe_param_get_4(sc, TWE_PARAM_UNITINFO + unit,
TWE_PARAM_UNITINFO_Capacity, &newsize);
if (rv != 0) {
aprint_error_dev(sc->sc_dev,
"error %d fetching capacity for unit %d\n",
rv, unit);
goto out;
}
/*
* Have a device, so we need to attach it. If there is currently
* something sitting at the slot, and the parameters are different,
* then we detach the old device before attaching the new one.
*/
if (td->td_dev != NULL &&
td->td_size == newsize &&
td->td_type == newtype &&
td->td_stripe == newstripe) {
/* Same as the old device; just keep using it. */
rv = 0;
goto out;
} else if (td->td_dev != NULL) {
/* Detach the old device first. */
(void) config_detach(td->td_dev, DETACH_FORCE);
td->td_dev = NULL;
} else if (td->td_size == 0)
sc->sc_nunits++;
/*
* Committed to the new array unit; assign its parameters and
* recompute the number of available command openings.
*/
td->td_size = newsize;
td->td_type = newtype;
td->td_stripe = newstripe;
twe_recompute_openings(sc);
/* Wait for attention... */
if (twe_status_wait(sc, TWE_STS_ATTN_INTR, 30)) {
aprint_error_dev(sc->sc_dev,
"timeout waiting for attention interrupt\n");
return (-1);
}
/* ...and ACK it. */
twe_outl(sc, TWE_REG_CTL, TWE_CTL_CLEAR_ATTN_INTR);
/*
* Pull AENs out of the controller; look for a soft reset AEN.
* Open code this, since we want to detect reset even if the
* queue for management tools is full.
*
* Note that since:
* - interrupts are blocked
* - we have reset the controller
* - acknowledged the pending ATTENTION
* that there is no way a pending asynchronous AEN fetch would
* finish, so clear the flag.
*/
sc->sc_flags &= ~TWEF_AEN;
for (got = 0;;) {
rv = twe_aen_get(sc, &aen);
if (rv != 0)
printf("%s: error %d while draining event queue\n",
device_xname(sc->sc_dev), rv);
if (TWE_AEN_CODE(aen) == TWE_AEN_QUEUE_EMPTY)
break;
if (TWE_AEN_CODE(aen) == TWE_AEN_SOFT_RESET)
got = 1;
twe_aen_enqueue(sc, aen, 1);
}
if (!got) {
printf("%s: reset not reported\n", device_xname(sc->sc_dev));
return (-1);
}
/*
* Attention interrupts, signalled when a controller or child device
* state change has occurred.
*/
if ((status & TWE_STS_ATTN_INTR) != 0) {
rv = twe_aen_get(sc, NULL);
if (rv != 0)
aprint_error_dev(sc->sc_dev,
"unable to retrieve AEN (%d)\n", rv);
else
twe_outl(sc, TWE_REG_CTL, TWE_CTL_CLEAR_ATTN_INTR);
caught = 1;
}
/*
* Command interrupts, signalled when the controller can accept more
* commands. We don't use this; instead, we try to submit commands
* when we receive them, and when other commands have completed.
* Mask it so we don't get another one.
*/
if ((status & TWE_STS_CMD_INTR) != 0) {
#ifdef DEBUG
printf("%s: command interrupt\n", device_xname(sc->sc_dev));
#endif
twe_outl(sc, TWE_REG_CTL, TWE_CTL_MASK_CMD_INTR);
caught = 1;
}
/*
* Fetch an AEN. Even though this is really like parameter
* retrieval, we handle this specially, because we issue this
* AEN retrieval command from interrupt context, and thus
* reserve a CCB for it to avoid resource shortage.
*
* XXX There are still potential resource shortages we could
* XXX encounter. Consider pre-allocating all AEN-related
* XXX resources.
*
* MUST BE CALLED AT splbio()!
*/
static int
twe_aen_get(struct twe_softc *sc, uint16_t *aenp)
{
struct twe_ccb *ccb;
struct twe_cmd *tc;
struct twe_param *tp;
int rv;
/*
* If we're already retrieving an AEN, just wait; another
* retrieval will be chained after the current one completes.
*/
if (sc->sc_flags & TWEF_AEN) {
/*
* It is a fatal software programming error to attempt
* to fetch an AEN synchronously when an AEN fetch is
* already pending.
*/
KASSERT(aenp == NULL);
return (0);
}
/*
* Handle an AEN returned by the controller.
* MUST BE CALLED AT splbio()!
*/
static void
twe_aen_handler(struct twe_ccb *ccb, int error)
{
struct twe_softc *sc;
struct twe_param *tp;
uint16_t aen;
int rv;
if (TWE_AEN_CODE(aen) == TWE_AEN_QUEUE_EMPTY) {
twe_outl(sc, TWE_REG_CTL, TWE_CTL_CLEAR_ATTN_INTR);
return;
}
twe_aen_enqueue(sc, aen, 0);
/*
* Chain another retrieval in case interrupts have been
* coalesced.
*/
rv = twe_aen_get(sc, NULL);
if (rv != 0)
aprint_error_dev(sc->sc_dev,
"unable to retrieve AEN (%d)\n", rv);
}
static void
twe_aen_enqueue(struct twe_softc *sc, uint16_t aen, int quiet)
{
const char *str, *msg;
int s, next, nextnext, level;
/*
* First report the AEN on the console. Maybe.
*/
if (! quiet) {
str = twe_describe_code(twe_table_aen, TWE_AEN_CODE(aen));
if (str == NULL) {
aprint_error_dev(sc->sc_dev,
"unknown AEN 0x%04x\n", aen);
} else {
msg = str + 3;
switch (str[1]) {
case 'E': level = LOG_EMERG; break;
case 'a': level = LOG_ALERT; break;
case 'c': level = LOG_CRIT; break;
case 'e': level = LOG_ERR; break;
case 'w': level = LOG_WARNING; break;
case 'n': level = LOG_NOTICE; break;
case 'i': level = LOG_INFO; break;
case 'd': level = LOG_DEBUG; break;
default:
/* Don't use syslog. */
level = -1;
}
if (level < 0) {
switch (str[0]) {
case 'u':
case 'p':
printf("%s: %s %d: %s\n",
device_xname(sc->sc_dev),
str[0] == 'u' ? "unit" : "port",
TWE_AEN_UNIT(aen), msg);
break;
/*
* These are short-hand functions that execute TWE_OP_GET_PARAM to
* fetch 1, 2, and 4 byte parameter values, respectively.
*/
int
twe_param_get_1(struct twe_softc *sc, int table_id, int param_id,
uint8_t *valp)
{
struct twe_param *tp;
int rv;
/*
* Execute a TWE_OP_GET_PARAM command. If a callback function is provided,
* it will be called with generated context when the command has completed.
* If no callback is provided, the command will be executed synchronously
* and a pointer to a buffer containing the data returned.
*
* The caller or callback is responsible for freeing the buffer.
*
* NOTE: We assume we can sleep here to wait for a CCB to become available.
*/
int
twe_param_get(struct twe_softc *sc, int table_id, int param_id, size_t size,
void (*func)(struct twe_ccb *, int), struct twe_param **pbuf)
{
struct twe_ccb *ccb;
struct twe_cmd *tc;
struct twe_param *tp;
int rv, s;
/* Fill in the outbound parameter data. */
tp->tp_table_id = htole16(table_id);
tp->tp_param_id = param_id;
tp->tp_param_size = size;
/* Map the transfer. */
if ((rv = twe_ccb_map(sc, ccb)) != 0) {
twe_ccb_free(sc, ccb);
goto done;
}
/* Submit the command and either wait or let the callback handle it. */
if (func == NULL) {
s = splbio();
rv = twe_ccb_poll(sc, ccb, 5);
twe_ccb_unmap(sc, ccb);
twe_ccb_free(sc, ccb);
splx(s);
} else {
#ifdef DEBUG
if (pbuf != NULL)
panic("both func and pbuf defined");
#endif
twe_ccb_enqueue(sc, ccb);
return 0;
}
/*
* Execute a TWE_OP_SET_PARAM command.
*
* NOTE: We assume we can sleep here to wait for a CCB to become available.
*/
static int
twe_param_set(struct twe_softc *sc, int table_id, int param_id, size_t size,
void *sbuf)
{
struct twe_ccb *ccb;
struct twe_cmd *tc;
struct twe_param *tp;
int rv, s;
/* Fill in the outbound parameter data. */
tp->tp_table_id = htole16(table_id);
tp->tp_param_id = param_id;
tp->tp_param_size = size;
memcpy(tp->tp_data, sbuf, size);
/* Map the transfer. */
if ((rv = twe_ccb_map(sc, ccb)) != 0) {
twe_ccb_free(sc, ccb);
goto done;
}
/* Submit the command and wait. */
s = splbio();
rv = twe_ccb_poll(sc, ccb, 5);
twe_ccb_unmap(sc, ccb);
twe_ccb_free(sc, ccb);
splx(s);
done:
free(tp, M_DEVBUF);
return (rv);
}
/*
* Execute a TWE_OP_INIT_CONNECTION command. Return non-zero on error.
* Must be called with interrupts blocked.
*/
static int
twe_init_connection(struct twe_softc *sc)
{
struct twe_ccb *ccb;
struct twe_cmd *tc;
int rv;
if ((ccb = twe_ccb_alloc(sc, 0)) == NULL)
return (EAGAIN);
/* Submit the command for immediate execution. */
rv = twe_ccb_poll(sc, ccb, 5);
twe_ccb_free(sc, ccb);
return (rv);
}
/*
* Poll the controller for completed commands. Must be called with
* interrupts blocked.
*/
static void
twe_poll(struct twe_softc *sc)
{
struct twe_ccb *ccb;
int found;
u_int status, cmdid;
found = 0;
for (;;) {
status = twe_inl(sc, TWE_REG_STS);
twe_status_check(sc, status);
/* Pass notification to upper layers. */
if (ccb->ccb_tx.tx_handler != NULL)
(*ccb->ccb_tx.tx_handler)(ccb,
ccb->ccb_cmd->tc_status != 0 ? EIO : 0);
}
/* If any commands have completed, run the software queue. */
if (found)
twe_ccb_enqueue(sc, NULL);
}
/*
* Wait for `status' to be set in the controller status register. Return
* zero if found, non-zero if the operation timed out.
*/
static int
twe_status_wait(struct twe_softc *sc, u_int32_t status, int timo)
{
for (timo *= 10; timo != 0; timo--) {
if ((twe_inl(sc, TWE_REG_STS) & status) == status)
break;
delay(100000);
}
/* The location of the S/G list is dependent upon command type. */
switch (tc->tc_opcode >> 5) {
case 2:
for (i = 0; i < nsegs; i++) {
tc->tc_args.param.sgl[i].tsg_address =
htole32(ccb->ccb_dmamap_xfer->dm_segs[i].ds_addr);
tc->tc_args.param.sgl[i].tsg_length =
htole32(ccb->ccb_dmamap_xfer->dm_segs[i].ds_len);
}
/* XXX Needed? */
for (; i < TWE_SG_SIZE; i++) {
tc->tc_args.param.sgl[i].tsg_address = 0;
tc->tc_args.param.sgl[i].tsg_length = 0;
}
break;
case 3:
for (i = 0; i < nsegs; i++) {
tc->tc_args.io.sgl[i].tsg_address =
htole32(ccb->ccb_dmamap_xfer->dm_segs[i].ds_addr);
tc->tc_args.io.sgl[i].tsg_length =
htole32(ccb->ccb_dmamap_xfer->dm_segs[i].ds_len);
}
/* XXX Needed? */
for (; i < TWE_SG_SIZE; i++) {
tc->tc_args.io.sgl[i].tsg_address = 0;
tc->tc_args.io.sgl[i].tsg_length = 0;
}
break;
default:
/*
* In all likelihood, this is a command passed from
* management tools in userspace where no S/G list is
* necessary because no data is being passed.
*/
break;
}
if (ccb->ccb_abuf != (vaddr_t)0) {
if ((ccb->ccb_flags & TWE_CCB_DATA_IN) != 0)
memcpy(ccb->ccb_data, (void *)ccb->ccb_abuf,
ccb->ccb_datasize);
s = splvm();
/* XXX */
uvm_km_kmem_free(kmem_va_arena, ccb->ccb_abuf,
ccb->ccb_datasize);
splx(s);
}
}
/*
* Submit a command to the controller and poll on completion. Return
* non-zero on timeout (but don't check status, as some command types don't
* return status). Must be called with interrupts blocked.
*/
int
twe_ccb_poll(struct twe_softc *sc, struct twe_ccb *ccb, int timo)
{
int rv;
if ((rv = twe_ccb_submit(sc, ccb)) != 0)
return (rv);
for (timo *= 1000; timo != 0; timo--) {
twe_poll(sc);
if ((ccb->ccb_flags & TWE_CCB_COMPLETE) != 0)
break;
DELAY(100);
}
return (timo == 0);
}
/*
* 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
twe_ccb_enqueue(struct twe_softc *sc, struct twe_ccb *ccb)
{
int s;
s = splbio();
if (ccb != NULL)
SIMPLEQ_INSERT_TAIL(&sc->sc_ccb_queue, ccb, ccb_chain.simpleq);
while ((ccb = SIMPLEQ_FIRST(&sc->sc_ccb_queue)) != NULL) {
if (twe_ccb_submit(sc, ccb))
break;
SIMPLEQ_REMOVE_HEAD(&sc->sc_ccb_queue, ccb_chain.simpleq);
}
splx(s);
}
/*
* Submit the command block associated with the specified CCB to the
* controller for execution. Must be called with interrupts blocked.
*/
int
twe_ccb_submit(struct twe_softc *sc, struct twe_ccb *ccb)
{
bus_addr_t pa;
int rv;
u_int status;
/* Check to see if we can post a command. */
status = twe_inl(sc, TWE_REG_STS);
twe_status_check(sc, status);
/* This is intended to be compatible with the FreeBSD interface. */
switch (cmd) {
case TWEIO_COMMAND:
error = kauth_authorize_device_passthru(l->l_cred, dev,
KAUTH_REQ_DEVICE_RAWIO_PASSTHRU_ALL, data);
if (error)
return (error);
/*
* Print some information about the controller
*/
static void
twe_describe_controller(struct twe_softc *sc)
{
struct twe_param *p[6];
int i, rv = 0;
uint32_t dsize;
uint8_t ports;
ports = 0;
/* get the port count */
rv |= twe_param_get_1(sc, TWE_PARAM_CONTROLLER,
TWE_PARAM_CONTROLLER_PortCount, &ports);
rv = twe_param_get(sc, TWE_PARAM_DRIVESUMMARY,
TWE_PARAM_DRIVESUMMARY_Status, 16, NULL, &p[0]);
if (rv) {
aprint_error_dev(sc->sc_dev,
"failed to get drive status summary\n");
return;
}
for (i = 0; i < ports; i++) {
if (p[0]->tp_data[i] != TWE_PARAM_DRIVESTATUS_Present)
continue;
rv = twe_param_get_4(sc, TWE_PARAM_DRIVEINFO + i,
TWE_PARAM_DRIVEINFO_Size, &dsize);
if (rv) {
aprint_error_dev(sc->sc_dev,
"unable to get drive size for port %d\n", i);
continue;
}
rv = twe_param_get(sc, TWE_PARAM_DRIVEINFO + i,
TWE_PARAM_DRIVEINFO_Model, 40, NULL, &p[1]);
if (rv) {
aprint_error_dev(sc->sc_dev,
"unable to get drive model for port %d\n", i);
continue;
}
aprint_verbose_dev(sc->sc_dev, "port %d: %.40s %d MB\n",
i, p[1]->tp_data, dsize / 2048);
free(p[1], M_DEVBUF);
}
free(p[0], M_DEVBUF);
}
MODULE(MODULE_CLASS_DRIVER, twe, "pci");
#ifdef _MODULE
#include "ioconf.c"
#endif
static int
twe_modcmd(modcmd_t cmd, void *opaque)
{
int error = 0;