diff options
Diffstat (limited to 'sys/dev/amr/amr.c')
-rw-r--r-- | sys/dev/amr/amr.c | 1498 |
1 files changed, 1498 insertions, 0 deletions
diff --git a/sys/dev/amr/amr.c b/sys/dev/amr/amr.c new file mode 100644 index 0000000..257479b --- /dev/null +++ b/sys/dev/amr/amr.c @@ -0,0 +1,1498 @@ +/*- + * Copyright (c) 1999 Michael Smith + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Driver for the AMI MegaRaid family of controllers + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/kernel.h> + +#include <sys/buf.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/devicestat.h> +#include <sys/disk.h> + +#include <machine/resource.h> +#include <machine/bus.h> +#include <machine/clock.h> +#include <sys/rman.h> + +#include <dev/amr/amrio.h> +#include <dev/amr/amrreg.h> +#include <dev/amr/amrvar.h> + +#if 0 +#define debug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args) +#else +#define debug(fmt, args...) +#endif + +#define AMR_CDEV_MAJOR 132 + +static struct cdevsw amr_cdevsw = { + /* open */ amr_open, + /* close */ amr_close, + /* read */ noread, + /* write */ nowrite, + /* ioctl */ amr_ioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "amr", + /* maj */ AMR_CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, + /* bmaj */ 254 /* XXX magic no-bdev */ +}; + +static int cdev_registered = 0; +devclass_t amr_devclass; + +/* + * 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 int amr_flush(struct amr_softc *sc); +static void amr_startio(struct amr_softc *sc); +static void amr_completeio(struct amr_command *ac); + +/* + * Command processing. + */ +static int amr_wait_command(struct amr_command *ac); +static int amr_poll_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 int amr_done(struct amr_softc *sc); +static void amr_complete(struct amr_softc *sc); + +/* + * Command buffer allocation. + */ +static struct amr_command *amr_alloccmd(struct amr_softc *sc); +static void amr_releasecmd(struct amr_command *ac); +static void amr_freecmd(struct amr_command *ac); + +/* + * Interface-specific shims + */ +static void amr_quartz_submit_command(struct amr_softc *sc); +static int amr_quartz_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave); +static void amr_quartz_attach_mailbox(struct amr_softc *sc); + +static void amr_std_submit_command(struct amr_softc *sc); +static int amr_std_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave); +static void amr_std_attach_mailbox(struct amr_softc *sc); + +/* + * Debugging + */ +static void amr_printcommand(struct amr_command *ac); + +/******************************************************************************** + ******************************************************************************** + Public Interfaces + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Free all of the resources associated with (sc) + * + * Should not be called if the controller is active. + */ +void +amr_free(struct amr_softc *sc) +{ + struct amr_command *ac; + u_int8_t *p; + + debug("called"); + + + /* throw away any command buffers */ + while ((ac = TAILQ_FIRST(&sc->amr_freecmds)) != NULL) { + TAILQ_REMOVE(&sc->amr_freecmds, ac, ac_link); + amr_freecmd(ac); + } + + /* destroy data-transfer DMA tag */ + if (sc->amr_buffer_dmat) + bus_dma_tag_destroy(sc->amr_buffer_dmat); + + /* free and destroy DMA memory and tag for s/g lists */ + if (sc->amr_sgtable) + bus_dmamem_free(sc->amr_sg_dmat, sc->amr_sgtable, sc->amr_sg_dmamap); + if (sc->amr_sg_dmat) + bus_dma_tag_destroy(sc->amr_sg_dmat); + + /* free and destroy DMA memory and tag for mailbox */ + if (sc->amr_mailbox) { + p = (u_int8_t *)sc->amr_mailbox; + bus_dmamem_free(sc->amr_sg_dmat, p - 16, sc->amr_sg_dmamap); + } + if (sc->amr_sg_dmat) + bus_dma_tag_destroy(sc->amr_sg_dmat); + + /* disconnect the interrupt handler */ + if (sc->amr_intr) + bus_teardown_intr(sc->amr_dev, sc->amr_irq, sc->amr_intr); + if (sc->amr_irq != NULL) + bus_release_resource(sc->amr_dev, SYS_RES_IRQ, 0, sc->amr_irq); + + /* destroy the parent DMA tag */ + if (sc->amr_parent_dmat) + bus_dma_tag_destroy(sc->amr_parent_dmat); + + /* release the register window mapping */ + if (sc->amr_reg != NULL) + bus_release_resource(sc->amr_dev, + (sc->amr_type == AMR_TYPE_QUARTZ) ? SYS_RES_MEMORY : SYS_RES_IOPORT, + AMR_CFG_BASE, sc->amr_reg); +} + +/******************************************************************************** + * Allocate and map the scatter/gather table in bus space. + */ +static void +amr_dma_map_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct amr_softc *sc = (struct amr_softc *)arg; + + debug("called"); + + /* save base of s/g table's address in bus space */ + sc->amr_sgbusaddr = segs->ds_addr; +} + +static int +amr_sglist_map(struct amr_softc *sc) +{ + size_t segsize; + int error; + + debug("called"); + + /* destroy any existing mappings */ + if (sc->amr_sgtable) + bus_dmamem_free(sc->amr_sg_dmat, sc->amr_sgtable, sc->amr_sg_dmamap); + if (sc->amr_sg_dmat) + bus_dma_tag_destroy(sc->amr_sg_dmat); + + /* + * Create a single tag describing a region large enough to hold all of + * the s/g lists we will need. + */ + segsize = sizeof(struct amr_sgentry) * AMR_NSEG * sc->amr_maxio; + error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ + 1, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + segsize, 1, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + 0, /* flags */ + &sc->amr_sg_dmat); + if (error != 0) { + device_printf(sc->amr_dev, "can't allocate scatter/gather DMA tag\n"); + return(ENOMEM); + } + + /* + * Allocate enough s/g maps for all commands and permanently map them into + * controller-visible space. + * + * XXX this assumes we can get enough space for all the s/g maps in one + * contiguous slab. We may need to switch to a more complex arrangement where + * we allocate in smaller chunks and keep a lookup table from slot to bus address. + */ + error = bus_dmamem_alloc(sc->amr_sg_dmat, (void **)&sc->amr_sgtable, BUS_DMA_NOWAIT, &sc->amr_sg_dmamap); + if (error) { + device_printf(sc->amr_dev, "can't allocate s/g table\n"); + return(ENOMEM); + } + bus_dmamap_load(sc->amr_sg_dmat, sc->amr_sg_dmamap, sc->amr_sgtable, segsize, amr_dma_map_sg, sc, 0); + return(0); +} + +/******************************************************************************** + * Allocate and set up mailbox areas for the controller (sc) + * + * The basic mailbox structure should be 16-byte aligned. This means that the + * mailbox64 structure has 4 bytes hanging off the bottom. + */ +static void +amr_map_mailbox(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct amr_softc *sc = (struct amr_softc *)arg; + + debug("called"); + + /* save phsyical base of the basic mailbox structure */ + sc->amr_mailboxphys = segs->ds_addr + 16; +} + +static int +amr_setup_mbox(struct amr_softc *sc) +{ + int error; + u_int8_t *p; + + debug("called"); + + /* + * Create a single tag describing a region large enough to hold the entire + * mailbox. + */ + error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ + 16, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + sizeof(struct amr_mailbox) + 16, 1, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + 0, /* flags */ + &sc->amr_mailbox_dmat); + if (error != 0) { + device_printf(sc->amr_dev, "can't allocate mailbox tag\n"); + return(ENOMEM); + } + + /* + * Allocate the mailbox structure and permanently map it into + * controller-visible space. + */ + error = bus_dmamem_alloc(sc->amr_mailbox_dmat, (void **)&p, BUS_DMA_NOWAIT, + &sc->amr_mailbox_dmamap); + if (error) { + device_printf(sc->amr_dev, "can't allocate mailbox memory\n"); + return(ENOMEM); + } + bus_dmamap_load(sc->amr_mailbox_dmat, sc->amr_mailbox_dmamap, p, + sizeof(struct amr_mailbox64), amr_map_mailbox, sc, 0); + /* + * Conventional mailbox is inside the mailbox64 region. + */ + bzero(p, sizeof(struct amr_mailbox64)); + sc->amr_mailbox64 = (struct amr_mailbox64 *)(p + 12); + sc->amr_mailbox = (struct amr_mailbox *)(p + 16); + + if (sc->amr_type == AMR_TYPE_STD) { + /* XXX we have to tell the controller where we put it */ + } + return(0); +} + + +/******************************************************************************** + * Initialise the controller and softc. + */ +int +amr_attach(struct amr_softc *sc) +{ + int rid, error; + + /* + * Initialise per-controller queues. + */ + TAILQ_INIT(&sc->amr_donecmds); + TAILQ_INIT(&sc->amr_freecmds); + bufq_init(&sc->amr_bufq); + + /* + * Configure for this controller type. + */ + if (sc->amr_type == AMR_TYPE_QUARTZ) { + sc->amr_submit_command = amr_quartz_submit_command; + sc->amr_get_work = amr_quartz_get_work; + sc->amr_attach_mailbox = amr_quartz_attach_mailbox; + } else { + sc->amr_submit_command = amr_std_submit_command; + sc->amr_get_work = amr_std_get_work; + sc->amr_attach_mailbox = amr_std_attach_mailbox; + } + + /* + * Allocate and connect our interrupt. + */ + rid = 0; + sc->amr_irq = bus_alloc_resource(sc->amr_dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); + if (sc->amr_irq == NULL) { + device_printf(sc->amr_dev, "couldn't allocate interrupt\n"); + amr_free(sc); + return(ENXIO); + } + error = bus_setup_intr(sc->amr_dev, sc->amr_irq, INTR_TYPE_BIO, amr_intr, sc, &sc->amr_intr); + if (error) { + device_printf(sc->amr_dev, "couldn't set up interrupt\n"); + amr_free(sc); + return(ENXIO); + } + + /* + * Create DMA tag for mapping buffers into controller-addressable space. + */ + error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ + 1, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MAXBSIZE, AMR_NSEG, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + 0, /* flags */ + &sc->amr_buffer_dmat); + if (error != 0) { + device_printf(sc->amr_dev, "can't allocate buffer DMA tag\n"); + return(ENOMEM); + } + + /* + * Allocate and set up mailbox in a bus-visible fashion, attach to controller. + */ + if ((error = amr_setup_mbox(sc)) != 0) + return(error); + sc->amr_attach_mailbox(sc); + + /* + * Build a temporary set of scatter/gather buffers. + */ + sc->amr_maxio = 2; + if (amr_sglist_map(sc)) + return(ENXIO); + + /* + * Quiz controller for features and limits. + */ + if (amr_query_controller(sc)) + return(ENXIO); + + /* + * Rebuild the scatter/gather buffers now we know how many we need. + */ + if (amr_sglist_map(sc)) + return(ENXIO); + + return(0); +} + +/******************************************************************************** + * Locate disk resources and attach children to them. + */ +void +amr_startup(struct amr_softc *sc) +{ + struct amr_logdrive *dr; + int i, error; + + debug("called"); + + /* get up-to-date drive information */ + if (amr_query_controller(sc)) { + device_printf(sc->amr_dev, "couldn'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, dr); + if (dr->al_disk == 0) + device_printf(sc->amr_dev, "device_add_child failed\n"); + } + } + + 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; +} + +/******************************************************************************** + * Disconnect from the controller completely, in preparation for unload. + */ +int +amr_detach(device_t dev) +{ + struct amr_softc *sc = device_get_softc(dev); + int error; + + debug("called"); + + if (sc->amr_state & AMR_STATE_OPEN) + return(EBUSY); + + if ((error = amr_shutdown(dev))) + return(error); + + amr_free(sc); + + /* + * Deregister the control device on last detach. + */ + if (--cdev_registered == 0) + cdevsw_remove(&amr_cdevsw); + + return(0); +} + +/******************************************************************************** + * Bring the controller down to a dormant state and detach all child devices. + * + * This function is called before detach, system shutdown, or before performing + * an operation which may add or delete system disks. (Call amr_startup to + * resume normal operation.) + * + * Note that we can assume that the bufq on the controller is empty, as we won't + * allow shutdown if any device is open. + */ +int +amr_shutdown(device_t dev) +{ + struct amr_softc *sc = device_get_softc(dev); + struct amrd_softc *ad; + int i, s, error; + + debug("called"); + + s = splbio(); + error = 0; + + + /* assume we're going to shut down */ + sc->amr_state |= AMR_STATE_SHUTDOWN; + for (i = 0; i < AMR_MAXLD; i++) { + if (sc->amr_drive[i].al_disk != 0) { + ad = device_get_softc(sc->amr_drive[i].al_disk); + if (ad->amrd_flags & AMRD_OPEN) { /* drive is mounted, abort shutdown */ + sc->amr_state &= ~AMR_STATE_SHUTDOWN; + device_printf(sc->amr_drive[i].al_disk, "still open, can't shutdown\n"); + error = EBUSY; + goto out; + } + } + } + + /* flush controller */ + device_printf(sc->amr_dev, "flushing cache..."); + if (amr_flush(sc)) { + printf("failed\n"); + } else { + printf("done\n"); + } + + /* delete all our child devices */ + for (i = 0; i < AMR_MAXLD; i++) { + if (sc->amr_drive[i].al_disk != 0) { + if ((error = device_delete_child(sc->amr_dev, sc->amr_drive[i].al_disk)) != 0) + goto out; + sc->amr_drive[i].al_disk = 0; + } + } + bus_generic_detach(sc->amr_dev); + + out: + splx(s); + return(error); +} + +/******************************************************************************** + * Bring the controller to a quiescent state, ready for system suspend. + */ +int +amr_suspend(device_t dev) +{ + struct amr_softc *sc = device_get_softc(dev); + + debug("called"); + + sc->amr_state |= AMR_STATE_SUSPEND; + + /* flush controller */ + device_printf(sc->amr_dev, "flushing cache..."); + printf("%s\n", amr_flush(sc) ? "failed" : "done"); + + return(0); +} + +/******************************************************************************** + * Bring the controller back to a state ready for operation. + */ +int +amr_resume(device_t dev) +{ + struct amr_softc *sc = device_get_softc(dev); + + debug("called"); + + sc->amr_state &= ~AMR_STATE_SUSPEND; + + return(0); +} + +/******************************************************************************* + * Take an interrupt, or be poked by other code to look for interrupt-worthy + * status. + */ +void +amr_intr(void *arg) +{ + struct amr_softc *sc = (struct amr_softc *)arg; + int worked; + + debug("called on %p", sc); + + /* spin collecting finished commands, process them if we find anything */ + worked = 0; + while (amr_done(sc)) + worked = 1; + if (worked) + amr_complete(sc); +}; + +/******************************************************************************* + * Receive a buf structure from a child device and queue it on a particular + * disk resource, then poke the disk resource to start as much work as it can. + */ +int +amr_submit_buf(struct amr_softc *sc, struct buf *bp) +{ + debug("called"); + + bufq_insert_tail(&sc->amr_bufq, bp); + sc->amr_waitbufs++; + amr_startio(sc); + return(0); +} + +/******************************************************************************** + * Accept an open operation on the control device. + */ +int +amr_open(dev_t dev, int flags, int fmt, struct proc *p) +{ + int unit = minor(dev); + struct amr_softc *sc = devclass_get_softc(amr_devclass, unit); + + sc->amr_state |= AMR_STATE_OPEN; + return(0); +} + +/******************************************************************************** + * Accept the last close on the control device. + */ +int +amr_close(dev_t dev, int flags, int fmt, struct proc *p) +{ + int unit = minor(dev); + struct amr_softc *sc = devclass_get_softc(amr_devclass, unit); + + sc->amr_state &= ~AMR_STATE_OPEN; + return (0); +} + +/******************************************************************************** + * Handle controller-specific control operations. + */ +int +amr_ioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) +{ + + switch(cmd) { + default: + return(ENOTTY); + } +} + +/******************************************************************************** + * Handle operations requested by a drive connected to this controller. + */ +int +amr_submit_ioctl(struct amr_softc *sc, struct amr_logdrive *drive, u_long cmd, + caddr_t addr, int32_t flag, struct proc *p) +{ + return(ENOTTY); +} + +/******************************************************************************** + ******************************************************************************** + Command Wrappers + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Interrogate the controller for the operational parameters we require. + */ +static int +amr_query_controller(struct amr_softc *sc) +{ + void *buf; + int i; + + /* try to issue an ENQUIRY3 command */ + if ((buf = amr_enquiry(sc, 2048, AMR_CMD_CONFIG, AMR_CONFIG_ENQ3, + AMR_CONFIG_ENQ3_SOLICITED_FULL)) == NULL) { + + struct amr_enquiry *ae; + + /* failed, try the old ENQUIRY command */ + if ((ae = (struct amr_enquiry *)amr_enquiry(sc, 2048, AMR_CMD_ENQUIRY, 0, 0)) == NULL) { + device_printf(sc->amr_dev, "could not obtain configuration data from controller\n"); + return(1); + } + /* first-time enquiry? */ + if (sc->amr_maxdrives == 0) { + device_printf(sc->amr_dev, "firmware %.4s bios %.4s %dMB memory, chipset %x\n", + ae->ae_adapter.aa_firmware, ae->ae_adapter.aa_bios, + ae->ae_adapter.aa_memorysize, ae->ae_adapter.aa_chipsetvalue); + } + sc->amr_maxdrives = 8; + sc->amr_maxio = ae->ae_adapter.aa_maxio; + for (i = 0; i < ae->ae_ldrv.al_numdrives; i++) { + sc->amr_drive[i].al_size = ae->ae_ldrv.al_size[i]; + sc->amr_drive[i].al_state = ae->ae_ldrv.al_state[i]; + sc->amr_drive[i].al_properties = ae->ae_ldrv.al_properties[i]; + debug(" drive %d: %d state %x properties %x\n", i, sc->amr_drive[i].al_size, + sc->amr_drive[i].al_state, sc->amr_drive[i].al_properties); + } + for (; i < AMR_MAXLD; i++) + sc->amr_drive[i].al_size = 0xffffffff; + free(ae, M_DEVBUF); + } else { + free(buf, M_DEVBUF); + sc->amr_maxdrives = 40; + + /* get static product info */ + if ((buf = amr_enquiry(sc, 2048, AMR_CMD_CONFIG, AMR_CONFIG_PRODINFO, 0)) == NULL) { + device_printf(sc->amr_dev, "controller supports 40ld but CONFIG_PRODINFO failed\n"); + return(1); + } + free(buf, M_DEVBUF); + device_printf(sc->amr_dev, "40LD firmware unsupported; send controller to msmith@freebsd.org\n"); + return(1); + } + 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"); + + 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; + /* get a command slot */ + ac->ac_flags |= AMR_CMD_PRIORITY | AMR_CMD_DATAOUT; + if (amr_getslot(ac)) + goto out; + + /* map the command so the controller can see it */ + ac->ac_data = result; + ac->ac_length = bufsize; + amr_mapcmd(ac); + + /* 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; + ac->ac_mailbox.mb_physaddr = ac->ac_dataphys; + + /* run the command in polled/wait mode as suits the current mode */ + if ((sc->amr_state & AMR_STATE_INTEN) ? amr_wait_command(ac) : 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. + */ +static 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; + /* get a command slot */ + ac->ac_flags |= AMR_CMD_PRIORITY | AMR_CMD_DATAOUT; + if (amr_getslot(ac)) + goto out; + + /* build the command proper */ + ac->ac_mailbox.mb_command = AMR_CMD_FLUSH; + + /* run the command in polled/wait mode as suits the current mode */ + if ((sc->amr_state & AMR_STATE_INTEN) ? amr_wait_command(ac) : amr_poll_command(ac)) + goto out; + error = ac->ac_status; + + out: + if (ac != NULL) + amr_releasecmd(ac); + return(error); +} + +/******************************************************************************** + * Pull as much work off the softc's work queue as possible and give it to the + * controller. Leave a couple of slots free for emergencies. + * + * Must be called at splbio or in an equivalent fashion that prevents + * reentry or activity on the bufq. + */ +static void +amr_startio(struct amr_softc *sc) +{ + struct amr_command *ac; + struct amrd_softc *amrd; + struct buf *bp; + int blkcount; + int driveno; + int cmd; + + /* spin until something prevents us from doing any work */ + for (;;) { + + /* see if there's work to be done */ + if ((bp = bufq_first(&sc->amr_bufq)) == NULL) + break; + /* get a command */ + if ((ac = amr_alloccmd(sc)) == NULL) + break; + /* get a slot for the command */ + if (amr_getslot(ac) != 0) { + amr_releasecmd(ac); + break; + } + /* get the buf containing our work */ + bufq_remove(&sc->amr_bufq, bp); + sc->amr_waitbufs--; + + /* connect the buf to the command */ + ac->ac_complete = amr_completeio; + ac->ac_private = bp; + ac->ac_data = bp->b_data; + ac->ac_length = bp->b_bcount; + if (bp->b_flags & B_READ) { + ac->ac_flags |= AMR_CMD_DATAIN; + cmd = AMR_CMD_LREAD; + } else { + ac->ac_flags |= AMR_CMD_DATAOUT; + cmd = AMR_CMD_LWRITE; + } + + /* map the command so the controller can work with it */ + amr_mapcmd(ac); + + /* build a suitable I/O command (assumes 512-byte rounded transfers) */ + amrd = (struct amrd_softc *)bp->b_driver1; + driveno = amrd->amrd_drive - &sc->amr_drive[0]; + blkcount = bp->b_bcount / AMR_BLKSIZE; + + if ((bp->b_blkno + blkcount) > sc->amr_drive[driveno].al_size) + device_printf(sc->amr_dev, "I/O beyond end of unit (%u,%d > %u)\n", + bp->b_blkno, blkcount, sc->amr_drive[driveno].al_size); + + /* + * Build the I/O command. + */ + ac->ac_mailbox.mb_command = cmd; + ac->ac_mailbox.mb_blkcount = blkcount; + ac->ac_mailbox.mb_lba = bp->b_blkno; + ac->ac_mailbox.mb_physaddr = ac->ac_sgphys; + ac->ac_mailbox.mb_drive = driveno; + ac->ac_mailbox.mb_nsgelem = ac->ac_nsgent; + + /* try to give command to controller */ + if (amr_start(ac) != 0) { + /* fail the command */ + ac->ac_status = AMR_STATUS_WEDGED; + amr_completeio(ac); + } + } +} + +/******************************************************************************** + * Handle completion of an I/O command. + */ +static void +amr_completeio(struct amr_command *ac) +{ + struct amr_softc *sc = ac->ac_sc; + struct buf *bp = (struct buf *)ac->ac_private; + + if (ac->ac_status != AMR_STATUS_SUCCESS) { /* could be more verbose here? */ + bp->b_error = EIO; + bp->b_flags |= B_ERROR; + + switch(ac->ac_status) { + /* XXX need more information on I/O error reasons */ + default: + device_printf(sc->amr_dev, "I/O error - %x\n", ac->ac_status); + amr_printcommand(ac); + break; + } + } + amr_releasecmd(ac); + amrd_intr(bp); +} + +/******************************************************************************** + ******************************************************************************** + Command Processing + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * 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) +{ + struct amr_softc *sc = ac->ac_sc; + int error, count; + + debug("called"); + + ac->ac_complete = NULL; + ac->ac_private = ac; + if ((error = amr_start(ac)) != 0) + return(error); + + count = 0; + /* XXX better timeout? */ + while ((ac->ac_status == AMR_STATUS_BUSY) && (count < 30)) { + tsleep(ac->ac_private, PRIBIO | PCATCH, "amrwcmd", hz); + } + + if (ac->ac_status != 0) { + device_printf(sc->amr_dev, "I/O error 0x%x\n", ac->ac_status); + return(EIO); + } + 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_poll_command(struct amr_command *ac) +{ + struct amr_softc *sc = ac->ac_sc; + int error, count, s; + + debug("called"); + + ac->ac_complete = NULL; + ac->ac_private = 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); + } while ((ac->ac_status == AMR_STATUS_BUSY) && (count++ < 100000)); + s = splbio(); + if (ac->ac_status != AMR_STATUS_BUSY) { + TAILQ_REMOVE(&sc->amr_donecmds, ac, ac_link); + sc->amr_donecmdcount--; + error = 0; + } else { + /* take the command out of the busy list, mark slot as bogus */ + sc->amr_busycmd[ac->ac_slot] = (struct amr_command *)sc; + error = EIO; + device_printf(sc->amr_dev, "I/O error 0x%x\n", ac->ac_status); + } + splx(s); + return(error); +} + +/******************************************************************************** + * Get a free command slot. + */ +static int +amr_getslot(struct amr_command *ac) +{ + struct amr_softc *sc = ac->ac_sc; + int s, slot, limit; + + debug("called"); + + /* enforce slot usage limit */ + limit = (ac->ac_flags & AMR_CMD_PRIORITY) ? sc->amr_maxio : sc->amr_maxio - 4; + if (sc->amr_busycmdcount > limit) + return(EBUSY); + + /* + * Allocate a slot + */ + s = splbio(); + for (slot = 0; slot < sc->amr_maxio; slot++) { + if (sc->amr_busycmd[slot] == NULL) + break; + } + if (slot < sc->amr_maxio) { + sc->amr_busycmdcount++; + sc->amr_busycmd[slot] = ac; + } + splx(s); + + /* out of slots? */ + if (slot >= sc->amr_maxio) + return(EBUSY); + + ac->ac_slot = slot; + return(0); +} + +/******************************************************************************** + * Map/unmap (ac)'s data in the controller's addressable space. + */ +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; + + debug("called"); + + /* get base address of s/g table */ + sg = sc->amr_sgtable + (ac->ac_slot * AMR_NSEG); + + /* save s/g table information in command */ + ac->ac_nsgent = nsegments; + ac->ac_sgphys = sc->amr_sgbusaddr + (ac->ac_slot * AMR_NSEG * sizeof(struct amr_sgentry)); + ac->ac_dataphys = segs[0].ds_addr; + + /* populate s/g table */ + for (i = 0; i < nsegments; i++, sg++) { + sg->sg_addr = segs[i].ds_addr; + sg->sg_count = segs[i].ds_len; + } +} + +static void +amr_mapcmd(struct amr_command *ac) +{ + struct amr_softc *sc = ac->ac_sc; + + debug("called"); + + /* if the command involves data at all */ + if (ac->ac_data != NULL) { + + /* map the data buffer 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); + } +} + +static void +amr_unmapcmd(struct amr_command *ac) +{ + struct amr_softc *sc = ac->ac_sc; + + debug("called"); + + /* if the command involved data at all */ + 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); + } +} + +/******************************************************************************** + * Take a command and give it to the controller. Take care of any completed + * commands we encouter while waiting. + */ +static int +amr_start(struct amr_command *ac) +{ + struct amr_softc *sc = ac->ac_sc; + int worked, done, s, i; + + debug("called"); + + /* + * 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; + + /* set the busy flag when we copy the mailbox in */ + ac->ac_mailbox.mb_busy = 1; + + /* set impossible status so that a woken sleeper can tell the command is busy */ + ac->ac_status = AMR_STATUS_BUSY; + + /* spin waiting for the mailbox */ + debug("wait for mailbox"); + for (i = 10000, done = 0, worked = 0; (i > 0) && !done; i--) { + s = splbio(); + + /* is the mailbox free? */ + if (sc->amr_mailbox->mb_busy == 0) { + debug("got mailbox"); + sc->amr_mailbox64->mb64_segment = 0; + bcopy(&ac->ac_mailbox, sc->amr_mailbox, AMR_MBOX_CMDSIZE); + sc->amr_submit_command(sc); + done = 1; + + /* not free, try to clean up while we wait */ + } else { + debug("busy flag %x\n", sc->amr_mailbox->mb_busy); + worked = amr_done(sc); + } + splx(s); + } + + /* do completion processing if we picked anything up */ + if (worked) + amr_complete(sc); + + /* command is enqueued? */ + if (done) { + ac->ac_stamp = time_second; + debug("posted command"); + return(0); + } + + /* + * The controller wouldn't take the command. Revoke the slot + * that the command was given and return with a bad status. + */ + sc->amr_busycmd[ac->ac_slot] = NULL; + device_printf(sc->amr_dev, "controller wedged (not taking commands)\n"); + ac->ac_status = AMR_STATUS_WEDGED; + return(EIO); +} + +/******************************************************************************** + * Extract one or more completed commands from the controller (sc) + * + * Returns nonzero if work was moved to the done queue. + */ +static int +amr_done(struct amr_softc *sc) +{ + struct amr_command *ac; + struct amr_mailbox mbox; + int i, idx, s, result; + + debug("called"); + + /* See if there's anything for us to do */ + result = 0; + if (sc->amr_get_work(sc, &mbox)) { + /* iterate over completed commands */ + 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_busycmdcount--; + + /* aborted command? */ + if (ac == (struct amr_command *)sc) { + device_printf(sc->amr_dev, "aborted command completed (%d)\n", idx); + ac = NULL; + + /* normally completed command, move it to the done queue */ + } else { + sc->amr_donecmdcount++; + s = splbio(); + TAILQ_INSERT_TAIL(&sc->amr_donecmds, ac, ac_link); + splx(s); + /* save completion status */ + ac->ac_status = mbox.mb_status; + debug("completed command with status %x", mbox.mb_status); + + /* unmap data buffer */ + amr_unmapcmd(ac); + } + result = 1; + } + } + } + return(result); +} + +/******************************************************************************** + * Do completion processing on done commands on (sc) + */ +static void +amr_complete(struct amr_softc *sc) +{ + struct amr_command *ac, *nc; + int s, count; + + debug("called"); + + count = 0; + + s = splbio(); + ac = TAILQ_FIRST(&sc->amr_donecmds); + while (ac != NULL) { + nc = TAILQ_NEXT(ac, ac_link); + + /* + * Is there a completion handler? + */ + if (ac->ac_complete != NULL) { + + /* remove and give to completion handler */ + TAILQ_REMOVE(&sc->amr_donecmds, ac, ac_link); + sc->amr_donecmdcount--; + ac->ac_complete(ac); + + /* + * Is someone sleeping on this one? + */ + } else if (ac->ac_private != NULL) { + + /* remove and wake up */ + TAILQ_REMOVE(&sc->amr_donecmds, ac, ac_link); + sc->amr_donecmdcount--; + wakeup_one(ac->ac_private); + + /* + * Leave it for a polling caller. + */ + } else { + } + ac = nc; + } + splx(s); + + /* queue more work if we can */ + amr_startio(sc); +} + +/******************************************************************************** + ******************************************************************************** + Command Buffer Management + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Get a new command buffer. + * + * This may return NULL in low-memory cases. + * + * Note that using malloc() is expensive (the command buffer is << 1 page) but + * necessary if we are to be a loadable module before the zone allocator is fixed. + * + * If possible, we recycle a command buffer that's been used before. + * + * XXX Note that command buffers are not cleaned out - it is the caller's + * responsibility to ensure that all required fields are filled in before + * using a buffer. + */ +static struct amr_command * +amr_alloccmd(struct amr_softc *sc) +{ + struct amr_command *ac; + int error; + int s; + + debug("called"); + + s = splbio(); + if ((ac = TAILQ_FIRST(&sc->amr_freecmds)) != NULL) + TAILQ_REMOVE(&sc->amr_freecmds, ac, ac_link); + splx(s); + + /* allocate a new command buffer? */ + if (ac == NULL) { + ac = (struct amr_command *)malloc(sizeof(*ac), M_DEVBUF, M_NOWAIT); + if (ac != NULL) { + bzero(ac, sizeof(*ac)); + ac->ac_sc = sc; + error = bus_dmamap_create(sc->amr_buffer_dmat, 0, &ac->ac_dmamap); + if (error) { + free(ac, M_DEVBUF); + return(NULL); + } + } + } + bzero(&ac->ac_mailbox, sizeof(struct amr_mailbox)); + return(ac); +} + +/******************************************************************************** + * Release a command buffer for recycling. + * + * XXX It might be a good idea to limit the number of commands we save for reuse + * if it's shown that this list bloats out massively. + */ +static void +amr_releasecmd(struct amr_command *ac) +{ + int s; + + debug("called"); + + s = splbio(); + TAILQ_INSERT_HEAD(&ac->ac_sc->amr_freecmds, ac, ac_link); + splx(s); +} + +/******************************************************************************** + * Permanently discard a command buffer. + */ +static void +amr_freecmd(struct amr_command *ac) +{ + struct amr_softc *sc = ac->ac_sc; + + debug("called"); + + bus_dmamap_destroy(sc->amr_buffer_dmat, ac->ac_dmamap); + free(ac, M_DEVBUF); +} + +/******************************************************************************** + ******************************************************************************** + Interface-specific Shims + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Tell the controller that the mailbox contains a valid command + */ +static void +amr_quartz_submit_command(struct amr_softc *sc) +{ + debug("called"); + + sc->amr_mailbox->mb_poll = 0; + sc->amr_mailbox->mb_ack = 0; + /* XXX write barrier? */ + while(AMR_QGET_IDB(sc) & AMR_QIDB_SUBMIT) + ; /* XXX aiee! what if it dies? */ + AMR_QPUT_IDB(sc, sc->amr_mailboxphys | AMR_QIDB_SUBMIT); +} + +static void +amr_std_submit_command(struct amr_softc *sc) +{ + debug("called"); + + /* XXX write barrier? */ + while (AMR_SGET_MBSTAT(sc) & AMR_SMBOX_BUSYFLAG) + ; /* XXX aiee! what if it dies? */ + AMR_SPOST_COMMAND(sc); +} + +/******************************************************************************** + * 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"); + + worked = 0; + s = splbio(); + + /* work waiting for us? */ + if ((outd = AMR_QGET_ODB(sc)) == AMR_QODB_READY) { + AMR_QPUT_ODB(sc, AMR_QODB_READY); + + /* save mailbox, which contains a list of completed commands */ + /* XXX read barrier? */ + bcopy(sc->amr_mailbox, mbsave, sizeof(*mbsave)); + + /* acknowledge that we have the commands */ + AMR_QPUT_IDB(sc, sc->amr_mailboxphys | AMR_QIDB_ACK); + while(AMR_QGET_IDB(sc) & AMR_QIDB_ACK) + ; /* XXX aiee! what if it dies? */ + 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"); + + 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 */ + /* XXX read barrier? */ + bcopy(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_quartz_attach_mailbox(struct amr_softc *sc) +{ + /* Quartz is given the mailbox location when a command is submitted */ +} + +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); +} + +/******************************************************************************** + ******************************************************************************** + Debugging + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Print the command (ac) in human-readable format + */ +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 %d\n", ac->ac_data, ac->ac_length); + device_printf(sc->amr_dev, "physaddr %08x nsg %d\n", + ac->ac_mailbox.mb_physaddr, ac->ac_mailbox.mb_nsgelem); + + /* 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); +} |