From 1ac4b82b11c460d0ef82bf760b71a8b4d34be28a Mon Sep 17 00:00:00 2001 From: Mike Smith Date: Thu, 7 Oct 1999 02:20:32 +0000 Subject: [PATCH] This is a driver for the Mylex DAC960 family of integrated RAID controllers. It currently supports the P, PL, PD and PU variants, with more to be supported shortly. --- sys/dev/mlx/mlx.c | 2295 ++++++++++++++++++++++++++++++++++++++ sys/dev/mlx/mlx_disk.c | 314 ++++++ sys/dev/mlx/mlx_pci.c | 199 ++++ sys/dev/mlx/mlxio.h | 88 ++ sys/dev/mlx/mlxreg.h | 188 ++++ sys/dev/mlx/mlxvar.h | 353 ++++++ sys/modules/mlx/Makefile | 22 + 7 files changed, 3459 insertions(+) create mode 100644 sys/dev/mlx/mlx.c create mode 100644 sys/dev/mlx/mlx_disk.c create mode 100644 sys/dev/mlx/mlx_pci.c create mode 100644 sys/dev/mlx/mlxio.h create mode 100644 sys/dev/mlx/mlxreg.h create mode 100644 sys/dev/mlx/mlxvar.h create mode 100644 sys/modules/mlx/Makefile diff --git a/sys/dev/mlx/mlx.c b/sys/dev/mlx/mlx.c new file mode 100644 index 000000000000..d353ada28e95 --- /dev/null +++ b/sys/dev/mlx/mlx.c @@ -0,0 +1,2295 @@ +/*- + * 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 Mylex DAC960 family of RAID controllers. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#if 0 +#define debug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args) +#else +#define debug(fmt, args...) +#endif + +#define MLX_CDEV_MAJOR 130 + +static struct cdevsw mlx_cdevsw = { + /* open */ mlx_open, + /* close */ mlx_close, + /* read */ noread, + /* write */ nowrite, + /* ioctl */ mlx_ioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "mlx", + /* maj */ MLX_CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, + /* bmaj */ 254 /* XXX magic no-bdev */ +}; + +static int cdev_registered = 0; +devclass_t mlx_devclass; + +/* + * Per-interface accessor methods + */ +static int mlx_v3_tryqueue(struct mlx_softc *sc, struct mlx_command *mc); +static int mlx_v3_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status); +static void mlx_v3_intaction(struct mlx_softc *sc, int action); + +/* + * Status monitoring + */ +static void mlx_periodic(void *data); +static void mlx_periodic_enquiry(struct mlx_command *mc); +static void mlx_periodic_eventlog_poll(struct mlx_softc *sc); +static void mlx_periodic_eventlog_respond(struct mlx_command *mc); +static void mlx_periodic_rebuild(struct mlx_command *mc); + +/* + * Channel Pause + */ +static void mlx_pause_action(struct mlx_softc *sc); +static void mlx_pause_done(struct mlx_command *mc); + +/* + * Command submission. + */ +static void *mlx_enquire(struct mlx_softc *sc, int command, size_t bufsize, + void (*complete)(struct mlx_command *mc)); +static int mlx_flush(struct mlx_softc *sc); +static int mlx_rebuild(struct mlx_softc *sc, int channel, int target); +static int mlx_wait_command(struct mlx_command *mc); +static int mlx_poll_command(struct mlx_command *mc); +static void mlx_startio(struct mlx_softc *sc); +static void mlx_completeio(struct mlx_command *mc); +static int mlx_user_command(struct mlx_softc *sc, struct mlx_usercommand *mu); + +/* + * Command buffer allocation. + */ +static struct mlx_command *mlx_alloccmd(struct mlx_softc *sc); +static void mlx_releasecmd(struct mlx_command *mc); +static void mlx_freecmd(struct mlx_command *mc); + +/* + * Command management. + */ +static int mlx_getslot(struct mlx_command *mc); +static void mlx_mapcmd(struct mlx_command *mc); +static void mlx_unmapcmd(struct mlx_command *mc); +static int mlx_start(struct mlx_command *mc); +static int mlx_done(struct mlx_softc *sc); +static void mlx_complete(struct mlx_softc *sc); + +/* + * Debugging. + */ +static char *mlx_diagnose_command(struct mlx_command *mc); +static char *mlx_name_controller(u_int32_t hwid); + + +/* + * Utility functions. + */ +static struct mlx_sysdrive *mlx_findunit(struct mlx_softc *sc, int unit); + +/******************************************************************************** + ******************************************************************************** + Public Interfaces + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Free all of the resources associated with (sc) + * + * Should not be called if the controller is active. + */ +void +mlx_free(struct mlx_softc *sc) +{ + struct mlx_command *mc; + + debug("called"); + + /* cancel status timeout */ + untimeout(mlx_periodic, sc, sc->mlx_timeout); + + /* throw away any command buffers */ + while ((mc = TAILQ_FIRST(&sc->mlx_freecmds)) != NULL) { + TAILQ_REMOVE(&sc->mlx_freecmds, mc, mc_link); + mlx_freecmd(mc); + } + + /* destroy data-transfer DMA tag */ + if (sc->mlx_buffer_dmat) + bus_dma_tag_destroy(sc->mlx_buffer_dmat); + + /* free and destroy DMA memory and tag for s/g lists */ + if (sc->mlx_sgtable) + bus_dmamem_free(sc->mlx_sg_dmat, sc->mlx_sgtable, sc->mlx_sg_dmamap); + if (sc->mlx_sg_dmat) + bus_dma_tag_destroy(sc->mlx_sg_dmat); + + /* disconnect the interrupt handler */ + if (sc->mlx_intr) + bus_teardown_intr(sc->mlx_dev, sc->mlx_irq, sc->mlx_intr); + if (sc->mlx_irq != NULL) + bus_release_resource(sc->mlx_dev, SYS_RES_IRQ, 0, sc->mlx_irq); + + /* destroy the parent DMA tag */ + if (sc->mlx_parent_dmat) + bus_dma_tag_destroy(sc->mlx_parent_dmat); + + /* release the register window mapping */ + if (sc->mlx_mem != NULL) + bus_release_resource(sc->mlx_dev, SYS_RES_MEMORY, + (sc->mlx_iftype == MLX_IFTYPE_3) ? MLX_CFG_BASE1 : MLX_CFG_BASE0, sc->mlx_mem); +} + +/******************************************************************************** + * Map the scatter/gather table into bus space + */ +static void +mlx_dma_map_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct mlx_softc *sc = (struct mlx_softc *)arg; + + debug("called"); + + /* save base of s/g table's address in bus space */ + sc->mlx_sgbusaddr = segs->ds_addr; +} + +static int +mlx_sglist_map(struct mlx_softc *sc) +{ + size_t segsize; + int error; + + debug("called"); + + /* destroy any existing mappings */ + if (sc->mlx_sgtable) + bus_dmamem_free(sc->mlx_sg_dmat, sc->mlx_sgtable, sc->mlx_sg_dmamap); + if (sc->mlx_sg_dmat) + bus_dma_tag_destroy(sc->mlx_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 mlx_sgentry) * MLX_NSEG * sc->mlx_maxiop; + error = bus_dma_tag_create(sc->mlx_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->mlx_sg_dmat); + if (error != 0) { + device_printf(sc->mlx_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->mlx_sg_dmat, (void **)&sc->mlx_sgtable, BUS_DMA_NOWAIT, &sc->mlx_sg_dmamap); + if (error) { + device_printf(sc->mlx_dev, "can't allocate s/g table\n"); + return(ENOMEM); + } + bus_dmamap_load(sc->mlx_sg_dmat, sc->mlx_sg_dmamap, sc->mlx_sgtable, segsize, mlx_dma_map_sg, sc, 0); + return(0); +} + +/******************************************************************************** + * Initialise the controller and softc + */ +int +mlx_attach(struct mlx_softc *sc) +{ + struct mlx_enquiry *me; + struct mlx_enquiry2 *me2; + int rid, error; + + debug("called"); + + /* + * Initialise per-controller queues. + */ + TAILQ_INIT(&sc->mlx_donecmd); + TAILQ_INIT(&sc->mlx_freecmds); + bufq_init(&sc->mlx_bufq); + + /* + * Select accessor methods based on controller interface type. + */ + switch(sc->mlx_iftype) { + case MLX_IFTYPE_3: + sc->mlx_tryqueue = mlx_v3_tryqueue; + sc->mlx_findcomplete = mlx_v3_findcomplete; + sc->mlx_intaction = mlx_v3_intaction; + break; + default: + device_printf(sc->mlx_dev, "attaching unsupported interface version %d\n", sc->mlx_iftype); + return(ENXIO); /* should never happen */ + } + + /* disable interrupts before we start talking to the controller */ + sc->mlx_intaction(sc, MLX_INTACTION_DISABLE); + + /* + * Allocate and connect our interrupt. + */ + rid = 0; + sc->mlx_irq = bus_alloc_resource(sc->mlx_dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); + if (sc->mlx_irq == NULL) { + device_printf(sc->mlx_dev, "couldn't allocate interrupt\n"); + mlx_free(sc); + return(ENXIO); + } + error = bus_setup_intr(sc->mlx_dev, sc->mlx_irq, INTR_TYPE_BIO, mlx_intr, sc, &sc->mlx_intr); + if (error) { + device_printf(sc->mlx_dev, "couldn't set up interrupt\n"); + mlx_free(sc); + return(ENXIO); + } + + /* + * Create DMA tag for mapping buffers into controller-addressable space. + */ + error = bus_dma_tag_create(sc->mlx_parent_dmat, /* parent */ + 1, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MAXBSIZE, MLX_NSEG, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + 0, /* flags */ + &sc->mlx_buffer_dmat); + if (error != 0) { + device_printf(sc->mlx_dev, "can't allocate buffer DMA tag\n"); + return(ENOMEM); + } + + /* + * Create an initial set of s/g mappings. + */ + sc->mlx_maxiop = 2; + error = mlx_sglist_map(sc); + if (error != 0) { + device_printf(sc->mlx_dev, "couldn't make initial s/g list mapping\n"); + return(error); + } + + /* + * Probe the controller for more information. + */ + /* send an ENQUIRY to the controller */ + if ((me = mlx_enquire(sc, MLX_CMD_ENQUIRY, sizeof(*me), NULL)) == NULL) { + device_printf(sc->mlx_dev, "ENQUIRY failed\n"); + return(ENXIO); + } + + /* pull information out of the ENQUIRY result */ + sc->mlx_fwminor = me->me_fwminor; + sc->mlx_fwmajor = me->me_fwmajor; + sc->mlx_maxiop = me->me_max_commands; + sc->mlx_lastevent = sc->mlx_currevent = me->me_event_log_seq_num; + free(me, M_DEVBUF); + + /* send an ENQUIRY2 to the controller */ + if ((me2 = mlx_enquire(sc, MLX_CMD_ENQUIRY2, sizeof(*me2), NULL)) == NULL) { + device_printf(sc->mlx_dev, "ENQUIRY2 failed\n"); + return(ENXIO); + } + + /* pull information out of the ENQUIRY2 result */ + sc->mlx_nchan = me2->me_configured_channels; + sc->mlx_maxiosize = me2->me_maxblk; + sc->mlx_maxtarg = me2->me_max_targets; + sc->mlx_maxtags = me2->me_max_tags; + sc->mlx_scsicap = me2->me_scsi_cap; + sc->mlx_hwid = me2->me_hardware_id; + + /* print a little information about the controller and ourselves */ + device_printf(sc->mlx_dev, "Mylex %s, firmware %d.%d, %dMB RAM\n", + mlx_name_controller(sc->mlx_hwid), sc->mlx_fwmajor, sc->mlx_fwminor, + me2->me_mem_size / (1024 * 1024)); + free(me2, M_DEVBUF); + + /* + * Do quirk/feature related things. + */ + switch(sc->mlx_iftype) { + case MLX_IFTYPE_3: + if (sc->mlx_fwminor != 51) { + device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is NOT SUPPORTED\n"); + device_printf(sc->mlx_dev, " *** WARNING *** Use revision 3.51 only\n"); + } + break; + default: + device_printf(sc->mlx_dev, "interface version corrupted to %d\n", sc->mlx_iftype); + return(ENXIO); /* should never happen */ + } + + /* + * Create the final set of s/g mappings now that we know how many commands + * the controller actually supports. + */ + error = mlx_sglist_map(sc); + if (error != 0) { + device_printf(sc->mlx_dev, "couldn't make initial s/g list mapping\n"); + return(error); + } + + /* + * No rebuild or check is in progress. + */ + sc->mlx_rebuild = -1; + sc->mlx_check = -1; + + /* + * Register the control device on first attach. + */ + if (cdev_registered++ == 0) + cdevsw_add(&mlx_cdevsw); + + /* + * Start the timeout routine. + */ + sc->mlx_timeout = timeout(mlx_periodic, sc, hz); + + return(0); +} + +/******************************************************************************** + * Locate disk resources and attach children to them. + */ +void +mlx_startup(struct mlx_softc *sc) +{ + struct mlx_enq_sys_drive *mes; + struct mlx_sysdrive *dr; + int i, error; + + debug("called"); + + /* + * Scan all the system drives and attach children for those that + * don't currently have them. + */ + mes = mlx_enquire(sc, MLX_CMD_ENQSYSDRIVE, sizeof(*mes) * MLX_MAXDRIVES, NULL); + if (mes == NULL) { + device_printf(sc->mlx_dev, "error fetching drive status"); + return; + } + + /* iterate over drives returned */ + for (i = 0, dr = &sc->mlx_sysdrive[0]; + (i < MLX_MAXDRIVES) && (mes[i].sd_size != 0xffffffff); + i++, dr++) { + /* are we already attached to this drive? */ + if (dr->ms_disk == 0) { + /* pick up drive information */ + dr->ms_size = mes[i].sd_size; + dr->ms_raidlevel = mes[i].sd_raidlevel; + dr->ms_state = mes[i].sd_state; + + /* generate geometry information */ + if (sc->mlx_geom == MLX_GEOM_128_32) { + dr->ms_heads = 128; + dr->ms_sectors = 32; + dr->ms_cylinders = dr->ms_size / (128 * 32); + } else { /* MLX_GEOM_255/63 */ + dr->ms_heads = 255; + dr->ms_sectors = 63; + dr->ms_cylinders = dr->ms_size / (255 * 63); + } + dr->ms_disk = device_add_child(sc->mlx_dev, /*"mlxd"*/NULL, -1, dr); + if (dr->ms_disk == 0) + device_printf(sc->mlx_dev, "device_add_child failed\n"); + } + } + free(mes, M_DEVBUF); + if ((error = bus_generic_attach(sc->mlx_dev)) != 0) + device_printf(sc->mlx_dev, "bus_generic_attach returned %d", error); + + /* mark controller back up */ + sc->mlx_state &= ~MLX_STATE_SHUTDOWN; + + /* enable interrupts */ + sc->mlx_intaction(sc, MLX_INTACTION_ENABLE); +} + +/******************************************************************************** + * Disconnect from the controller completely, in preparation for unload. + */ +int +mlx_detach(device_t dev) +{ + struct mlx_softc *sc = device_get_softc(dev); + int error; + + debug("called"); + + if (sc->mlx_state & MLX_STATE_OPEN) + return(EBUSY); + + if ((error = mlx_shutdown(dev))) + return(error); + + mlx_free(sc); + + /* + * Deregister the control device on last detach. + */ + if (--cdev_registered == 0) + cdevsw_remove(&mlx_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 mlx_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 +mlx_shutdown(device_t dev) +{ + struct mlx_softc *sc = device_get_softc(dev); + struct mlxd_softc *mlxd; + int i, s, error; + + debug("called"); + + s = splbio(); + error = 0; + + /* assume we're going to shut down */ + sc->mlx_state |= MLX_STATE_SHUTDOWN; + for (i = 0; i < MLX_MAXDRIVES; i++) { + if (sc->mlx_sysdrive[i].ms_disk != 0) { + mlxd = device_get_softc(sc->mlx_sysdrive[i].ms_disk); + if (mlxd->mlxd_flags & MLXD_OPEN) { /* drive is mounted, abort shutdown */ + sc->mlx_state &= ~MLX_STATE_SHUTDOWN; + device_printf(sc->mlx_sysdrive[i].ms_disk, "still open, can't shutdown\n"); + error = EBUSY; + goto out; + } + } + } + + /* flush controller */ + device_printf(sc->mlx_dev, "flushing cache..."); + if (mlx_flush(sc)) { + printf("failed\n"); + } else { + printf("done\n"); + } + + /* delete all our child devices */ + for (i = 0; i < MLX_MAXDRIVES; i++) { + if (sc->mlx_sysdrive[i].ms_disk != 0) { + if ((error = device_delete_child(sc->mlx_dev, sc->mlx_sysdrive[i].ms_disk)) != 0) + goto out; + sc->mlx_sysdrive[i].ms_disk = 0; + } + } + + bus_generic_detach(sc->mlx_dev); + + out: + splx(s); + return(error); +} + +/******************************************************************************** + * Bring the controller to a quiescent state, ready for system suspend. + */ +int +mlx_suspend(device_t dev) +{ + struct mlx_softc *sc = device_get_softc(dev); + int s; + + debug("called"); + + s = splbio(); + sc->mlx_state |= MLX_STATE_SUSPEND; + + /* flush controller */ + device_printf(sc->mlx_dev, "flushing cache..."); + printf("%s\n", mlx_flush(sc) ? "failed" : "done"); + + sc->mlx_intaction(sc, MLX_INTACTION_DISABLE); + splx(s); + + return(0); +} + +/******************************************************************************** + * Bring the controller back to a state ready for operation. + */ +int +mlx_resume(device_t dev) +{ + struct mlx_softc *sc = device_get_softc(dev); + + debug("called"); + + sc->mlx_state &= ~MLX_STATE_SUSPEND; + sc->mlx_intaction(sc, MLX_INTACTION_ENABLE); + + return(0); +} + +/******************************************************************************* + * Take an interrupt, or be poked by other code to look for interrupt-worthy + * status. + */ +void +mlx_intr(void *arg) +{ + struct mlx_softc *sc = (struct mlx_softc *)arg; + int worked; + + debug("called"); + + /* ack the interrupt */ + sc->mlx_intaction(sc, MLX_INTACTION_ACKNOWLEDGE); + + /* spin collecting finished commands */ + worked = 0; + while (mlx_done(sc)) + worked = 1; + + /* did we do anything? */ + if (worked) + mlx_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 +mlx_submit_buf(struct mlx_softc *sc, struct buf *bp) +{ + debug("called"); + + bufq_insert_tail(&sc->mlx_bufq, bp); + sc->mlx_waitbufs++; + mlx_startio(sc); + return(0); +} + +/******************************************************************************** + * Accept an open operation on the control device. + */ +int +mlx_open(dev_t dev, int flags, int fmt, struct proc *p) +{ + int unit = minor(dev); + struct mlx_softc *sc = devclass_get_softc(mlx_devclass, unit); + + sc->mlx_state |= MLX_STATE_OPEN; + return(0); +} + +/******************************************************************************** + * Accept the last close on the control device. + */ +int +mlx_close(dev_t dev, int flags, int fmt, struct proc *p) +{ + int unit = minor(dev); + struct mlx_softc *sc = devclass_get_softc(mlx_devclass, unit); + + sc->mlx_state &= ~MLX_STATE_OPEN; + return (0); +} + +/******************************************************************************** + * Handle controller-specific control operations. + */ +int +mlx_ioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) +{ + int unit = minor(dev); + struct mlx_softc *sc = devclass_get_softc(mlx_devclass, unit); + int *arg = (int *)addr; + struct mlx_pause *mp; + struct mlx_sysdrive *dr; + struct mlxd_softc *mlxd; + int i, error; + + switch(cmd) { + /* + * Enumerate connected system drives; returns the first system drive's + * unit number if *arg is -1, or the next unit after *arg if it's + * a valid unit on this controller. + */ + case MLX_NEXT_CHILD: + /* search system drives */ + for (i = 0; i < MLX_MAXDRIVES; i++) { + /* is this one attached? */ + if (sc->mlx_sysdrive[i].ms_disk != 0) { + /* looking for the next one we come across? */ + if (*arg == -1) { + *arg = device_get_unit(sc->mlx_sysdrive[i].ms_disk); + return(0); + } + /* we want the one after this one */ + if (*arg == device_get_unit(sc->mlx_sysdrive[i].ms_disk)) + *arg = -1; + } + } + return(ENOENT); + + /* + * Scan the controller to see whether new drives have appeared. + */ + case MLX_RESCAN_DRIVES: + mlx_startup(sc); + return(0); + + /* + * Disconnect from the specified drive; it may be about to go + * away. + */ + case MLX_DETACH_DRIVE: /* detach one drive */ + + if (((dr = mlx_findunit(sc, *arg)) == NULL) || + ((mlxd = device_get_softc(dr->ms_disk)) == NULL)) + return(ENOENT); + + device_printf(dr->ms_disk, "detaching..."); + error = 0; + if (mlxd->mlxd_flags & MLXD_OPEN) { + error = EBUSY; + goto detach_out; + } + + /* flush controller */ + if (mlx_flush(sc)) { + error = EBUSY; + goto detach_out; + } + + /* nuke drive */ + if ((error = device_delete_child(sc->mlx_dev, dr->ms_disk)) != 0) + goto detach_out; + dr->ms_disk = 0; + bus_generic_detach(sc->mlx_dev); + + detach_out: + if (error) { + printf("failed\n"); + } else { + printf("done\n"); + } + return(error); + + /* + * Pause one or more SCSI channels for a period of time, to assist + * in the process of hot-swapping devices. + * + * Note that at least the 3.51 firmware on the DAC960PL doesn't seem + * to do this right. + */ + case MLX_PAUSE_CHANNEL: /* schedule a channel pause */ + /* Does this command work on this firmware? */ + if (!(sc->mlx_feature & MLX_FEAT_PAUSEWORKS)) + return(EOPNOTSUPP); + + mp = (struct mlx_pause *)addr; + if ((mp->mp_which == MLX_PAUSE_CANCEL) && (sc->mlx_pause.mp_when != 0)) { + /* cancel a pending pause operation */ + sc->mlx_pause.mp_which = 0; + } else { + /* fix for legal channels */ + mp->mp_which &= ((1 << sc->mlx_nchan) -1); + /* check time values */ + if ((mp->mp_when < 0) || (mp->mp_when > 3600)) + return(EINVAL); + if ((mp->mp_howlong < 1) || (mp->mp_howlong > (0xf * 30))) + return(EINVAL); + + /* check for a pause currently running */ + if ((sc->mlx_pause.mp_which != 0) && (sc->mlx_pause.mp_when == 0)) + return(EBUSY); + + /* looks ok, go with it */ + sc->mlx_pause.mp_which = mp->mp_which; + sc->mlx_pause.mp_when = time_second + mp->mp_when; + sc->mlx_pause.mp_howlong = sc->mlx_pause.mp_when + mp->mp_howlong; + } + return(0); + + /* + * Accept a command passthrough-style. + */ + case MLX_COMMAND: + return(mlx_user_command(sc, (struct mlx_usercommand *)addr)); + + default: + return(ENOTTY); + } +} + +/******************************************************************************** + * Handle operations requested by a System Drive connected to this controller. + */ +int +mlx_submit_ioctl(struct mlx_softc *sc, struct mlx_sysdrive *drive, u_long cmd, + caddr_t addr, int32_t flag, struct proc *p) +{ + struct mlxd_rebuild *mr = (struct mlxd_rebuild *)addr; + struct mlxd_rebuild_status *mp = (struct mlxd_rebuild_status *)addr; + int *arg = (int *)addr; + int error; + + switch(cmd) { + /* + * Return the current status of this drive. + */ + case MLXD_STATUS: + *arg = drive->ms_state; + return(0); + + /* + * Start a background rebuild on this drive. + */ + case MLXD_REBUILDASYNC: + /* XXX lock? */ + if (sc->mlx_rebuild >= 0) + return(EBUSY); + sc->mlx_rebuild = drive - &sc->mlx_sysdrive[0]; + + switch (mlx_rebuild(sc, mr->rb_channel, mr->rb_target)) { + case 0: + drive->ms_state = MLX_SYSD_REBUILD; + error = 0; + break; + case 0x10000: + error = ENOMEM; /* couldn't set up the command */ + break; + case 0x0002: + case 0x0106: + error = EBUSY; + break; + case 0x0004: + error = EIO; + break; + case 0x0105: + error = ERANGE; + break; + default: + error = EINVAL; + break; + } + if (error != 0) + sc->mlx_rebuild = -1; + return(error); + + /* + * Start a background consistency check on this drive. + */ + case MLXD_CHECKASYNC: /* start a background consistency check */ + /* XXX implement */ + break; + + /* + * Get the status of the current rebuild or consistency check. + */ + case MLXD_REBUILDSTAT: + + if (sc->mlx_rebuild >= 0) { /* may be a second or so out of date */ + mp->rs_drive = sc->mlx_rebuild; + mp->rs_size = sc->mlx_sysdrive[sc->mlx_rebuild].ms_size; + mp->rs_remaining = sc->mlx_rebuildstat; + return(0); + } else if (sc->mlx_check >= 0) { + /* XXX implement */ + } else { + /* XXX should return status of last completed operation? */ + return(EINVAL); + } + + } + return(ENOIOCTL); +} + + +/******************************************************************************** + ******************************************************************************** + Status Monitoring + ******************************************************************************** + ********************************************************************************/ + +#define MLX_PERIODIC_ISBUSY(sc) (sc->mlx_polling <= 0) +#define MLX_PERIODIC_BUSY(sc) atomic_add_int(&sc->mlx_polling, 1); +#define MLX_PERIODIC_UNBUSY(sc) atomic_subtract_int(&sc->mlx_polling, 1); + +/******************************************************************************** + * Fire off commands to periodically check the status of connected drives. + */ +static void +mlx_periodic(void *data) +{ + struct mlx_softc *sc = (struct mlx_softc *)data; + + debug("called"); + + /* + * Run a bus pause? + */ + if ((sc->mlx_pause.mp_which != 0) && + (sc->mlx_pause.mp_when > 0) && + (time_second >= sc->mlx_pause.mp_when)){ + + mlx_pause_action(sc); /* pause is running */ + sc->mlx_pause.mp_when = 0; + sysbeep(500, hz); + + /* + * Bus pause still running? + */ + } else if ((sc->mlx_pause.mp_which != 0) && + (sc->mlx_pause.mp_when == 0)) { + + /* time to stop bus pause? */ + if (time_second >= sc->mlx_pause.mp_howlong) { + mlx_pause_action(sc); + sc->mlx_pause.mp_which = 0; /* pause is complete */ + sysbeep(500, hz); + } else { + sysbeep((time_second % 5) * 100 + 500, hz/8); + } + + /* + * Run normal periodic activities? + */ + } else if (MLX_PERIODIC_ISBUSY(sc)) { + + /* time to perform a periodic status poll? XXX tuneable interval? */ + if (time_second > (sc->mlx_lastpoll + 5)) { + sc->mlx_lastpoll = time_second; + + /* for caution's sake */ + if (sc->mlx_polling < 0) { + device_printf(sc->mlx_dev, "mlx_polling < 0\n"); + atomic_set_int(&sc->mlx_polling, 0); + } + + /* + * Check controller status. + */ + MLX_PERIODIC_BUSY(sc); + mlx_enquire(sc, MLX_CMD_ENQUIRY, sizeof(struct mlx_enquiry), mlx_periodic_enquiry); + + /* + * Check system drive status. + * + * XXX This might be better left to event-driven detection, eg. I/O to an offline + * drive will detect it's offline, rebuilds etc. should detect the drive is back + * online. + */ + MLX_PERIODIC_BUSY(sc); + mlx_enquire(sc, MLX_CMD_ENQSYSDRIVE, sizeof(struct mlx_enq_sys_drive) * MLX_MAXDRIVES, + mlx_periodic_enquiry); + } + + /* + * Get drive rebuild/check status + */ + if (sc->mlx_rebuild >= 0) { + MLX_PERIODIC_BUSY(sc); + mlx_enquire(sc, MLX_CMD_REBUILDSTAT, sizeof(struct mlx_rebuild_stat), mlx_periodic_rebuild); + } + } else { + /* + * If things are still running from the last poll, complain about it. + * + * XXX If this becomes an issue, we should have some way of telling what + * has become stuck. + */ + device_printf(sc->mlx_dev, "poll still busy (%d)\n", sc->mlx_polling); + } + + /* XXX check for wedged/timed out commands? */ + + /* reschedule another poll next second or so */ + sc->mlx_timeout = timeout(mlx_periodic, sc, hz); +} + +/******************************************************************************** + * Handle the result of an ENQUIRY command instigated by periodic status polling. + */ +static void +mlx_periodic_enquiry(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + + debug("called"); + + /* Command completed OK? */ + if (mc->mc_status != 0) { + device_printf(sc->mlx_dev, "periodic enquiry failed\n"); + goto out; + } + + /* respond to command */ + switch(mc->mc_mailbox[0]) { + /* + * Generic controller status update. We could do more with this than just + * checking the event log. + */ + case MLX_CMD_ENQUIRY: + { + struct mlx_enquiry *me = (struct mlx_enquiry *)mc->mc_data; + + /* New stuff in the event log? */ + if (me->me_event_log_seq_num != sc->mlx_lastevent) { + /* record where current events are up to */ + sc->mlx_currevent = me->me_event_log_seq_num; + device_printf(sc->mlx_dev, "event log pointer was %d, now %d\n", + sc->mlx_lastevent, sc->mlx_currevent); + + /* start poll of event log */ + mlx_periodic_eventlog_poll(sc); + } + break; + } + case MLX_CMD_ENQSYSDRIVE: + { + struct mlx_enq_sys_drive *mes = (struct mlx_enq_sys_drive *)mc->mc_data; + struct mlx_sysdrive *dr; + int i; + + for (i = 0, dr = &sc->mlx_sysdrive[0]; + (i < MLX_MAXDRIVES) && (mes[i].sd_size != 0xffffffff); + i++) { + + /* if disk is being rebuilt, we should not check it */ + if (dr->ms_state == MLX_SYSD_REBUILD) { + /* has state been changed by controller? */ + if (dr->ms_state != mes[i].sd_state) { + switch(mes[i].sd_state) { + case MLX_SYSD_OFFLINE: + device_printf(dr->ms_disk, "drive offline\n"); + break; + case MLX_SYSD_ONLINE: + device_printf(dr->ms_disk, "drive online\n"); + break; + case MLX_SYSD_CRITICAL: + device_printf(dr->ms_disk, "drive critical\n"); + break; + } + /* save new state */ + dr->ms_state = mes[i].sd_state; + } + } + } + break; + } + default: + break; + } + + out: + free(mc->mc_data, M_DEVBUF); + mlx_releasecmd(mc); + /* this event is done */ + MLX_PERIODIC_UNBUSY(sc); +} + +/******************************************************************************** + * Instigate a poll for one event log message on (sc). + * We only poll for one message at a time, to keep our command usage down. + */ +static void +mlx_periodic_eventlog_poll(struct mlx_softc *sc) +{ + struct mlx_command *mc; + void *result = NULL; + int error; + + debug("called"); + + /* presume we are going to create another event */ + MLX_PERIODIC_BUSY(sc); + + /* get ourselves a command buffer */ + error = 1; + if ((mc = mlx_alloccmd(sc)) == NULL) + goto out; + /* allocate the response structure */ + if ((result = malloc(sizeof(struct mlx_eventlog_entry), M_DEVBUF, M_NOWAIT)) == NULL) + goto out; + /* get a command slot */ + if (mlx_getslot(mc)) + goto out; + + /* map the command so the controller can see it */ + mc->mc_data = result; + mc->mc_length = sizeof(struct mlx_eventlog_entry); + mlx_mapcmd(mc); + + /* build the command to get one entry */ + mlx_make_type3(mc, MLX_CMD_LOGOP, MLX_LOGOP_GET, 1, sc->mlx_lastevent, 0, 0, mc->mc_dataphys, 0); + mc->mc_complete = mlx_periodic_eventlog_respond; + mc->mc_private = mc; + + /* start the command */ + if ((error = mlx_start(mc)) != 0) + goto out; + + error = 0; /* success */ + out: + if (mc != NULL) + mlx_releasecmd(mc); + if ((error != 0) && (result != NULL)) { + free(result, M_DEVBUF); + } + /* abort this event */ + if (error != 0) + MLX_PERIODIC_UNBUSY(sc); +} + +/******************************************************************************** + * Handle the result of polling for a log message, generate diagnostic output. + * If this wasn't the last message waiting for us, we'll go collect another. + */ +static char *mlx_sense_messages[] = { + "because write recovery failed", + "because of SCSI bus reset failure", + "because of double check condition", + "because it was removed", + "because of gross error on SCSI chip", + "because of bad tag returned from drive", + "because of timeout on SCSI command", + "because of reset SCSI command issued from system", + "because busy or parity error count exceeded limit", + "because of 'kill drive' command from system", + "because of selection timeout", + "due to SCSI phase sequence error", + "due to unknown status" +}; + +static void +mlx_periodic_eventlog_respond(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + struct mlx_eventlog_entry *el = (struct mlx_eventlog_entry *)mc->mc_data; + char *reason; + + debug("called"); + + if (mc->mc_status == 0) { + sc->mlx_lastevent++; /* got the message OK */ + + /* handle event log message */ + switch(el->el_type) { + /* + * This is the only sort of message we understand at the moment. + * The tests here are probably incomplete. + */ + case MLX_LOGMSG_SENSE: /* sense data */ + /* Mylex vendor-specific message indicating a drive was killed? */ + if ((el->el_sensekey == 9) && + (el->el_asc == 0x80)) { + if (el->el_asq < (sizeof(mlx_sense_messages) / sizeof(mlx_sense_messages[0]))) { + reason = mlx_sense_messages[el->el_asq]; + } else { + reason = "for unknown reason"; + } + device_printf(sc->mlx_dev, "physical drive %d:%d killed %s\n", + el->el_channel, el->el_target, reason); + } + /* SCSI drive was reset? */ + if ((el->el_sensekey == 6) && (el->el_asc == 0x29)) { + device_printf(sc->mlx_dev, "physical drive %d:%d reset\n", + el->el_channel, el->el_target); + } + /* SCSI drive error? */ + if (!((el->el_sensekey == 0) || + ((el->el_sensekey == 2) && + (el->el_asc == 0x04) && + ((el->el_asq == 0x01) || + (el->el_asq == 0x02))))) { + device_printf(sc->mlx_dev, "physical drive %d:%d error log: sense = %d asc = %x asq = %x\n", + el->el_channel, el->el_target, el->el_sensekey, el->el_asc, el->el_asq); + device_printf(sc->mlx_dev, " info %4D csi %4D\n", el->el_information, ":", el->el_csi, ":"); + } + break; + + default: + device_printf(sc->mlx_dev, "unknown log message type 0x%x\n", el->el_type); + break; + } + } else { + device_printf(sc->mlx_dev, "error reading message log - %s\n", mlx_diagnose_command(mc)); + } + + /* dispose of command and data */ + free(mc->mc_data, M_DEVBUF); + mlx_releasecmd(mc); + + /* is there another message to obtain? */ + if (sc->mlx_lastevent != sc->mlx_currevent) + mlx_periodic_eventlog_poll(sc); + + /* this event is done */ + MLX_PERIODIC_UNBUSY(sc); +} + +/******************************************************************************** + * Handle the completion of a rebuild operation. + */ +static void +mlx_periodic_rebuild(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + struct mlx_rebuild_stat *mr = (struct mlx_rebuild_stat *)mc->mc_private; + + switch(mc->mc_status) { + case 0: /* all OK, rebuild still running */ + sc->mlx_rebuildstat = mr->rb_remaining; + break; + + case 0x0105: /* rebuild/check finished */ + if (sc->mlx_rebuild >= 0) { + device_printf(sc->mlx_sysdrive[sc->mlx_rebuild].ms_disk, "rebuild completed\n"); + sc->mlx_rebuild = -1; + } else if (sc->mlx_check >= 0) { + device_printf(sc->mlx_sysdrive[sc->mlx_check].ms_disk, "consistency check completed\n"); + sc->mlx_check = -1; + } else { + device_printf(sc->mlx_dev, "consistency check completed\n"); + } + break; + } + free(mc->mc_data, M_DEVBUF); + mlx_releasecmd(mc); + /* this event is done */ + MLX_PERIODIC_UNBUSY(sc); +} + +/******************************************************************************** + ******************************************************************************** + Channel Pause + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * It's time to perform a channel pause action for (sc), either start or stop + * the pause. + */ +static void +mlx_pause_action(struct mlx_softc *sc) +{ + struct mlx_command *mc; + int failsafe, i, command; + + /* What are we doing here? */ + if (sc->mlx_pause.mp_when == 0) { + command = MLX_CMD_STARTCHANNEL; + failsafe = 0; + + } else { + command = MLX_CMD_STOPCHANNEL; + + /* + * Channels will always start again after the failsafe period, + * which is specified in multiples of 30 seconds. + * This constrains us to a maximum pause of 450 seconds. + */ + failsafe = ((sc->mlx_pause.mp_howlong - time_second) + 5) / 30; + if (failsafe > 0xf) { + failsafe = 0xf; + sc->mlx_pause.mp_howlong = time_second + (0xf * 30) - 5; + } + } + + /* build commands for every channel requested */ + for (i = 0; i < sc->mlx_nchan; i++) { + if ((1 << i) & sc->mlx_pause.mp_which) { + + /* get ourselves a command buffer */ + if ((mc = mlx_alloccmd(sc)) == NULL) + goto fail; + /* get a command slot */ + mc->mc_flags |= MLX_CMD_PRIORITY; + if (mlx_getslot(mc)) + goto fail; + + /* build the command */ + mlx_make_type2(mc, command, (failsafe << 4) | i, 0, 0, 0, 0, 0, 0, 0); + mc->mc_complete = mlx_pause_done; + mc->mc_private = sc; /* XXX not needed */ + if (mlx_start(mc)) + goto fail; + /* command submitted OK */ + return; + + fail: + device_printf(sc->mlx_dev, "%s failed for channel %d\n", + command == MLX_CMD_STOPCHANNEL ? "pause" : "resume", i); + if (mc != NULL) + mlx_releasecmd(mc); + } + } +} + +static void +mlx_pause_done(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + int command = mc->mc_mailbox[0]; + int channel = mc->mc_mailbox[2] & 0xf; + + if (mc->mc_status != 0) { + device_printf(sc->mlx_dev, "%s command failed - %s\n", + command == MLX_CMD_STOPCHANNEL ? "pause" : "resume", mlx_diagnose_command(mc)); + } else if (command == MLX_CMD_STOPCHANNEL) { + device_printf(sc->mlx_dev, "channel %d pausing for %ld seconds\n", + channel, sc->mlx_pause.mp_howlong - time_second); + } else { + device_printf(sc->mlx_dev, "channel %d resuming\n", channel); + } + mlx_releasecmd(mc); +} + +/******************************************************************************** + ******************************************************************************** + Command Submission + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Perform an Enquiry command using a type-3 command buffer and a return a single + * linear result buffer. If the completion function is specified, it will + * be called with the completed command (and the result response will not be + * valid until that point). Otherwise, the command will either be busy-waited + * for (interrupts not enabled), or slept for. + */ +static void * +mlx_enquire(struct mlx_softc *sc, int command, size_t bufsize, void (* complete)(struct mlx_command *mc)) +{ + struct mlx_command *mc; + void *result; + int error; + + debug("called"); + + /* get ourselves a command buffer */ + error = 1; + result = NULL; + if ((mc = mlx_alloccmd(sc)) == NULL) + goto out; + /* allocate the response structure */ + if ((result = malloc(bufsize, M_DEVBUF, M_NOWAIT)) == NULL) + goto out; + /* get a command slot */ + mc->mc_flags |= MLX_CMD_PRIORITY | MLX_CMD_DATAOUT; + if (mlx_getslot(mc)) + goto out; + + /* map the command so the controller can see it */ + mc->mc_data = result; + mc->mc_length = bufsize; + mlx_mapcmd(mc); + + /* build an enquiry command */ + mlx_make_type2(mc, command, 0, 0, 0, 0, 0, 0, mc->mc_dataphys, 0); + + /* do we want a completion callback? */ + if (complete != NULL) { + mc->mc_complete = complete; + mc->mc_private = mc; + if ((error = mlx_start(mc)) != 0) + goto out; + } else { + /* run the command in either polled or wait mode */ + if ((sc->mlx_state & MLX_STATE_INTEN) ? mlx_wait_command(mc) : mlx_poll_command(mc)) + goto out; + + /* command completed OK? */ + if (mc->mc_status != 0) { + device_printf(sc->mlx_dev, "ENQUIRY failed - %s\n", mlx_diagnose_command(mc)); + goto out; + } + } + error = 0; /* success */ + out: + /* we got a command, but nobody else will free it */ + if ((complete == NULL) && (mc != NULL)) + mlx_releasecmd(mc); + if ((error != 0) && (result != NULL)) { + free(result, M_DEVBUF); + result = NULL; + } + return(result); +} + + +/******************************************************************************** + * Perform a Flush command on the nominated controller. + * + * May be called with interrupts enabled or disabled; will not return until + * the flush operation completes or fails. + */ +static int +mlx_flush(struct mlx_softc *sc) +{ + struct mlx_command *mc; + int error; + + debug("called"); + + /* get ourselves a command buffer */ + error = 1; + if ((mc = mlx_alloccmd(sc)) == NULL) + goto out; + /* get a command slot */ + if (mlx_getslot(mc)) + goto out; + + /* build a flush command */ + mlx_make_type2(mc, MLX_CMD_FLUSH, 0, 0, 0, 0, 0, 0, 0, 0); + + /* run the command in either polled or wait mode */ + if ((sc->mlx_state & MLX_STATE_INTEN) ? mlx_wait_command(mc) : mlx_poll_command(mc)) + goto out; + + /* command completed OK? */ + if (mc->mc_status != 0) { + device_printf(sc->mlx_dev, "FLUSH failed - %s\n", mlx_diagnose_command(mc)); + goto out; + } + + error = 0; /* success */ + out: + if (mc != NULL) + mlx_releasecmd(mc); + return(error); +} + +/******************************************************************************** + * Start a background rebuild on the nominated controller/channel/target. + * + * May be called with interrupts enabled or disabled; will return as soon as the + * operation has started or been refused. + */ +static int +mlx_rebuild(struct mlx_softc *sc, int channel, int target) +{ + struct mlx_command *mc; + int error; + + debug("called"); + + /* get ourselves a command buffer */ + error = 0x10000; + if ((mc = mlx_alloccmd(sc)) == NULL) + goto out; + /* get a command slot */ + if (mlx_getslot(mc)) + goto out; + + /* build a rebuild command */ + mlx_make_type2(mc, MLX_CMD_REBUILDASYNC, channel, target, 0, 0, 0, 0, 0, 0); + + /* run the command in either polled or wait mode */ + if ((sc->mlx_state & MLX_STATE_INTEN) ? mlx_wait_command(mc) : mlx_poll_command(mc)) + goto out; + + /* command completed OK? */ + if (mc->mc_status != 0) { + device_printf(sc->mlx_dev, "REBUILD ASYNC failed - %s\n", mlx_diagnose_command(mc)); + } else { + device_printf(sc->mlx_sysdrive[sc->mlx_rebuild].ms_disk, "rebuild started"); + } + error = mc->mc_status; + + out: + if (mc != NULL) + mlx_releasecmd(mc); + return(error); +} + +/******************************************************************************** + * Run the command (mc) and return when it completes. + * + * Interrupts need to be enabled; returns nonzero on error. + */ +static int +mlx_wait_command(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + int error, count; + + debug("called"); + + mc->mc_complete = NULL; + mc->mc_private = mc; /* wake us when you're done */ + if ((error = mlx_start(mc)) != 0) + return(error); + + count = 0; + /* XXX better timeout? */ + while ((mc->mc_status == MLX_STATUS_BUSY) && (count < 30)) { + tsleep(mc->mc_private, PRIBIO | PCATCH, "mlxwcmd", hz); + } + + if (mc->mc_status != 0) { + device_printf(sc->mlx_dev, "I/O error 0x%x\n", mc->mc_status); + return(EIO); + } + return(0); +} + + +/******************************************************************************** + * Start the command (mc) and busy-wait for it to complete. + * + * Should only be used when interrupts are not available. Returns 0 on + * success, nonzero on error. + * Successfully completed commands are dequeued. + */ +static int +mlx_poll_command(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + int error, count, s; + + debug("called"); + + mc->mc_complete = NULL; + mc->mc_private = NULL; /* we will poll for it */ + if ((error = mlx_start(mc)) != 0) + return(error); + + count = 0; + do { + /* poll for completion */ + mlx_done(mc->mc_sc); + } while ((mc->mc_status == MLX_STATUS_BUSY) && (count < 10000)); + if (mc->mc_status != MLX_STATUS_BUSY) { + s = splbio(); + TAILQ_REMOVE(&sc->mlx_donecmd, mc, mc_link); + splx(s); + return(0); + } + device_printf(sc->mlx_dev, "I/O error 0x%x\n", mc->mc_status); + return(EIO); +} + +/******************************************************************************** + * 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 +mlx_startio(struct mlx_softc *sc) +{ + struct mlx_command *mc; + struct mlxd_softc *mlxd; + 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->mlx_bufq)) == NULL) + break; + /* get a command */ + if ((mc = mlx_alloccmd(sc)) == NULL) + break; + /* get a slot for the command */ + if (mlx_getslot(mc) != 0) { + mlx_releasecmd(mc); + break; + } + /* get the buf containing our work */ + bufq_remove(&sc->mlx_bufq, bp); + sc->mlx_waitbufs--; + + /* connect the buf to the command */ + mc->mc_complete = mlx_completeio; + mc->mc_private = bp; + mc->mc_data = bp->b_data; + mc->mc_length = bp->b_bcount; + if (bp->b_flags & B_READ) { + mc->mc_flags |= MLX_CMD_DATAIN; + cmd = MLX_CMD_READOLDSG; + } else { + mc->mc_flags |= MLX_CMD_DATAOUT; + cmd = MLX_CMD_WRITEOLDSG; + } + + /* map the command so the controller can work with it */ + mlx_mapcmd(mc); + + /* build a suitable I/O command (assumes 512-byte rounded transfers) */ + mlxd = (struct mlxd_softc *)bp->b_driver1; + driveno = mlxd->mlxd_drive - &sc->mlx_sysdrive[0]; + blkcount = bp->b_bcount / MLX_BLKSIZE; + + if ((bp->b_blkno + blkcount) > sc->mlx_sysdrive[driveno].ms_size) + device_printf(sc->mlx_dev, "I/O beyond end of unit (%u,%d > %u)\n", + bp->b_blkno, blkcount, sc->mlx_sysdrive[driveno].ms_size); + + /* + * Build the I/O command. Note that the SG list type bits are set to zero, + * denoting the format of SG list that we are using. + */ + mlx_make_type5(mc, cmd, + blkcount & 0xff, /* xfer length low byte */ + (driveno << 4) | ((blkcount >> 8) & 0x0f), /* target and length high nybble */ + bp->b_blkno, /* physical block number */ + mc->mc_sgphys, /* location of SG list */ + mc->mc_nsgent & 0x3f); /* size of SG list (top 2 bits clear) */ + + + /* try to give command to controller */ + if (mlx_start(mc) != 0) { + /* fail the command */ + mc->mc_status = MLX_STATUS_WEDGED; + mlx_completeio(mc); + } + } +} + +/******************************************************************************** + * Handle completion of an I/O command. + */ +static void +mlx_completeio(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + struct buf *bp = (struct buf *)mc->mc_private; + struct mlxd_softc *mlxd = (struct mlxd_softc *)bp->b_driver1; + + if (mc->mc_status != MLX_STATUS_OK) { /* could be more verbose here? */ + bp->b_error = EIO; + bp->b_flags |= B_ERROR; + + switch(mc->mc_status) { + case MLX_STATUS_RDWROFFLINE: /* system drive has gone offline */ + device_printf(mlxd->mlxd_dev, "drive offline\n"); + device_printf(sc->mlx_dev, "drive offline\n"); + mlxd->mlxd_drive->ms_state = MLX_SYSD_OFFLINE; + break; + + default: /* other I/O error */ + device_printf(sc->mlx_dev, "I/O error - %s\n", mlx_diagnose_command(mc)); +#if 0 + device_printf(sc->mlx_dev, " b_bcount %ld blkcount %ld b_blkno %d\n", + bp->b_bcount, bp->b_bcount / MLX_BLKSIZE, bp->b_blkno); + device_printf(sc->mlx_dev, " %13D\n", mc->mc_mailbox, " "); +#endif + break; + } + } + mlx_releasecmd(mc); + mlxd_intr(bp); +} + +/******************************************************************************** + * Take a command from user-space and try to run it. + */ +static int +mlx_user_command(struct mlx_softc *sc, struct mlx_usercommand *mu) +{ + struct mlx_command *mc; + void *kbuf; + int error; + + kbuf = NULL; + mc = NULL; + error = ENOMEM; + /* get a kernel buffer for the transfer */ + if (mu->mu_datasize > 0) { + if ((kbuf = malloc(mu->mu_datasize, M_DEVBUF, M_WAITOK)) == NULL) + goto out; + if ((mu->mu_bufptr < 0) || (mu->mu_bufptr > (sizeof(mu->mu_command) < sizeof(u_int32_t)))) { + error = EINVAL; + goto out; + } + } + /* get ourselves a command buffer */ + if ((mc = mlx_alloccmd(sc)) == NULL) + goto out; + + /* copy the command and data */ + bcopy(mu->mu_command, mc->mc_mailbox, sizeof(mc->mc_mailbox)); + if ((mu->mu_datasize > 0) && ((error = copyin(mu->mu_buf, kbuf, mu->mu_datasize)))) + goto out; + + /* get a command slot */ + if (mlx_getslot(mc)) + goto out; + + /* map the command so the controller can see it */ + mc->mc_data = kbuf; + mc->mc_length = mu->mu_datasize; + mlx_mapcmd(mc); + + /* if there's a data buffer, fix up the command */ + if (mu->mu_datasize > 0) { + mc->mc_mailbox[mu->mu_bufptr ] = mc->mc_length & 0xff; + mc->mc_mailbox[mu->mu_bufptr + 1] = (mc->mc_length >> 8) & 0xff; + mc->mc_mailbox[mu->mu_bufptr + 2] = (mc->mc_length >> 16) & 0xff; + mc->mc_mailbox[mu->mu_bufptr + 3] = (mc->mc_length >> 24) & 0xff; + } + + /* submit the command and wait */ + if ((error = mlx_wait_command(mc)) != 0) + goto out; + + /* copy out status and data */ + mu->mu_status = mc->mc_status; + if ((mu->mu_datasize > 0) && ((error = copyout(kbuf, mu->mu_buf, mu->mu_datasize)))) + goto out; + error = 0; + + out: + mlx_releasecmd(mc); + if (kbuf != NULL) + free(kbuf, M_DEVBUF); + return(error); +} + +/******************************************************************************** + ******************************************************************************** + Command I/O to Controller + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Find a free command slot for (mc). + * + * Don't hand out a slot to a normal-priority command unless there are at least + * 4 slots free for priority commands. + */ +static int +mlx_getslot(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + int s, slot, limit; + + debug("called mc %p sc %p", mc, sc); + + /* enforce slot-usage limit */ + limit = (mc->mc_flags & MLX_CMD_PRIORITY) ? sc->mlx_maxiop : sc->mlx_maxiop - 4; + if (sc->mlx_busycmds > limit) + return(EBUSY); + + /* + * Allocate an outstanding command slot + * + * XXX linear search is slow + */ + s = splbio(); + for (slot = 0; slot < sc->mlx_maxiop; slot++) { + debug("try slot %d", slot); + if (sc->mlx_busycmd[slot] == NULL) + break; + } + if (slot < sc->mlx_maxiop) { + sc->mlx_busycmd[slot] = mc; + sc->mlx_busycmds++; + } + splx(s); + + /* out of slots? */ + if (slot >= sc->mlx_maxiop) + return(EBUSY); + + debug("got slot %d", slot); + mc->mc_slot = slot; + return(0); +} + +/******************************************************************************** + * Map/unmap (mc)'s data in the controller's addressable space. + */ +static void +mlx_setup_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error) +{ + struct mlx_command *mc = (struct mlx_command *)arg; + struct mlx_softc *sc = mc->mc_sc; + struct mlx_sgentry *sg; + int i; + + debug("called"); + + /* get base address of s/g table */ + sg = sc->mlx_sgtable + (mc->mc_slot * MLX_NSEG); + + /* save s/g table information in command */ + mc->mc_nsgent = nsegments; + mc->mc_sgphys = sc->mlx_sgbusaddr + (mc->mc_slot * MLX_NSEG * sizeof(struct mlx_sgentry)); + mc->mc_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 +mlx_mapcmd(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + + debug("called"); + + /* if the command involves data at all */ + if (mc->mc_data != NULL) { + + /* map the data buffer into bus space and build the s/g list */ + bus_dmamap_load(sc->mlx_buffer_dmat, mc->mc_dmamap, mc->mc_data, mc->mc_length, + mlx_setup_dmamap, mc, 0); + if (mc->mc_flags & MLX_CMD_DATAIN) + bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_PREREAD); + if (mc->mc_flags & MLX_CMD_DATAOUT) + bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_PREWRITE); + } +} + +static void +mlx_unmapcmd(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + + debug("called"); + + /* if the command involved data at all */ + if (mc->mc_data != NULL) { + + if (mc->mc_flags & MLX_CMD_DATAIN) + bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_POSTREAD); + if (mc->mc_flags & MLX_CMD_DATAOUT) + bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_POSTWRITE); + + bus_dmamap_unload(sc->mlx_buffer_dmat, mc->mc_dmamap); + } +} + +/******************************************************************************** + * Try to deliver (mc) to the controller. Take care of any completed commands + * that we encounter while doing so. + * + * Can be called at any interrupt level, with or without interrupts enabled. + */ +static int +mlx_start(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + int i, s, done, worked; + + debug("called"); + + /* save the slot number as ident so we can handle this command when complete */ + mc->mc_mailbox[0x1] = mc->mc_slot; + + /* set impossible status so that a woken sleeper can tell the command is in progress */ + mc->mc_status = MLX_STATUS_BUSY; + + /* assume we don't collect any completed commands */ + worked = 0; + + /* spin waiting for the mailbox */ + for (i = 100000, done = 0; (i > 0) && !done; i--) { + s = splbio(); + done = sc->mlx_tryqueue(sc, mc); + splx(s); + /* check for command completion while we're at it */ + if (mlx_done(sc)) + worked = 1; + } + /* check to see if we picked up any completed commands */ + if (worked) + mlx_complete(sc); + + /* command is enqueued */ + if (done) + return(0); + + /* + * We couldn't get the controller to take the command. Revoke the slot + * that the command was given and return it with a bad status. + */ + sc->mlx_busycmd[mc->mc_slot] = NULL; + device_printf(sc->mlx_dev, "controller wedged (not taking commands)\n"); + mc->mc_status = MLX_STATUS_WEDGED; + return(EIO); +} + +/******************************************************************************** + * Look at the controller (sc) and see if a command has been completed. + * If so, move the command buffer to the done queue for later collection + * and free the slot for immediate reuse. + * + * Returns nonzero if anything was added to the done queue. + */ +static int +mlx_done(struct mlx_softc *sc) +{ + struct mlx_command *mc; + int s; + u_int8_t slot; + u_int16_t status; + + debug("called"); + + s = splbio(); + mc = NULL; + slot = 0; + + /* poll for a completed command's identifier and status */ + if (sc->mlx_findcomplete(sc, &slot, &status)) { + mc = sc->mlx_busycmd[slot]; /* find command */ + if (mc != NULL) { /* paranoia */ + if (mc->mc_status == MLX_STATUS_BUSY) { + mc->mc_status = status; /* save status */ + + /* move completed command to 'done' queue */ + TAILQ_INSERT_TAIL(&sc->mlx_donecmd, mc, mc_link); + + /* free slot for reuse */ + sc->mlx_busycmd[slot] = NULL; + sc->mlx_busycmds--; + } else { + device_printf(sc->mlx_dev, "duplicate done event for slot %d\n", slot); + mc = NULL; + } + } else { + device_printf(sc->mlx_dev, "done event for nonbusy slot %d\n", slot); + } + } + splx(s); + + if (mc != NULL) { + /* unmap the command's data buffer */ + mlx_unmapcmd(mc); + return(1); + } + return(0); +} + +/******************************************************************************** + * Handle completion for all commands on (sc)'s done queue. + */ +static void +mlx_complete(struct mlx_softc *sc) +{ + struct mlx_command *mc, *nc; + int s, count; + + debug("called"); + + s = splbio(); + count = 0; + + /* scan the list of done commands */ + mc = TAILQ_FIRST(&sc->mlx_donecmd); + while (mc != NULL) { + nc = TAILQ_NEXT(mc, mc_link); + + /* XXX this is slightly bogus */ + if (count++ > (sc->mlx_maxiop * 2)) + panic("mlx_donecmd list corrupt!"); + + /* + * Does the command have a completion handler? + */ + if (mc->mc_complete != NULL) { + /* remove from list and give to handler */ + TAILQ_REMOVE(&sc->mlx_donecmd, mc, mc_link); + mc->mc_complete(mc); + + /* + * Is there a sleeper waiting on this command? + */ + } else if (mc->mc_private != NULL) { /* sleeping caller wants to know about it */ + + /* remove from list and wake up sleeper */ + TAILQ_REMOVE(&sc->mlx_donecmd, mc, mc_link); + wakeup_one(mc->mc_private); + + /* + * Leave the command for a caller that's polling for it. + */ + } else { + } + mc = nc; + } + splx(s); + + /* queue some more work if there is any */ + mlx_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 mlx_command * +mlx_alloccmd(struct mlx_softc *sc) +{ + struct mlx_command *mc; + int error; + int s; + + debug("called"); + + s = splbio(); + if ((mc = TAILQ_FIRST(&sc->mlx_freecmds)) != NULL) + TAILQ_REMOVE(&sc->mlx_freecmds, mc, mc_link); + splx(s); + + /* allocate a new command buffer? */ + if (mc == NULL) { + mc = (struct mlx_command *)malloc(sizeof(*mc), M_DEVBUF, M_NOWAIT); + if (mc != NULL) { + bzero(mc, sizeof(*mc)); + mc->mc_sc = sc; + error = bus_dmamap_create(sc->mlx_buffer_dmat, 0, &mc->mc_dmamap); + if (error) { + free(mc, M_DEVBUF); + return(NULL); + } + } + } + return(mc); +} + +/******************************************************************************** + * 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 +mlx_releasecmd(struct mlx_command *mc) +{ + int s; + + debug("called"); + + s = splbio(); + TAILQ_INSERT_HEAD(&mc->mc_sc->mlx_freecmds, mc, mc_link); + splx(s); +} + +/******************************************************************************** + * Permanently discard a command buffer. + */ +static void +mlx_freecmd(struct mlx_command *mc) +{ + struct mlx_softc *sc = mc->mc_sc; + + debug("called"); + + bus_dmamap_destroy(sc->mlx_buffer_dmat, mc->mc_dmamap); + free(mc, M_DEVBUF); +} + + +/******************************************************************************** + ******************************************************************************** + Type 3 interface accessor methods + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Try to give (mc) to the controller. Returns 1 if successful, 0 on failure + * (the controller is not ready to take a command). + * + * Must be called at splbio or in a fashion that prevents reentry. + */ +static int +mlx_v3_tryqueue(struct mlx_softc *sc, struct mlx_command *mc) +{ + int i; + + debug("called"); + + /* ready for our command? */ + if (!(MLX_V3_GET_IDBR(sc) & MLX_V3_IDB_FULL)) { + /* copy mailbox data to window */ + for (i = 0; i < 13; i++) + MLX_V3_PUT_MAILBOX(sc, i, mc->mc_mailbox[i]); + + /* post command */ + MLX_V3_PUT_IDBR(sc, MLX_V3_GET_IDBR(sc) | MLX_V3_IDB_FULL); + return(1); + } + return(0); +} + +/******************************************************************************** + * See if a command has been completed, if so acknowledge its completion + * and recover the slot number and status code. + * + * Must be called at splbio or in a fashion that prevents reentry. + */ +static int +mlx_v3_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status) +{ + + debug("called"); + + /* status available? */ + if (MLX_V3_GET_ODBR(sc) & MLX_V3_ODB_SAVAIL) { + *slot = MLX_V3_GET_STATUS_IDENT(sc); /* get command identifier */ + *status = MLX_V3_GET_STATUS(sc); /* get status */ + + /* acknowledge completion */ + MLX_V3_PUT_ODBR(sc, MLX_V3_GET_ODBR(sc) | MLX_V3_ODB_SAVAIL); + MLX_V3_PUT_IDBR(sc, MLX_V3_GET_IDBR(sc) | MLX_V3_IDB_SACK); + return(1); + } + return(0); +} + +/******************************************************************************** + * Enable/disable interrupts as requested. (No acknowledge required) + * + * Must be called at splbio or in a fashion that prevents reentry. + */ +static void +mlx_v3_intaction(struct mlx_softc *sc, int action) +{ + debug("called"); + + switch(action) { + case MLX_INTACTION_DISABLE: + MLX_V3_PUT_IER(sc, 0); + sc->mlx_state &= ~MLX_STATE_INTEN; + break; + case MLX_INTACTION_ENABLE: + MLX_V3_PUT_IER(sc, 1); + sc->mlx_state |= MLX_STATE_INTEN; + break; + } +} + + +/******************************************************************************** + ******************************************************************************** + Debugging + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Return a status message describing (mc) + */ +static char *mlx_status_messages[] = { + "normal completion", /* 00 */ + "irrecoverable data error", /* 01 */ + "drive does not exist, or is offline", /* 02 */ + "attempt to write beyond end of drive", /* 03 */ + "bad data encountered", /* 04 */ + "invalid log entry request", /* 05 */ + "attempt to rebuild online drive", /* 06 */ + "new disk failed during rebuild", /* 07 */ + "invalid channel/target", /* 08 */ + "rebuild/check already in progress", /* 09 */ + "one or more disks are dead", /* 10 */ + "invalid or non-redundant drive", /* 11 */ + "channel is busy", /* 12 */ + "channel is not stopped", /* 13 */ +}; + +static struct +{ + int command; + u_int16_t status; + int msg; +} mlx_messages[] = { + {MLX_CMD_READOLDSG, 0x0001, 1}, + {MLX_CMD_READOLDSG, 0x0002, 1}, + {MLX_CMD_READOLDSG, 0x0105, 3}, + {MLX_CMD_READOLDSG, 0x010c, 4}, + {MLX_CMD_WRITEOLDSG, 0x0001, 1}, + {MLX_CMD_WRITEOLDSG, 0x0002, 1}, + {MLX_CMD_WRITEOLDSG, 0x0105, 3}, + {MLX_CMD_LOGOP, 0x0105, 5}, + {MLX_CMD_REBUILDASYNC, 0x0002, 6}, + {MLX_CMD_REBUILDASYNC, 0x0004, 7}, + {MLX_CMD_REBUILDASYNC, 0x0105, 8}, + {MLX_CMD_REBUILDASYNC, 0x0106, 9}, + {MLX_CMD_CHECKASYNC, 0x0002, 10}, + {MLX_CMD_CHECKASYNC, 0x0105, 11}, + {MLX_CMD_CHECKASYNC, 0x0106, 9}, + {MLX_CMD_STOPCHANNEL, 0x0106, 12}, + {MLX_CMD_STOPCHANNEL, 0x0105, 8}, + {MLX_CMD_STARTCHANNEL, 0x0005, 13}, + {MLX_CMD_STARTCHANNEL, 0x0105, 8}, + {-1, 0, 0} +}; + +static char * +mlx_diagnose_command(struct mlx_command *mc) +{ + static char unkmsg[80]; + int i; + + /* look up message in table */ + for (i = 0; mlx_messages[i].command != -1; i++) + if ((mc->mc_mailbox[0] == mlx_messages[i].command) && + (mc->mc_status == mlx_messages[i].command)) + return(mlx_status_messages[mlx_messages[i].msg]); + + sprintf(unkmsg, "unknown response 0x%x for command 0x%x", (int)mc->mc_status, (int)mc->mc_mailbox[0]); + return(unkmsg); +} + +/******************************************************************************* + * Return a string describing the controller (hwid) + */ +static char * +mlx_name_controller(u_int32_t hwid) +{ + static char buf[80]; + char smbuf[16]; + char *submodel; + int nchn; + + switch(hwid & 0xff) { + case 0x01: + submodel = "P/PD"; + break; + case 0x02: + submodel = "PL"; + break; + case 0x10: + submodel = "PG"; + break; + default: + sprintf(smbuf, " model 0x%x", hwid & 0xff); + submodel = smbuf; + break; + } + nchn = (hwid >> 8) & 0xff; + sprintf(buf, "DAC960%s, %d channel%s", submodel, nchn, nchn > 1 ? "s" : ""); + return(buf); +} + +/******************************************************************************** + ******************************************************************************** + Utility Functions + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Find the disk whose unit number is (unit) on this controller + */ +static struct mlx_sysdrive * +mlx_findunit(struct mlx_softc *sc, int unit) +{ + int i; + + /* search system drives */ + for (i = 0; i < MLX_MAXDRIVES; i++) { + /* is this one attached? */ + if (sc->mlx_sysdrive[i].ms_disk != 0) { + /* is this the one? */ + if (unit == device_get_unit(sc->mlx_sysdrive[i].ms_disk)) + return(&sc->mlx_sysdrive[i]); + } + } + return(NULL); +} diff --git a/sys/dev/mlx/mlx_disk.c b/sys/dev/mlx/mlx_disk.c new file mode 100644 index 000000000000..85f8314cb675 --- /dev/null +++ b/sys/dev/mlx/mlx_disk.c @@ -0,0 +1,314 @@ +/*- + * 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 Mylex DAC960 RAID adapters. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#if 0 +#define debug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args) +#else +#define debug(fmt, args...) +#endif + +/* prototypes */ +static int mlxd_probe(device_t dev); +static int mlxd_attach(device_t dev); +static int mlxd_detach(device_t dev); + +static d_open_t mlxd_open; +static d_close_t mlxd_close; +static d_strategy_t mlxd_strategy; +static d_ioctl_t mlxd_ioctl; + +#define MLXD_BDEV_MAJOR 27 +#define MLXD_CDEV_MAJOR 131 + +static struct cdevsw mlxd_cdevsw = { + /* open */ mlxd_open, + /* close */ mlxd_close, + /* read */ physread, + /* write */ physwrite, + /* ioctl */ mlxd_ioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ mlxd_strategy, + /* name */ "mlxd", + /* maj */ MLXD_CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ D_DISK, + /* bmaj */ MLXD_BDEV_MAJOR +}; + +static devclass_t mlxd_devclass; +static struct cdevsw mlxddisk_cdevsw; +static int disks_registered = 0; + +static device_method_t mlxd_methods[] = { + DEVMETHOD(device_probe, mlxd_probe), + DEVMETHOD(device_attach, mlxd_attach), + DEVMETHOD(device_detach, mlxd_detach), + { 0, 0 } +}; + +static driver_t mlxd_driver = { + "mlxd", + mlxd_methods, + sizeof(struct mlxd_softc) +}; + +DRIVER_MODULE(mlxd, mlx, mlxd_driver, mlxd_devclass, 0, 0); + +static __inline struct mlxd_softc * +mlxd_getsoftc(dev_t dev) +{ + int unit; + + unit = dkunit(dev); + return ((struct mlxd_softc *)devclass_get_softc(mlxd_devclass, unit)); +} + +static int +mlxd_open(dev_t dev, int flags, int fmt, struct proc *p) +{ + struct mlxd_softc *sc = mlxd_getsoftc(dev); + struct disklabel *label; + + debug("called"); + + if (sc == NULL) + return (ENXIO); + + /* controller not active? */ + if (sc->mlxd_controller->mlx_state & MLX_STATE_SHUTDOWN) + return(ENXIO); + + label = &sc->mlxd_disk.d_label; + bzero(label, sizeof(*label)); + label->d_type = DTYPE_SCSI; + label->d_secsize = MLX_BLKSIZE; + label->d_nsectors = sc->mlxd_drive->ms_sectors; + label->d_ntracks = sc->mlxd_drive->ms_heads; + label->d_ncylinders = sc->mlxd_drive->ms_cylinders; + label->d_secpercyl = sc->mlxd_drive->ms_sectors * sc->mlxd_drive->ms_heads; + label->d_secperunit = sc->mlxd_drive->ms_size; + + /* set maximum I/O size */ + dev->si_iosize_max = sc->mlxd_controller->mlx_maxiosize; + + sc->mlxd_flags |= MLXD_OPEN; + return (0); +} + +static int +mlxd_close(dev_t dev, int flags, int fmt, struct proc *p) +{ + struct mlxd_softc *sc = mlxd_getsoftc(dev); + + debug("called"); + + if (sc == NULL) + return (ENXIO); + sc->mlxd_flags &= ~MLXD_OPEN; + return (0); +} + +static int +mlxd_ioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) +{ + struct mlxd_softc *sc = mlxd_getsoftc(dev); + int error; + + debug("called"); + + if (sc == NULL) + return (ENXIO); + + if ((error = mlx_submit_ioctl(sc->mlxd_controller, sc->mlxd_drive, cmd, addr, flag, p)) != ENOIOCTL) { + debug("mlx_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 +mlxd_strategy(struct buf *bp) +{ + struct mlxd_softc *sc = mlxd_getsoftc(bp->b_dev); + int s; + + debug("called"); + + /* bogus disk? */ + if (sc == NULL) { + bp->b_error = EINVAL; + goto bad; + } + + /* XXX may only be temporarily offline - sleep? */ + if (sc->mlxd_drive->ms_state == MLX_SYSD_OFFLINE) { + bp->b_error = ENXIO; + goto bad; + } + + /* do-nothing operation */ + if (bp->b_bcount == 0) + goto done; + + /* pass reference to us */ + bp->b_driver1 = sc; + s = splbio(); + devstat_start_transaction(&sc->mlxd_stats); + mlx_submit_buf(sc->mlxd_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 +mlxd_intr(void *data) +{ + struct buf *bp = (struct buf *)data; + struct mlxd_softc *sc = (struct mlxd_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->mlxd_stats, bp); + biodone(bp); +} + +static int +mlxd_probe(device_t dev) +{ + + debug("called"); + + device_set_desc(dev, "Mylex System Drive"); + return (0); +} + +static int +mlxd_attach(device_t dev) +{ + struct mlxd_softc *sc = (struct mlxd_softc *)device_get_softc(dev); + device_t parent; + char *state; + + debug("called"); + + parent = device_get_parent(dev); + sc->mlxd_controller = (struct mlx_softc *)device_get_softc(parent); + sc->mlxd_unit = device_get_unit(dev); + sc->mlxd_drive = device_get_ivars(dev); + + switch(sc->mlxd_drive->ms_state) { + case MLX_SYSD_ONLINE: + state = "online"; + break; + case MLX_SYSD_CRITICAL: + state = "critical"; + break; + case MLX_SYSD_OFFLINE: + state = "offline"; + break; + default: + state = "unknown state"; + } + + device_printf(dev, "%uMB (%u sectors), RAID %d (%s)\n", + sc->mlxd_drive->ms_size / ((1024 * 1024) / MLX_BLKSIZE), + sc->mlxd_drive->ms_size, sc->mlxd_drive->ms_raidlevel, state); + + devstat_add_entry(&sc->mlxd_stats, "mlxd", sc->mlxd_unit, MLX_BLKSIZE, + DEVSTAT_NO_ORDERED_TAGS, + DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_OTHER, + DEVSTAT_PRIORITY_DA); + + disk_create(sc->mlxd_unit, &sc->mlxd_disk, 0, &mlxd_cdevsw, &mlxddisk_cdevsw); + disks_registered++; + + return (0); +} + +static int +mlxd_detach(device_t dev) +{ + struct mlxd_softc *sc = (struct mlxd_softc *)device_get_softc(dev); + + debug("called"); + + devstat_remove_entry(&sc->mlxd_stats); + + /* hack to handle lack of destroy_disk() */ + if (--disks_registered == 0) + cdevsw_remove(&mlxddisk_cdevsw); + + return(0); +} + diff --git a/sys/dev/mlx/mlx_pci.c b/sys/dev/mlx/mlx_pci.c new file mode 100644 index 000000000000..7d791a8423b4 --- /dev/null +++ b/sys/dev/mlx/mlx_pci.c @@ -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$ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#if 0 +#define debug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args) +#else +#define debug(fmt, args...) +#endif + +static int mlx_pci_probe(device_t dev); +static int mlx_pci_attach(device_t dev); + +static device_method_t mlx_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, mlx_pci_probe), + DEVMETHOD(device_attach, mlx_pci_attach), + DEVMETHOD(device_detach, mlx_detach), + DEVMETHOD(device_shutdown, mlx_shutdown), + DEVMETHOD(device_suspend, mlx_suspend), + DEVMETHOD(device_resume, mlx_resume), + + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + { 0, 0 } +}; + +static driver_t mlx_pci_driver = { + "mlx", + mlx_methods, + sizeof(struct mlx_softc) +}; + +DRIVER_MODULE(mlx, pci, mlx_pci_driver, mlx_devclass, 0, 0); + +struct +{ + u_int16_t vendor; + u_int16_t device; + int iftype; + char *desc; +} mlx_identifiers[] = { + {0x1069, 0x0002, MLX_IFTYPE_3, "Mylex version 3 RAID interface"}, /* Mylex v3 software interface */ + {0, 0, 0, 0} +}; + +static int +mlx_pci_probe(device_t dev) +{ + int i; + + debug("called"); + + for (i = 0; mlx_identifiers[i].vendor != 0; i++) { + if ((mlx_identifiers[i].vendor == pci_get_vendor(dev)) && + (mlx_identifiers[i].device == pci_get_device(dev))) { + + device_set_desc(dev, mlx_identifiers[i].desc); + return(0); + } + } + return(ENXIO); +} + +static int +mlx_pci_attach(device_t dev) +{ + struct mlx_softc *sc; + int i, rid, error; + u_int32_t command; + + debug("called"); + + /* + * Make sure we are going to be able to talk to this board. + */ + command = pci_read_config(dev, PCIR_COMMAND, 1); + if ((command & PCIM_CMD_MEMEN) == 0) { + device_printf(dev, "memory window not available\n"); + return(ENXIO); + } + + /* + * Initialise softc. + */ + sc = device_get_softc(dev); + bzero(sc, sizeof(*sc)); + sc->mlx_dev = dev; + + /* + * Work out what sort of adapter this is (we need to know this in order + * to map the appropriate interface resources). + */ + sc->mlx_iftype = 0; + for (i = 0; mlx_identifiers[i].vendor != 0; i++) { + if ((mlx_identifiers[i].vendor == pci_get_vendor(dev)) && + (mlx_identifiers[i].device == pci_get_device(dev))) { + sc->mlx_iftype = mlx_identifiers[i].iftype; + break; + } + } + if (sc->mlx_iftype == 0) /* shouldn't happen */ + return(ENXIO); + + /* + * Allocate the PCI register window. + */ + + /* type 3 adapters have an I/O region we don't use at base 0 */ + rid = (sc->mlx_iftype == MLX_IFTYPE_3) ? MLX_CFG_BASE1 : MLX_CFG_BASE0; + sc->mlx_mem = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, 0, ~0, 1, RF_ACTIVE); + if (sc->mlx_mem == NULL) { + device_printf(sc->mlx_dev, "couldn't allocate mailbox window\n"); + mlx_free(sc); + return(ENXIO); + } + sc->mlx_btag = rman_get_bustag(sc->mlx_mem); + sc->mlx_bhandle = rman_get_bushandle(sc->mlx_mem); + + /* + * 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, MLX_NSEG, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + &sc->mlx_parent_dmat); + if (error != 0) { + device_printf(dev, "can't allocate parent DMA tag\n"); + mlx_free(sc); + return(ENOMEM); + } + + /* + * Do bus-independant initialisation. + */ + error = mlx_attach(sc); + if (error != 0) { + mlx_free(sc); + return(error); + } + + /* + * Start the controller. + */ + mlx_startup(sc); + return(0); +} diff --git a/sys/dev/mlx/mlxio.h b/sys/dev/mlx/mlxio.h new file mode 100644 index 000000000000..eabeb5047d20 --- /dev/null +++ b/sys/dev/mlx/mlxio.h @@ -0,0 +1,88 @@ +/*- + * 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 + +/* + * System Disk ioctls + */ +struct mlxd_rebuild +{ + int rb_channel; + int rb_target; +}; + +struct mlxd_rebuild_status +{ + int rs_drive; + int rs_size; + int rs_remaining; +}; + +#define MLXD_STATUS _IOR ('M', 100, int) +#define MLXD_REBUILDASYNC _IOW ('M', 101, struct mlxd_rebuild) +#define MLXD_CHECKASYNC _IOW ('M', 102, int) +#define MLXD_REBUILDSTAT _IOR ('M', 103, struct mlxd_rebuild_status) + +/* + * System Disk status values + */ +#define MLX_SYSD_ONLINE 0x03 +#define MLX_SYSD_CRITICAL 0x04 +#define MLX_SYSD_REBUILD 0xfe +#define MLX_SYSD_OFFLINE 0xff + +/* + * Controller ioctls + */ +struct mlx_pause +{ + int mp_which; +#define MLX_PAUSE_ALL 0xff +#define MLX_PAUSE_CANCEL 0x00 + int mp_when; + int mp_howlong; +}; + +struct mlx_usercommand +{ + /* data buffer */ + size_t mu_datasize; /* size of databuffer */ + void *mu_buf; /* address in userspace of databuffer */ + int mu_bufptr; /* offset into command mailbox to place databuffer address */ + + /* command */ + u_int16_t mu_status; /* command status returned */ + u_int8_t mu_command[16]; /* command mailbox contents */ +}; + +#define MLX_NEXT_CHILD _IOWR('M', 0, int) +#define MLX_RESCAN_DRIVES _IO ('M', 1) +#define MLX_DETACH_DRIVE _IOW ('M', 2, int) +#define MLX_PAUSE_CHANNEL _IOW ('M', 3, struct mlx_pause) +#define MLX_COMMAND _IOWR('M', 4, struct mlx_usercommand) diff --git a/sys/dev/mlx/mlxreg.h b/sys/dev/mlx/mlxreg.h new file mode 100644 index 000000000000..713022334dfa --- /dev/null +++ b/sys/dev/mlx/mlxreg.h @@ -0,0 +1,188 @@ +/*- + * 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$ + */ + +/* + * Selected command codes. + */ +#define MLX_CMD_ENQUIRY 0x53 +#define MLX_CMD_ENQUIRY2 0x1c +#define MLX_CMD_ENQSYSDRIVE 0x19 +#define MLX_CMD_READOLDSG 0xb6 +#define MLX_CMD_WRITEOLDSG 0xb7 +#define MLX_CMD_FLUSH 0x0a +#define MLX_CMD_LOGOP 0x72 +#define MLX_CMD_REBUILDASYNC 0x16 +#define MLX_CMD_CHECKASYNC 0x1e +#define MLX_CMD_REBUILDSTAT 0x0c +#define MLX_CMD_STOPCHANNEL 0x13 +#define MLX_CMD_STARTCHANNEL 0x12 + +/* + * Status values. + */ +#define MLX_STATUS_OK 0x0000 +#define MLX_STATUS_RDWROFFLINE 0x0002 /* read/write claims drive is offline */ +#define MLX_STATUS_WEDGED 0xdead /* controller not listening */ +#define MLX_STATUS_BUSY 0xffff /* command is in controller */ + +/* + * Scatter-gather list format, type 1, kind 00. + */ +struct mlx_sgentry +{ + u_int32_t sg_addr; + u_int32_t sg_count; +} __attribute__ ((packed)); + +/* + * Command result buffers, as placed in system memory by the controller. + */ +struct mlx_enquiry /* MLX_CMD_ENQUIRY */ +{ + u_int8_t me_num_sys_drvs; + u_int8_t res1[3]; + u_int32_t me_drvsize[32]; + u_int16_t me_flash_age; + u_int8_t me_status_flags; +#define MLX_ENQ_SFLAG_DEFWRERR (1<<0) /* deferred write error indicator */ +#define MLX_ENQ_SFLAG_BATTLOW (1<<1) /* battery low */ + u_int8_t res2; + u_int8_t me_fwminor; + u_int8_t me_fwmajor; + u_int8_t me_rebuild_flag; + u_int8_t me_max_commands; + u_int8_t me_offline_sd_count; + u_int8_t res3; + u_int16_t me_event_log_seq_num; + u_int8_t me_critical_sd_count; + u_int8_t res4[3]; + u_int8_t me_dead_count; + u_int8_t res5; + u_int8_t me_rebuild_count; + u_int8_t me_misc_flags; +#define MLX_ENQ_MISC_BBU (1<<3) /* battery backup present */ + struct + { + u_int8_t dd_targ; + u_int8_t dd_chan; + } __attribute__ ((packed)) me_dead[20]; +} __attribute__ ((packed)); + +struct mlx_enquiry2 /* MLX_CMD_ENQUIRY2 */ +{ + u_int32_t me_hardware_id; + u_int32_t me_firmware_id; + u_int32_t res1; + u_int8_t me_configured_channels; + u_int8_t me_actual_channels; + u_int8_t me_max_targets; + u_int8_t me_max_tags; + u_int8_t me_max_sys_drives; + u_int8_t me_max_arms; + u_int8_t me_max_spans; + u_int8_t res2; + u_int32_t res3; + u_int32_t me_mem_size; + u_int32_t me_cache_size; + u_int32_t me_flash_size; + u_int32_t me_nvram_size; + u_int16_t me_mem_type; + u_int16_t me_clock_speed; + u_int16_t me_mem_speed; + u_int16_t me_hardware_speed; + u_int8_t res4[10]; + u_int16_t me_max_commands; + u_int16_t me_max_sg; + u_int16_t me_max_dp; + u_int16_t me_max_iod; + u_int16_t me_max_comb; + u_int8_t me_latency; + u_int8_t res5; + u_int8_t me_scsi_timeout; + u_int8_t res6; + u_int16_t me_min_freelines; + u_int8_t res7[8]; + u_int8_t me_rate_const; + u_int8_t res8[11]; + u_int16_t me_physblk; + u_int16_t me_logblk; + u_int16_t me_maxblk; + u_int16_t me_blocking_factor; + u_int16_t me_cacheline; + u_int8_t me_scsi_cap; + u_int8_t res9[5]; + u_int16_t me_fimware_build; + u_int8_t me_fault_mgmt_type; + u_int8_t res10; + u_int32_t me_firmware_features; + u_int8_t res11[8]; +} __attribute__ ((packed)); + +struct mlx_enq_sys_drive /* MLX_CMD_ENQSYSDRIVE returns an array of 32 of these */ +{ + u_int32_t sd_size; + u_int8_t sd_state; + u_int8_t sd_raidlevel; + u_int16_t res1; +} __attribute__ ((packed)); + +struct mlx_eventlog_entry /* MLX_CMD_LOGOP/MLX_LOGOP_GET */ +{ + u_int8_t el_type; + u_int8_t el_length; + u_char el_target:5; + u_char el_channel:3; + u_char el_lun:6; + u_char res1:2; + u_int16_t el_seqno; + u_char el_errorcode:7; + u_char el_valid:1; + u_int8_t el_segment; + u_char el_sensekey:4; + u_char res2:1; + u_char el_ILI:1; + u_char el_EOM:1; + u_char el_filemark:1; + u_int8_t el_information[4]; + u_int8_t el_addsense; + u_int8_t el_csi[4]; + u_int8_t el_asc; + u_int8_t el_asq; + u_int8_t res3[12]; +} __attribute__ ((packed)); + +#define MLX_LOGOP_GET 0x00 /* operation codes for MLX_CMD_LOGOP */ +#define MLX_LOGMSG_SENSE 0x00 /* log message contents codes */ + +struct mlx_rebuild_stat /* MLX_CMD_REBUILDSTAT */ +{ + u_int32_t rb_drive; + u_int32_t rb_size; + u_int32_t rb_remaining; +} __attribute__ ((packed)); + diff --git a/sys/dev/mlx/mlxvar.h b/sys/dev/mlx/mlxvar.h new file mode 100644 index 000000000000..7679dbbce9b5 --- /dev/null +++ b/sys/dev/mlx/mlxvar.h @@ -0,0 +1,353 @@ +/*- + * 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 33 segments, but using only 32 means that + * each scatter/gather map is 256 bytes in size, and thus we don't have to worry about + * maps crossing page boundaries. + */ +#define MLX_NSEG 32 /* max scatter/gather segments we use */ +#define MLX_NSLOTS 256 /* max number of command slots */ + +#define MLX_CFG_BASE0 0x10 /* first region */ +#define MLX_CFG_BASE1 0x14 /* second region (type 3 only) */ + +#define MLX_MAXDRIVES 32 + +#define MLX_BLKSIZE 512 /* fixed feature */ + +/* + * Structure describing a System Drive as attached to the controller. + */ +struct mlx_sysdrive +{ + /* from MLX_CMD_ENQSYSDRIVE */ + u_int32_t ms_size; + int ms_state; + int ms_raidlevel; + + /* synthetic geometry */ + int ms_cylinders; + int ms_heads; + int ms_sectors; + + /* handle for attached driver */ + device_t ms_disk; +}; + +/* + * Per-command control structure. + */ +struct mlx_command +{ + TAILQ_ENTRY(mlx_command) mc_link; /* list linkage */ + + struct mlx_softc *mc_sc; /* controller that owns us */ + u_int8_t mc_slot; /* command slot we occupy */ + u_int16_t mc_status; /* command completion status */ + u_int8_t mc_mailbox[16]; /* command mailbox */ + u_int32_t mc_sgphys; /* physical address of s/g array in controller space */ + int mc_nsgent; /* number of entries in s/g map */ + int mc_flags; +#define MLX_CMD_DATAIN (1<<0) +#define MLX_CMD_DATAOUT (1<<1) +#define MLX_CMD_PRIORITY (1<<2) /* high-priority command */ + + void *mc_data; /* data buffer */ + size_t mc_length; + bus_dmamap_t mc_dmamap; /* DMA map for data */ + u_int32_t mc_dataphys; /* data buffer base address controller space */ + + void (* mc_complete)(struct mlx_command *mc); /* completion handler */ + void *mc_private; /* submitter-private data or wait channel */ +}; + +/* + * Per-controller structure. + */ +struct mlx_softc +{ + /* bus connections */ + device_t mlx_dev; + struct resource *mlx_mem; /* mailbox interface window */ + bus_space_handle_t mlx_bhandle; /* bus space handle */ + bus_space_tag_t mlx_btag; /* bus space tag */ + bus_dma_tag_t mlx_parent_dmat;/* parent DMA tag */ + bus_dma_tag_t mlx_buffer_dmat;/* data buffer DMA tag */ + struct resource *mlx_irq; /* interrupt */ + void *mlx_intr; /* interrupt handle */ + + /* scatter/gather lists and their controller-visible mappings */ + struct mlx_sgentry *mlx_sgtable; /* s/g lists */ + u_int32_t mlx_sgbusaddr; /* s/g table base address in bus space */ + bus_dma_tag_t mlx_sg_dmat; /* s/g buffer DMA tag */ + bus_dmamap_t mlx_sg_dmamap; /* map for s/g buffers */ + + /* controller limits and features */ + int mlx_hwid; /* hardware identifier */ + int mlx_maxiop; /* maximum number of I/O operations */ + int mlx_nchan; /* number of active channels */ + int mlx_maxiosize; /* largest I/O for this controller */ + int mlx_maxtarg; /* maximum number of targets per channel */ + int mlx_maxtags; /* maximum number of tags per device */ + int mlx_scsicap; /* SCSI capabilities */ + int mlx_feature; /* controller features/quirks */ +#define MLX_FEAT_PAUSEWORKS (1<<0) /* channel pause works as expected */ + + /* controller queues and arrays */ + TAILQ_HEAD(, mlx_command) mlx_freecmds; /* command structures available for reuse */ + TAILQ_HEAD(, mlx_command) mlx_donecmd; /* commands waiting for completion processing */ + struct mlx_command *mlx_busycmd[MLX_NSLOTS]; /* busy commands */ + int mlx_busycmds; /* count of busy commands */ + struct mlx_sysdrive mlx_sysdrive[MLX_MAXDRIVES]; /* system drives */ + struct buf_queue_head mlx_bufq; /* outstanding I/O operations */ + int mlx_waitbufs; /* number of bufs awaiting commands */ + + /* controller status */ + u_int8_t mlx_fwminor; /* firmware revision */ + u_int8_t mlx_fwmajor; + int mlx_geom; +#define MLX_GEOM_128_32 0 /* geoemetry translation modes */ +#define MLX_GEOM_256_63 1 + int mlx_state; +#define MLX_STATE_INTEN (1<<0) /* interrupts have been enabled */ +#define MLX_STATE_SHUTDOWN (1<<1) /* controller is shut down */ +#define MLX_STATE_OPEN (1<<2) /* control device is open */ +#define MLX_STATE_SUSPEND (1<<3) /* controller is suspended */ + struct callout_handle mlx_timeout; /* periodic status monitor */ + time_t mlx_lastpoll; /* last time_second we polled for status */ + u_int16_t mlx_lastevent; /* sequence number of the last event we recorded */ + u_int16_t mlx_currevent; /* sequence number last time we looked */ + int mlx_polling; /* if > 0, polling operations still running */ + int mlx_rebuild; /* if >= 0, drive is being rebuilt */ + u_int32_t mlx_rebuildstat;/* blocks left to rebuild if active */ + int mlx_check; /* if >= 0, drive is being checked */ + struct mlx_pause mlx_pause; /* pending pause operation details */ + + /* interface-specific accessor functions */ + int mlx_iftype; /* interface protocol */ +#define MLX_IFTYPE_3 3 +#define MLX_IFTYPE_4 4 +#define MLX_IFTYPE_5 5 + int (* mlx_tryqueue)(struct mlx_softc *sc, struct mlx_command *mc); + int (* mlx_findcomplete)(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status); + void (* mlx_intaction)(struct mlx_softc *sc, int action); +#define MLX_INTACTION_DISABLE 0 +#define MLX_INTACTION_ENABLE 1 +#define MLX_INTACTION_ACKNOWLEDGE 2 + +}; + +/* + * Interface between bus connections and driver core. + */ +extern void mlx_free(struct mlx_softc *sc); +extern int mlx_attach(struct mlx_softc *sc); +extern void mlx_startup(struct mlx_softc *sc); +extern void mlx_intr(void *data); +extern int mlx_detach(device_t dev); +extern int mlx_shutdown(device_t dev); +extern int mlx_suspend(device_t dev); +extern int mlx_resume(device_t dev); +extern d_open_t mlx_open; +extern d_close_t mlx_close; +extern d_ioctl_t mlx_ioctl; + +extern devclass_t mlx_devclass; + +/* + * Mylex System Disk driver + */ +struct mlxd_softc +{ + device_t mlxd_dev; + struct mlx_softc *mlxd_controller; + struct mlx_sysdrive *mlxd_drive; + struct disk mlxd_disk; + struct devstat mlxd_stats; + struct disklabel mlxd_label; + int mlxd_unit; + int mlxd_flags; +#define MLXD_OPEN (1<<0) /* drive is open (can't shut down) */ +}; + +/* + * Interface between driver core and disk driver (should be using a bus?) + */ +extern int mlx_submit_buf(struct mlx_softc *sc, struct buf *bp); +extern int mlx_submit_ioctl(struct mlx_softc *sc, struct mlx_sysdrive *drive, u_long cmd, + caddr_t addr, int32_t flag, struct proc *p); +extern void mlxd_intr(void *data); + +/* + * Accessor defines for the V3 interface. + */ +#define MLX_V3_MAILBOX 0x00 +#define MLX_V3_STATUS_IDENT 0x0d +#define MLX_V3_STATUS 0x0e +#define MLX_V3_IDBR 0x40 +#define MLX_V3_ODBR 0x41 +#define MLX_V3_IER 0x43 + +#define MLX_V3_PUT_MAILBOX(sc, idx, val) bus_space_write_1(sc->mlx_btag, sc->mlx_bhandle, MLX_V3_MAILBOX + idx, val) +#define MLX_V3_GET_STATUS_IDENT(sc) bus_space_read_1 (sc->mlx_btag, sc->mlx_bhandle, MLX_V3_STATUS_IDENT) +#define MLX_V3_GET_STATUS(sc) bus_space_read_2 (sc->mlx_btag, sc->mlx_bhandle, MLX_V3_STATUS) +#define MLX_V3_GET_IDBR(sc) bus_space_read_1 (sc->mlx_btag, sc->mlx_bhandle, MLX_V3_IDBR) +#define MLX_V3_PUT_IDBR(sc, val) bus_space_write_1(sc->mlx_btag, sc->mlx_bhandle, MLX_V3_IDBR, val) +#define MLX_V3_GET_ODBR(sc) bus_space_read_1 (sc->mlx_btag, sc->mlx_bhandle, MLX_V3_ODBR) +#define MLX_V3_PUT_ODBR(sc, val) bus_space_write_1(sc->mlx_btag, sc->mlx_bhandle, MLX_V3_ODBR, val) +#define MLX_V3_PUT_IER(sc, val) bus_space_write_1(sc->mlx_btag, sc->mlx_bhandle, MLX_V3_IER, val) + +#define MLX_V3_IDB_FULL (1<<0) /* mailbox is full */ +#define MLX_V3_IDB_SACK (1<<1) /* acknowledge status read */ +#define MLX_V3_IDB_RESET (1<<3) /* request soft reset */ + +#define MLX_V3_ODB_SAVAIL (1<<0) /* status is available */ + +/* + * Inlines to build various command structures + */ +static __inline void +mlx_make_type1(struct mlx_command *mc, + u_int8_t code, + u_int16_t f1, + u_int32_t f2, + u_int8_t f3, + u_int32_t f4, + u_int8_t f5) +{ + mc->mc_mailbox[0x0] = code; + mc->mc_mailbox[0x2] = f1 & 0xff; + mc->mc_mailbox[0x3] = (((f2 >> 24) & 0x3) << 6) | ((f1 >> 8) & 0x3f); + mc->mc_mailbox[0x4] = f2 & 0xff; + mc->mc_mailbox[0x5] = (f2 >> 8) & 0xff; + mc->mc_mailbox[0x6] = (f2 >> 16) & 0xff; + mc->mc_mailbox[0x7] = f3; + mc->mc_mailbox[0x8] = f4 & 0xff; + mc->mc_mailbox[0x9] = (f4 >> 8) & 0xff; + mc->mc_mailbox[0xa] = (f4 >> 16) & 0xff; + mc->mc_mailbox[0xb] = (f4 >> 24) & 0xff; + mc->mc_mailbox[0xc] = f5; +} + +static __inline void +mlx_make_type2(struct mlx_command *mc, + u_int8_t code, + u_int8_t f1, + u_int8_t f2, + u_int8_t f3, + u_int8_t f4, + u_int8_t f5, + u_int8_t f6, + u_int32_t f7, + u_int8_t f8) +{ + mc->mc_mailbox[0x0] = code; + mc->mc_mailbox[0x2] = f1; + mc->mc_mailbox[0x3] = f2; + mc->mc_mailbox[0x4] = f3; + mc->mc_mailbox[0x5] = f4; + mc->mc_mailbox[0x6] = f5; + mc->mc_mailbox[0x7] = f6; + mc->mc_mailbox[0x8] = f7 & 0xff; + mc->mc_mailbox[0x9] = (f7 >> 8) & 0xff; + mc->mc_mailbox[0xa] = (f7 >> 16) & 0xff; + mc->mc_mailbox[0xb] = (f7 >> 24) & 0xff; + mc->mc_mailbox[0xc] = f8; +} + +static __inline void +mlx_make_type3(struct mlx_command *mc, + u_int8_t code, + u_int8_t f1, + u_int8_t f2, + u_int16_t f3, + u_int8_t f4, + u_int8_t f5, + u_int32_t f6, + u_int8_t f7) +{ + mc->mc_mailbox[0x0] = code; + mc->mc_mailbox[0x2] = f1; + mc->mc_mailbox[0x3] = f2; + mc->mc_mailbox[0x4] = f3 & 0xff; + mc->mc_mailbox[0x5] = (f3 >> 8) & 0xff; + mc->mc_mailbox[0x6] = f4; + mc->mc_mailbox[0x7] = f5; + mc->mc_mailbox[0x8] = f6 & 0xff; + mc->mc_mailbox[0x9] = (f6 >> 8) & 0xff; + mc->mc_mailbox[0xa] = (f6 >> 16) & 0xff; + mc->mc_mailbox[0xb] = (f6 >> 24) & 0xff; + mc->mc_mailbox[0xc] = f7; +} + +static __inline void +mlx_make_type4(struct mlx_command *mc, + u_int8_t code, + u_int16_t f1, + u_int32_t f2, + u_int32_t f3, + u_int8_t f4) +{ + mc->mc_mailbox[0x0] = code; + mc->mc_mailbox[0x2] = f1 & 0xff; + mc->mc_mailbox[0x3] = (f1 >> 8) & 0xff; + mc->mc_mailbox[0x4] = f2 & 0xff; + mc->mc_mailbox[0x5] = (f2 >> 8) & 0xff; + mc->mc_mailbox[0x6] = (f2 >> 16) & 0xff; + mc->mc_mailbox[0x7] = (f2 >> 24) & 0xff; + mc->mc_mailbox[0x8] = f3 & 0xff; + mc->mc_mailbox[0x9] = (f3 >> 8) & 0xff; + mc->mc_mailbox[0xa] = (f3 >> 16) & 0xff; + mc->mc_mailbox[0xb] = (f3 >> 24) & 0xff; + mc->mc_mailbox[0xc] = f4; +} + +static __inline void +mlx_make_type5(struct mlx_command *mc, + u_int8_t code, + u_int8_t f1, + u_int8_t f2, + u_int32_t f3, + u_int32_t f4, + u_int8_t f5) +{ + mc->mc_mailbox[0x0] = code; + mc->mc_mailbox[0x2] = f1; + mc->mc_mailbox[0x3] = f2; + mc->mc_mailbox[0x4] = f3 & 0xff; + mc->mc_mailbox[0x5] = (f3 >> 8) & 0xff; + mc->mc_mailbox[0x6] = (f3 >> 16) & 0xff; + mc->mc_mailbox[0x7] = (f3 >> 24) & 0xff; + mc->mc_mailbox[0x8] = f4 & 0xff; + mc->mc_mailbox[0x9] = (f4 >> 8) & 0xff; + mc->mc_mailbox[0xa] = (f4 >> 16) & 0xff; + mc->mc_mailbox[0xb] = (f4 >> 24) & 0xff; + mc->mc_mailbox[0xc] = f5; +} + diff --git a/sys/modules/mlx/Makefile b/sys/modules/mlx/Makefile new file mode 100644 index 000000000000..d804e85cb047 --- /dev/null +++ b/sys/modules/mlx/Makefile @@ -0,0 +1,22 @@ +# $FreeBSD$ + +S = ${.CURDIR}/../.. +.PATH: $S/dev/mlx +KMOD = mlx +SRCS = mlx.c mlx_pci.c mlx_disk.c device_if.h bus_if.h pci_if.h +CLEANFILES += mlx.h device_if.h bus_if.h pci_if.h +CFLAGS += ${DEBUG_FLAGS} + +mlx.h: + echo "#define NMLX 1" > mlx.h + +device_if.h: $S/kern/makedevops.pl $S/kern/device_if.m + perl $S/kern/makedevops.pl -h $S/kern/device_if.m + +bus_if.h: $S/kern/makedevops.pl $S/kern/bus_if.m + perl $S/kern/makedevops.pl -h $S/kern/bus_if.m + +pci_if.h: $S/kern/makedevops.pl $S/pci/pci_if.m + perl $S/kern/makedevops.pl -h $S/pci/pci_if.m + +.include