diff options
author | msmith <msmith@FreeBSD.org> | 1999-10-07 02:23:12 +0000 |
---|---|---|
committer | msmith <msmith@FreeBSD.org> | 1999-10-07 02:23:12 +0000 |
commit | 8582d1db60ab1514e7c997b5a7dacb44520e0e2f (patch) | |
tree | a348a3683a759ad26e14e05d75e8499a16887e46 /sys/dev/amr | |
parent | 685ad456458d562e46df50ddeed3eb1681377f14 (diff) | |
download | FreeBSD-src-8582d1db60ab1514e7c997b5a7dacb44520e0e2f.zip FreeBSD-src-8582d1db60ab1514e7c997b5a7dacb44520e0e2f.tar.gz |
This is a driver for the AMI MegaRAID family of controllers. It all of
the AMI PCI controllers using the 8LD firmware interface (40LD firmware
will be supported as soon as I have hardware to test with).
These controllers are rebadged by Dell as the PERC, as well as by HP
and possibly other vendors.
Diffstat (limited to 'sys/dev/amr')
-rw-r--r-- | sys/dev/amr/amr.c | 1498 | ||||
-rw-r--r-- | sys/dev/amr/amr_disk.c | 299 | ||||
-rw-r--r-- | sys/dev/amr/amr_pci.c | 208 | ||||
-rw-r--r-- | sys/dev/amr/amrio.h | 28 | ||||
-rw-r--r-- | sys/dev/amr/amrreg.h | 199 | ||||
-rw-r--r-- | sys/dev/amr/amrvar.h | 216 |
6 files changed, 2448 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); +} diff --git a/sys/dev/amr/amr_disk.c b/sys/dev/amr/amr_disk.c new file mode 100644 index 0000000..958e5ad --- /dev/null +++ b/sys/dev/amr/amr_disk.c @@ -0,0 +1,299 @@ +/*- + * Copyright (c) 1999 Jonathan Lemon + * 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$ + */ + +/* + * Disk driver for AMI MegaRaid 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/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 + +/* prototypes */ +static int amrd_probe(device_t dev); +static int amrd_attach(device_t dev); +static int amrd_detach(device_t dev); + +static d_open_t amrd_open; +static d_close_t amrd_close; +static d_strategy_t amrd_strategy; +static d_ioctl_t amrd_ioctl; + +#define AMRD_BDEV_MAJOR 35 +#define AMRD_CDEV_MAJOR 133 + +static struct cdevsw amrd_cdevsw = { + /* open */ amrd_open, + /* close */ amrd_close, + /* read */ physread, + /* write */ physwrite, + /* ioctl */ amrd_ioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ amrd_strategy, + /* name */ "amrd", + /* maj */ AMRD_CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ D_DISK, + /* bmaj */ AMRD_BDEV_MAJOR +}; + +static devclass_t amrd_devclass; +static struct cdevsw amrddisk_cdevsw; +static int disks_registered = 0; + +static device_method_t amrd_methods[] = { + DEVMETHOD(device_probe, amrd_probe), + DEVMETHOD(device_attach, amrd_attach), + DEVMETHOD(device_detach, amrd_detach), + { 0, 0 } +}; + +static driver_t amrd_driver = { + "amrd", + amrd_methods, + sizeof(struct amrd_softc) +}; + +DRIVER_MODULE(amrd, amr, amrd_driver, amrd_devclass, 0, 0); + +static __inline struct amrd_softc * +amrd_getsoftc(dev_t dev) +{ + int unit; + + unit = dkunit(dev); + return ((struct amrd_softc *)devclass_get_softc(amrd_devclass, unit)); +} + +static int +amrd_open(dev_t dev, int flags, int fmt, struct proc *p) +{ + struct amrd_softc *sc = amrd_getsoftc(dev); + struct disklabel *label; + + debug("called"); + + if (sc == NULL) + return (ENXIO); + + /* controller not active? */ + if (sc->amrd_controller->amr_state & AMR_STATE_SHUTDOWN) + return(ENXIO); + + label = &sc->amrd_disk.d_label; + bzero(label, sizeof(*label)); + label->d_type = DTYPE_SCSI; + label->d_secsize = AMR_BLKSIZE; + label->d_nsectors = sc->amrd_drive->al_sectors; + label->d_ntracks = sc->amrd_drive->al_heads; + label->d_ncylinders = sc->amrd_drive->al_cylinders; + label->d_secpercyl = sc->amrd_drive->al_sectors * sc->amrd_drive->al_heads; + label->d_secperunit = sc->amrd_drive->al_size; + + sc->amrd_flags |= AMRD_OPEN; + return (0); +} + +static int +amrd_close(dev_t dev, int flags, int fmt, struct proc *p) +{ + struct amrd_softc *sc = amrd_getsoftc(dev); + + debug("called"); + + if (sc == NULL) + return (ENXIO); + sc->amrd_flags &= ~AMRD_OPEN; + return (0); +} + +static int +amrd_ioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) +{ + struct amrd_softc *sc = amrd_getsoftc(dev); + int error; + + debug("called"); + + if (sc == NULL) + return (ENXIO); + + if ((error = amr_submit_ioctl(sc->amrd_controller, sc->amrd_drive, cmd, addr, flag, p)) != ENOIOCTL) { + debug("amr_submit_ioctl returned %d\n", error); + return(error); + } + return (ENOTTY); +} + +/* + * Read/write routine for a buffer. Finds the proper unit, range checks + * arguments, and schedules the transfer. Does not wait for the transfer + * to complete. Multi-page transfers are supported. All I/O requests must + * be a multiple of a sector in length. + */ +static void +amrd_strategy(struct buf *bp) +{ + struct amrd_softc *sc = amrd_getsoftc(bp->b_dev); + int s; + + debug("called"); + + /* bogus disk? */ + if (sc == NULL) { + bp->b_error = EINVAL; + goto bad; + } + +#if 0 + /* XXX may only be temporarily offline - sleep? */ + if (sc->amrd_drive->ld_state == AMR_SYSD_OFFLINE) { + bp->b_error = ENXIO; + goto bad; + } +#endif + + /* do-nothing operation */ + if (bp->b_bcount == 0) + goto done; + + /* pass reference to us */ + bp->b_driver1 = sc; + s = splbio(); + devstat_start_transaction(&sc->amrd_stats); + amr_submit_buf(sc->amrd_controller, bp); + splx(s); + return; + + bad: + bp->b_flags |= B_ERROR; + + done: + /* + * Correctly set the buf to indicate a completed transfer + */ + bp->b_resid = bp->b_bcount; + biodone(bp); + return; +} + +void +amrd_intr(void *data) +{ + struct buf *bp = (struct buf *)data; + struct amrd_softc *sc = (struct amrd_softc *)bp->b_driver1; + + debug("called"); + + if (bp->b_flags & B_ERROR) + bp->b_error = EIO; + else + bp->b_resid = 0; + + devstat_end_transaction_buf(&sc->amrd_stats, bp); + biodone(bp); +} + +static int +amrd_probe(device_t dev) +{ + + debug("called"); + + device_set_desc(dev, "MegaRAID logical drive"); + return (0); +} + +static int +amrd_attach(device_t dev) +{ + struct amrd_softc *sc = (struct amrd_softc *)device_get_softc(dev); + device_t parent; + + debug("called"); + + parent = device_get_parent(dev); + sc->amrd_controller = (struct amr_softc *)device_get_softc(parent); + sc->amrd_unit = device_get_unit(dev); + sc->amrd_drive = device_get_ivars(dev); + + device_printf(dev, "%uMB (%u sectors), state 0x%x properties 0x%x\n", + sc->amrd_drive->al_size / ((1024 * 1024) / AMR_BLKSIZE), + sc->amrd_drive->al_size, sc->amrd_drive->al_state, sc->amrd_drive->al_properties); + + devstat_add_entry(&sc->amrd_stats, "amrd", sc->amrd_unit, AMR_BLKSIZE, + DEVSTAT_NO_ORDERED_TAGS, + DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_OTHER, + DEVSTAT_PRIORITY_DA); + + disk_create(sc->amrd_unit, &sc->amrd_disk, 0, &amrd_cdevsw, &amrddisk_cdevsw); + disks_registered++; + + return (0); +} + +static int +amrd_detach(device_t dev) +{ + struct amrd_softc *sc = (struct amrd_softc *)device_get_softc(dev); + + debug("called"); + + devstat_remove_entry(&sc->amrd_stats); + + /* hack to handle lack of destroy_disk() */ + if (--disks_registered == 0) + cdevsw_remove(&amrddisk_cdevsw); + + return(0); +} + diff --git a/sys/dev/amr/amr_pci.c b/sys/dev/amr/amr_pci.c new file mode 100644 index 0000000..7968a32 --- /dev/null +++ b/sys/dev/amr/amr_pci.c @@ -0,0 +1,208 @@ +/*- + * 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$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/kernel.h> + +#include <sys/bus.h> +#include <sys/buf.h> +#include <sys/conf.h> +#include <sys/devicestat.h> +#include <sys/disk.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> + +#if 0 +#define debug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args) +#else +#define debug(fmt, args...) +#endif + +static int amr_pci_probe(device_t dev); +static int amr_pci_attach(device_t dev); + +static device_method_t amr_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, amr_pci_probe), + DEVMETHOD(device_attach, amr_pci_attach), + DEVMETHOD(device_detach, amr_detach), + DEVMETHOD(device_shutdown, amr_shutdown), + DEVMETHOD(device_suspend, amr_suspend), + DEVMETHOD(device_resume, amr_resume), + + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + { 0, 0 } +}; + +static driver_t amr_pci_driver = { + "amr", + amr_methods, + sizeof(struct amr_softc) +}; + +DRIVER_MODULE(amr, pci, amr_pci_driver, amr_devclass, 0, 0); + +static struct +{ + int vendor; + int device; + int flag; +#define PROBE_SIGNATURE (1<<0) +} amr_device_ids[] = { + {0x101e, 0x9010, 0}, + {0x101e, 0x9060, 0}, + {0x8086, 0x1960, PROBE_SIGNATURE}, /* generic i960RD, check signature */ + {0, 0, 0} +}; + +static int +amr_pci_probe(device_t dev) +{ + int i; + + debug("called"); + + for (i = 0; amr_device_ids[i].vendor != 0; i++) { + if ((pci_get_vendor(dev) == amr_device_ids[i].vendor) && + (pci_get_device(dev) == amr_device_ids[i].device)) { + + /* do we need to test for a signature? */ + if ((amr_device_ids[i].flag & PROBE_SIGNATURE) && + (pci_read_config(dev, AMR_CFG_SIG, 2) != AMR_SIGNATURE)) + continue; + device_set_desc(dev, "AMI MegaRAID"); + return(0); + } + } + return(ENXIO); +} + +static int +amr_pci_attach(device_t dev) +{ + struct amr_softc *sc; + int rid, rtype, error; + u_int32_t command; + + debug("called"); + + /* + * Initialise softc. + */ + sc = device_get_softc(dev); + bzero(sc, sizeof(*sc)); + sc->amr_dev = dev; + + /* + * Determine board type.. + */ + command = pci_read_config(dev, PCIR_COMMAND, 1); + if ((pci_get_vendor(dev) == 0x8086) && (pci_get_device(dev) == 0x1960)) { + sc->amr_type = AMR_TYPE_QUARTZ; + + /* + * Make sure we are going to be able to talk to this board. + */ + if ((command & PCIM_CMD_MEMEN) == 0) { + device_printf(dev, "memory window not available\n"); + return(ENXIO); + } + + } else { + sc->amr_type = AMR_TYPE_STD; + + /* + * Make sure we are going to be able to talk to this board. + */ + if ((command & PCIM_CMD_PORTEN) == 0) { + device_printf(dev, "I/O window not available\n"); + return(ENXIO); + } + } + + /* + * Allocate the PCI register window. + */ + rid = AMR_CFG_BASE; + rtype = (sc->amr_type == AMR_TYPE_QUARTZ) ? SYS_RES_MEMORY : SYS_RES_IOPORT; + sc->amr_reg = bus_alloc_resource(dev, rtype, &rid, 0, ~0, 1, RF_ACTIVE); + if (sc->amr_reg == NULL) { + device_printf(sc->amr_dev, "couldn't allocate register window\n"); + amr_free(sc); + return(ENXIO); + } + sc->amr_btag = rman_get_bustag(sc->amr_reg); + sc->amr_bhandle = rman_get_bushandle(sc->amr_reg); + + /* + * Allocate the parent bus DMA tag appropriate for PCI. + */ + error = bus_dma_tag_create(NULL, /* parent */ + 1, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MAXBSIZE, AMR_NSEG, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + &sc->amr_parent_dmat); + if (error != 0) { + device_printf(dev, "can't allocate parent DMA tag\n"); + amr_free(sc); + return(ENOMEM); + } + + /* + * Do bus-independant initialisation. + */ + error = amr_attach(sc); + if (error != 0) { + amr_free(sc); + return(error); + } + + /* + * Start the controller. + */ + amr_startup(sc); + return(0); +} diff --git a/sys/dev/amr/amrio.h b/sys/dev/amr/amrio.h new file mode 100644 index 0000000..c35a133 --- /dev/null +++ b/sys/dev/amr/amrio.h @@ -0,0 +1,28 @@ +/*- + * 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$ + */ + diff --git a/sys/dev/amr/amrreg.h b/sys/dev/amr/amrreg.h new file mode 100644 index 0000000..3ae84b9 --- /dev/null +++ b/sys/dev/amr/amrreg.h @@ -0,0 +1,199 @@ +/*- + * 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$ + */ + +/* + * Mailbox commands + */ +#define AMR_CMD_LREAD 0x01 +#define AMR_CMD_LWRITE 0x02 +#define AMR_CMD_ENQUIRY 0x05 +#define AMR_CMD_FLUSH 0x0a +#define AMR_CMD_CONFIG 0xa1 +#define AMR_CONFIG_PRODINFO 0x0e +#define AMR_CONFIG_ENQ3 0x0f +#define AMR_CONFIG_ENQ3_SOLICITED_NOTIFY 0x01 +#define AMR_CONFIG_ENQ3_SOLICITED_FULL 0x02 +#define AMR_CONFIG_ENQ3_UNSOLICITED 0x03 + +/* + * Command results + */ +#define AMR_STATUS_SUCCESS 0x00 +#define AMR_STATUS_ABORTED 0x02 +#define AMR_STATUS_FAILED 0x80 + +/* + * Quartz doorbell registers + */ +#define AMR_QIDB 0x20 +#define AMR_QODB 0x2c +#define AMR_QIDB_SUBMIT 0x00000001 /* mailbox ready for work */ +#define AMR_QIDB_ACK 0x00000002 /* mailbox done */ +#define AMR_QODB_READY 0x10001234 /* work ready to be processed */ + +/* + * Standard I/O registers + */ +#define AMR_SCMD 0x10 /* command/ack register (write) */ +#define AMR_SMBOX_BUSY 0x10 /* mailbox status (read) */ +#define AMR_STOGGLE 0x11 /* interrupt enable bit here */ +#define AMR_SMBOX_0 0x14 /* mailbox physical address low byte */ +#define AMR_SMBOX_1 0x15 +#define AMR_SMBOX_2 0x16 +#define AMR_SMBOX_3 0x17 /* high byte */ +#define AMR_SMBOX_ENABLE 0x18 /* atomic mailbox address enable */ +#define AMR_SINTR 0x1a /* interrupt status */ + +/* + * Standard I/O magic numbers + */ +#define AMR_SCMD_POST 0x10 /* -> SCMD to initiate action on mailbox */ +#define AMR_SCMD_ACKINTR 0x08 /* -> SCMD to ack mailbox retrieved */ +#define AMR_STOGL_IENABLE 0xc0 /* in STOGGLE */ +#define AMR_SINTR_VALID 0x40 /* in SINTR */ +#define AMR_SMBOX_BUSYFLAG 0x10 /* in SMBOX_BUSY */ +#define AMR_SMBOX_ADDR 0x00 /* -> SMBOX_ENABLE */ + +/* + * Old Enquiry results + */ +#define AMR_8LD_MAXDRIVES 8 +#define AMR_8LD_MAXCHAN 5 +#define AMR_8LD_MAXTARG 15 +#define AMR_8LD_MAXPHYSDRIVES (AMR_8LD_MAXCHAN * AMR_8LD_MAXTARG) + +struct amr_adapter_info +{ + u_int8_t aa_maxio; + u_int8_t aa_rebuild_rate; + u_int8_t aa_maxtargchan; + u_int8_t aa_channels; + u_int8_t aa_firmware[4]; + u_int16_t aa_flashage; + u_int8_t aa_chipsetvalue; + u_int8_t aa_memorysize; + u_int8_t aa_cacheflush; + u_int8_t aa_bios[4]; + u_int8_t res1[7]; +} __attribute__ ((packed)); + +struct amr_logdrive_info +{ + u_int8_t al_numdrives; + u_int8_t res1[3]; + u_int32_t al_size[AMR_8LD_MAXDRIVES]; + u_int8_t al_properties[AMR_8LD_MAXDRIVES]; + u_int8_t al_state[AMR_8LD_MAXDRIVES]; +} __attribute__ ((packed)); + +struct amr_physdrive_info +{ + u_int8_t ap_state[AMR_8LD_MAXPHYSDRIVES]; + u_int8_t res1; +} __attribute__ ((packed)); + +struct amr_enquiry +{ + struct amr_adapter_info ae_adapter; + struct amr_logdrive_info ae_ldrv; + struct amr_physdrive_info ae_pdrv; +} __attribute__ ((packed)); + +struct amr_prodinfo +{ + u_int32_t ap_size; /* current size in bytes (not including resvd) */ + u_int32_t ap_configsig; /* default is 0x00282008, indicating 0x28 maximum + * logical drives, 0x20 maximum stripes and 0x08 + * maximum spans */ + u_int8_t ap_firmware[16]; /* printable identifiers */ + u_int8_t ap_bios[16]; + u_int8_t ap_product[80]; + u_int8_t ap_maxio; /* maximum number of concurrent commands supported */ + u_int8_t ap_nschan; /* number of SCSI channels present */ + u_int8_t ap_fcloops; /* number of fibre loops present */ + u_int8_t ap_memtype; /* memory type */ + u_int32_t ap_signature; + u_int16_t ap_memsize; /* onboard memory in MB */ + u_int16_t ap_subsystem; /* subsystem identifier */ + u_int16_t ap_subvendor; /* subsystem vendor ID */ + u_int8_t ap_numnotifyctr; /* number of notify counters */ +} __attribute__((packed)); + +#define AMR_MBOX_CMDSIZE 0x10 /* portion worth copying for controller */ + +struct amr_mailbox +{ + u_int8_t mb_command; + u_int8_t mb_ident; + u_int16_t mb_blkcount; + u_int32_t mb_lba; + u_int32_t mb_physaddr; + u_int8_t mb_drive; + u_int8_t mb_nsgelem; + u_int8_t res1; + u_int8_t mb_busy; + u_int8_t mb_nstatus; + u_int8_t mb_status; + u_int8_t mb_completed[46]; + u_int8_t mb_poll; + u_int8_t mb_ack; + u_int8_t res2[16]; +} __attribute__ ((packed)); + +struct amr_mailbox64 +{ + u_int32_t mb64_segment; /* for 64-bit controllers */ + struct amr_mailbox mb; +} __attribute__ ((packed)); + +struct amr_mailbox_ioctl +{ + u_int8_t mb_command; + u_int8_t mb_ident; + u_int8_t mb_channel; + u_int8_t mb_param; + u_int8_t res1[4]; + u_int32_t mb_physaddr; + u_int8_t mb_drive; + u_int8_t mb_nsgelem; + u_int8_t res2; + u_int8_t mb_busy; + u_int8_t mb_nstatus; + u_int8_t mb_completed[46]; + u_int8_t mb_poll; + u_int8_t mb_ack; + u_int8_t res3[16]; +} __attribute__ ((packed)); + +struct amr_sgentry +{ + u_int32_t sg_addr; + u_int32_t sg_count; +} __attribute__ ((packed)); + + diff --git a/sys/dev/amr/amrvar.h b/sys/dev/amr/amrvar.h new file mode 100644 index 0000000..c35d5db --- /dev/null +++ b/sys/dev/amr/amrvar.h @@ -0,0 +1,216 @@ +/*- + * 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$ + */ + +/* + * We could actually use all 17 segments, but using only 16 means that + * each scatter/gather map is 128 bytes in size, and thus we don't have to worry about + * maps crossing page boundaries. + */ +#define AMR_NSEG 16 + +#define AMR_CFG_BASE 0x10 +#define AMR_CFG_SIG 0xa0 +#define AMR_SIGNATURE 0x3344 + +#define AMR_MAXCMD 255 /* ident = 0 not allowed */ +#define AMR_MAXLD 40 + +#define AMR_BLKSIZE 512 + +struct amr_softc; + +/* + * Per-logical-drive datastructure + */ +struct amr_logdrive +{ + u_int32_t al_size; + int al_state; + int al_properties; + + /* synthetic geometry */ + int al_cylinders; + int al_heads; + int al_sectors; + + /* driver */ + device_t al_disk; +}; + + +/* + * Per-command control structure. + */ +struct amr_command +{ + TAILQ_ENTRY(amr_command) ac_link; + + struct amr_softc *ac_sc; + u_int8_t ac_slot; + int ac_status; +#define AMR_STATUS_BUSY 0xffff +#define AMR_STATUS_WEDGED 0xdead + struct amr_mailbox ac_mailbox; + u_int32_t ac_sgphys; + int ac_nsgent; + int ac_flags; +#define AMR_CMD_DATAIN (1<<0) +#define AMR_CMD_DATAOUT (1<<1) +#define AMR_CMD_PRIORITY (1<<2) + time_t ac_stamp; + + void *ac_data; + size_t ac_length; + bus_dmamap_t ac_dmamap; + u_int32_t ac_dataphys; + + void (* ac_complete)(struct amr_command *ac); + void *ac_private; +}; + +struct amr_softc +{ + /* bus attachments */ + device_t amr_dev; + struct resource *amr_reg; /* control registers */ + bus_space_handle_t amr_bhandle; + bus_space_tag_t amr_btag; + bus_dma_tag_t amr_parent_dmat; /* parent DMA tag */ + bus_dma_tag_t amr_buffer_dmat; /* data buffer DMA tag */ + struct resource *amr_irq; /* interrupt */ + void *amr_intr; + + /* mailbox */ + struct amr_mailbox *amr_mailbox; + struct amr_mailbox64 *amr_mailbox64; + u_int32_t amr_mailboxphys; + bus_dma_tag_t amr_mailbox_dmat; + bus_dmamap_t amr_mailbox_dmamap; + + /* scatter/gather lists and their controller-visible mappings */ + struct amr_sgentry *amr_sgtable; /* s/g lists */ + u_int32_t amr_sgbusaddr; /* s/g table base address in bus space */ + bus_dma_tag_t amr_sg_dmat; /* s/g buffer DMA tag */ + bus_dmamap_t amr_sg_dmamap; /* map for s/g buffers */ + + /* controller limits and features */ + int amr_maxio; /* maximum number of I/O transactions */ + int amr_maxdrives; /* max number of logical drives */ + + /* connected logical drives */ + struct amr_logdrive amr_drive[AMR_MAXLD]; + + /* controller status */ + int amr_state; +#define AMR_STATE_OPEN (1<<0) +#define AMR_STATE_SUSPEND (1<<1) +#define AMR_STATE_INTEN (1<<2) +#define AMR_STATE_SHUTDOWN (1<<3) + + /* per-controller queues */ + struct buf_queue_head amr_bufq; /* pending I/O */ + int amr_waitbufs; + struct amr_command *amr_busycmd[AMR_MAXCMD]; + int amr_busycmdcount; + TAILQ_HEAD(,amr_command) amr_donecmds; + int amr_donecmdcount; + TAILQ_HEAD(,amr_command) amr_freecmds; + + /* controller type-specific support */ + int amr_type; +#define AMR_TYPE_STD 0 +#define AMR_TYPE_QUARTZ 1 + void (* amr_submit_command)(struct amr_softc *sc); + int (* amr_get_work)(struct amr_softc *sc, struct amr_mailbox *mbsave); + void (* amr_attach_mailbox)(struct amr_softc *sc); +}; + +/* + * I/O primitives + */ +/* Quartz */ +#define AMR_QPUT_IDB(sc, val) bus_space_write_4(sc->amr_btag, sc->amr_bhandle, AMR_QIDB, val) +#define AMR_QGET_IDB(sc) bus_space_read_4 (sc->amr_btag, sc->amr_bhandle, AMR_QIDB) +#define AMR_QPUT_ODB(sc, val) bus_space_write_4(sc->amr_btag, sc->amr_bhandle, AMR_QODB, val) +#define AMR_QGET_ODB(sc) bus_space_read_4 (sc->amr_btag, sc->amr_bhandle, AMR_QODB) + +/* Standard */ +#define AMR_SPUT_ISTAT(sc, val) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_SINTR, val) +#define AMR_SGET_ISTAT(sc) bus_space_read_1 (sc->amr_btag, sc->amr_bhandle, AMR_SINTR) +#define AMR_SACK_INTERRUPT(sc) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_SCMD, AMR_SCMD_ACKINTR) +#define AMR_SPOST_COMMAND(sc) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_SCMD, AMR_SCMD_POST) +#define AMR_SGET_MBSTAT(sc) bus_space_read_1 (sc->amr_btag, sc->amr_bhandle, AMR_SMBOX_BUSY) +#define AMR_SENABLE_INTR(sc) \ + bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE, \ + bus_space_read_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE) | AMR_STOGL_IENABLE) +#define AMR_SDISABLE_INTR(sc) \ + bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE, \ + bus_space_read_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE) & ~AMR_STOGL_IENABLE) +#define AMR_SBYTE_SET(sc, reg, val) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, reg, val) + +/* + * Interface between bus connections and driver core. + */ +extern void amr_free(struct amr_softc *sc); +extern int amr_attach(struct amr_softc *sc); +extern void amr_startup(struct amr_softc *sc); +extern void amr_intr(void *data); +extern int amr_detach(device_t dev); +extern int amr_shutdown(device_t dev); +extern int amr_suspend(device_t dev); +extern int amr_resume(device_t dev); +extern d_open_t amr_open; +extern d_close_t amr_close; +extern d_ioctl_t amr_ioctl; + +extern devclass_t amr_devclass; + +/* + * MegaRAID logical disk driver + */ +struct amrd_softc +{ + device_t amrd_dev; + struct amr_softc *amrd_controller; + struct amr_logdrive *amrd_drive; + struct disk amrd_disk; + struct devstat amrd_stats; + struct disklabel amrd_label; + int amrd_unit; + int amrd_flags; +#define AMRD_OPEN (1<<0) /* drive is open (can't shut down) */ +}; + +/* + * Interface between driver core and disk driver (should be using a bus?) + */ +extern int amr_submit_buf(struct amr_softc *sc, struct buf *bp); +extern int amr_submit_ioctl(struct amr_softc *sc, struct amr_logdrive *drive, u_long cmd, + caddr_t addr, int32_t flag, struct proc *p); +extern void amrd_intr(void *data); + |