summaryrefslogtreecommitdiffstats
path: root/sys/dev/amr/amr.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/amr/amr.c')
-rw-r--r--sys/dev/amr/amr.c1808
1 files changed, 1808 insertions, 0 deletions
diff --git a/sys/dev/amr/amr.c b/sys/dev/amr/amr.c
new file mode 100644
index 0000000..29e0a30
--- /dev/null
+++ b/sys/dev/amr/amr.c
@@ -0,0 +1,1808 @@
+/*-
+ * Copyright (c) 1999,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.
+ *
+ * Copyright (c) 2002 Eric Moore
+ * Copyright (c) 2002 LSI Logic Corporation
+ * 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.
+ * 3. The party using or redistributing the source code and binary forms
+ * agrees to the disclaimer below and the terms and conditions set forth
+ * herein.
+ *
+ * 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 AMI MegaRaid family of controllers.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/kernel.h>
+
+#include <dev/amr/amr_compat.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/stat.h>
+
+#include <machine/bus_memio.h>
+#include <machine/bus_pio.h>
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <sys/rman.h>
+
+#include <pci/pcireg.h>
+#include <pci/pcivar.h>
+
+#include <dev/amr/amrio.h>
+#include <dev/amr/amrreg.h>
+#include <dev/amr/amrvar.h>
+#define AMR_DEFINE_TABLES
+#include <dev/amr/amr_tables.h>
+
+#define AMR_CDEV_MAJOR 132
+
+static d_open_t amr_open;
+static d_close_t amr_close;
+static d_ioctl_t amr_ioctl;
+
+static struct cdevsw amr_cdevsw = {
+ .d_open = amr_open,
+ .d_close = amr_close,
+ .d_ioctl = amr_ioctl,
+ .d_name = "amr",
+ .d_maj = AMR_CDEV_MAJOR,
+};
+
+/*
+ * Initialisation, bus interface.
+ */
+static void amr_startup(void *arg);
+
+/*
+ * Command wrappers
+ */
+static int amr_query_controller(struct amr_softc *sc);
+static void *amr_enquiry(struct amr_softc *sc, size_t bufsize,
+ u_int8_t cmd, u_int8_t cmdsub, u_int8_t cmdqual);
+static void amr_completeio(struct amr_command *ac);
+static int amr_support_ext_cdb(struct amr_softc *sc);
+
+/*
+ * Command buffer allocation.
+ */
+static void amr_alloccmd_cluster(struct amr_softc *sc);
+static void amr_freecmd_cluster(struct amr_command_cluster *acc);
+
+/*
+ * Command processing.
+ */
+static int amr_bio_command(struct amr_softc *sc, struct amr_command **acp);
+static int amr_wait_command(struct amr_command *ac);
+static int amr_getslot(struct amr_command *ac);
+static void amr_mapcmd(struct amr_command *ac);
+static void amr_unmapcmd(struct amr_command *ac);
+static int amr_start(struct amr_command *ac);
+static void amr_complete(void *context, int pending);
+
+/*
+ * Status monitoring
+ */
+static void amr_periodic(void *data);
+
+/*
+ * Interface-specific shims
+ */
+static int amr_quartz_submit_command(struct amr_softc *sc);
+static int amr_quartz_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave);
+static int amr_quartz_poll_command(struct amr_command *ac);
+
+static int amr_std_submit_command(struct amr_softc *sc);
+static int amr_std_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave);
+static int amr_std_poll_command(struct amr_command *ac);
+static void amr_std_attach_mailbox(struct amr_softc *sc);
+
+#ifdef AMR_BOARD_INIT
+static int amr_quartz_init(struct amr_softc *sc);
+static int amr_std_init(struct amr_softc *sc);
+#endif
+
+/*
+ * Debugging
+ */
+static void amr_describe_controller(struct amr_softc *sc);
+#ifdef AMR_DEBUG
+#if 0
+static void amr_printcommand(struct amr_command *ac);
+#endif
+#endif
+
+/********************************************************************************
+ ********************************************************************************
+ Inline Glue
+ ********************************************************************************
+ ********************************************************************************/
+
+/********************************************************************************
+ ********************************************************************************
+ Public Interfaces
+ ********************************************************************************
+ ********************************************************************************/
+
+/********************************************************************************
+ * Initialise the controller and softc.
+ */
+int
+amr_attach(struct amr_softc *sc)
+{
+
+ debug_called(1);
+
+ /*
+ * Initialise per-controller queues.
+ */
+ TAILQ_INIT(&sc->amr_completed);
+ TAILQ_INIT(&sc->amr_freecmds);
+ TAILQ_INIT(&sc->amr_cmd_clusters);
+ TAILQ_INIT(&sc->amr_ready);
+ bioq_init(&sc->amr_bioq);
+
+#if __FreeBSD_version >= 500005
+ /*
+ * Initialise command-completion task.
+ */
+ TASK_INIT(&sc->amr_task_complete, 0, amr_complete, sc);
+#endif
+
+ debug(2, "queue init done");
+
+ /*
+ * Configure for this controller type.
+ */
+ if (AMR_IS_QUARTZ(sc)) {
+ sc->amr_submit_command = amr_quartz_submit_command;
+ sc->amr_get_work = amr_quartz_get_work;
+ sc->amr_poll_command = amr_quartz_poll_command;
+ } else {
+ sc->amr_submit_command = amr_std_submit_command;
+ sc->amr_get_work = amr_std_get_work;
+ sc->amr_poll_command = amr_std_poll_command;
+ amr_std_attach_mailbox(sc);;
+ }
+
+#ifdef AMR_BOARD_INIT
+ if ((AMR_IS_QUARTZ(sc) ? amr_quartz_init(sc) : amr_std_init(sc))))
+ return(ENXIO);
+#endif
+
+ /*
+ * Quiz controller for features and limits.
+ */
+ if (amr_query_controller(sc))
+ return(ENXIO);
+
+ debug(2, "controller query complete");
+
+ /*
+ * Attach our 'real' SCSI channels to CAM.
+ */
+ if (amr_cam_attach(sc))
+ return(ENXIO);
+ debug(2, "CAM attach done");
+
+ /*
+ * Create the control device.
+ */
+ sc->amr_dev_t = make_dev(&amr_cdevsw, device_get_unit(sc->amr_dev), UID_ROOT, GID_OPERATOR,
+ S_IRUSR | S_IWUSR, "amr%d", device_get_unit(sc->amr_dev));
+ sc->amr_dev_t->si_drv1 = sc;
+
+ /*
+ * Schedule ourselves to bring the controller up once interrupts are
+ * available.
+ */
+ bzero(&sc->amr_ich, sizeof(struct intr_config_hook));
+ sc->amr_ich.ich_func = amr_startup;
+ sc->amr_ich.ich_arg = sc;
+ if (config_intrhook_establish(&sc->amr_ich) != 0) {
+ device_printf(sc->amr_dev, "can't establish configuration hook\n");
+ return(ENOMEM);
+ }
+
+ /*
+ * Print a little information about the controller.
+ */
+ amr_describe_controller(sc);
+
+ debug(2, "attach complete");
+ return(0);
+}
+
+/********************************************************************************
+ * Locate disk resources and attach children to them.
+ */
+static void
+amr_startup(void *arg)
+{
+ struct amr_softc *sc = (struct amr_softc *)arg;
+ struct amr_logdrive *dr;
+ int i, error;
+
+ debug_called(1);
+
+ /* pull ourselves off the intrhook chain */
+ config_intrhook_disestablish(&sc->amr_ich);
+
+ /* get up-to-date drive information */
+ if (amr_query_controller(sc)) {
+ device_printf(sc->amr_dev, "can't scan controller for drives\n");
+ return;
+ }
+
+ /* iterate over available drives */
+ for (i = 0, dr = &sc->amr_drive[0]; (i < AMR_MAXLD) && (dr->al_size != 0xffffffff); i++, dr++) {
+ /* are we already attached to this drive? */
+ if (dr->al_disk == 0) {
+ /* generate geometry information */
+ if (dr->al_size > 0x200000) { /* extended translation? */
+ dr->al_heads = 255;
+ dr->al_sectors = 63;
+ } else {
+ dr->al_heads = 64;
+ dr->al_sectors = 32;
+ }
+ dr->al_cylinders = dr->al_size / (dr->al_heads * dr->al_sectors);
+
+ dr->al_disk = device_add_child(sc->amr_dev, NULL, -1);
+ if (dr->al_disk == 0)
+ device_printf(sc->amr_dev, "device_add_child failed\n");
+ device_set_ivars(dr->al_disk, dr);
+ }
+ }
+
+ if ((error = bus_generic_attach(sc->amr_dev)) != 0)
+ device_printf(sc->amr_dev, "bus_generic_attach returned %d\n", error);
+
+ /* mark controller back up */
+ sc->amr_state &= ~AMR_STATE_SHUTDOWN;
+
+ /* interrupts will be enabled before we do anything more */
+ sc->amr_state |= AMR_STATE_INTEN;
+
+ /*
+ * Start the timeout routine.
+ */
+/* sc->amr_timeout = timeout(amr_periodic, sc, hz);*/
+
+ return;
+}
+
+/*******************************************************************************
+ * Free resources associated with a controller instance
+ */
+void
+amr_free(struct amr_softc *sc)
+{
+ struct amr_command_cluster *acc;
+
+ /* detach from CAM */
+ amr_cam_detach(sc);
+
+ /* cancel status timeout */
+ untimeout(amr_periodic, sc, sc->amr_timeout);
+
+ /* throw away any command buffers */
+ while ((acc = TAILQ_FIRST(&sc->amr_cmd_clusters)) != NULL) {
+ TAILQ_REMOVE(&sc->amr_cmd_clusters, acc, acc_link);
+ amr_freecmd_cluster(acc);
+ }
+
+ /* destroy control device */
+ if( sc->amr_dev_t != (dev_t)NULL)
+ destroy_dev(sc->amr_dev_t);
+}
+
+/*******************************************************************************
+ * Receive a bio 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
+amr_submit_bio(struct amr_softc *sc, struct bio *bio)
+{
+ debug_called(2);
+
+ amr_enqueue_bio(sc, bio);
+ amr_startio(sc);
+ return(0);
+}
+
+/********************************************************************************
+ * Accept an open operation on the control device.
+ */
+static int
+amr_open(dev_t dev, int flags, int fmt, d_thread_t *td)
+{
+ int unit = minor(dev);
+ struct amr_softc *sc = devclass_get_softc(devclass_find("amr"), unit);
+
+ debug_called(1);
+
+ sc->amr_state |= AMR_STATE_OPEN;
+ return(0);
+}
+
+/********************************************************************************
+ * Accept the last close on the control device.
+ */
+static int
+amr_close(dev_t dev, int flags, int fmt, d_thread_t *td)
+{
+ int unit = minor(dev);
+ struct amr_softc *sc = devclass_get_softc(devclass_find("amr"), unit);
+
+ debug_called(1);
+
+ sc->amr_state &= ~AMR_STATE_OPEN;
+ return (0);
+}
+
+/********************************************************************************
+ * Handle controller-specific control operations.
+ */
+static int
+amr_ioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, d_thread_t *td)
+{
+ struct amr_softc *sc = (struct amr_softc *)dev->si_drv1;
+ int *arg = (int *)addr;
+ struct amr_user_ioctl *au = (struct amr_user_ioctl *)addr;
+ struct amr_command *ac;
+ struct amr_mailbox_ioctl *mbi;
+ struct amr_passthrough *ap;
+ void *dp;
+ int error;
+
+ debug_called(1);
+
+ error = 0;
+ dp = NULL;
+ ap = NULL;
+ ac = NULL;
+ switch(cmd) {
+
+ case AMR_IO_VERSION:
+ debug(1, "AMR_IO_VERSION");
+ *arg = AMR_IO_VERSION_NUMBER;
+ break;
+
+ case AMR_IO_COMMAND:
+ debug(1, "AMR_IO_COMMAND 0x%x", au->au_cmd[0]);
+ /* handle inbound data buffer */
+ if (au->au_length != 0) {
+ if ((dp = malloc(au->au_length, M_DEVBUF, M_WAITOK)) == NULL) {
+ error = ENOMEM;
+ break;
+ }
+ if ((error = copyin(au->au_buffer, dp, au->au_length)) != 0)
+ break;
+ debug(2, "copyin %ld bytes from %p -> %p", au->au_length, au->au_buffer, dp);
+ }
+
+ if ((ac = amr_alloccmd(sc)) == NULL) {
+ error = ENOMEM;
+ break;
+ }
+
+ /* handle SCSI passthrough command */
+ if (au->au_cmd[0] == AMR_CMD_PASS) {
+ if ((ap = malloc(sizeof(*ap), M_DEVBUF, M_WAITOK | M_ZERO)) == NULL) {
+ error = ENOMEM;
+ break;
+ }
+
+ /* copy cdb */
+ ap->ap_cdb_length = au->au_cmd[2];
+ bcopy(&au->au_cmd[3], &ap->ap_cdb[0], ap->ap_cdb_length);
+
+ /* build passthrough */
+ ap->ap_timeout = au->au_cmd[ap->ap_cdb_length + 3] & 0x07;
+ ap->ap_ars = (au->au_cmd[ap->ap_cdb_length + 3] & 0x08) ? 1 : 0;
+ ap->ap_islogical = (au->au_cmd[ap->ap_cdb_length + 3] & 0x80) ? 1 : 0;
+ ap->ap_logical_drive_no = au->au_cmd[ap->ap_cdb_length + 4];
+ ap->ap_channel = au->au_cmd[ap->ap_cdb_length + 5];
+ ap->ap_scsi_id = au->au_cmd[ap->ap_cdb_length + 6];
+ ap->ap_request_sense_length = 14;
+ ap->ap_data_transfer_length = au->au_length;
+ /* XXX what about the request-sense area? does the caller want it? */
+
+ /* build command */
+ ac->ac_data = ap;
+ ac->ac_length = sizeof(*ap);
+ ac->ac_flags |= AMR_CMD_DATAOUT;
+ ac->ac_ccb_data = dp;
+ ac->ac_ccb_length = au->au_length;
+ if (au->au_direction & AMR_IO_READ)
+ ac->ac_flags |= AMR_CMD_CCB_DATAIN;
+ if (au->au_direction & AMR_IO_WRITE)
+ ac->ac_flags |= AMR_CMD_CCB_DATAOUT;
+
+ ac->ac_mailbox.mb_command = AMR_CMD_PASS;
+
+ } else {
+ /* direct command to controller */
+ mbi = (struct amr_mailbox_ioctl *)&ac->ac_mailbox;
+
+ /* copy pertinent mailbox items */
+ mbi->mb_command = au->au_cmd[0];
+ mbi->mb_channel = au->au_cmd[1];
+ mbi->mb_param = au->au_cmd[2];
+ mbi->mb_pad[0] = au->au_cmd[3];
+ mbi->mb_drive = au->au_cmd[4];
+
+ /* build the command */
+ ac->ac_data = dp;
+ ac->ac_length = au->au_length;
+ if (au->au_direction & AMR_IO_READ)
+ ac->ac_flags |= AMR_CMD_DATAIN;
+ if (au->au_direction & AMR_IO_WRITE)
+ ac->ac_flags |= AMR_CMD_DATAOUT;
+ }
+
+ /* run the command */
+ if ((error = amr_wait_command(ac)) != 0)
+ break;
+
+ /* copy out data and set status */
+ if (au->au_length != 0)
+ error = copyout(dp, au->au_buffer, au->au_length);
+ debug(2, "copyout %ld bytes from %p -> %p", au->au_length, dp, au->au_buffer);
+ if (dp != NULL)
+ debug(2, "%16d", (int)dp);
+ au->au_status = ac->ac_status;
+ break;
+
+ default:
+ debug(1, "unknown ioctl 0x%lx", cmd);
+ error = ENOIOCTL;
+ break;
+ }
+
+ if (dp != NULL)
+ free(dp, M_DEVBUF);
+ if (ap != NULL)
+ free(ap, M_DEVBUF);
+ if (ac != NULL)
+ amr_releasecmd(ac);
+ return(error);
+}
+
+/********************************************************************************
+ ********************************************************************************
+ Status Monitoring
+ ********************************************************************************
+ ********************************************************************************/
+
+/********************************************************************************
+ * Perform a periodic check of the controller status
+ */
+static void
+amr_periodic(void *data)
+{
+ struct amr_softc *sc = (struct amr_softc *)data;
+
+ debug_called(2);
+
+ /* XXX perform periodic status checks here */
+
+ /* compensate for missed interrupts */
+ amr_done(sc);
+
+ /* reschedule */
+ sc->amr_timeout = timeout(amr_periodic, sc, hz);
+}
+
+/********************************************************************************
+ ********************************************************************************
+ Command Wrappers
+ ********************************************************************************
+ ********************************************************************************/
+
+/********************************************************************************
+ * Interrogate the controller for the operational parameters we require.
+ */
+static int
+amr_query_controller(struct amr_softc *sc)
+{
+ struct amr_enquiry3 *aex;
+ struct amr_prodinfo *ap;
+ struct amr_enquiry *ae;
+ int ldrv;
+
+ /*
+ * If we haven't found the real limit yet, let us have a couple of commands in
+ * order to be able to probe.
+ */
+ if (sc->amr_maxio == 0)
+ sc->amr_maxio = 2;
+
+ /*
+ * Greater than 10 byte cdb support
+ */
+ sc->support_ext_cdb = amr_support_ext_cdb(sc);
+
+ if(sc->support_ext_cdb) {
+ debug(2,"supports extended CDBs.");
+ }
+
+ /*
+ * Try to issue an ENQUIRY3 command
+ */
+ if ((aex = amr_enquiry(sc, 2048, AMR_CMD_CONFIG, AMR_CONFIG_ENQ3,
+ AMR_CONFIG_ENQ3_SOLICITED_FULL)) != NULL) {
+
+ /*
+ * Fetch current state of logical drives.
+ */
+ for (ldrv = 0; ldrv < aex->ae_numldrives; ldrv++) {
+ sc->amr_drive[ldrv].al_size = aex->ae_drivesize[ldrv];
+ sc->amr_drive[ldrv].al_state = aex->ae_drivestate[ldrv];
+ sc->amr_drive[ldrv].al_properties = aex->ae_driveprop[ldrv];
+ debug(2, " drive %d: %d state %x properties %x\n", ldrv, sc->amr_drive[ldrv].al_size,
+ sc->amr_drive[ldrv].al_state, sc->amr_drive[ldrv].al_properties);
+ }
+ free(aex, M_DEVBUF);
+
+ /*
+ * Get product info for channel count.
+ */
+ if ((ap = amr_enquiry(sc, 2048, AMR_CMD_CONFIG, AMR_CONFIG_PRODUCT_INFO, 0)) == NULL) {
+ device_printf(sc->amr_dev, "can't obtain product data from controller\n");
+ return(1);
+ }
+ sc->amr_maxdrives = 40;
+ sc->amr_maxchan = ap->ap_nschan;
+ sc->amr_maxio = ap->ap_maxio;
+ sc->amr_type |= AMR_TYPE_40LD;
+ free(ap, M_DEVBUF);
+
+ } else {
+
+ /* failed, try the 8LD ENQUIRY commands */
+ if ((ae = (struct amr_enquiry *)amr_enquiry(sc, 2048, AMR_CMD_EXT_ENQUIRY2, 0, 0)) == NULL) {
+ if ((ae = (struct amr_enquiry *)amr_enquiry(sc, 2048, AMR_CMD_ENQUIRY, 0, 0)) == NULL) {
+ device_printf(sc->amr_dev, "can't obtain configuration data from controller\n");
+ return(1);
+ }
+ ae->ae_signature = 0;
+ }
+
+ /*
+ * Fetch current state of logical drives.
+ */
+ for (ldrv = 0; ldrv < ae->ae_ldrv.al_numdrives; ldrv++) {
+ sc->amr_drive[ldrv].al_size = ae->ae_ldrv.al_size[ldrv];
+ sc->amr_drive[ldrv].al_state = ae->ae_ldrv.al_state[ldrv];
+ sc->amr_drive[ldrv].al_properties = ae->ae_ldrv.al_properties[ldrv];
+ debug(2, " drive %d: %d state %x properties %x\n", ldrv, sc->amr_drive[ldrv].al_size,
+ sc->amr_drive[ldrv].al_state, sc->amr_drive[ldrv].al_properties);
+ }
+
+ sc->amr_maxdrives = 8;
+ sc->amr_maxchan = ae->ae_adapter.aa_channels;
+ sc->amr_maxio = ae->ae_adapter.aa_maxio;
+ free(ae, M_DEVBUF);
+ }
+
+ /*
+ * Mark remaining drives as unused.
+ */
+ for (; ldrv < AMR_MAXLD; ldrv++)
+ sc->amr_drive[ldrv].al_size = 0xffffffff;
+
+ /*
+ * Cap the maximum number of outstanding I/Os. AMI's Linux driver doesn't trust
+ * the controller's reported value, and lockups have been seen when we do.
+ */
+ sc->amr_maxio = imin(sc->amr_maxio, AMR_LIMITCMD);
+
+ return(0);
+}
+
+/********************************************************************************
+ * Run a generic enquiry-style command.
+ */
+static void *
+amr_enquiry(struct amr_softc *sc, size_t bufsize, u_int8_t cmd, u_int8_t cmdsub, u_int8_t cmdqual)
+{
+ struct amr_command *ac;
+ void *result;
+ u_int8_t *mbox;
+ int error;
+
+ debug_called(1);
+
+ error = 1;
+ result = NULL;
+
+ /* get ourselves a command buffer */
+ if ((ac = amr_alloccmd(sc)) == NULL)
+ goto out;
+ /* allocate the response structure */
+ if ((result = malloc(bufsize, M_DEVBUF, M_NOWAIT)) == NULL)
+ goto out;
+ /* set command flags */
+ ac->ac_flags |= AMR_CMD_PRIORITY | AMR_CMD_DATAOUT;
+
+ /* point the command at our data */
+ ac->ac_data = result;
+ ac->ac_length = bufsize;
+
+ /* build the command proper */
+ mbox = (u_int8_t *)&ac->ac_mailbox; /* XXX want a real structure for this? */
+ mbox[0] = cmd;
+ mbox[2] = cmdsub;
+ mbox[3] = cmdqual;
+
+ /* can't assume that interrupts are going to work here, so play it safe */
+ if (sc->amr_poll_command(ac))
+ goto out;
+ error = ac->ac_status;
+
+ out:
+ if (ac != NULL)
+ amr_releasecmd(ac);
+ if ((error != 0) && (result != NULL)) {
+ free(result, M_DEVBUF);
+ result = NULL;
+ }
+ return(result);
+}
+
+/********************************************************************************
+ * Flush the controller's internal cache, return status.
+ */
+int
+amr_flush(struct amr_softc *sc)
+{
+ struct amr_command *ac;
+ int error;
+
+ /* get ourselves a command buffer */
+ error = 1;
+ if ((ac = amr_alloccmd(sc)) == NULL)
+ goto out;
+ /* set command flags */
+ ac->ac_flags |= AMR_CMD_PRIORITY | AMR_CMD_DATAOUT;
+
+ /* build the command proper */
+ ac->ac_mailbox.mb_command = AMR_CMD_FLUSH;
+
+ /* we have to poll, as the system may be going down or otherwise damaged */
+ if (sc->amr_poll_command(ac))
+ goto out;
+ error = ac->ac_status;
+
+ out:
+ if (ac != NULL)
+ amr_releasecmd(ac);
+ return(error);
+}
+
+/********************************************************************************
+ * Detect extented cdb >> greater than 10 byte cdb support
+ * returns '1' means this support exist
+ * returns '0' means this support doesn't exist
+ */
+static int
+amr_support_ext_cdb(struct amr_softc *sc)
+{
+ struct amr_command *ac;
+ u_int8_t *mbox;
+ int error;
+
+ /* get ourselves a command buffer */
+ error = 0;
+ if ((ac = amr_alloccmd(sc)) == NULL)
+ goto out;
+ /* set command flags */
+ ac->ac_flags |= AMR_CMD_PRIORITY | AMR_CMD_DATAOUT;
+
+ /* build the command proper */
+ mbox = (u_int8_t *)&ac->ac_mailbox; /* XXX want a real structure for this? */
+ mbox[0] = 0xA4;
+ mbox[2] = 0x16;
+
+
+ /* we have to poll, as the system may be going down or otherwise damaged */
+ if (sc->amr_poll_command(ac))
+ goto out;
+ if( ac->ac_status == AMR_STATUS_SUCCESS ) {
+ error = 1;
+ }
+
+out:
+ if (ac != NULL)
+ amr_releasecmd(ac);
+ return(error);
+}
+
+/********************************************************************************
+ * Try to find I/O work for the controller from one or more of the work queues.
+ *
+ * We make the assumption that if the controller is not ready to take a command
+ * at some given time, it will generate an interrupt at some later time when
+ * it is.
+ */
+void
+amr_startio(struct amr_softc *sc)
+{
+ struct amr_command *ac;
+
+ /* spin until something prevents us from doing any work */
+ for (;;) {
+
+ /* try to get a ready command */
+ ac = amr_dequeue_ready(sc);
+
+ /* if that failed, build a command from a bio */
+ if (ac == NULL)
+ (void)amr_bio_command(sc, &ac);
+
+ /* if that failed, build a command from a ccb */
+ if (ac == NULL)
+ (void)amr_cam_command(sc, &ac);
+
+ /* if we don't have anything to do, give up */
+ if (ac == NULL)
+ break;
+
+ /* try to give the command to the controller; if this fails save it for later and give up */
+ if (amr_start(ac)) {
+ debug(2, "controller busy, command deferred");
+ amr_requeue_ready(ac); /* XXX schedule retry very soon? */
+ break;
+ }
+ }
+}
+
+/********************************************************************************
+ * Handle completion of an I/O command.
+ */
+static void
+amr_completeio(struct amr_command *ac)
+{
+ struct amr_softc *sc = ac->ac_sc;
+
+ if (ac->ac_status != AMR_STATUS_SUCCESS) { /* could be more verbose here? */
+ ac->ac_bio->bio_error = EIO;
+ ac->ac_bio->bio_flags |= BIO_ERROR;
+
+ device_printf(sc->amr_dev, "I/O error - 0x%x\n", ac->ac_status);
+/* amr_printcommand(ac);*/
+ }
+ amrd_intr(ac->ac_bio);
+ amr_releasecmd(ac);
+}
+
+/********************************************************************************
+ ********************************************************************************
+ Command Processing
+ ********************************************************************************
+ ********************************************************************************/
+
+/********************************************************************************
+ * Convert a bio off the top of the bio queue into a command.
+ */
+static int
+amr_bio_command(struct amr_softc *sc, struct amr_command **acp)
+{
+ struct amr_command *ac;
+ struct amrd_softc *amrd;
+ struct bio *bio;
+ int error;
+ int blkcount;
+ int driveno;
+ int cmd;
+
+ ac = NULL;
+ error = 0;
+
+ /* get a bio to work on */
+ if ((bio = amr_dequeue_bio(sc)) == NULL)
+ goto out;
+
+ /* get a command */
+ if ((ac = amr_alloccmd(sc)) == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+
+ /* connect the bio to the command */
+ ac->ac_complete = amr_completeio;
+ ac->ac_bio = bio;
+ ac->ac_data = bio->bio_data;
+ ac->ac_length = bio->bio_bcount;
+ if (BIO_IS_READ(bio)) {
+ ac->ac_flags |= AMR_CMD_DATAIN;
+ cmd = AMR_CMD_LREAD;
+ } else {
+ ac->ac_flags |= AMR_CMD_DATAOUT;
+ cmd = AMR_CMD_LWRITE;
+ }
+ amrd = (struct amrd_softc *)bio->bio_disk->d_drv1;
+ driveno = amrd->amrd_drive - sc->amr_drive;
+ blkcount = (bio->bio_bcount + AMR_BLKSIZE - 1) / AMR_BLKSIZE;
+
+ ac->ac_mailbox.mb_command = cmd;
+ ac->ac_mailbox.mb_blkcount = blkcount;
+ ac->ac_mailbox.mb_lba = bio->bio_pblkno;
+ ac->ac_mailbox.mb_drive = driveno;
+ /* we fill in the s/g related data when the command is mapped */
+
+ if ((bio->bio_pblkno + blkcount) > sc->amr_drive[driveno].al_size)
+ device_printf(sc->amr_dev, "I/O beyond end of unit (%lld,%d > %lu)\n",
+ (long long)bio->bio_pblkno, blkcount,
+ (u_long)sc->amr_drive[driveno].al_size);
+
+out:
+ if (error != 0) {
+ if (ac != NULL)
+ amr_releasecmd(ac);
+ if (bio != NULL) /* this breaks ordering... */
+ amr_enqueue_bio(sc, bio);
+ }
+ *acp = ac;
+ return(error);
+}
+
+/********************************************************************************
+ * Take a command, submit it to the controller and sleep until it completes
+ * or fails. Interrupts must be enabled, returns nonzero on error.
+ */
+static int
+amr_wait_command(struct amr_command *ac)
+{
+ int error, count;
+
+ debug_called(1);
+
+ ac->ac_complete = NULL;
+ ac->ac_flags |= AMR_CMD_SLEEP;
+ if ((error = amr_start(ac)) != 0)
+ return(error);
+
+ count = 0;
+ /* XXX better timeout? */
+ while ((ac->ac_flags & AMR_CMD_BUSY) && (count < 30)) {
+ tsleep(ac, PRIBIO | PCATCH, "amrwcmd", hz);
+ }
+ return(0);
+}
+
+/********************************************************************************
+ * Take a command, submit it to the controller and busy-wait for it to return.
+ * Returns nonzero on error. Can be safely called with interrupts enabled.
+ */
+static int
+amr_std_poll_command(struct amr_command *ac)
+{
+ struct amr_softc *sc = ac->ac_sc;
+ int error, count;
+
+ debug_called(2);
+
+ ac->ac_complete = NULL;
+ if ((error = amr_start(ac)) != 0)
+ return(error);
+
+ count = 0;
+ do {
+ /*
+ * Poll for completion, although the interrupt handler may beat us to it.
+ * Note that the timeout here is somewhat arbitrary.
+ */
+ amr_done(sc);
+ DELAY(1000);
+ } while ((ac->ac_flags & AMR_CMD_BUSY) && (count++ < 1000));
+ if (!(ac->ac_flags & AMR_CMD_BUSY)) {
+ error = 0;
+ } else {
+ /* XXX the slot is now marked permanently busy */
+ error = EIO;
+ device_printf(sc->amr_dev, "polled command timeout\n");
+ }
+ return(error);
+}
+
+/********************************************************************************
+ * Take a command, submit it to the controller and busy-wait for it to return.
+ * Returns nonzero on error. Can be safely called with interrupts enabled.
+ */
+static int
+amr_quartz_poll_command(struct amr_command *ac)
+{
+ struct amr_softc *sc = ac->ac_sc;
+ int s;
+ int error,count;
+
+ debug_called(2);
+
+ /* now we have a slot, we can map the command (unmapped in amr_complete) */
+ amr_mapcmd(ac);
+
+ s = splbio();
+
+ count=0;
+ while (sc->amr_busyslots){
+ tsleep(sc, PRIBIO | PCATCH, "amrpoll", hz);
+ if(count++>10) {
+ break;
+ }
+ }
+
+ if(sc->amr_busyslots) {
+ device_printf(sc->amr_dev, "adapter is busy\n");
+ splx(s);
+ amr_unmapcmd(ac);
+ ac->ac_status=0;
+ return(1);
+ }
+
+ bcopy(&ac->ac_mailbox, (void *)(uintptr_t)(volatile void *)sc->amr_mailbox, AMR_MBOX_CMDSIZE);
+
+ /* clear the poll/ack fields in the mailbox */
+ sc->amr_mailbox->mb_ident = 0xFE;
+ sc->amr_mailbox->mb_nstatus = 0xFF;
+ sc->amr_mailbox->mb_status = 0xFF;
+ sc->amr_mailbox->mb_poll = 0;
+ sc->amr_mailbox->mb_ack = 0;
+ sc->amr_mailbox->mb_busy = 1;
+
+ AMR_QPUT_IDB(sc, sc->amr_mailboxphys | AMR_QIDB_SUBMIT);
+
+ while(sc->amr_mailbox->mb_nstatus == 0xFF);
+ while(sc->amr_mailbox->mb_status == 0xFF);
+ ac->ac_status=sc->amr_mailbox->mb_status;
+ error = (ac->ac_status !=AMR_STATUS_SUCCESS) ? 1:0;
+ while(sc->amr_mailbox->mb_poll != 0x77);
+ sc->amr_mailbox->mb_poll = 0;
+ sc->amr_mailbox->mb_ack = 0x77;
+
+ /* acknowledge that we have the commands */
+ AMR_QPUT_IDB(sc, sc->amr_mailboxphys | AMR_QIDB_ACK);
+ while(AMR_QGET_IDB(sc) & AMR_QIDB_ACK);
+
+ splx(s);
+
+ /* unmap the command's data buffer */
+ amr_unmapcmd(ac);
+
+ return(error);
+}
+
+/********************************************************************************
+ * Get a free command slot for a command if it doesn't already have one.
+ *
+ * May be safely called multiple times for a given command.
+ */
+static int
+amr_getslot(struct amr_command *ac)
+{
+ struct amr_softc *sc = ac->ac_sc;
+ int s, slot, limit, error;
+
+ debug_called(3);
+
+ /* if the command already has a slot, don't try to give it another one */
+ if (ac->ac_slot != 0)
+ return(0);
+
+ /* enforce slot usage limit */
+ limit = (ac->ac_flags & AMR_CMD_PRIORITY) ? sc->amr_maxio : sc->amr_maxio - 4;
+ if (sc->amr_busyslots > limit)
+ return(EBUSY);
+
+ /*
+ * Allocate a slot. XXX linear scan is slow
+ */
+ error = EBUSY;
+ s = splbio();
+ for (slot = 0; slot < sc->amr_maxio; slot++) {
+ if (sc->amr_busycmd[slot] == NULL) {
+ sc->amr_busycmd[slot] = ac;
+ sc->amr_busyslots++;
+ ac->ac_slot = slot;
+ error = 0;
+ break;
+ }
+ }
+ splx(s);
+
+ return(error);
+}
+
+/********************************************************************************
+ * Map/unmap (ac)'s data in the controller's addressable space as required.
+ *
+ * These functions may be safely called multiple times on a given command.
+ */
+static void
+amr_setup_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error)
+{
+ struct amr_command *ac = (struct amr_command *)arg;
+ struct amr_softc *sc = ac->ac_sc;
+ struct amr_sgentry *sg;
+ int i;
+ u_int8_t *sgc;
+
+ debug_called(3);
+
+ /* get base address of s/g table */
+ sg = sc->amr_sgtable + (ac->ac_slot * AMR_NSEG);
+
+ /* save data physical address */
+ ac->ac_dataphys = segs[0].ds_addr;
+
+ /* for AMR_CMD_CONFIG the s/g count goes elsewhere */
+ if (ac->ac_mailbox.mb_command == AMR_CMD_CONFIG) {
+ sgc = &(((struct amr_mailbox_ioctl *)&ac->ac_mailbox)->mb_param);
+ } else {
+ sgc = &ac->ac_mailbox.mb_nsgelem;
+ }
+
+ /* decide whether we need to populate the s/g table */
+ if (nsegments < 2) {
+ *sgc = 0;
+ ac->ac_mailbox.mb_nsgelem = 0;
+ ac->ac_mailbox.mb_physaddr = ac->ac_dataphys;
+ } else {
+ ac->ac_mailbox.mb_nsgelem = nsegments;
+ *sgc = nsegments;
+ ac->ac_mailbox.mb_physaddr = sc->amr_sgbusaddr + (ac->ac_slot * AMR_NSEG * sizeof(struct amr_sgentry));
+ for (i = 0; i < nsegments; i++, sg++) {
+ sg->sg_addr = segs[i].ds_addr;
+ sg->sg_count = segs[i].ds_len;
+ }
+ }
+}
+
+static void
+amr_setup_ccbmap(void *arg, bus_dma_segment_t *segs, int nsegments, int error)
+{
+ struct amr_command *ac = (struct amr_command *)arg;
+ struct amr_softc *sc = ac->ac_sc;
+ struct amr_sgentry *sg;
+ struct amr_passthrough *ap = (struct amr_passthrough *)ac->ac_data;
+ struct amr_ext_passthrough *aep = (struct amr_ext_passthrough *)ac->ac_data;
+ int i;
+
+ /* get base address of s/g table */
+ sg = sc->amr_sgtable + (ac->ac_slot * AMR_NSEG);
+
+ /* decide whether we need to populate the s/g table */
+ if( ac->ac_mailbox.mb_command == AMR_CMD_EXTPASS ) {
+ if (nsegments < 2) {
+ aep->ap_no_sg_elements = 0;
+ aep->ap_data_transfer_address = segs[0].ds_addr;
+ } else {
+ /* save s/g table information in passthrough */
+ aep->ap_no_sg_elements = nsegments;
+ aep->ap_data_transfer_address = sc->amr_sgbusaddr + (ac->ac_slot * AMR_NSEG * sizeof(struct amr_sgentry));
+ /* populate s/g table (overwrites previous call which mapped the passthrough) */
+ for (i = 0; i < nsegments; i++, sg++) {
+ sg->sg_addr = segs[i].ds_addr;
+ sg->sg_count = segs[i].ds_len;
+ debug(3, " %d: 0x%x/%d", i, sg->sg_addr, sg->sg_count);
+ }
+ }
+ debug(3, "slot %d %d segments at 0x%x, passthrough at 0x%x", ac->ac_slot,
+ aep->ap_no_sg_elements, aep->ap_data_transfer_address, ac->ac_dataphys);
+ } else {
+ if (nsegments < 2) {
+ ap->ap_no_sg_elements = 0;
+ ap->ap_data_transfer_address = segs[0].ds_addr;
+ } else {
+ /* save s/g table information in passthrough */
+ ap->ap_no_sg_elements = nsegments;
+ ap->ap_data_transfer_address = sc->amr_sgbusaddr + (ac->ac_slot * AMR_NSEG * sizeof(struct amr_sgentry));
+ /* populate s/g table (overwrites previous call which mapped the passthrough) */
+ for (i = 0; i < nsegments; i++, sg++) {
+ sg->sg_addr = segs[i].ds_addr;
+ sg->sg_count = segs[i].ds_len;
+ debug(3, " %d: 0x%x/%d", i, sg->sg_addr, sg->sg_count);
+ }
+ }
+ debug(3, "slot %d %d segments at 0x%x, passthrough at 0x%x", ac->ac_slot,
+ ap->ap_no_sg_elements, ap->ap_data_transfer_address, ac->ac_dataphys);
+ }
+}
+
+static void
+amr_mapcmd(struct amr_command *ac)
+{
+ struct amr_softc *sc = ac->ac_sc;
+
+ debug_called(3);
+
+ /* if the command involves data at all, and hasn't been mapped */
+ if (!(ac->ac_flags & AMR_CMD_MAPPED)) {
+
+ if (ac->ac_data != NULL) {
+ /* map the data buffers into bus space and build the s/g list */
+ bus_dmamap_load(sc->amr_buffer_dmat, ac->ac_dmamap, ac->ac_data, ac->ac_length,
+ amr_setup_dmamap, ac, 0);
+ if (ac->ac_flags & AMR_CMD_DATAIN)
+ bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_PREREAD);
+ if (ac->ac_flags & AMR_CMD_DATAOUT)
+ bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_PREWRITE);
+ }
+
+ if (ac->ac_ccb_data != NULL) {
+ bus_dmamap_load(sc->amr_buffer_dmat, ac->ac_ccb_dmamap, ac->ac_ccb_data, ac->ac_ccb_length,
+ amr_setup_ccbmap, ac, 0);
+ if (ac->ac_flags & AMR_CMD_CCB_DATAIN)
+ bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_ccb_dmamap, BUS_DMASYNC_PREREAD);
+ if (ac->ac_flags & AMR_CMD_CCB_DATAOUT)
+ bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_ccb_dmamap, BUS_DMASYNC_PREWRITE);
+ }
+ ac->ac_flags |= AMR_CMD_MAPPED;
+ }
+}
+
+static void
+amr_unmapcmd(struct amr_command *ac)
+{
+ struct amr_softc *sc = ac->ac_sc;
+
+ debug_called(3);
+
+ /* if the command involved data at all and was mapped */
+ if (ac->ac_flags & AMR_CMD_MAPPED) {
+
+ if (ac->ac_data != NULL) {
+ if (ac->ac_flags & AMR_CMD_DATAIN)
+ bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_POSTREAD);
+ if (ac->ac_flags & AMR_CMD_DATAOUT)
+ bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_unload(sc->amr_buffer_dmat, ac->ac_dmamap);
+ }
+
+ if (ac->ac_ccb_data != NULL) {
+ if (ac->ac_flags & AMR_CMD_CCB_DATAIN)
+ bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_ccb_dmamap, BUS_DMASYNC_POSTREAD);
+ if (ac->ac_flags & AMR_CMD_CCB_DATAOUT)
+ bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_ccb_dmamap, BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_unload(sc->amr_buffer_dmat, ac->ac_ccb_dmamap);
+ }
+ ac->ac_flags &= ~AMR_CMD_MAPPED;
+ }
+}
+
+/********************************************************************************
+ * Take a command and give it to the controller, returns 0 if successful, or
+ * EBUSY if the command should be retried later.
+ */
+static int
+amr_start(struct amr_command *ac)
+{
+ struct amr_softc *sc = ac->ac_sc;
+ int done, s, i;
+
+ debug_called(3);
+
+ /* mark command as busy so that polling consumer can tell */
+ ac->ac_flags |= AMR_CMD_BUSY;
+
+ /* get a command slot (freed in amr_done) */
+ if (amr_getslot(ac))
+ return(EBUSY);
+
+ /* now we have a slot, we can map the command (unmapped in amr_complete) */
+ amr_mapcmd(ac);
+
+ /* mark the new mailbox we are going to copy in as busy */
+ ac->ac_mailbox.mb_busy = 1;
+
+ /* clear the poll/ack fields in the mailbox */
+ sc->amr_mailbox->mb_poll = 0;
+ sc->amr_mailbox->mb_ack = 0;
+
+ /*
+ * Save the slot number so that we can locate this command when complete.
+ * Note that ident = 0 seems to be special, so we don't use it.
+ */
+ ac->ac_mailbox.mb_ident = ac->ac_slot + 1;
+
+ /*
+ * Spin waiting for the mailbox, give up after ~1 second. We expect the
+ * controller to be able to handle our I/O.
+ *
+ * XXX perhaps we should wait for less time, and count on the deferred command
+ * handling to deal with retries?
+ */
+ debug(4, "wait for mailbox");
+ for (i = 10000, done = 0; (i > 0) && !done; i--) {
+ s = splbio();
+
+ /* is the mailbox free? */
+ if (sc->amr_mailbox->mb_busy == 0) {
+ debug(4, "got mailbox");
+ sc->amr_mailbox64->mb64_segment = 0;
+ bcopy(&ac->ac_mailbox, (void *)(uintptr_t)(volatile void *)sc->amr_mailbox, AMR_MBOX_CMDSIZE);
+ done = 1;
+
+ /* not free, spin waiting */
+ } else {
+ debug(4, "busy flag %x\n", sc->amr_mailbox->mb_busy);
+ /* this is somewhat ugly */
+ DELAY(100);
+ }
+ splx(s); /* drop spl to allow completion interrupts */
+ }
+
+ /*
+ * Now give the command to the controller
+ */
+ if (done) {
+ if (sc->amr_submit_command(sc)) {
+ /* the controller wasn't ready to take the command, forget that we tried to post it */
+ sc->amr_mailbox->mb_busy = 0;
+ return(EBUSY);
+ }
+ debug(3, "posted command");
+ return(0);
+ }
+
+ /*
+ * The controller wouldn't take the command. Return the command as busy
+ * so that it is retried later.
+ */
+ return(EBUSY);
+}
+
+/********************************************************************************
+ * Extract one or more completed commands from the controller (sc)
+ *
+ * Returns nonzero if any commands on the work queue were marked as completed.
+ */
+int
+amr_done(struct amr_softc *sc)
+{
+ struct amr_command *ac;
+ struct amr_mailbox mbox;
+ int i, idx, result;
+
+ debug_called(3);
+
+ /* See if there's anything for us to do */
+ result = 0;
+
+ /* loop collecting completed commands */
+ for (;;) {
+ /* poll for a completed command's identifier and status */
+ if (sc->amr_get_work(sc, &mbox)) {
+ result = 1;
+
+ /* iterate over completed commands in this result */
+ for (i = 0; i < mbox.mb_nstatus; i++) {
+ /* get pointer to busy command */
+ idx = mbox.mb_completed[i] - 1;
+ ac = sc->amr_busycmd[idx];
+
+ /* really a busy command? */
+ if (ac != NULL) {
+
+ /* pull the command from the busy index */
+ sc->amr_busycmd[idx] = NULL;
+ sc->amr_busyslots--;
+
+ /* save status for later use */
+ ac->ac_status = mbox.mb_status;
+ amr_enqueue_completed(ac);
+ debug(3, "completed command with status %x", mbox.mb_status);
+ } else {
+ device_printf(sc->amr_dev, "bad slot %d completed\n", idx);
+ }
+ }
+ } else {
+ break; /* no work */
+ }
+ }
+
+ /* if we've completed any commands, try posting some more */
+ if (result)
+ amr_startio(sc);
+
+ /* handle completion and timeouts */
+#if __FreeBSD_version >= 500005
+ if (sc->amr_state & AMR_STATE_INTEN)
+ taskqueue_enqueue(taskqueue_swi_giant, &sc->amr_task_complete);
+ else
+#endif
+ amr_complete(sc, 0);
+
+ return(result);
+}
+
+/********************************************************************************
+ * Do completion processing on done commands on (sc)
+ */
+static void
+amr_complete(void *context, int pending)
+{
+ struct amr_softc *sc = (struct amr_softc *)context;
+ struct amr_command *ac;
+
+ debug_called(3);
+
+ /* pull completed commands off the queue */
+ for (;;) {
+ ac = amr_dequeue_completed(sc);
+ if (ac == NULL)
+ break;
+
+ /* unmap the command's data buffer */
+ amr_unmapcmd(ac);
+
+ /* unbusy the command */
+ ac->ac_flags &= ~AMR_CMD_BUSY;
+
+ /*
+ * Is there a completion handler?
+ */
+ if (ac->ac_complete != NULL) {
+ ac->ac_complete(ac);
+
+ /*
+ * Is someone sleeping on this one?
+ */
+ } else if (ac->ac_flags & AMR_CMD_SLEEP) {
+ wakeup(ac);
+ }
+
+ if(!sc->amr_busyslots) {
+ wakeup(sc);
+ }
+ }
+}
+
+/********************************************************************************
+ ********************************************************************************
+ Command Buffer Management
+ ********************************************************************************
+ ********************************************************************************/
+
+/********************************************************************************
+ * Get a new command buffer.
+ *
+ * This may return NULL in low-memory cases.
+ *
+ * If possible, we recycle a command buffer that's been used before.
+ */
+struct amr_command *
+amr_alloccmd(struct amr_softc *sc)
+{
+ struct amr_command *ac;
+
+ debug_called(3);
+
+ ac = amr_dequeue_free(sc);
+ if (ac == NULL) {
+ amr_alloccmd_cluster(sc);
+ ac = amr_dequeue_free(sc);
+ }
+ if (ac == NULL)
+ return(NULL);
+
+ /* clear out significant fields */
+ ac->ac_slot = 0;
+ ac->ac_status = 0;
+ bzero(&ac->ac_mailbox, sizeof(struct amr_mailbox));
+ ac->ac_flags = 0;
+ ac->ac_bio = NULL;
+ ac->ac_data = NULL;
+ ac->ac_ccb_data = NULL;
+ ac->ac_complete = NULL;
+ return(ac);
+}
+
+/********************************************************************************
+ * Release a command buffer for recycling.
+ */
+void
+amr_releasecmd(struct amr_command *ac)
+{
+ debug_called(3);
+
+ amr_enqueue_free(ac);
+}
+
+/********************************************************************************
+ * Allocate a new command cluster and initialise it.
+ */
+static void
+amr_alloccmd_cluster(struct amr_softc *sc)
+{
+ struct amr_command_cluster *acc;
+ struct amr_command *ac;
+ int s, i;
+
+ acc = malloc(AMR_CMD_CLUSTERSIZE, M_DEVBUF, M_NOWAIT);
+ if (acc != NULL) {
+ s = splbio();
+ TAILQ_INSERT_TAIL(&sc->amr_cmd_clusters, acc, acc_link);
+ splx(s);
+ for (i = 0; i < AMR_CMD_CLUSTERCOUNT; i++) {
+ ac = &acc->acc_command[i];
+ bzero(ac, sizeof(*ac));
+ ac->ac_sc = sc;
+ if (!bus_dmamap_create(sc->amr_buffer_dmat, 0, &ac->ac_dmamap) &&
+ !bus_dmamap_create(sc->amr_buffer_dmat, 0, &ac->ac_ccb_dmamap))
+ amr_releasecmd(ac);
+ }
+ }
+}
+
+/********************************************************************************
+ * Free a command cluster
+ */
+static void
+amr_freecmd_cluster(struct amr_command_cluster *acc)
+{
+ struct amr_softc *sc = acc->acc_command[0].ac_sc;
+ int i;
+
+ for (i = 0; i < AMR_CMD_CLUSTERCOUNT; i++)
+ bus_dmamap_destroy(sc->amr_buffer_dmat, acc->acc_command[i].ac_dmamap);
+ free(acc, M_DEVBUF);
+}
+
+/********************************************************************************
+ ********************************************************************************
+ Interface-specific Shims
+ ********************************************************************************
+ ********************************************************************************/
+
+/********************************************************************************
+ * Tell the controller that the mailbox contains a valid command
+ */
+static int
+amr_quartz_submit_command(struct amr_softc *sc)
+{
+ debug_called(3);
+
+ if (AMR_QGET_IDB(sc) & AMR_QIDB_SUBMIT)
+ return(EBUSY);
+ AMR_QPUT_IDB(sc, sc->amr_mailboxphys | AMR_QIDB_SUBMIT);
+ return(0);
+}
+
+static int
+amr_std_submit_command(struct amr_softc *sc)
+{
+ debug_called(3);
+
+ if (AMR_SGET_MBSTAT(sc) & AMR_SMBOX_BUSYFLAG)
+ return(EBUSY);
+ AMR_SPOST_COMMAND(sc);
+ return(0);
+}
+
+/********************************************************************************
+ * Claim any work that the controller has completed; acknowledge completion,
+ * save details of the completion in (mbsave)
+ */
+static int
+amr_quartz_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave)
+{
+ int s, worked;
+ u_int32_t outd;
+
+ debug_called(3);
+
+ worked = 0;
+ s = splbio();
+
+ /* work waiting for us? */
+ if ((outd = AMR_QGET_ODB(sc)) == AMR_QODB_READY) {
+
+ /* save mailbox, which contains a list of completed commands */
+ bcopy((void *)(uintptr_t)(volatile void *)sc->amr_mailbox, mbsave, sizeof(*mbsave));
+
+ /* acknowledge interrupt */
+ AMR_QPUT_ODB(sc, AMR_QODB_READY);
+
+ /* acknowledge that we have the commands */
+ AMR_QPUT_IDB(sc, sc->amr_mailboxphys | AMR_QIDB_ACK);
+
+#ifndef AMR_QUARTZ_GOFASTER
+ /*
+ * This waits for the controller to notice that we've taken the
+ * command from it. It's very inefficient, and we shouldn't do it,
+ * but if we remove this code, we stop completing commands under
+ * load.
+ *
+ * Peter J says we shouldn't do this. The documentation says we
+ * should. Who is right?
+ */
+ while(AMR_QGET_IDB(sc) & AMR_QIDB_ACK)
+ ; /* XXX aiee! what if it dies? */
+#endif
+
+ worked = 1; /* got some work */
+ }
+
+ splx(s);
+ return(worked);
+}
+
+static int
+amr_std_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave)
+{
+ int s, worked;
+ u_int8_t istat;
+
+ debug_called(3);
+
+ worked = 0;
+ s = splbio();
+
+ /* check for valid interrupt status */
+ istat = AMR_SGET_ISTAT(sc);
+ if ((istat & AMR_SINTR_VALID) != 0) {
+ AMR_SPUT_ISTAT(sc, istat); /* ack interrupt status */
+
+ /* save mailbox, which contains a list of completed commands */
+ bcopy((void *)(uintptr_t)(volatile void *)sc->amr_mailbox, mbsave, sizeof(*mbsave));
+
+ AMR_SACK_INTERRUPT(sc); /* acknowledge we have the mailbox */
+ worked = 1;
+ }
+
+ splx(s);
+ return(worked);
+}
+
+/********************************************************************************
+ * Notify the controller of the mailbox location.
+ */
+static void
+amr_std_attach_mailbox(struct amr_softc *sc)
+{
+
+ /* program the mailbox physical address */
+ AMR_SBYTE_SET(sc, AMR_SMBOX_0, sc->amr_mailboxphys & 0xff);
+ AMR_SBYTE_SET(sc, AMR_SMBOX_1, (sc->amr_mailboxphys >> 8) & 0xff);
+ AMR_SBYTE_SET(sc, AMR_SMBOX_2, (sc->amr_mailboxphys >> 16) & 0xff);
+ AMR_SBYTE_SET(sc, AMR_SMBOX_3, (sc->amr_mailboxphys >> 24) & 0xff);
+ AMR_SBYTE_SET(sc, AMR_SMBOX_ENABLE, AMR_SMBOX_ADDR);
+
+ /* clear any outstanding interrupt and enable interrupts proper */
+ AMR_SACK_INTERRUPT(sc);
+ AMR_SENABLE_INTR(sc);
+}
+
+#ifdef AMR_BOARD_INIT
+/********************************************************************************
+ * Initialise the controller
+ */
+static int
+amr_quartz_init(struct amr_softc *sc)
+{
+ int status, ostatus;
+
+ device_printf(sc->amr_dev, "initial init status %x\n", AMR_QGET_INITSTATUS(sc));
+
+ AMR_QRESET(sc);
+
+ ostatus = 0xff;
+ while ((status = AMR_QGET_INITSTATUS(sc)) != AMR_QINIT_DONE) {
+ if (status != ostatus) {
+ device_printf(sc->amr_dev, "(%x) %s\n", status, amr_describe_code(amr_table_qinit, status));
+ ostatus = status;
+ }
+ switch (status) {
+ case AMR_QINIT_NOMEM:
+ return(ENOMEM);
+
+ case AMR_QINIT_SCAN:
+ /* XXX we could print channel/target here */
+ break;
+ }
+ }
+ return(0);
+}
+
+static int
+amr_std_init(struct amr_softc *sc)
+{
+ int status, ostatus;
+
+ device_printf(sc->amr_dev, "initial init status %x\n", AMR_SGET_INITSTATUS(sc));
+
+ AMR_SRESET(sc);
+
+ ostatus = 0xff;
+ while ((status = AMR_SGET_INITSTATUS(sc)) != AMR_SINIT_DONE) {
+ if (status != ostatus) {
+ device_printf(sc->amr_dev, "(%x) %s\n", status, amr_describe_code(amr_table_sinit, status));
+ ostatus = status;
+ }
+ switch (status) {
+ case AMR_SINIT_NOMEM:
+ return(ENOMEM);
+
+ case AMR_SINIT_INPROG:
+ /* XXX we could print channel/target here? */
+ break;
+ }
+ }
+ return(0);
+}
+#endif
+
+/********************************************************************************
+ ********************************************************************************
+ Debugging
+ ********************************************************************************
+ ********************************************************************************/
+
+/********************************************************************************
+ * Identify the controller and print some information about it.
+ */
+static void
+amr_describe_controller(struct amr_softc *sc)
+{
+ struct amr_prodinfo *ap;
+ struct amr_enquiry *ae;
+ char *prod;
+
+ /*
+ * Try to get 40LD product info, which tells us what the card is labelled as.
+ */
+ if ((ap = amr_enquiry(sc, 2048, AMR_CMD_CONFIG, AMR_CONFIG_PRODUCT_INFO, 0)) != NULL) {
+ device_printf(sc->amr_dev, "<LSILogic %.80s> Firmware %.16s, BIOS %.16s, %dMB RAM\n",
+ ap->ap_product, ap->ap_firmware, ap->ap_bios,
+ ap->ap_memsize);
+
+ free(ap, M_DEVBUF);
+ return;
+ }
+
+ /*
+ * Try 8LD extended ENQUIRY to get controller signature, and use lookup table.
+ */
+ if ((ae = (struct amr_enquiry *)amr_enquiry(sc, 2048, AMR_CMD_EXT_ENQUIRY2, 0, 0)) != NULL) {
+ prod = amr_describe_code(amr_table_adaptertype, ae->ae_signature);
+
+ } else if ((ae = (struct amr_enquiry *)amr_enquiry(sc, 2048, AMR_CMD_ENQUIRY, 0, 0)) != NULL) {
+
+ /*
+ * Try to work it out based on the PCI signatures.
+ */
+ switch (pci_get_device(sc->amr_dev)) {
+ case 0x9010:
+ prod = "Series 428";
+ break;
+ case 0x9060:
+ prod = "Series 434";
+ break;
+ default:
+ prod = "unknown controller";
+ break;
+ }
+ } else {
+ prod = "unsupported controller";
+ }
+
+ /*
+ * HP NetRaid controllers have a special encoding of the firmware and
+ * BIOS versions. The AMI version seems to have it as strings whereas
+ * the HP version does it with a leading uppercase character and two
+ * binary numbers.
+ */
+
+ if(ae->ae_adapter.aa_firmware[2] >= 'A' &&
+ ae->ae_adapter.aa_firmware[2] <= 'Z' &&
+ ae->ae_adapter.aa_firmware[1] < ' ' &&
+ ae->ae_adapter.aa_firmware[0] < ' ' &&
+ ae->ae_adapter.aa_bios[2] >= 'A' &&
+ ae->ae_adapter.aa_bios[2] <= 'Z' &&
+ ae->ae_adapter.aa_bios[1] < ' ' &&
+ ae->ae_adapter.aa_bios[0] < ' ') {
+
+ /* this looks like we have an HP NetRaid version of the MegaRaid */
+
+ if(ae->ae_signature == AMR_SIG_438) {
+ /* the AMI 438 is a NetRaid 3si in HP-land */
+ prod = "HP NetRaid 3si";
+ }
+
+ device_printf(sc->amr_dev, "<%s> Firmware %c.%02d.%02d, BIOS %c.%02d.%02d, %dMB RAM\n",
+ prod, ae->ae_adapter.aa_firmware[2],
+ ae->ae_adapter.aa_firmware[1],
+ ae->ae_adapter.aa_firmware[0],
+ ae->ae_adapter.aa_bios[2],
+ ae->ae_adapter.aa_bios[1],
+ ae->ae_adapter.aa_bios[0],
+ ae->ae_adapter.aa_memorysize);
+ } else {
+ device_printf(sc->amr_dev, "<%s> Firmware %.4s, BIOS %.4s, %dMB RAM\n",
+ prod, ae->ae_adapter.aa_firmware, ae->ae_adapter.aa_bios,
+ ae->ae_adapter.aa_memorysize);
+ }
+ free(ae, M_DEVBUF);
+}
+
+#ifdef AMR_DEBUG
+/********************************************************************************
+ * Print the command (ac) in human-readable format
+ */
+#if 0
+static void
+amr_printcommand(struct amr_command *ac)
+{
+ struct amr_softc *sc = ac->ac_sc;
+ struct amr_sgentry *sg;
+ int i;
+
+ device_printf(sc->amr_dev, "cmd %x ident %d drive %d\n",
+ ac->ac_mailbox.mb_command, ac->ac_mailbox.mb_ident, ac->ac_mailbox.mb_drive);
+ device_printf(sc->amr_dev, "blkcount %d lba %d\n",
+ ac->ac_mailbox.mb_blkcount, ac->ac_mailbox.mb_lba);
+ device_printf(sc->amr_dev, "virtaddr %p length %lu\n", ac->ac_data, (unsigned long)ac->ac_length);
+ device_printf(sc->amr_dev, "sg physaddr %08x nsg %d\n",
+ ac->ac_mailbox.mb_physaddr, ac->ac_mailbox.mb_nsgelem);
+ device_printf(sc->amr_dev, "ccb %p bio %p\n", ac->ac_ccb_data, ac->ac_bio);
+
+ /* get base address of s/g table */
+ sg = sc->amr_sgtable + (ac->ac_slot * AMR_NSEG);
+ for (i = 0; i < ac->ac_mailbox.mb_nsgelem; i++, sg++)
+ device_printf(sc->amr_dev, " %x/%d\n", sg->sg_addr, sg->sg_count);
+}
+#endif
+#endif
OpenPOWER on IntegriCloud