diff options
author | Mike Smith <msmith@FreeBSD.org> | 2000-08-30 07:52:50 +0000 |
---|---|---|
committer | Mike Smith <msmith@FreeBSD.org> | 2000-08-30 07:52:50 +0000 |
commit | 9f1776230d63537648a5174e3dac7a18c7ffdbca (patch) | |
tree | 3130d6b7c48e164dc29c8ce2a7de16a42cff3581 /sys | |
parent | 429a82acc661b83cceaaae92f4ba169dfe5aaa04 (diff) | |
download | src-9f1776230d63537648a5174e3dac7a18c7ffdbca.tar.gz src-9f1776230d63537648a5174e3dac7a18c7ffdbca.zip |
Major update to the AMI MegaRAID driver.
- New support for 40LD firmware found in Series 475 and 471 adapters.
- Better support for 8LD firmware adapters
- Ioctl passthrough interface for userland utilities.
- Improved error handling and queueing.
- Several bugfixes (including the 'still open' shutdown bug and
closing some small race conditions).
- Zone-style command allocator, reducing memory wasted under heavy
load conditions.
- CAM interface (disabled and not fully working) for SCSI passthrough
access to non-disk devices
Thanks to AMI for supplying a pile of new adapters and various other
help in making this happen.
Notes
Notes:
svn path=/head/; revision=65245
Diffstat (limited to 'sys')
-rw-r--r-- | sys/dev/amr/amr.c | 1689 | ||||
-rw-r--r-- | sys/dev/amr/amr_cam.c | 452 | ||||
-rw-r--r-- | sys/dev/amr/amr_compat.h | 61 | ||||
-rw-r--r-- | sys/dev/amr/amr_disk.c | 123 | ||||
-rw-r--r-- | sys/dev/amr/amr_pci.c | 429 | ||||
-rw-r--r-- | sys/dev/amr/amr_tables.h | 110 | ||||
-rw-r--r-- | sys/dev/amr/amrio.h | 54 | ||||
-rw-r--r-- | sys/dev/amr/amrreg.h | 425 | ||||
-rw-r--r-- | sys/dev/amr/amrvar.h | 292 | ||||
-rw-r--r-- | sys/modules/amr/Makefile | 12 |
10 files changed, 2514 insertions, 1133 deletions
diff --git a/sys/dev/amr/amr.c b/sys/dev/amr/amr.c index 2f25a21e14f8..dec047a10fc9 100644 --- a/sys/dev/amr/amr.c +++ b/sys/dev/amr/amr.c @@ -1,5 +1,6 @@ /*- - * Copyright (c) 1999 Michael Smith + * Copyright (c) 1999,2000 Michael Smith + * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,7 +28,7 @@ */ /* - * Driver for the AMI MegaRaid family of controllers + * Driver for the AMI MegaRaid family of controllers. */ #include <sys/param.h> @@ -35,29 +36,35 @@ #include <sys/malloc.h> #include <sys/kernel.h> -#include <sys/bio.h> +#include <dev/amr/amr_compat.h> #include <sys/bus.h> #include <sys/conf.h> #include <sys/devicestat.h> #include <sys/disk.h> +#include <sys/stat.h> -#include <machine/resource.h> +#include <machine/bus_memio.h> +#include <machine/bus_pio.h> #include <machine/bus.h> +#include <machine/resource.h> #include <machine/clock.h> #include <sys/rman.h> +#include <pci/pcireg.h> +#include <pci/pcivar.h> + #include <dev/amr/amrio.h> #include <dev/amr/amrreg.h> #include <dev/amr/amrvar.h> - -#if 0 -#define debug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args) -#else -#define debug(fmt, args...) -#endif +#define AMR_DEFINE_TABLES +#include <dev/amr/amr_tables.h> #define AMR_CDEV_MAJOR 132 +static d_open_t amr_open; +static d_close_t amr_close; +static d_ioctl_t amr_ioctl; + static struct cdevsw amr_cdevsw = { /* open */ amr_open, /* close */ amr_close, @@ -75,263 +82,76 @@ static struct cdevsw amr_cdevsw = { /* bmaj */ 254 /* XXX magic no-bdev */ }; -static int cdev_registered = 0; -devclass_t amr_devclass; +/* + * Initialisation, bus interface. + */ +static void amr_startup(void *arg); /* * Command wrappers */ -static int amr_query_controller(struct amr_softc *sc); -static void *amr_enquiry(struct amr_softc *sc, size_t bufsize, - u_int8_t cmd, u_int8_t cmdsub, u_int8_t cmdqual); -static int amr_flush(struct amr_softc *sc); -static void amr_startio(struct amr_softc *sc); -static void amr_completeio(struct amr_command *ac); +static int amr_query_controller(struct amr_softc *sc); +static void *amr_enquiry(struct amr_softc *sc, size_t bufsize, + u_int8_t cmd, u_int8_t cmdsub, u_int8_t cmdqual); +static void amr_completeio(struct amr_command *ac); /* - * Command processing. + * Command buffer allocation. */ -static int amr_wait_command(struct amr_command *ac); -static int amr_poll_command(struct amr_command *ac); -static int amr_getslot(struct amr_command *ac); -static void amr_mapcmd(struct amr_command *ac); -static void amr_unmapcmd(struct amr_command *ac); -static int amr_start(struct amr_command *ac); -static int amr_done(struct amr_softc *sc); -static void amr_complete(struct amr_softc *sc); +static void amr_alloccmd_cluster(struct amr_softc *sc); +static void amr_freecmd_cluster(struct amr_command_cluster *acc); /* - * Command buffer allocation. + * Command processing. */ -static struct amr_command *amr_alloccmd(struct amr_softc *sc); -static void amr_releasecmd(struct amr_command *ac); -static void amr_freecmd(struct amr_command *ac); +static int amr_bio_command(struct amr_softc *sc, struct amr_command **acp); +static int amr_wait_command(struct amr_command *ac); +static int amr_poll_command(struct amr_command *ac); +static int amr_getslot(struct amr_command *ac); +static void amr_mapcmd(struct amr_command *ac); +static void amr_unmapcmd(struct amr_command *ac); +static int amr_start(struct amr_command *ac); +static void amr_complete(void *context, int pending); /* * Status monitoring */ -static void amr_periodic(void *data); +static void amr_periodic(void *data); /* * Interface-specific shims */ -static void amr_quartz_submit_command(struct amr_softc *sc); -static int amr_quartz_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave); -static void amr_quartz_attach_mailbox(struct amr_softc *sc); +static int amr_quartz_submit_command(struct amr_softc *sc); +static int amr_quartz_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave); -static void amr_std_submit_command(struct amr_softc *sc); -static int amr_std_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave); -static void amr_std_attach_mailbox(struct amr_softc *sc); +static int amr_std_submit_command(struct amr_softc *sc); +static int amr_std_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave); +static void amr_std_attach_mailbox(struct amr_softc *sc); + +#ifdef AMR_BOARD_INIT +static int amr_quartz_init(struct amr_softc *sc); +static int amr_std_init(struct amr_softc *sc); +#endif /* * Debugging */ -static void amr_printcommand(struct amr_command *ac); +static void amr_describe_controller(struct amr_softc *sc); +#ifdef AMR_DEBUG +static void amr_printcommand(struct amr_command *ac); +#endif /******************************************************************************** ******************************************************************************** - Public Interfaces + Inline Glue ******************************************************************************** ********************************************************************************/ /******************************************************************************** - * Free all of the resources associated with (sc) - * - * Should not be called if the controller is active. - */ -void -amr_free(struct amr_softc *sc) -{ - struct amr_command *ac; - u_int8_t *p; - - debug("called"); - - /* cancel status timeout */ - untimeout(amr_periodic, sc, sc->amr_timeout); - - /* throw away any command buffers */ - while ((ac = TAILQ_FIRST(&sc->amr_freecmds)) != NULL) { - TAILQ_REMOVE(&sc->amr_freecmds, ac, ac_link); - amr_freecmd(ac); - } - - /* destroy data-transfer DMA tag */ - if (sc->amr_buffer_dmat) - bus_dma_tag_destroy(sc->amr_buffer_dmat); - - /* free and destroy DMA memory and tag for s/g lists */ - if (sc->amr_sgtable) - bus_dmamem_free(sc->amr_sg_dmat, sc->amr_sgtable, sc->amr_sg_dmamap); - if (sc->amr_sg_dmat) - bus_dma_tag_destroy(sc->amr_sg_dmat); - - /* free and destroy DMA memory and tag for mailbox */ - if (sc->amr_mailbox) { - p = (u_int8_t *)sc->amr_mailbox; - bus_dmamem_free(sc->amr_sg_dmat, p - 16, sc->amr_sg_dmamap); - } - if (sc->amr_sg_dmat) - bus_dma_tag_destroy(sc->amr_sg_dmat); - - /* disconnect the interrupt handler */ - if (sc->amr_intr) - bus_teardown_intr(sc->amr_dev, sc->amr_irq, sc->amr_intr); - if (sc->amr_irq != NULL) - bus_release_resource(sc->amr_dev, SYS_RES_IRQ, 0, sc->amr_irq); - - /* destroy the parent DMA tag */ - if (sc->amr_parent_dmat) - bus_dma_tag_destroy(sc->amr_parent_dmat); - - /* release the register window mapping */ - if (sc->amr_reg != NULL) - bus_release_resource(sc->amr_dev, - (sc->amr_type == AMR_TYPE_QUARTZ) ? SYS_RES_MEMORY : SYS_RES_IOPORT, - AMR_CFG_BASE, sc->amr_reg); -} - -/******************************************************************************** - * Allocate and map the scatter/gather table in bus space. - */ -static void -amr_dma_map_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error) -{ - struct amr_softc *sc = (struct amr_softc *)arg; - - debug("called"); - - /* save base of s/g table's address in bus space */ - sc->amr_sgbusaddr = segs->ds_addr; -} - -static int -amr_sglist_map(struct amr_softc *sc) -{ - size_t segsize; - int error; - - debug("called"); - - /* destroy any existing mappings */ - if (sc->amr_sgtable) - bus_dmamem_free(sc->amr_sg_dmat, sc->amr_sgtable, sc->amr_sg_dmamap); - if (sc->amr_sg_dmat) - bus_dma_tag_destroy(sc->amr_sg_dmat); - - /* - * Create a single tag describing a region large enough to hold all of - * the s/g lists we will need. - */ - segsize = sizeof(struct amr_sgentry) * AMR_NSEG * sc->amr_maxio; - error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ - 1, 0, /* alignment, boundary */ - BUS_SPACE_MAXADDR, /* lowaddr */ - BUS_SPACE_MAXADDR, /* highaddr */ - NULL, NULL, /* filter, filterarg */ - segsize, 1, /* maxsize, nsegments */ - BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ - 0, /* flags */ - &sc->amr_sg_dmat); - if (error != 0) { - device_printf(sc->amr_dev, "can't allocate scatter/gather DMA tag\n"); - return(ENOMEM); - } - - /* - * Allocate enough s/g maps for all commands and permanently map them into - * controller-visible space. - * - * XXX this assumes we can get enough space for all the s/g maps in one - * contiguous slab. We may need to switch to a more complex arrangement where - * we allocate in smaller chunks and keep a lookup table from slot to bus address. - * - * XXX HACK ALERT: at least some controllers don't like the s/g memory being - * allocated below 0x2000. We leak some memory if we get some - * below this mark and allocate again. - */ -retry: - error = bus_dmamem_alloc(sc->amr_sg_dmat, (void **)&sc->amr_sgtable, BUS_DMA_NOWAIT, &sc->amr_sg_dmamap); - if (error) { - device_printf(sc->amr_dev, "can't allocate s/g table\n"); - return(ENOMEM); - } - bus_dmamap_load(sc->amr_sg_dmat, sc->amr_sg_dmamap, sc->amr_sgtable, segsize, amr_dma_map_sg, sc, 0); - if (sc->amr_sgbusaddr < 0x2000) { - device_printf(sc->amr_dev, "s/g table too low (0x%x), reallocating\n", sc->amr_sgbusaddr); - goto retry; - } - return(0); -} - -/******************************************************************************** - * Allocate and set up mailbox areas for the controller (sc) - * - * The basic mailbox structure should be 16-byte aligned. This means that the - * mailbox64 structure has 4 bytes hanging off the bottom. - */ -static void -amr_map_mailbox(void *arg, bus_dma_segment_t *segs, int nseg, int error) -{ - struct amr_softc *sc = (struct amr_softc *)arg; - - debug("called"); - - /* save phsyical base of the basic mailbox structure */ - sc->amr_mailboxphys = segs->ds_addr + 16; -} - -static int -amr_setup_mbox(struct amr_softc *sc) -{ - int error; - u_int8_t *p; - - debug("called"); - - /* - * Create a single tag describing a region large enough to hold the entire - * mailbox. - */ - error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ - 16, 0, /* alignment, boundary */ - BUS_SPACE_MAXADDR, /* lowaddr */ - BUS_SPACE_MAXADDR, /* highaddr */ - NULL, NULL, /* filter, filterarg */ - sizeof(struct amr_mailbox) + 16, 1, /* maxsize, nsegments */ - BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ - 0, /* flags */ - &sc->amr_mailbox_dmat); - if (error != 0) { - device_printf(sc->amr_dev, "can't allocate mailbox tag\n"); - return(ENOMEM); - } - - /* - * Allocate the mailbox structure and permanently map it into - * controller-visible space. - */ - error = bus_dmamem_alloc(sc->amr_mailbox_dmat, (void **)&p, BUS_DMA_NOWAIT, - &sc->amr_mailbox_dmamap); - if (error) { - device_printf(sc->amr_dev, "can't allocate mailbox memory\n"); - return(ENOMEM); - } - bus_dmamap_load(sc->amr_mailbox_dmat, sc->amr_mailbox_dmamap, p, - sizeof(struct amr_mailbox64), amr_map_mailbox, sc, 0); - /* - * Conventional mailbox is inside the mailbox64 region. - */ - bzero(p, sizeof(struct amr_mailbox64)); - sc->amr_mailbox64 = (struct amr_mailbox64 *)(p + 12); - sc->amr_mailbox = (struct amr_mailbox *)(p + 16); - - if (sc->amr_type == AMR_TYPE_STD) { - /* XXX we have to tell the controller where we put it */ - } - return(0); -} - + ******************************************************************************** + Public Interfaces + ******************************************************************************** + ********************************************************************************/ /******************************************************************************** * Initialise the controller and softc. @@ -339,110 +159,107 @@ amr_setup_mbox(struct amr_softc *sc) int amr_attach(struct amr_softc *sc) { - int rid, error; + + debug_called(1); /* * Initialise per-controller queues. */ - TAILQ_INIT(&sc->amr_work); + TAILQ_INIT(&sc->amr_completed); TAILQ_INIT(&sc->amr_freecmds); + TAILQ_INIT(&sc->amr_cmd_clusters); + TAILQ_INIT(&sc->amr_ready); bioq_init(&sc->amr_bioq); +#if __FreeBSD_version >= 500005 + /* + * Initialise command-completion task. + */ + TASK_INIT(&sc->amr_task_complete, 0, amr_complete, sc); +#endif + + debug(2, "queue init done"); + /* * Configure for this controller type. */ - if (sc->amr_type == AMR_TYPE_QUARTZ) { + if (AMR_IS_QUARTZ(sc)) { sc->amr_submit_command = amr_quartz_submit_command; sc->amr_get_work = amr_quartz_get_work; - sc->amr_attach_mailbox = amr_quartz_attach_mailbox; } else { sc->amr_submit_command = amr_std_submit_command; sc->amr_get_work = amr_std_get_work; - sc->amr_attach_mailbox = amr_std_attach_mailbox; + amr_std_attach_mailbox(sc);; } - /* - * Allocate and connect our interrupt. - */ - rid = 0; - sc->amr_irq = bus_alloc_resource(sc->amr_dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); - if (sc->amr_irq == NULL) { - device_printf(sc->amr_dev, "couldn't allocate interrupt\n"); - amr_free(sc); - return(ENXIO); - } - error = bus_setup_intr(sc->amr_dev, sc->amr_irq, INTR_TYPE_BIO, amr_intr, sc, &sc->amr_intr); - if (error) { - device_printf(sc->amr_dev, "couldn't set up interrupt\n"); - amr_free(sc); - return(ENXIO); - } +#ifdef AMR_BOARD_INIT + if ((AMR_IS_QUARTZ(sc) ? amr_quartz_init(sc) : amr_std_init(sc)))) + return(ENXIO); +#endif /* - * Create DMA tag for mapping buffers into controller-addressable space. + * Quiz controller for features and limits. */ - error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ - 1, 0, /* alignment, boundary */ - BUS_SPACE_MAXADDR, /* lowaddr */ - BUS_SPACE_MAXADDR, /* highaddr */ - NULL, NULL, /* filter, filterarg */ - MAXBSIZE, AMR_NSEG, /* maxsize, nsegments */ - BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ - 0, /* flags */ - &sc->amr_buffer_dmat); - if (error != 0) { - device_printf(sc->amr_dev, "can't allocate buffer DMA tag\n"); - return(ENOMEM); - } + if (amr_query_controller(sc)) + return(ENXIO); - /* - * Allocate and set up mailbox in a bus-visible fashion, attach to controller. - */ - if ((error = amr_setup_mbox(sc)) != 0) - return(error); - sc->amr_attach_mailbox(sc); + debug(2, "controller query complete"); +#ifdef AMR_SCSI_PASSTHROUGH /* - * Build a temporary set of scatter/gather buffers. + * Attach our 'real' SCSI channels to CAM. */ - sc->amr_maxio = 2; - if (amr_sglist_map(sc)) + if (amr_cam_attach(sc)) return(ENXIO); + debug(2, "CAM attach done"); +#endif /* - * Quiz controller for features and limits. + * Create the control device. */ - if (amr_query_controller(sc)) - return(ENXIO); + sc->amr_dev_t = make_dev(&amr_cdevsw, device_get_unit(sc->amr_dev), UID_ROOT, GID_OPERATOR, + S_IRUSR | S_IWUSR, "amr%d", device_get_unit(sc->amr_dev)); + sc->amr_dev_t->si_drv1 = sc; /* - * Rebuild the scatter/gather buffers now we know how many we need. + * Schedule ourselves to bring the controller up once interrupts are + * available. */ - if (amr_sglist_map(sc)) - return(ENXIO); + bzero(&sc->amr_ich, sizeof(struct intr_config_hook)); + sc->amr_ich.ich_func = amr_startup; + sc->amr_ich.ich_arg = sc; + if (config_intrhook_establish(&sc->amr_ich) != 0) { + device_printf(sc->amr_dev, "can't establish configuration hook\n"); + return(ENOMEM); + } /* - * Start the timeout routine. + * Print a little information about the controller. */ - sc->amr_timeout = timeout(amr_periodic, sc, hz); + amr_describe_controller(sc); + debug(2, "attach complete"); return(0); } /******************************************************************************** * Locate disk resources and attach children to them. */ -void -amr_startup(struct amr_softc *sc) +static void +amr_startup(void *arg) { + struct amr_softc *sc = (struct amr_softc *)arg; struct amr_logdrive *dr; int i, error; - debug("called"); + debug_called(1); + + /* pull ourselves off the intrhook chain */ + config_intrhook_disestablish(&sc->amr_ich); /* get up-to-date drive information */ if (amr_query_controller(sc)) { - device_printf(sc->amr_dev, "couldn't scan controller for drives\n"); + device_printf(sc->amr_dev, "can't scan controller for drives\n"); return; } @@ -475,158 +292,48 @@ amr_startup(struct amr_softc *sc) /* interrupts will be enabled before we do anything more */ sc->amr_state |= AMR_STATE_INTEN; -} - -/******************************************************************************** - * Disconnect from the controller completely, in preparation for unload. - */ -int -amr_detach(device_t dev) -{ - struct amr_softc *sc = device_get_softc(dev); - int error; - - debug("called"); - - if (sc->amr_state & AMR_STATE_OPEN) - return(EBUSY); - - if ((error = amr_shutdown(dev))) - return(error); - - amr_free(sc); /* - * Deregister the control device on last detach. + * Start the timeout routine. */ - if (--cdev_registered == 0) - cdevsw_remove(&amr_cdevsw); +/* sc->amr_timeout = timeout(amr_periodic, sc, hz);*/ - return(0); + return; } -/******************************************************************************** - * Bring the controller down to a dormant state and detach all child devices. - * - * This function is called before detach, system shutdown, or before performing - * an operation which may add or delete system disks. (Call amr_startup to - * resume normal operation.) - * - * Note that we can assume that the bioq on the controller is empty, as we won't - * allow shutdown if any device is open. +/******************************************************************************* + * Free resources associated with a controller instance */ -int -amr_shutdown(device_t dev) +void +amr_free(struct amr_softc *sc) { - struct amr_softc *sc = device_get_softc(dev); - struct amrd_softc *ad; - int i, s, error; - - debug("called"); + struct amr_command_cluster *acc; - s = splbio(); - error = 0; - - /* assume we're going to shut down */ - sc->amr_state |= AMR_STATE_SHUTDOWN; - for (i = 0; i < AMR_MAXLD; i++) { - if (sc->amr_drive[i].al_disk != 0) { - ad = device_get_softc(sc->amr_drive[i].al_disk); - if (ad->amrd_flags & AMRD_OPEN) { /* drive is mounted, abort shutdown */ - sc->amr_state &= ~AMR_STATE_SHUTDOWN; - device_printf(sc->amr_drive[i].al_disk, "still open, can't shutdown\n"); - error = EBUSY; - goto out; - } - } - } +#ifdef AMR_SCSI_PASSTHROUGH + /* detach from CAM */ + amr_cam_detach(sc); +#endif - /* flush controller */ - device_printf(sc->amr_dev, "flushing cache..."); - if (amr_flush(sc)) { - printf("failed\n"); - } else { - printf("done\n"); - } + /* cancel status timeout */ + untimeout(amr_periodic, sc, sc->amr_timeout); - /* delete all our child devices */ - for (i = 0; i < AMR_MAXLD; i++) { - if (sc->amr_drive[i].al_disk != 0) { - if ((error = device_delete_child(sc->amr_dev, sc->amr_drive[i].al_disk)) != 0) - goto out; - sc->amr_drive[i].al_disk = 0; - } + /* throw away any command buffers */ + while ((acc = TAILQ_FIRST(&sc->amr_cmd_clusters)) != NULL) { + TAILQ_REMOVE(&sc->amr_cmd_clusters, acc, acc_link); + amr_freecmd_cluster(acc); } - - out: - splx(s); - return(error); -} - -/******************************************************************************** - * Bring the controller to a quiescent state, ready for system suspend. - */ -int -amr_suspend(device_t dev) -{ - struct amr_softc *sc = device_get_softc(dev); - - debug("called"); - - sc->amr_state |= AMR_STATE_SUSPEND; - - /* flush controller */ - device_printf(sc->amr_dev, "flushing cache..."); - printf("%s\n", amr_flush(sc) ? "failed" : "done"); - - return(0); -} - -/******************************************************************************** - * Bring the controller back to a state ready for operation. - */ -int -amr_resume(device_t dev) -{ - struct amr_softc *sc = device_get_softc(dev); - - debug("called"); - - sc->amr_state &= ~AMR_STATE_SUSPEND; - - return(0); } /******************************************************************************* - * Take an interrupt, or be poked by other code to look for interrupt-worthy - * status. - */ -void -amr_intr(void *arg) -{ - struct amr_softc *sc = (struct amr_softc *)arg; - - debug("called"); - - /* collect finished commands, queue anything waiting */ - amr_done(sc); -}; - -/******************************************************************************* - * Receive a buf structure from a child device and queue it on a particular + * Receive a bio structure from a child device and queue it on a particular * disk resource, then poke the disk resource to start as much work as it can. */ int -amr_submit_buf(struct amr_softc *sc, struct bio *bp) +amr_submit_bio(struct amr_softc *sc, struct bio *bio) { - int s; - - debug("called"); + debug_called(2); - s = splbio(); - bioq_insert_tail(&sc->amr_bioq, bp); - splx(s); - sc->amr_waitbufs++; + amr_enqueue_bio(sc, bio); amr_startio(sc); return(0); } @@ -640,6 +347,8 @@ amr_open(dev_t dev, int flags, int fmt, struct proc *p) int unit = minor(dev); struct amr_softc *sc = devclass_get_softc(amr_devclass, unit); + debug_called(1); + sc->amr_state |= AMR_STATE_OPEN; return(0); } @@ -653,6 +362,8 @@ amr_close(dev_t dev, int flags, int fmt, struct proc *p) int unit = minor(dev); struct amr_softc *sc = devclass_get_softc(amr_devclass, unit); + debug_called(1); + sc->amr_state &= ~AMR_STATE_OPEN; return (0); } @@ -663,21 +374,123 @@ amr_close(dev_t dev, int flags, int fmt, struct proc *p) int amr_ioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) { - + struct amr_softc *sc = (struct amr_softc *)dev->si_drv1; + int *arg = (int *)addr; + struct amr_user_ioctl *au = (struct amr_user_ioctl *)addr; + struct amr_command *ac; + struct amr_mailbox_ioctl *mbi; + struct amr_passthrough *ap; + void *dp; + int error; + + debug_called(1); + + error = 0; + dp = NULL; + ap = NULL; + ac = NULL; switch(cmd) { - default: - return(ENOTTY); + + case AMR_IO_VERSION: + debug(1, "AMR_IO_VERSION"); + *arg = AMR_IO_VERSION_NUMBER; + break; + + case AMR_IO_COMMAND: + debug(1, "AMR_IO_COMMAND"); + /* handle inbound data buffer */ + if (au->au_length != 0) { + if ((dp = malloc(au->au_length, M_DEVBUF, M_WAITOK)) == NULL) { + error = ENOMEM; + break; + } + if ((error = copyin(au->au_buffer, dp, au->au_length)) != 0) + break; + } + + if ((ac = amr_alloccmd(sc)) == NULL) { + error = ENOMEM; + break; + } + + /* handle SCSI passthrough command */ + if (au->au_cmd[0] == AMR_CMD_PASS) { + if ((ap = malloc(sizeof(*ap), M_DEVBUF, M_WAITOK)) == NULL) { + error = ENOMEM; + break; + } + bzero(ap, sizeof(*ap)); + + /* copy cdb */ + ap->ap_cdb_length = au->au_cmd[2]; + bcopy(&au->au_cmd[3], &ap->ap_cdb[0], ap->ap_cdb_length); + + /* build passthrough */ + ap->ap_timeout = au->au_cmd[ap->ap_cdb_length + 3] & 0x07; + ap->ap_ars = (au->au_cmd[ap->ap_cdb_length + 3] & 0x08) ? 1 : 0; + ap->ap_islogical = (au->au_cmd[ap->ap_cdb_length + 3] & 0x80) ? 1 : 0; + ap->ap_logical_drive_no = au->au_cmd[ap->ap_cdb_length + 4]; + ap->ap_channel = au->au_cmd[ap->ap_cdb_length + 5]; + ap->ap_scsi_id = au->au_cmd[ap->ap_cdb_length + 6]; + ap->ap_request_sense_length = 14; + /* XXX what about the request-sense area? does the caller want it? */ + + /* build command */ + ac->ac_data = ap; + ac->ac_length = sizeof(*ap); + ac->ac_flags |= AMR_CMD_DATAOUT; + ac->ac_ccb_data = dp; + ac->ac_ccb_length = au->au_length; + if (au->au_direction & AMR_IO_READ) + ac->ac_flags |= AMR_CMD_CCB_DATAIN; + if (au->au_direction & AMR_IO_WRITE) + ac->ac_flags |= AMR_CMD_CCB_DATAOUT; + + ac->ac_mailbox.mb_command = AMR_CMD_PASS; + + } else { + /* direct command to controller */ + mbi = (struct amr_mailbox_ioctl *)&ac->ac_mailbox; + + /* copy pertinent mailbox items */ + mbi->mb_command = au->au_cmd[0]; + mbi->mb_channel = au->au_cmd[1]; + mbi->mb_param = au->au_cmd[2]; + mbi->mb_pad[0] = au->au_cmd[3]; + mbi->mb_drive = au->au_cmd[4]; + + /* build the command */ + ac->ac_data = dp; + ac->ac_length = au->au_length; + if (au->au_direction & AMR_IO_READ) + ac->ac_flags |= AMR_CMD_DATAIN; + if (au->au_direction & AMR_IO_WRITE) + ac->ac_flags |= AMR_CMD_DATAOUT; + } + + /* run the command */ + if ((error = amr_wait_command(ac)) != 0) + break; + + /* copy out data and set status */ + if (au->au_length != 0) + error = copyout(dp, au->au_buffer, au->au_length); + au->au_status = ac->ac_status; + break; + + default: + debug(1, "unknown ioctl 0x%lx", cmd); + error = ENOIOCTL; + break; } -} -/******************************************************************************** - * Handle operations requested by a drive connected to this controller. - */ -int -amr_submit_ioctl(struct amr_softc *sc, struct amr_logdrive *drive, u_long cmd, - caddr_t addr, int32_t flag, struct proc *p) -{ - return(ENOTTY); + if (dp != NULL) + free(dp, M_DEVBUF); + if (ap != NULL) + free(ap, M_DEVBUF); + if (ac != NULL) + amr_releasecmd(ac); + return(error); } /******************************************************************************** @@ -693,46 +506,18 @@ static void amr_periodic(void *data) { struct amr_softc *sc = (struct amr_softc *)data; - int s, i; - debug("called"); + debug_called(2); -#if 0 - /* - * XXX this is basically wrong - returning a command that's wedged - * leaves us vulnerable to the controller later completing the command - * and overwriting memory that may have subsequently been reused. - */ - - /* - * Check for commands that are massively late. This will need to be - * revisited if/when we deal with eg. device format commands. - * The 30 second value is entirely arbitrary. - */ - s = splbio(); - if (sc->amr_busycmdcount > 0) { - for (i = 0; i < AMR_MAXCMD; i++) { - /* - * If the command has been busy for more than 30 seconds, declare it - * wedged and retire it with an error. - */ - if ((sc->amr_busycmd[i] != NULL) && - (sc->amr_busycmd[i]->ac_status == AMR_STATUS_BUSY) && - ((sc->amr_busycmd[i]->ac_stamp + 30) < time_second)) { - device_printf(sc->amr_dev, "command %d wedged after 30 seconds\n", i); - sc->amr_busycmd[i]->ac_status = AMR_STATUS_WEDGED; - amr_completeio(sc->amr_busycmd[i]); - } - } - } - splx(s); -#endif + /* XXX perform periodic status checks here */ + + /* compensate for missed interrupts */ + amr_done(sc); /* reschedule */ sc->amr_timeout = timeout(amr_periodic, sc, hz); } - /******************************************************************************** ******************************************************************************** Command Wrappers @@ -745,61 +530,89 @@ amr_periodic(void *data) static int amr_query_controller(struct amr_softc *sc) { - void *buf; - int i; + struct amr_enquiry3 *aex; + struct amr_prodinfo *ap; + struct amr_enquiry *ae; + int ldrv; - /* try to issue an ENQUIRY3 command */ - if ((buf = amr_enquiry(sc, 2048, AMR_CMD_CONFIG, AMR_CONFIG_ENQ3, - AMR_CONFIG_ENQ3_SOLICITED_FULL)) == NULL) { + /* + * If we haven't found the real limit yet, let us have a couple of commands in + * order to be able to probe. + */ + if (sc->amr_maxio == 0) + sc->amr_maxio = 2; - struct amr_enquiry *ae; + /* + * Try to issue an ENQUIRY3 command + */ + if ((aex = amr_enquiry(sc, 2048, AMR_CMD_CONFIG, AMR_CONFIG_ENQ3, + AMR_CONFIG_ENQ3_SOLICITED_FULL)) != NULL) { - /* failed, try the old ENQUIRY command */ - if ((ae = (struct amr_enquiry *)amr_enquiry(sc, 2048, AMR_CMD_ENQUIRY, 0, 0)) == NULL) { - device_printf(sc->amr_dev, "could not obtain configuration data from controller\n"); - return(1); - } - /* first-time enquiry? */ - if (sc->amr_maxdrives == 0) { - device_printf(sc->amr_dev, "firmware %.4s bios %.4s %dMB memory\n", - ae->ae_adapter.aa_firmware, ae->ae_adapter.aa_bios, - ae->ae_adapter.aa_memorysize); + /* + * Fetch current state of logical drives. + */ + for (ldrv = 0; ldrv < aex->ae_numldrives; ldrv++) { + sc->amr_drive[ldrv].al_size = aex->ae_drivesize[ldrv]; + sc->amr_drive[ldrv].al_state = aex->ae_drivestate[ldrv]; + sc->amr_drive[ldrv].al_properties = aex->ae_driveprop[ldrv]; + debug(2, " drive %d: %d state %x properties %x\n", ldrv, sc->amr_drive[ldrv].al_size, + sc->amr_drive[ldrv].al_state, sc->amr_drive[ldrv].al_properties); } - sc->amr_maxdrives = 8; + free(aex, M_DEVBUF); - /* - * Cap the maximum number of outstanding I/Os. AMI's Linux driver doesn't trust - * the controller's reported value, and lockups have been seen when we do. + /* + * Get product info for channel count. */ - sc->amr_maxio = imin(ae->ae_adapter.aa_maxio, AMR_LIMITCMD); - - for (i = 0; i < ae->ae_ldrv.al_numdrives; i++) { - sc->amr_drive[i].al_size = ae->ae_ldrv.al_size[i]; - sc->amr_drive[i].al_state = ae->ae_ldrv.al_state[i]; - sc->amr_drive[i].al_properties = ae->ae_ldrv.al_properties[i]; - debug(" drive %d: %d state %x properties %x\n", i, sc->amr_drive[i].al_size, - sc->amr_drive[i].al_state, sc->amr_drive[i].al_properties); + if ((ap = amr_enquiry(sc, 2048, AMR_CMD_CONFIG, AMR_CONFIG_PRODUCT_INFO, 0)) == NULL) { + device_printf(sc->amr_dev, "can't obtain product data from controller\n"); + return(1); } - for (; i < AMR_MAXLD; i++) - sc->amr_drive[i].al_size = 0xffffffff; - free(ae, M_DEVBUF); + sc->amr_maxdrives = 40; + sc->amr_maxchan = ap->ap_nschan; + sc->amr_maxio = ap->ap_maxio; + sc->amr_type |= AMR_TYPE_40LD; + free(ap, M_DEVBUF); + } else { + + /* failed, try the 8LD ENQUIRY commands */ + if ((ae = (struct amr_enquiry *)amr_enquiry(sc, 2048, AMR_CMD_EXT_ENQUIRY2, 0, 0)) == NULL) { + if ((ae = (struct amr_enquiry *)amr_enquiry(sc, 2048, AMR_CMD_ENQUIRY, 0, 0)) == NULL) { + device_printf(sc->amr_dev, "can't obtain configuration data from controller\n"); + return(1); + } + ae->ae_signature = 0; + } + /* - * The "40LD" (40 logical drive support) firmware is mentioned in the Linux - * driver, but no adapters from AMI appear to support it. + * Fetch current state of logical drives. */ - free(buf, M_DEVBUF); - sc->amr_maxdrives = 40; - - /* get static product info */ - if ((buf = amr_enquiry(sc, 2048, AMR_CMD_CONFIG, AMR_CONFIG_PRODINFO, 0)) == NULL) { - device_printf(sc->amr_dev, "controller supports 40ld but CONFIG_PRODINFO failed\n"); - return(1); + for (ldrv = 0; ldrv < ae->ae_ldrv.al_numdrives; ldrv++) { + sc->amr_drive[ldrv].al_size = ae->ae_ldrv.al_size[ldrv]; + sc->amr_drive[ldrv].al_state = ae->ae_ldrv.al_state[ldrv]; + sc->amr_drive[ldrv].al_properties = ae->ae_ldrv.al_properties[ldrv]; + debug(2, " drive %d: %d state %x properties %x\n", ldrv, sc->amr_drive[ldrv].al_size, + sc->amr_drive[ldrv].al_state, sc->amr_drive[ldrv].al_properties); } - free(buf, M_DEVBUF); - device_printf(sc->amr_dev, "40LD firmware unsupported; send controller to msmith@freebsd.org\n"); - return(1); + + sc->amr_maxdrives = 8; + sc->amr_maxchan = ae->ae_adapter.aa_channels; + sc->amr_maxio = ae->ae_adapter.aa_maxio; + free(ae, M_DEVBUF); } + + /* + * Mark remaining drives as unused. + */ + for (; ldrv < AMR_MAXLD; ldrv++) + sc->amr_drive[ldrv].al_size = 0xffffffff; + + /* + * Cap the maximum number of outstanding I/Os. AMI's Linux driver doesn't trust + * the controller's reported value, and lockups have been seen when we do. + */ + sc->amr_maxio = imin(sc->amr_maxio, AMR_LIMITCMD); + return(0); } @@ -814,7 +627,7 @@ amr_enquiry(struct amr_softc *sc, size_t bufsize, u_int8_t cmd, u_int8_t cmdsub, u_int8_t *mbox; int error; - debug("called"); + debug_called(1); error = 1; result = NULL; @@ -825,22 +638,18 @@ amr_enquiry(struct amr_softc *sc, size_t bufsize, u_int8_t cmd, u_int8_t cmdsub, /* allocate the response structure */ if ((result = malloc(bufsize, M_DEVBUF, M_NOWAIT)) == NULL) goto out; - /* get a command slot */ + /* set command flags */ ac->ac_flags |= AMR_CMD_PRIORITY | AMR_CMD_DATAOUT; - if (amr_getslot(ac)) - goto out; - /* map the command so the controller can see it */ + /* point the command at our data */ ac->ac_data = result; ac->ac_length = bufsize; - amr_mapcmd(ac); /* build the command proper */ mbox = (u_int8_t *)&ac->ac_mailbox; /* XXX want a real structure for this? */ mbox[0] = cmd; mbox[2] = cmdsub; mbox[3] = cmdqual; - ac->ac_mailbox.mb_physaddr = ac->ac_dataphys; /* can't assume that interrupts are going to work here, so play it safe */ if (amr_poll_command(ac)) @@ -860,7 +669,7 @@ amr_enquiry(struct amr_softc *sc, size_t bufsize, u_int8_t cmd, u_int8_t cmdsub, /******************************************************************************** * Flush the controller's internal cache, return status. */ -static int +int amr_flush(struct amr_softc *sc) { struct amr_command *ac; @@ -870,10 +679,8 @@ amr_flush(struct amr_softc *sc) error = 1; if ((ac = amr_alloccmd(sc)) == NULL) goto out; - /* get a command slot */ + /* set command flags */ ac->ac_flags |= AMR_CMD_PRIORITY | AMR_CMD_DATAOUT; - if (amr_getslot(ac)) - goto out; /* build the command proper */ ac->ac_mailbox.mb_command = AMR_CMD_FLUSH; @@ -890,91 +697,44 @@ amr_flush(struct amr_softc *sc) } /******************************************************************************** - * 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. + * Try to find I/O work for the controller from one or more of the work queues. * - * We avoid running at splbio() whenever possible. + * We make the assumption that if the controller is not ready to take a command + * at some given time, it will generate an interrupt at some later time when + * it is. */ -static void +void amr_startio(struct amr_softc *sc) { struct amr_command *ac; - struct amrd_softc *amrd; - struct bio *bp; - int blkcount; - int driveno; - int cmd; - int s; - - /* avoid reentrancy */ - if (amr_lock_tas(sc, AMR_LOCK_STARTING)) - return; /* spin until something prevents us from doing any work */ - s = splbio(); for (;;) { - /* see if there's work to be done */ - if ((bp = bioq_first(&sc->amr_bioq)) == NULL) - break; - /* get a command */ - if ((ac = amr_alloccmd(sc)) == NULL) - break; - /* get a slot for the command */ - if (amr_getslot(ac) != 0) { - amr_releasecmd(ac); - break; - } - /* get the buf containing our work */ - bioq_remove(&sc->amr_bioq, bp); - sc->amr_waitbufs--; - splx(s); - - /* connect the buf to the command */ - ac->ac_complete = amr_completeio; - ac->ac_private = bp; - ac->ac_data = bp->bio_data; - ac->ac_length = bp->bio_bcount; - if (bp->bio_cmd == BIO_READ) { - ac->ac_flags |= AMR_CMD_DATAIN; - cmd = AMR_CMD_LREAD; - } else { - ac->ac_flags |= AMR_CMD_DATAOUT; - cmd = AMR_CMD_LWRITE; - } - - /* map the command so the controller can work with it */ - amr_mapcmd(ac); - - /* build a suitable I/O command (assumes 512-byte rounded transfers) */ - amrd = (struct amrd_softc *)bp->bio_dev->si_drv1; - driveno = amrd->amrd_drive - sc->amr_drive; - blkcount = (bp->bio_bcount + AMR_BLKSIZE - 1) / AMR_BLKSIZE; + /* try to get a ready command */ + ac = amr_dequeue_ready(sc); - if ((bp->bio_pblkno + blkcount) > sc->amr_drive[driveno].al_size) - device_printf(sc->amr_dev, "I/O beyond end of unit (%u,%d > %u)\n", - bp->bio_pblkno, blkcount, sc->amr_drive[driveno].al_size); + /* if that failed, build a command from a bio */ + if (ac == NULL) + (void)amr_bio_command(sc, &ac); - /* - * Build the I/O command. - */ - ac->ac_mailbox.mb_command = cmd; - ac->ac_mailbox.mb_blkcount = blkcount; - ac->ac_mailbox.mb_lba = bp->bio_pblkno; - ac->ac_mailbox.mb_physaddr = ac->ac_sgphys; - ac->ac_mailbox.mb_drive = driveno; - ac->ac_mailbox.mb_nsgelem = ac->ac_nsgent; - - /* try to give command to controller */ - if (amr_start(ac) != 0) { - /* fail the command */ - ac->ac_status = AMR_STATUS_WEDGED; - amr_completeio(ac); +#ifdef AMR_SCSI_PASSTHROUGH + /* if that failed, build a command from a ccb */ + if (ac == NULL) + (void)amr_cam_command(sc, &ac); +#endif + + /* if we don't have anything to do, give up */ + if (ac == NULL) + break; + + /* try to give the command to the controller; if this fails save it for later and give up */ + if (amr_start(ac)) { + debug(2, "controller busy, command deferred"); + amr_requeue_ready(ac); /* XXX schedule retry very soon? */ + break; } - s = splbio(); } - splx(s); - amr_lock_clr(sc, AMR_LOCK_STARTING); } /******************************************************************************** @@ -984,36 +744,16 @@ static void amr_completeio(struct amr_command *ac) { struct amr_softc *sc = ac->ac_sc; - struct bio *bp = (struct bio *)ac->ac_private; - int notify, release; - - notify = 1; - release = 1; if (ac->ac_status != AMR_STATUS_SUCCESS) { /* could be more verbose here? */ - bp->bio_error = EIO; - bp->bio_flags |= BIO_ERROR; + ac->ac_bio->bio_error = EIO; + ac->ac_bio->bio_flags |= BIO_ERROR; - switch(ac->ac_status) { - /* XXX need more information on I/O error reasons */ - case AMR_STATUS_LATE: - notify = 0; /* we've already notified the parent */ - break; - - case AMR_STATUS_WEDGED: - release = 0; /* the command is still outstanding, we can't release */ - break; - - default: - device_printf(sc->amr_dev, "I/O error - %x\n", ac->ac_status); - amr_printcommand(ac); - break; - } + device_printf(sc->amr_dev, "I/O error - 0x%x\n", ac->ac_status); +/* amr_printcommand(ac);*/ } - if (release) - amr_releasecmd(ac); - if (notify) - amrd_intr(bp); + amrd_intr(ac->ac_bio); + amr_releasecmd(ac); } /******************************************************************************** @@ -1023,6 +763,70 @@ amr_completeio(struct amr_command *ac) ********************************************************************************/ /******************************************************************************** + * Convert a bio off the top of the bio queue into a command. + */ +static int +amr_bio_command(struct amr_softc *sc, struct amr_command **acp) +{ + struct amr_command *ac; + struct amrd_softc *amrd; + struct bio *bio; + int error; + int blkcount; + int driveno; + int cmd; + + ac = NULL; + error = 0; + + /* get a bio to work on */ + if ((bio = amr_dequeue_bio(sc)) == NULL) + goto out; + + /* get a command */ + if ((ac = amr_alloccmd(sc)) == NULL) { + error = ENOMEM; + goto out; + } + + /* connect the bio to the command */ + ac->ac_complete = amr_completeio; + ac->ac_bio = bio; + ac->ac_data = bio->bio_data; + ac->ac_length = bio->bio_bcount; + if (BIO_IS_READ(bio)) { + ac->ac_flags |= AMR_CMD_DATAIN; + cmd = AMR_CMD_LREAD; + } else { + ac->ac_flags |= AMR_CMD_DATAOUT; + cmd = AMR_CMD_LWRITE; + } + amrd = (struct amrd_softc *)bio->bio_dev->si_drv1; + driveno = amrd->amrd_drive - sc->amr_drive; + blkcount = (bio->bio_bcount + AMR_BLKSIZE - 1) / AMR_BLKSIZE; + + ac->ac_mailbox.mb_command = cmd; + ac->ac_mailbox.mb_blkcount = blkcount; + ac->ac_mailbox.mb_lba = bio->bio_pblkno; + ac->ac_mailbox.mb_drive = driveno; + /* we fill in the s/g related data when the command is mapped */ + + if ((bio->bio_pblkno + blkcount) > sc->amr_drive[driveno].al_size) + device_printf(sc->amr_dev, "I/O beyond end of unit (%u,%d > %u)\n", + bio->bio_pblkno, blkcount, sc->amr_drive[driveno].al_size); + +out: + if (error != 0) { + if (ac != NULL) + amr_releasecmd(ac); + if (bio != NULL) /* this breaks ordering... */ + amr_enqueue_bio(sc, bio); + } + *acp = ac; + return(error); +} + +/******************************************************************************** * Take a command, submit it to the controller and sleep until it completes * or fails. Interrupts must be enabled, returns nonzero on error. */ @@ -1032,21 +836,21 @@ amr_wait_command(struct amr_command *ac) struct amr_softc *sc = ac->ac_sc; int error, count; - debug("called"); + debug_called(1); ac->ac_complete = NULL; - ac->ac_private = ac; + ac->ac_flags |= AMR_CMD_SLEEP; if ((error = amr_start(ac)) != 0) return(error); count = 0; /* XXX better timeout? */ - while ((ac->ac_status == AMR_STATUS_BUSY) && (count < 30)) { - tsleep(ac->ac_private, PRIBIO | PCATCH, "amrwcmd", hz); + while ((ac->ac_flags & AMR_CMD_BUSY) && (count < 30)) { + tsleep(ac, PRIBIO | PCATCH, "amrwcmd", hz); } if (ac->ac_status != 0) { - device_printf(sc->amr_dev, "I/O error 0x%x\n", ac->ac_status); + device_printf(sc->amr_dev, "I/O error - 0x%x\n", ac->ac_status); return(EIO); } return(0); @@ -1060,12 +864,11 @@ static int amr_poll_command(struct amr_command *ac) { struct amr_softc *sc = ac->ac_sc; - int error, count, s; + int error, count; - debug("called"); + debug_called(2); ac->ac_complete = NULL; - ac->ac_private = NULL; if ((error = amr_start(ac)) != 0) return(error); @@ -1076,62 +879,63 @@ amr_poll_command(struct amr_command *ac) * Note that the timeout here is somewhat arbitrary. */ amr_done(sc); - } while ((ac->ac_status == AMR_STATUS_BUSY) && (count++ < 100000)); - s = splbio(); - if (ac->ac_status != AMR_STATUS_BUSY) { - TAILQ_REMOVE(&sc->amr_work, ac, ac_link); - sc->amr_workcount--; + DELAY(1000); + } while ((ac->ac_flags & AMR_CMD_BUSY) && (count++ < 1000)); + if (!(ac->ac_flags & AMR_CMD_BUSY)) { error = 0; } else { - /* take the command out of the busy list, mark slot as bogus */ - sc->amr_busycmd[ac->ac_slot] = (struct amr_command *)sc; + /* XXX the slot is now marked permanently busy */ error = EIO; - device_printf(sc->amr_dev, "I/O error 0x%x\n", ac->ac_status); + device_printf(sc->amr_dev, "polled command timeout\n"); } - splx(s); return(error); } /******************************************************************************** - * Get a free command slot. + * Get a free command slot for a command if it doesn't already have one. + * + * May be safely called multiple times for a given command. */ static int amr_getslot(struct amr_command *ac) { struct amr_softc *sc = ac->ac_sc; - int s, slot, limit; + int s, slot, limit, error; + + debug_called(3); + + /* if the command already has a slot, don't try to give it another one */ + if (ac->ac_slot != 0) + return(0); - debug("called"); - /* enforce slot usage limit */ limit = (ac->ac_flags & AMR_CMD_PRIORITY) ? sc->amr_maxio : sc->amr_maxio - 4; - if (sc->amr_busycmdcount > limit) + if (sc->amr_busyslots > limit) return(EBUSY); /* - * Allocate a slot + * Allocate a slot. XXX linear scan is slow */ + error = EBUSY; s = splbio(); for (slot = 0; slot < sc->amr_maxio; slot++) { - if (sc->amr_busycmd[slot] == NULL) + if (sc->amr_busycmd[slot] == NULL) { + sc->amr_busycmd[slot] = ac; + sc->amr_busyslots++; + ac->ac_slot = slot; + error = 0; break; - } - if (slot < sc->amr_maxio) { - sc->amr_busycmdcount++; - sc->amr_busycmd[slot] = ac; + } } splx(s); - /* out of slots? */ - if (slot >= sc->amr_maxio) - return(EBUSY); - - ac->ac_slot = slot; - return(0); + return(error); } /******************************************************************************** - * Map/unmap (ac)'s data in the controller's addressable space. + * Map/unmap (ac)'s data in the controller's addressable space as required. + * + * These functions may be safely called multiple times on a given command. */ static void amr_setup_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error) @@ -1141,20 +945,55 @@ amr_setup_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error) struct amr_sgentry *sg; int i; - debug("called"); + debug_called(3); /* get base address of s/g table */ sg = sc->amr_sgtable + (ac->ac_slot * AMR_NSEG); - /* save s/g table information in command */ - ac->ac_nsgent = nsegments; - ac->ac_sgphys = sc->amr_sgbusaddr + (ac->ac_slot * AMR_NSEG * sizeof(struct amr_sgentry)); + /* save data physical address */ ac->ac_dataphys = segs[0].ds_addr; - /* populate s/g table */ + /* decide whether we need to populate the s/g table */ + if (nsegments < 2) { + ac->ac_mailbox.mb_nsgelem = 0; + ac->ac_mailbox.mb_physaddr = ac->ac_dataphys; + } else { + ac->ac_mailbox.mb_nsgelem = nsegments; + ac->ac_mailbox.mb_physaddr = sc->amr_sgbusaddr + (ac->ac_slot * AMR_NSEG * sizeof(struct amr_sgentry)); + for (i = 0; i < nsegments; i++, sg++) { + sg->sg_addr = segs[i].ds_addr; + sg->sg_count = segs[i].ds_len; + } + } +} + +static void +amr_setup_ccbmap(void *arg, bus_dma_segment_t *segs, int nsegments, int error) +{ + struct amr_command *ac = (struct amr_command *)arg; + struct amr_softc *sc = ac->ac_sc; + struct amr_sgentry *sg; + struct amr_passthrough *ap = (struct amr_passthrough *)ac->ac_data; + int i; + + /* get base address of s/g table */ + sg = sc->amr_sgtable + (ac->ac_slot * AMR_NSEG); + + /* save s/g table information in passthrough */ + ap->ap_no_sg_elements = nsegments; + ap->ap_data_transfer_address = sc->amr_sgbusaddr + (ac->ac_slot * AMR_NSEG * sizeof(struct amr_sgentry)); + + /* save pointer to passthrough in command XXX is this already done above? */ + ac->ac_mailbox.mb_physaddr = ac->ac_dataphys; + + debug(2, "slot %d %d segments at 0x%x, passthrough at 0x%x", ac->ac_slot, + ap->ap_no_sg_elements, ap->ap_data_transfer_address, ac->ac_dataphys); + + /* populate s/g table (overwrites previous call which mapped the passthrough) */ for (i = 0; i < nsegments; i++, sg++) { sg->sg_addr = segs[i].ds_addr; sg->sg_count = segs[i].ds_len; + debug(2, " %d: 0x%x/%d", i, sg->sg_addr, sg->sg_count); } } @@ -1163,18 +1002,30 @@ amr_mapcmd(struct amr_command *ac) { struct amr_softc *sc = ac->ac_sc; - debug("called"); + debug_called(2); - /* if the command involves data at all */ - if (ac->ac_data != NULL) { - - /* map the data buffer into bus space and build the s/g list */ - bus_dmamap_load(sc->amr_buffer_dmat, ac->ac_dmamap, ac->ac_data, ac->ac_length, - amr_setup_dmamap, ac, 0); - if (ac->ac_flags & AMR_CMD_DATAIN) - bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_PREREAD); - if (ac->ac_flags & AMR_CMD_DATAOUT) - bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_PREWRITE); + /* if the command involves data at all, and hasn't been mapped */ + if (!(ac->ac_flags & AMR_CMD_MAPPED)) { + + if (ac->ac_data != NULL) { + /* map the data buffers into bus space and build the s/g list */ + bus_dmamap_load(sc->amr_buffer_dmat, ac->ac_dmamap, ac->ac_data, ac->ac_length, + amr_setup_dmamap, ac, 0); + if (ac->ac_flags & AMR_CMD_DATAIN) + bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_PREREAD); + if (ac->ac_flags & AMR_CMD_DATAOUT) + bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_PREWRITE); + } + + if (ac->ac_ccb_data != NULL) { + bus_dmamap_load(sc->amr_buffer_dmat, ac->ac_ccb_dmamap, ac->ac_ccb_data, ac->ac_ccb_length, + amr_setup_ccbmap, ac, 0); + if (ac->ac_flags & AMR_CMD_CCB_DATAIN) + bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_ccb_dmamap, BUS_DMASYNC_PREREAD); + if (ac->ac_flags & AMR_CMD_CCB_DATAOUT) + bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_ccb_dmamap, BUS_DMASYNC_PREWRITE); + } + ac->ac_flags |= AMR_CMD_MAPPED; } } @@ -1183,22 +1034,33 @@ amr_unmapcmd(struct amr_command *ac) { struct amr_softc *sc = ac->ac_sc; - debug("called"); + debug_called(2); - /* if the command involved data at all */ - if (ac->ac_data != NULL) { - - if (ac->ac_flags & AMR_CMD_DATAIN) - bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_POSTREAD); - if (ac->ac_flags & AMR_CMD_DATAOUT) - bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_POSTWRITE); + /* if the command involved data at all and was mapped */ + if (ac->ac_flags & AMR_CMD_MAPPED) { + + if (ac->ac_data != NULL) { + if (ac->ac_flags & AMR_CMD_DATAIN) + bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_POSTREAD); + if (ac->ac_flags & AMR_CMD_DATAOUT) + bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_dmamap, BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->amr_buffer_dmat, ac->ac_dmamap); + } - bus_dmamap_unload(sc->amr_buffer_dmat, ac->ac_dmamap); + if (ac->ac_ccb_data != NULL) { + if (ac->ac_flags & AMR_CMD_CCB_DATAIN) + bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_ccb_dmamap, BUS_DMASYNC_POSTREAD); + if (ac->ac_flags & AMR_CMD_CCB_DATAOUT) + bus_dmamap_sync(sc->amr_buffer_dmat, ac->ac_ccb_dmamap, BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->amr_buffer_dmat, ac->ac_ccb_dmamap); + } + ac->ac_flags &= ~AMR_CMD_MAPPED; } } /******************************************************************************** - * Take a command and give it to the controller. + * Take a command and give it to the controller, returns 0 if successful, or + * EBUSY if the command should be retried later. */ static int amr_start(struct amr_command *ac) @@ -1206,7 +1068,24 @@ amr_start(struct amr_command *ac) struct amr_softc *sc = ac->ac_sc; int done, s, i; - debug("called"); + debug_called(2); + + /* mark command as busy so that polling consumer can tell */ + ac->ac_flags |= AMR_CMD_BUSY; + + /* get a command slot (freed in amr_done) */ + if (amr_getslot(ac)) + return(EBUSY); + + /* now we have a slot, we can map the command (unmapped in amr_complete) */ + amr_mapcmd(ac); + + /* mark the new mailbox we are going to copy in as busy */ + ac->ac_mailbox.mb_busy = 1; + + /* clear the poll/ack fields in the mailbox */ + sc->amr_mailbox->mb_poll = 0; + sc->amr_mailbox->mb_ack = 0; /* * Save the slot number so that we can locate this command when complete. @@ -1214,54 +1093,51 @@ amr_start(struct amr_command *ac) */ ac->ac_mailbox.mb_ident = ac->ac_slot + 1; - /* set the busy flag when we copy the mailbox in */ - ac->ac_mailbox.mb_busy = 1; - - /* set impossible status so that a woken sleeper can tell the command is busy */ - ac->ac_status = AMR_STATUS_BUSY; - /* - * Spin waiting for the mailbox, give up after ~1 second. + * Spin waiting for the mailbox, give up after ~1 second. We expect the + * controller to be able to handle our I/O. + * + * XXX perhaps we should wait for less time, and count on the deferred command + * handling to deal with retries? */ - debug("wait for mailbox"); + debug(2, "wait for mailbox"); for (i = 10000, done = 0; (i > 0) && !done; i--) { s = splbio(); /* is the mailbox free? */ if (sc->amr_mailbox->mb_busy == 0) { - debug("got mailbox"); + debug(2, "got mailbox"); sc->amr_mailbox64->mb64_segment = 0; - bcopy(&ac->ac_mailbox, sc->amr_mailbox, AMR_MBOX_CMDSIZE); - sc->amr_submit_command(sc); + bcopy(&ac->ac_mailbox, (void *)(uintptr_t)(volatile void *)sc->amr_mailbox, AMR_MBOX_CMDSIZE); done = 1; - sc->amr_workcount++; - TAILQ_INSERT_TAIL(&sc->amr_work, ac, ac_link); - /* not free, try to clean up while we wait */ + /* not free, spin waiting */ } else { - debug("busy flag %x\n", sc->amr_mailbox->mb_busy); + debug(3, "busy flag %x\n", sc->amr_mailbox->mb_busy); /* this is somewhat ugly */ DELAY(100); } splx(s); /* drop spl to allow completion interrupts */ } - - /* command is enqueued? */ + + /* + * Now give the command to the controller + */ if (done) { - ac->ac_stamp = time_second; - debug("posted command"); + if (sc->amr_submit_command(sc)) { + /* the controller wasn't ready to take the command, forget that we tried to post it */ + sc->amr_mailbox->mb_busy = 0; + return(EBUSY); + } + debug(2, "posted command"); return(0); } /* - * The controller wouldn't take the command. Revoke the slot - * that the command was given and return with a bad status. + * The controller wouldn't take the command. Return the command as busy + * so that it is retried later. */ - sc->amr_busycmd[ac->ac_slot] = NULL; - device_printf(sc->amr_dev, "controller wedged (not taking commands)\n"); - ac->ac_status = AMR_STATUS_WEDGED; - amr_complete(sc); - return(EIO); + return(EBUSY); } /******************************************************************************** @@ -1269,20 +1145,19 @@ amr_start(struct amr_command *ac) * * Returns nonzero if any commands on the work queue were marked as completed. */ -static int +int amr_done(struct amr_softc *sc) { struct amr_command *ac; struct amr_mailbox mbox; - int i, idx, s, result; + int i, idx, result; - debug("called"); + debug_called(2); /* See if there's anything for us to do */ result = 0; /* loop collecting completed commands */ - s = splbio(); for (;;) { /* poll for a completed command's identifier and status */ if (sc->amr_get_work(sc, &mbox)) { @@ -1299,28 +1174,18 @@ amr_done(struct amr_softc *sc) /* pull the command from the busy index */ sc->amr_busycmd[idx] = NULL; - sc->amr_busycmdcount--; + sc->amr_busyslots--; - /* aborted command? */ - if (ac == (struct amr_command *)sc) { - device_printf(sc->amr_dev, "aborted command completed (%d)\n", idx); - sc->amr_busycmd[idx] = NULL; /* free the slot again */ - ac = NULL; - - /* wedged command? */ - } else if (ac->ac_status == AMR_STATUS_WEDGED) { - device_printf(sc->amr_dev, "wedged command completed (%d)\n", idx); - ac->ac_status = AMR_STATUS_LATE; - - /* completed normally, save status */ - } else { - ac->ac_status = mbox.mb_status; - debug("completed command with status %x", mbox.mb_status); - } + /* save status for later use */ + ac->ac_status = mbox.mb_status; + amr_enqueue_completed(ac); + debug(3, "completed command with status %x", mbox.mb_status); + } else { + device_printf(sc->amr_dev, "bad slot %d completed\n", idx); } } } else { - break; + break; /* no work */ } } @@ -1329,7 +1194,12 @@ amr_done(struct amr_softc *sc) amr_startio(sc); /* handle completion and timeouts */ - amr_complete(sc); +#if __FreeBSD_version >= 500005 + if (sc->amr_state & AMR_STATE_INTEN) + taskqueue_enqueue(taskqueue_swi, &sc->amr_task_complete); + else +#endif + amr_complete(sc, 0); return(result); } @@ -1338,61 +1208,38 @@ amr_done(struct amr_softc *sc) * Do completion processing on done commands on (sc) */ static void -amr_complete(struct amr_softc *sc) +amr_complete(void *context, int pending) { - struct amr_command *ac, *nc; - int s, count; - - debug("called"); - - if (amr_lock_tas(sc, AMR_LOCK_COMPLETING)) - return; + struct amr_softc *sc = (struct amr_softc *)context; + struct amr_command *ac; - s = splbio(); - count = 0; + debug_called(2); - /* scan the list of busy/done commands */ - ac = TAILQ_FIRST(&sc->amr_work); - while (ac != NULL) { - nc = TAILQ_NEXT(ac, ac_link); + /* pull completed commands off the queue */ + for (;;) { + ac = amr_dequeue_completed(sc); + if (ac == NULL) + break; - /* Command has been completed in some fashion */ - if (ac->ac_status != AMR_STATUS_BUSY) { + /* unmap the command's data buffer */ + amr_unmapcmd(ac); - /* unmap the command's data buffer */ - amr_unmapcmd(ac); + /* unbusy the command */ + ac->ac_flags &= ~AMR_CMD_BUSY; + + /* + * Is there a completion handler? + */ + if (ac->ac_complete != NULL) { + ac->ac_complete(ac); /* - * Is there a completion handler? + * Is someone sleeping on this one? */ - if (ac->ac_complete != NULL) { - - /* remove and give to completion handler */ - TAILQ_REMOVE(&sc->amr_work, ac, ac_link); - sc->amr_workcount--; - ac->ac_complete(ac); - - /* - * Is someone sleeping on this one? - */ - } else if (ac->ac_private != NULL) { - - /* remove and wake up */ - TAILQ_REMOVE(&sc->amr_work, ac, ac_link); - sc->amr_workcount--; - wakeup_one(ac->ac_private); - - /* - * Leave it for a polling caller. - */ - } else { - } + } else if (ac->ac_flags & AMR_CMD_SLEEP) { + wakeup(ac); } - ac = nc; } - splx(s); - - amr_lock_clr(sc, AMR_LOCK_COMPLETING); } /******************************************************************************** @@ -1406,76 +1253,84 @@ amr_complete(struct amr_softc *sc) * * This may return NULL in low-memory cases. * - * Note that using malloc() is expensive (the command buffer is << 1 page) but - * necessary if we are to be a loadable module before the zone allocator is fixed. - * * If possible, we recycle a command buffer that's been used before. - * - * XXX Note that command buffers are not cleaned out - it is the caller's - * responsibility to ensure that all required fields are filled in before - * using a buffer. */ -static struct amr_command * +struct amr_command * amr_alloccmd(struct amr_softc *sc) { struct amr_command *ac; - int error; - int s; - debug("called"); + debug_called(3); - s = splbio(); - if ((ac = TAILQ_FIRST(&sc->amr_freecmds)) != NULL) - TAILQ_REMOVE(&sc->amr_freecmds, ac, ac_link); - splx(s); - - /* allocate a new command buffer? */ + ac = amr_dequeue_free(sc); if (ac == NULL) { - ac = (struct amr_command *)malloc(sizeof(*ac), M_DEVBUF, M_NOWAIT); - if (ac != NULL) { - bzero(ac, sizeof(*ac)); - ac->ac_sc = sc; - error = bus_dmamap_create(sc->amr_buffer_dmat, 0, &ac->ac_dmamap); - if (error) { - free(ac, M_DEVBUF); - return(NULL); - } - } + amr_alloccmd_cluster(sc); + ac = amr_dequeue_free(sc); } + if (ac == NULL) + return(NULL); + + /* clear out significant fields */ + ac->ac_slot = 0; + ac->ac_status = 0; bzero(&ac->ac_mailbox, sizeof(struct amr_mailbox)); + ac->ac_flags = 0; + ac->ac_bio = NULL; + ac->ac_data = NULL; + ac->ac_ccb_data = NULL; + ac->ac_complete = NULL; return(ac); } /******************************************************************************** * Release a command buffer for recycling. - * - * 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 +void amr_releasecmd(struct amr_command *ac) { - int s; - - debug("called"); + debug_called(3); - s = splbio(); - TAILQ_INSERT_HEAD(&ac->ac_sc->amr_freecmds, ac, ac_link); - splx(s); + amr_enqueue_free(ac); } /******************************************************************************** - * Permanently discard a command buffer. + * Allocate a new command cluster and initialise it. */ -static void -amr_freecmd(struct amr_command *ac) +void +amr_alloccmd_cluster(struct amr_softc *sc) { - struct amr_softc *sc = ac->ac_sc; - - debug("called"); + struct amr_command_cluster *acc; + struct amr_command *ac; + int s, i; + + acc = malloc(AMR_CMD_CLUSTERSIZE, M_DEVBUF, M_NOWAIT); + if (acc != NULL) { + s = splbio(); + TAILQ_INSERT_TAIL(&sc->amr_cmd_clusters, acc, acc_link); + splx(s); + for (i = 0; i < AMR_CMD_CLUSTERCOUNT; i++) { + ac = &acc->acc_command[i]; + bzero(ac, sizeof(*ac)); + ac->ac_sc = sc; + if (!bus_dmamap_create(sc->amr_buffer_dmat, 0, &ac->ac_dmamap) && + !bus_dmamap_create(sc->amr_buffer_dmat, 0, &ac->ac_ccb_dmamap)) + amr_releasecmd(ac); + } + } +} - bus_dmamap_destroy(sc->amr_buffer_dmat, ac->ac_dmamap); - free(ac, M_DEVBUF); +/******************************************************************************** + * Free a command cluster + */ +void +amr_freecmd_cluster(struct amr_command_cluster *acc) +{ + struct amr_softc *sc = acc->acc_command[0].ac_sc; + int i; + + for (i = 0; i < AMR_CMD_CLUSTERCOUNT; i++) + bus_dmamap_destroy(sc->amr_buffer_dmat, acc->acc_command[i].ac_dmamap); + free(acc, M_DEVBUF); } /******************************************************************************** @@ -1487,27 +1342,26 @@ amr_freecmd(struct amr_command *ac) /******************************************************************************** * Tell the controller that the mailbox contains a valid command */ -static void +static int amr_quartz_submit_command(struct amr_softc *sc) { - debug("called"); + debug_called(3); - sc->amr_mailbox->mb_poll = 0; - sc->amr_mailbox->mb_ack = 0; - while(AMR_QGET_IDB(sc) & AMR_QIDB_SUBMIT) - ; /* XXX aiee! what if it dies? */ + if (AMR_QGET_IDB(sc) & AMR_QIDB_SUBMIT) + return(EBUSY); AMR_QPUT_IDB(sc, sc->amr_mailboxphys | AMR_QIDB_SUBMIT); + return(0); } -static void +static int amr_std_submit_command(struct amr_softc *sc) { - debug("called"); + debug_called(3); - /* XXX write barrier? */ - while (AMR_SGET_MBSTAT(sc) & AMR_SMBOX_BUSYFLAG) - ; /* XXX aiee! what if it dies? */ + if (AMR_SGET_MBSTAT(sc) & AMR_SMBOX_BUSYFLAG) + return(EBUSY); AMR_SPOST_COMMAND(sc); + return(0); } /******************************************************************************** @@ -1519,24 +1373,38 @@ amr_quartz_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave) { int s, worked; u_int32_t outd; - -/* debug("called"); */ + + debug_called(3); worked = 0; s = splbio(); /* work waiting for us? */ if ((outd = AMR_QGET_ODB(sc)) == AMR_QODB_READY) { - AMR_QPUT_ODB(sc, AMR_QODB_READY); /* save mailbox, which contains a list of completed commands */ - /* XXX read barrier? */ - bcopy(sc->amr_mailbox, mbsave, sizeof(*mbsave)); + bcopy((void *)(uintptr_t)(volatile void *)sc->amr_mailbox, mbsave, sizeof(*mbsave)); + + /* acknowledge interrupt */ + AMR_QPUT_ODB(sc, AMR_QODB_READY); /* acknowledge that we have the commands */ AMR_QPUT_IDB(sc, sc->amr_mailboxphys | AMR_QIDB_ACK); + +#if AMR_QUARTZ_GOFASTER + /* + * This waits for the controller to notice that we've taken the + * command from it. It's very inefficient, and we shouldn't do it, + * but if we remove this code, we stop completing commands under + * load. + * + * Peter J says we shouldn't do this. The documentation says we + * should. Who is right? + */ while(AMR_QGET_IDB(sc) & AMR_QIDB_ACK) ; /* XXX aiee! what if it dies? */ +#endif + worked = 1; /* got some work */ } @@ -1550,7 +1418,7 @@ amr_std_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave) int s, worked; u_int8_t istat; - debug("called"); + debug_called(3); worked = 0; s = splbio(); @@ -1561,8 +1429,7 @@ amr_std_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave) AMR_SPUT_ISTAT(sc, istat); /* ack interrupt status */ /* save mailbox, which contains a list of completed commands */ - /* XXX read barrier? */ - bcopy(sc->amr_mailbox, mbsave, sizeof(*mbsave)); + bcopy((void *)(uintptr_t)(volatile void *)sc->amr_mailbox, mbsave, sizeof(*mbsave)); AMR_SACK_INTERRUPT(sc); /* acknowledge we have the mailbox */ worked = 1; @@ -1576,12 +1443,6 @@ amr_std_get_work(struct amr_softc *sc, struct amr_mailbox *mbsave) * Notify the controller of the mailbox location. */ static void -amr_quartz_attach_mailbox(struct amr_softc *sc) -{ - /* Quartz is given the mailbox location when a command is submitted */ -} - -static void amr_std_attach_mailbox(struct amr_softc *sc) { @@ -1597,6 +1458,65 @@ amr_std_attach_mailbox(struct amr_softc *sc) AMR_SENABLE_INTR(sc); } +#ifdef AMR_BOARD_INIT +/******************************************************************************** + * Initialise the controller + */ +static int +amr_quartz_init(struct amr_softc *sc) +{ + int status, ostatus; + + device_printf(sc->amr_dev, "initial init status %x\n", AMR_QGET_INITSTATUS(sc)); + + AMR_QRESET(sc); + + ostatus = 0xff; + while ((status = AMR_QGET_INITSTATUS(sc)) != AMR_QINIT_DONE) { + if (status != ostatus) { + device_printf(sc->amr_dev, "(%x) %s\n", status, amr_describe_code(amr_table_qinit, status)); + ostatus = status; + } + switch (status) { + case AMR_QINIT_NOMEM: + return(ENOMEM); + + case AMR_QINIT_SCAN: + /* XXX we could print channel/target here */ + break; + } + } + return(0); +} + +static int +amr_std_init(struct amr_softc *sc) +{ + int status, ostatus; + + device_printf(sc->amr_dev, "initial init status %x\n", AMR_SGET_INITSTATUS(sc)); + + AMR_SRESET(sc); + + ostatus = 0xff; + while ((status = AMR_SGET_INITSTATUS(sc)) != AMR_SINIT_DONE) { + if (status != ostatus) { + device_printf(sc->amr_dev, "(%x) %s\n", status, amr_describe_code(amr_table_sinit, status)); + ostatus = status; + } + switch (status) { + case AMR_SINIT_NOMEM: + return(ENOMEM); + + case AMR_SINIT_INPROG: + /* XXX we could print channel/target here? */ + break; + } + } + return(0); +} +#endif + /******************************************************************************** ******************************************************************************** Debugging @@ -1604,6 +1524,60 @@ amr_std_attach_mailbox(struct amr_softc *sc) ********************************************************************************/ /******************************************************************************** + * Identify the controller and print some information about it. + */ +static void +amr_describe_controller(struct amr_softc *sc) +{ + struct amr_prodinfo *ap; + struct amr_enquiry *ae; + char *prod; + + /* + * Try to get 40LD product info, which tells us what the card is labelled as. + */ + if ((ap = amr_enquiry(sc, 2048, AMR_CMD_CONFIG, AMR_CONFIG_PRODUCT_INFO, 0)) != NULL) { + device_printf(sc->amr_dev, "<%.80s> Firmware %.16s, BIOS %.16s, %dMB RAM\n", + ap->ap_product, ap->ap_firmware, ap->ap_bios, + ap->ap_memsize); + + free(ap, M_DEVBUF); + return; + } + + /* + * Try 8LD extended ENQUIRY to get controller signature, and use lookup table. + */ + if ((ae = (struct amr_enquiry *)amr_enquiry(sc, 2048, AMR_CMD_EXT_ENQUIRY2, 0, 0)) != NULL) { + prod = amr_describe_code(amr_table_adaptertype, ae->ae_signature); + + } else if ((ae = (struct amr_enquiry *)amr_enquiry(sc, 2048, AMR_CMD_ENQUIRY, 0, 0)) != NULL) { + + /* + * Try to work it out based on the PCI signatures. + */ + switch (pci_get_device(sc->amr_dev)) { + case 0x9010: + prod = "Series 428"; + break; + case 0x9060: + prod = "Series 434"; + break; + default: + prod = "unknown controller"; + break; + } + } else { + prod = "unsupported controller"; + } + device_printf(sc->amr_dev, "<%s> Firmware %.4s, BIOS %.4s, %dMB RAM\n", + prod, ae->ae_adapter.aa_firmware, ae->ae_adapter.aa_bios, + ae->ae_adapter.aa_memorysize); + free(ae, M_DEVBUF); +} + +#ifdef AMR_DEBUG +/******************************************************************************** * Print the command (ac) in human-readable format */ static void @@ -1620,26 +1594,11 @@ amr_printcommand(struct amr_command *ac) device_printf(sc->amr_dev, "virtaddr %p length %lu\n", ac->ac_data, (unsigned long)ac->ac_length); device_printf(sc->amr_dev, "sg physaddr %08x nsg %d\n", ac->ac_mailbox.mb_physaddr, ac->ac_mailbox.mb_nsgelem); + device_printf(sc->amr_dev, "ccb %p bio %p\n", ac->ac_ccb_data, ac->ac_bio); /* get base address of s/g table */ sg = sc->amr_sgtable + (ac->ac_slot * AMR_NSEG); for (i = 0; i < ac->ac_mailbox.mb_nsgelem; i++, sg++) device_printf(sc->amr_dev, " %x/%d\n", sg->sg_addr, sg->sg_count); } - -/******************************************************************************** - * Print information on all the controllers in the system, useful mostly - * for calling from DDB. - */ -void -amr_report(void) -{ - struct amr_softc *sc; - int i, s; - - s = splbio(); - for (i = 0; (sc = devclass_get_softc(amr_devclass, i)) != NULL; i++) { - device_printf(sc->amr_dev, "amr_waitbufs %d amr_busycmdcount %d amr_workcount %d\n", - sc->amr_waitbufs, sc->amr_busycmdcount, sc->amr_workcount); - } -} +#endif diff --git a/sys/dev/amr/amr_cam.c b/sys/dev/amr/amr_cam.c new file mode 100644 index 000000000000..45ecc523c3aa --- /dev/null +++ b/sys/dev/amr/amr_cam.c @@ -0,0 +1,452 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/kernel.h> + +#include <dev/amr/amr_compat.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/devicestat.h> +#include <sys/disk.h> +#include <sys/stat.h> + +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_sim.h> +#include <cam/cam_xpt.h> +#include <cam/cam_xpt_sim.h> +#include <cam/cam_debug.h> +#include <cam/scsi/scsi_all.h> +#include <cam/scsi/scsi_message.h> + +#include <machine/resource.h> +#include <machine/bus.h> + +#include <dev/amr/amrreg.h> +#include <dev/amr/amrvar.h> + +static void amr_cam_action(struct cam_sim *sim, union ccb *ccb); +static void amr_cam_poll(struct cam_sim *sim); +static void amr_cam_complete(struct amr_command *ac); + + +/******************************************************************************** + * Enqueue/dequeue functions + */ +static __inline void +amr_enqueue_ccb(struct amr_softc *sc, union ccb *ccb) +{ + int s; + + s = splbio(); + TAILQ_INSERT_TAIL(&sc->amr_cam_ccbq, &ccb->ccb_h, sim_links.tqe); + splx(s); +} + +static __inline void +amr_requeue_ccb(struct amr_softc *sc, union ccb *ccb) +{ + int s; + + s = splbio(); + TAILQ_INSERT_HEAD(&sc->amr_cam_ccbq, &ccb->ccb_h, sim_links.tqe); + splx(s); +} + +static __inline union ccb * +amr_dequeue_ccb(struct amr_softc *sc) +{ + union ccb *ccb; + int s; + + s = splbio(); + if ((ccb = (union ccb *)TAILQ_FIRST(&sc->amr_cam_ccbq)) != NULL) + TAILQ_REMOVE(&sc->amr_cam_ccbq, &ccb->ccb_h, sim_links.tqe); + splx(s); + return(ccb); +} + +/******************************************************************************** + * Attach our 'real' SCSI channels to CAM + */ +int +amr_cam_attach(struct amr_softc *sc) +{ + struct cam_devq *devq; + int chn; + + /* initialise the ccb queue */ + TAILQ_INIT(&sc->amr_cam_ccbq); + + /* + * Allocate a devq for all our channels combined. This should + * allow for the maximum number of SCSI commands we will accept + * at one time. + */ + if ((devq = cam_simq_alloc(AMR_MAX_SCSI_CMDS)) == NULL) + return(ENOMEM); + + /* + * Iterate over our channels, registering them with CAM + */ + for (chn = 0; chn < sc->amr_maxchan; chn++) { + + /* allocate a sim */ + if ((sc->amr_cam_sim[chn] = cam_sim_alloc(amr_cam_action, + amr_cam_poll, + "amr", + sc, + device_get_unit(sc->amr_dev), + 1, + AMR_MAX_SCSI_CMDS, + devq)) == NULL) { + cam_simq_free(devq); + device_printf(sc->amr_dev, "CAM SIM attach failed\n"); + return(ENOMEM); + } + + /* register the bus ID so we can get it later */ + if (xpt_bus_register(sc->amr_cam_sim[chn], chn)) { + device_printf(sc->amr_dev, "CAM XPT bus registration failed\n"); + return(ENXIO); + } + } + /* + * XXX we should scan the config and work out which devices are actually + * protected. + */ + return(0); +} + +/******************************************************************************** + * Disconnect ourselves from CAM + */ +void +amr_cam_detach(struct amr_softc *sc) +{ + int chn, first; + + for (chn = 0, first = 1; chn < sc->amr_maxchan; chn++) { + + /* + * If a sim was allocated for this channel, free it + */ + if (sc->amr_cam_sim[chn] != NULL) { + xpt_bus_deregister(cam_sim_path(sc->amr_cam_sim[chn])); + cam_sim_free(sc->amr_cam_sim[chn], first ? TRUE : FALSE); + first = 0; + } + } +} + +/******************************************************************************** + ******************************************************************************** + CAM passthrough interface + ******************************************************************************** + ********************************************************************************/ + +/******************************************************************************** + * Handle a request for action from CAM + */ +static void +amr_cam_action(struct cam_sim *sim, union ccb *ccb) +{ + struct amr_softc *sc = cam_sim_softc(sim); + + switch(ccb->ccb_h.func_code) { + + /* + * Perform SCSI I/O to a physical device. + */ + case XPT_SCSI_IO: + { + struct ccb_hdr *ccbh = &ccb->ccb_h; + struct ccb_scsiio *csio = &ccb->csio; + + /* Validate the CCB */ + ccbh->status = CAM_REQ_INPROG; + + /* check the CDB length */ + if (csio->cdb_len > AMR_MAX_CDB_LEN) + ccbh->status = CAM_REQ_CMP_ERR; + + /* check that the CDB pointer is not to a physical address */ + if ((ccbh->flags & CAM_CDB_POINTER) && (ccbh->flags & CAM_CDB_PHYS)) + ccbh->status = CAM_REQ_CMP_ERR; + + /* if there is data transfer, it must be to/from a virtual address */ + if ((ccbh->flags & CAM_DIR_MASK) != CAM_DIR_NONE) { + if (ccbh->flags & CAM_DATA_PHYS) /* we can't map it */ + ccbh->status = CAM_REQ_CMP_ERR; + if (ccbh->flags & CAM_SCATTER_VALID) /* we want to do the s/g setup */ + ccbh->status = CAM_REQ_CMP_ERR; + } + + /* + * If the command is to a LUN other than 0, fail it. + * This is probably incorrect, but during testing the firmware did not + * seem to respect the LUN field, and thus devices appear echoed. + */ + if (csio->ccb_h.target_lun != 0) + ccbh->status = CAM_REQ_CMP_ERR; + + /* if we're happy with the request, queue it for attention */ + if (ccbh->status == CAM_REQ_INPROG) { + + /* save the channel number in the ccb */ + csio->ccb_h.sim_priv.entries[0].field = cam_sim_bus(sim); + + amr_enqueue_ccb(sc, ccb); + amr_startio(sc); + return; + } + break; + } + + case XPT_CALC_GEOMETRY: + { + struct ccb_calc_geometry *ccg = &ccb->ccg; + u_int32_t size_in_mb; + u_int32_t secs_per_cylinder; + + size_in_mb = ccg->volume_size / ((1024L * 1024L) / ccg->block_size); + + if (size_in_mb > 1024) { + ccg->heads = 255; + ccg->secs_per_track = 63; + } else { + ccg->heads = 64; + ccg->secs_per_track = 32; + } + secs_per_cylinder = ccg->heads * ccg->secs_per_track; + ccg->cylinders = ccg->volume_size / secs_per_cylinder; + ccb->ccb_h.status = CAM_REQ_CMP; + break; + } + + /* + * Return path stats. Some of these should probably be + * amended. + */ + case XPT_PATH_INQ: + { + struct ccb_pathinq *cpi = & ccb->cpi; + + cpi->version_num = 1; /* XXX??? */ + cpi->hba_inquiry = PI_SDTR_ABLE; + cpi->target_sprt = 0; + cpi->hba_misc = 0; + cpi->hba_eng_cnt = 0; + cpi->max_target = AMR_MAX_TARGETS; + cpi->max_lun = AMR_MAX_LUNS; + cpi->initiator_id = 7; /* XXX variable? */ + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "BSDi", HBA_IDLEN); + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->bus_id = cam_sim_bus(sim); + cpi->base_transfer_speed = 132 * 1024; /* XXX get from controller? */ + cpi->ccb_h.status = CAM_REQ_CMP; + + break; + } + + /* + * Reject anything else as unsupported. + */ + default: + /* we can't do this */ + ccb->ccb_h.status = CAM_REQ_INVALID; + break; + } + xpt_done(ccb); +} + +/******************************************************************************** + * Convert a CAM CCB off the top of the CCB queue to a passthrough SCSI command. + */ +int +amr_cam_command(struct amr_softc *sc, struct amr_command **acp) +{ + struct amr_command *ac; + struct amr_passthrough *ap; + struct ccb_scsiio *csio; + int bus, target, error; + + error = 0; + ac = NULL; + ap = NULL; + + /* check to see if there is a ccb for us to work with */ + if ((csio = (struct ccb_scsiio *)amr_dequeue_ccb(sc)) == NULL) + goto out; + + /* get bus/target, XXX validate against protected devices? */ + bus = csio->ccb_h.sim_priv.entries[0].field; + target = csio->ccb_h.target_id; + + /* + * Build a passthrough command. + */ + + /* construct passthrough */ + if ((ap = malloc(sizeof(*ap), M_DEVBUF, M_NOWAIT)) == NULL) { + error = ENOMEM; + goto out; + } + bzero(ap, sizeof(*ap)); + ap->ap_timeout = 0; + ap->ap_ars = 1; + ap->ap_request_sense_length = 14; + ap->ap_islogical = 0; + ap->ap_channel = bus; + ap->ap_scsi_id = target; + ap->ap_logical_drive_no = csio->ccb_h.target_lun; + ap->ap_cdb_length = csio->cdb_len; + if (csio->ccb_h.flags & CAM_CDB_POINTER) { + bcopy(csio->cdb_io.cdb_ptr, ap->ap_cdb, csio->cdb_len); + } else { + bcopy(csio->cdb_io.cdb_bytes, ap->ap_cdb, csio->cdb_len); + } + /* we leave the data s/g list and s/g count to the map routine later */ + + debug(2, " COMMAND %x/%d+%d to %d:%d:%d", ap->ap_cdb[0], ap->ap_cdb_length, csio->dxfer_len, + ap->ap_channel, ap->ap_scsi_id, ap->ap_logical_drive_no); + + /* construct command */ + if ((ac = amr_alloccmd(sc)) == NULL) { + error = ENOMEM; + goto out; + } + + ac->ac_data = ap; + ac->ac_length = sizeof(*ap); + ac->ac_flags |= AMR_CMD_DATAOUT; + + ac->ac_ccb_data = csio->data_ptr; + ac->ac_ccb_length = csio->dxfer_len; + if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) + ac->ac_flags |= AMR_CMD_CCB_DATAIN; + if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) + ac->ac_flags |= AMR_CMD_CCB_DATAOUT; + + ac->ac_complete = amr_cam_complete; + ac->ac_private = csio; + ac->ac_mailbox.mb_command = AMR_CMD_PASS; + +out: + if (error != 0) { + if (ac != NULL) + amr_releasecmd(ac); + if (ap != NULL) + free(ap, M_DEVBUF); + if (csio != NULL) /* put it back and try again later */ + amr_requeue_ccb(sc, (union ccb *)csio); + } + *acp = ac; + return(error); +} + +/******************************************************************************** + * Check for interrupt status + */ +static void +amr_cam_poll(struct cam_sim *sim) +{ + amr_done(cam_sim_softc(sim)); +} + +/******************************************************************************** + * Handle completion of a command submitted via CAM. + */ +static void +amr_cam_complete(struct amr_command *ac) +{ + struct amr_passthrough *ap = (struct amr_passthrough *)ac->ac_data; + struct ccb_scsiio *csio = (struct ccb_scsiio *)ac->ac_private; + struct scsi_inquiry_data *inq = (struct scsi_inquiry_data *)csio->data_ptr; + + /* XXX note that we're ignoring ac->ac_status - good idea? */ + + debug(1, "status 0x%x scsi_status 0x%x", ac->ac_status, ap->ap_scsi_status); + + /* + * Hide disks from CAM so that they're not picked up and treated as 'normal' disks. + * + * If the configuration provides a mechanism to mark a disk a "not managed", we + * could add handling for that to allow disks to be selectively visible. + */ +#if 0 + if ((ap->ap_cdb[0] == INQUIRY) && (SID_TYPE(inq) == T_DIRECT)) { + bzero(csio->data_ptr, csio->dxfer_len); + if (ap->ap_scsi_status == 0xf0) { + csio->ccb_h.status = CAM_SCSI_STATUS_ERROR; + } else { + csio->ccb_h.status = CAM_DEV_NOT_THERE; + } + } else { +#else + { +#endif + + /* handle passthrough SCSI status */ + switch(ap->ap_scsi_status) { + case 0: /* completed OK */ + csio->ccb_h.status = CAM_REQ_CMP; + break; + + case 0x02: + csio->ccb_h.status = CAM_SCSI_STATUS_ERROR; + csio->scsi_status = SCSI_STATUS_CHECK_COND; + bcopy(ap->ap_request_sense_area, &csio->sense_data, AMR_MAX_REQ_SENSE_LEN); + csio->sense_len = AMR_MAX_REQ_SENSE_LEN; + csio->ccb_h.status |= CAM_AUTOSNS_VALID; + break; + + case 0x08: + csio->ccb_h.status = CAM_SCSI_BUSY; + break; + + case 0xf0: + case 0xf4: + default: + csio->ccb_h.status = CAM_REQ_CMP_ERR; + break; + } + } + free(ap, M_DEVBUF); + if ((csio->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) + debug(2, "%*D\n", imin(csio->dxfer_len, 16), csio->data_ptr, " "); + xpt_done((union ccb *)csio); + amr_releasecmd(ac); +} + diff --git a/sys/dev/amr/amr_compat.h b/sys/dev/amr/amr_compat.h new file mode 100644 index 000000000000..c85b309b50ee --- /dev/null +++ b/sys/dev/amr/amr_compat.h @@ -0,0 +1,61 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +/* + * Backwards compatibility support. + */ + +#if __FreeBSD_version < 500003 /* old buf style */ +# include <sys/buf.h> + +# define FREEBSD_4 +# define bio buf +# define bioq_init(x) bufq_init(x) +# define bioq_insert_tail(x, y) bufq_insert_tail(x, y) +# define bioq_remove(x, y) bufq_remove(x, y) +# define bioq_first(x) bufq_first(x) +# define bio_queue_head buf_queue_head +# define bio_bcount b_bcount +# define bio_blkno b_blkno +# define bio_caller1 b_caller1 +# define bio_data b_data +# define bio_dev b_dev +# define bio_driver1 b_driver1 +# define bio_driver2 b_driver2 +# define bio_error b_error +# define bio_flags b_flags +# define bio_pblkno b_pblkno +# define bio_resid b_resid +# define BIO_ERROR B_ERROR +# define devstat_end_transaction_bio(x, y) devstat_end_transaction_buf(x, y) +# define BIO_IS_READ(x) ((x)-b_flags & B_READ) + +#else +# include <sys/bio.h> +# define BIO_IS_READ(x) ((x)->bio_cmd == BIO_READ) +#endif diff --git a/sys/dev/amr/amr_disk.c b/sys/dev/amr/amr_disk.c index 1e5082ad22c8..65fc4457013e 100644 --- a/sys/dev/amr/amr_disk.c +++ b/sys/dev/amr/amr_disk.c @@ -1,6 +1,7 @@ /*- * Copyright (c) 1999 Jonathan Lemon - * Copyright (c) 1999 Michael Smith + * Copyright (c) 1999, 2000 Michael Smith + * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,7 +36,7 @@ #include <sys/systm.h> #include <sys/kernel.h> -#include <sys/bio.h> +#include <dev/amr/amr_compat.h> #include <sys/bus.h> #include <sys/conf.h> #include <sys/devicestat.h> @@ -48,12 +49,7 @@ #include <dev/amr/amrio.h> #include <dev/amr/amrreg.h> #include <dev/amr/amrvar.h> - -#if 0 -#define debug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args) -#else -#define debug(fmt, args...) -#endif +#include <dev/amr/amr_tables.h> /* prototypes */ static int amrd_probe(device_t dev); @@ -65,7 +61,6 @@ static d_close_t amrd_close; static d_strategy_t amrd_strategy; static d_ioctl_t amrd_ioctl; -#define AMRD_BDEV_MAJOR 35 #define AMRD_CDEV_MAJOR 133 static struct cdevsw amrd_cdevsw = { @@ -82,12 +77,14 @@ static struct cdevsw amrd_cdevsw = { /* dump */ nodump, /* psize */ nopsize, /* flags */ D_DISK, - /* bmaj */ AMRD_BDEV_MAJOR + /* bmaj */ 254 }; static devclass_t amrd_devclass; static struct cdevsw amrddisk_cdevsw; +#ifdef FREEBSD_4 static int disks_registered = 0; +#endif static device_method_t amrd_methods[] = { DEVMETHOD(device_probe, amrd_probe), @@ -110,7 +107,7 @@ amrd_open(dev_t dev, int flags, int fmt, struct proc *p) struct amrd_softc *sc = (struct amrd_softc *)dev->si_drv1; struct disklabel *label; - debug("called"); + debug_called(1); if (sc == NULL) return (ENXIO); @@ -138,7 +135,7 @@ amrd_close(dev_t dev, int flags, int fmt, struct proc *p) { struct amrd_softc *sc = (struct amrd_softc *)dev->si_drv1; - debug("called"); + debug_called(1); if (sc == NULL) return (ENXIO); @@ -149,18 +146,7 @@ amrd_close(dev_t dev, int flags, int fmt, struct proc *p) static int amrd_ioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) { - struct amrd_softc *sc = (struct amrd_softc *)dev->si_drv1; - int error; - debug("called"); - - if (sc == NULL) - return (ENXIO); - - if ((error = amr_submit_ioctl(sc->amrd_controller, sc->amrd_drive, cmd, addr, flag, p)) != ENOIOCTL) { - debug("amr_submit_ioctl returned %d\n", error); - return(error); - } return (ENOTTY); } @@ -171,76 +157,60 @@ amrd_ioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) * be a multiple of a sector in length. */ static void -amrd_strategy(struct bio *bp) +amrd_strategy(struct bio *bio) { - struct amrd_softc *sc = (struct amrd_softc *)bp->bio_dev->si_drv1; - - debug("called to %s %d bytes at b_blkno 0x%x b_pblkno 0x%x", - (bp->b_flags & B_READ) ? "read" : "write", bp->bio_bcount, bp->bio_blkno, bp->bio_pblkno); + struct amrd_softc *sc = (struct amrd_softc *)bio->bio_dev->si_drv1; /* bogus disk? */ if (sc == NULL) { - bp->bio_error = EINVAL; + bio->bio_error = EINVAL; goto bad; } -#if 0 - /* XXX may only be temporarily offline - sleep? */ - if (sc->amrd_drive->ld_state == AMR_SYSD_OFFLINE) { - bp->bio_error = ENXIO; - goto bad; - } -#endif - /* do-nothing operation */ - if (bp->bio_bcount == 0) + if (bio->bio_bcount == 0) goto done; devstat_start_transaction(&sc->amrd_stats); - amr_submit_buf(sc->amrd_controller, bp); + amr_submit_bio(sc->amrd_controller, bio); return; bad: - bp->bio_flags |= BIO_ERROR; + bio->bio_flags |= BIO_ERROR; done: /* * Correctly set the buf to indicate a completed transfer */ - bp->bio_resid = bp->bio_bcount; - biodone(bp); + bio->bio_resid = bio->bio_bcount; + biodone(bio); return; } void amrd_intr(void *data) { - struct bio *bp = (struct bio *)data; - struct amrd_softc *sc = (struct amrd_softc *)bp->bio_dev->si_drv1; + struct bio *bio = (struct bio *)data; + struct amrd_softc *sc = (struct amrd_softc *)bio->bio_dev->si_drv1; - debug("called"); + debug_called(2); - if (bp->bio_flags & BIO_ERROR) { - bp->bio_error = EIO; - debug("i/o error\n"); + if (bio->bio_flags & BIO_ERROR) { + bio->bio_error = EIO; + debug(1, "i/o error\n"); } else { -#if 0 - int i; - for (i = 0; i < 512; i += 16) - debug(" %04x %16D", i, bp->bio_data + i, " "); -#endif - bp->bio_resid = 0; + bio->bio_resid = 0; } - devstat_end_transaction_bio(&sc->amrd_stats, bp); - biodone(bp); + devstat_end_transaction_bio(&sc->amrd_stats, bio); + biodone(bio); } static int amrd_probe(device_t dev) { - debug("called"); + debug_called(1); device_set_desc(dev, "MegaRAID logical drive"); return (0); @@ -251,9 +221,8 @@ amrd_attach(device_t dev) { struct amrd_softc *sc = (struct amrd_softc *)device_get_softc(dev); device_t parent; - char *state; - debug("called"); + debug_called(1); parent = device_get_parent(dev); sc->amrd_controller = (struct amr_softc *)device_get_softc(parent); @@ -261,26 +230,10 @@ amrd_attach(device_t dev) sc->amrd_drive = device_get_ivars(dev); sc->amrd_dev = dev; - switch(sc->amrd_drive->al_state) { - case AMRD_OFFLINE: - state = "offline"; - break; - case AMRD_DEGRADED: - state = "degraded"; - break; - case AMRD_OPTIMAL: - state = "optimal"; - break; - case AMRD_DELETED: - state = "deleted"; - break; - default: - state = "unknown state"; - } - device_printf(dev, "%uMB (%u sectors) RAID %d (%s)\n", sc->amrd_drive->al_size / ((1024 * 1024) / AMR_BLKSIZE), - sc->amrd_drive->al_size, sc->amrd_drive->al_properties & 0xf, state); + sc->amrd_drive->al_size, sc->amrd_drive->al_properties & AMR_DRV_RAID_MASK, + amr_describe_code(amr_table_drvstate, AMR_DRV_CURSTATE(sc->amrd_drive->al_state))); devstat_add_entry(&sc->amrd_stats, "amrd", sc->amrd_unit, AMR_BLKSIZE, DEVSTAT_NO_ORDERED_TAGS, @@ -289,10 +242,12 @@ amrd_attach(device_t dev) sc->amrd_dev_t = disk_create(sc->amrd_unit, &sc->amrd_disk, 0, &amrd_cdevsw, &amrddisk_cdevsw); sc->amrd_dev_t->si_drv1 = sc; +#ifdef FREEBSD_4 disks_registered++; +#endif - /* set maximum I/O size */ - /* dsk->si_iosize_max = ??? */; + /* set maximum I/O size to match the maximum s/g size */ + sc->amrd_dev_t->si_iosize_max = (AMR_NSEG - 1) * PAGE_SIZE; return (0); } @@ -302,10 +257,18 @@ amrd_detach(device_t dev) { struct amrd_softc *sc = (struct amrd_softc *)device_get_softc(dev); - debug("called"); + debug_called(1); + + if (sc->amrd_flags & AMRD_OPEN) + return(EBUSY); devstat_remove_entry(&sc->amrd_stats); +#ifdef FREEBSD_4 + if (--disks_registered == 0) + cdevsw_remove(&amrddisk_cdevsw); +#else disk_destroy(sc->amrd_dev_t); +#endif return(0); } diff --git a/sys/dev/amr/amr_pci.c b/sys/dev/amr/amr_pci.c index fc9f8a9ea2d4..e338e5944884 100644 --- a/sys/dev/amr/amr_pci.c +++ b/sys/dev/amr/amr_pci.c @@ -1,5 +1,6 @@ /*- - * Copyright (c) 1999 Michael Smith + * Copyright (c) 1999,2000 Michael Smith + * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,8 +31,8 @@ #include <sys/systm.h> #include <sys/kernel.h> +#include <dev/amr/amr_compat.h> #include <sys/bus.h> -#include <sys/bio.h> #include <sys/conf.h> #include <sys/devicestat.h> #include <sys/disk.h> @@ -49,23 +50,27 @@ #include <dev/amr/amrreg.h> #include <dev/amr/amrvar.h> -#if 0 -#define debug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args) -#else -#define debug(fmt, args...) -#endif - -static int amr_pci_probe(device_t dev); -static int amr_pci_attach(device_t dev); +static int amr_pci_probe(device_t dev); +static int amr_pci_attach(device_t dev); +static int amr_pci_detach(device_t dev); +static int amr_pci_shutdown(device_t dev); +static int amr_pci_suspend(device_t dev); +static int amr_pci_resume(device_t dev); +static void amr_pci_intr(void *arg); +static void amr_pci_free(struct amr_softc *sc); +static void amr_sglist_map_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error); +static int amr_sglist_map(struct amr_softc *sc); +static void amr_setup_mbox_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error); +static int amr_setup_mbox(struct amr_softc *sc); static device_method_t amr_methods[] = { /* Device interface */ DEVMETHOD(device_probe, amr_pci_probe), DEVMETHOD(device_attach, amr_pci_attach), - DEVMETHOD(device_detach, amr_detach), - DEVMETHOD(device_shutdown, amr_shutdown), - DEVMETHOD(device_suspend, amr_suspend), - DEVMETHOD(device_resume, amr_resume), + DEVMETHOD(device_detach, amr_pci_detach), + DEVMETHOD(device_shutdown, amr_pci_shutdown), + DEVMETHOD(device_suspend, amr_pci_suspend), + DEVMETHOD(device_resume, amr_pci_resume), DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), @@ -78,6 +83,7 @@ static driver_t amr_pci_driver = { sizeof(struct amr_softc) }; +devclass_t amr_devclass; DRIVER_MODULE(amr, pci, amr_pci_driver, amr_devclass, 0, 0); static struct @@ -89,7 +95,8 @@ static struct } amr_device_ids[] = { {0x101e, 0x9010, 0}, {0x101e, 0x9060, 0}, - {0x8086, 0x1960, PROBE_SIGNATURE}, /* generic i960RD, check signature */ + {0x8086, 0x1960, PROBE_SIGNATURE}, /* generic i960RD, check for signature */ + {0x101e, 0x1960, 0}, {0, 0, 0} }; @@ -98,7 +105,7 @@ amr_pci_probe(device_t dev) { int i; - debug("called"); + debug_called(1); for (i = 0; amr_device_ids[i].vendor != 0; i++) { if ((pci_get_vendor(dev) == amr_device_ids[i].vendor) && @@ -122,7 +129,7 @@ amr_pci_attach(device_t dev) int rid, rtype, error; u_int32_t command; - debug("called"); + debug_called(1); /* * Initialise softc. @@ -131,30 +138,30 @@ amr_pci_attach(device_t dev) bzero(sc, sizeof(*sc)); sc->amr_dev = dev; + /* assume failure is 'not configured' */ + error = ENXIO; + /* - * Determine board type.. + * Determine board type. */ command = pci_read_config(dev, PCIR_COMMAND, 1); - if ((pci_get_vendor(dev) == 0x8086) && (pci_get_device(dev) == 0x1960)) { - sc->amr_type = AMR_TYPE_QUARTZ; - + if (pci_get_device(dev) == 0x1960) { /* * Make sure we are going to be able to talk to this board. */ if ((command & PCIM_CMD_MEMEN) == 0) { device_printf(dev, "memory window not available\n"); - return(ENXIO); + goto out; } + sc->amr_type |= AMR_TYPE_QUARTZ; } else { - sc->amr_type = AMR_TYPE_STD; - /* * Make sure we are going to be able to talk to this board. */ if ((command & PCIM_CMD_PORTEN) == 0) { device_printf(dev, "I/O window not available\n"); - return(ENXIO); + goto out; } } @@ -168,47 +175,381 @@ amr_pci_attach(device_t dev) /* * Allocate the PCI register window. */ - rid = AMR_CFG_BASE; - rtype = (sc->amr_type == AMR_TYPE_QUARTZ) ? SYS_RES_MEMORY : SYS_RES_IOPORT; + rid = PCIR_MAPS; + rtype = AMR_IS_QUARTZ(sc) ? SYS_RES_MEMORY : SYS_RES_IOPORT; sc->amr_reg = bus_alloc_resource(dev, rtype, &rid, 0, ~0, 1, RF_ACTIVE); if (sc->amr_reg == NULL) { - device_printf(sc->amr_dev, "couldn't allocate register window\n"); - amr_free(sc); - return(ENXIO); + device_printf(sc->amr_dev, "can't allocate register window\n"); + goto out; } sc->amr_btag = rman_get_bustag(sc->amr_reg); sc->amr_bhandle = rman_get_bushandle(sc->amr_reg); /* + * Allocate and connect our interrupt. + */ + rid = 0; + sc->amr_irq = bus_alloc_resource(sc->amr_dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); + if (sc->amr_irq == NULL) { + device_printf(sc->amr_dev, "can't allocate interrupt\n"); + goto out; + } + if (bus_setup_intr(sc->amr_dev, sc->amr_irq, INTR_TYPE_BIO, amr_pci_intr, sc, &sc->amr_intr)) { + device_printf(sc->amr_dev, "can't set up interrupt\n"); + goto out; + } + + debug(2, "interrupt attached"); + + /* assume failure is 'out of memory' */ + error = ENOMEM; + + /* * Allocate the parent bus DMA tag appropriate for PCI. */ - error = bus_dma_tag_create(NULL, /* parent */ + if (bus_dma_tag_create(NULL, /* parent */ + 1, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MAXBSIZE, AMR_NSEG, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + &sc->amr_parent_dmat)) { + device_printf(dev, "can't allocate parent DMA tag\n"); + goto out; + } + + /* + * Create DMA tag for mapping buffers into controller-addressable space. + */ + if (bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ + 1, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MAXBSIZE, AMR_NSEG, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + 0, /* flags */ + &sc->amr_buffer_dmat)) { + device_printf(sc->amr_dev, "can't allocate buffer DMA tag\n"); + goto out; + } + + debug(2, "dma tag done"); + + /* + * Allocate and set up mailbox in a bus-visible fashion. + */ + if ((error = amr_setup_mbox(sc)) != 0) + goto out; + + debug(2, "mailbox setup"); + + /* + * Build the scatter/gather buffers. + */ + if (amr_sglist_map(sc)) + goto out; + + debug(2, "s/g list mapped"); + + /* + * Do bus-independant initialisation, bring controller online. + */ + error = amr_attach(sc); + +out: + if (error) + amr_pci_free(sc); + return(error); +} + +/******************************************************************************** + * Disconnect from the controller completely, in preparation for unload. + */ +static int +amr_pci_detach(device_t dev) +{ + struct amr_softc *sc = device_get_softc(dev); + int error; + + debug_called(1); + + if (sc->amr_state & AMR_STATE_OPEN) + return(EBUSY); + + if ((error = amr_pci_shutdown(dev))) + return(error); + + amr_pci_free(sc); + + return(0); +} + +/******************************************************************************** + * Bring the controller down to a dormant state and detach all child devices. + * + * This function is called before detach, system shutdown, or before performing + * an operation which may add or delete system disks. (Call amr_startup to + * resume normal operation.) + * + * Note that we can assume that the bioq on the controller is empty, as we won't + * allow shutdown if any device is open. + */ +static int +amr_pci_shutdown(device_t dev) +{ + struct amr_softc *sc = device_get_softc(dev); + + debug_called(1); + + /* mark ourselves as in-shutdown */ + sc->amr_state |= AMR_STATE_SHUTDOWN; + + + /* flush controller */ + device_printf(sc->amr_dev, "flushing cache..."); + printf("%s\n", amr_flush(sc) ? "failed" : "done"); + + /* XXX disable interrupts? */ + + return(0); +} + +/******************************************************************************** + * Bring the controller to a quiescent state, ready for system suspend. + */ +static int +amr_pci_suspend(device_t dev) +{ + struct amr_softc *sc = device_get_softc(dev); + + debug_called(1); + + sc->amr_state |= AMR_STATE_SUSPEND; + + /* flush controller */ + device_printf(sc->amr_dev, "flushing cache..."); + printf("%s\n", amr_flush(sc) ? "failed" : "done"); + + /* XXX disable interrupts? */ + + return(0); +} + +/******************************************************************************** + * Bring the controller back to a state ready for operation. + */ +static int +amr_pci_resume(device_t dev) +{ + struct amr_softc *sc = device_get_softc(dev); + + debug_called(1); + + sc->amr_state &= ~AMR_STATE_SUSPEND; + + /* XXX enable interrupts? */ + + return(0); +} + +/******************************************************************************* + * Take an interrupt, or be poked by other code to look for interrupt-worthy + * status. + */ +static void +amr_pci_intr(void *arg) +{ + struct amr_softc *sc = (struct amr_softc *)arg; + + debug_called(2); + + /* collect finished commands, queue anything waiting */ + amr_done(sc); +} + +/******************************************************************************** + * Free all of the resources associated with (sc) + * + * Should not be called if the controller is active. + */ +static void +amr_pci_free(struct amr_softc *sc) +{ + u_int8_t *p; + + debug_called(1); + + amr_free(sc); + + /* destroy data-transfer DMA tag */ + if (sc->amr_buffer_dmat) + bus_dma_tag_destroy(sc->amr_buffer_dmat); + + /* free and destroy DMA memory and tag for s/g lists */ + if (sc->amr_sgtable) + bus_dmamem_free(sc->amr_sg_dmat, sc->amr_sgtable, sc->amr_sg_dmamap); + if (sc->amr_sg_dmat) + bus_dma_tag_destroy(sc->amr_sg_dmat); + + /* free and destroy DMA memory and tag for mailbox */ + if (sc->amr_mailbox) { + p = (u_int8_t *)(uintptr_t)(volatile void *)sc->amr_mailbox; + bus_dmamem_free(sc->amr_sg_dmat, p - 16, sc->amr_sg_dmamap); + } + if (sc->amr_sg_dmat) + bus_dma_tag_destroy(sc->amr_sg_dmat); + + /* disconnect the interrupt handler */ + if (sc->amr_intr) + bus_teardown_intr(sc->amr_dev, sc->amr_irq, sc->amr_intr); + if (sc->amr_irq != NULL) + bus_release_resource(sc->amr_dev, SYS_RES_IRQ, 0, sc->amr_irq); + + /* destroy the parent DMA tag */ + if (sc->amr_parent_dmat) + bus_dma_tag_destroy(sc->amr_parent_dmat); + + /* release the register window mapping */ + if (sc->amr_reg != NULL) + bus_release_resource(sc->amr_dev, + AMR_IS_QUARTZ(sc) ? SYS_RES_MEMORY : SYS_RES_IOPORT, + PCIR_MAPS, sc->amr_reg); +} + +/******************************************************************************** + * Allocate and map the scatter/gather table in bus space. + */ +static void +amr_sglist_map_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct amr_softc *sc = (struct amr_softc *)arg; + + debug_called(1); + + /* save base of s/g table's address in bus space */ + sc->amr_sgbusaddr = segs->ds_addr; +} + +static int +amr_sglist_map(struct amr_softc *sc) +{ + size_t segsize; + int error; + + debug_called(1); + + /* + * Create a single tag describing a region large enough to hold all of + * the s/g lists we will need. + * + * Note that we could probably use AMR_LIMITCMD here, but that may become tunable. + */ + segsize = sizeof(struct amr_sgentry) * AMR_NSEG * AMR_MAXCMD; + error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ 1, 0, /* alignment, boundary */ - BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, AMR_NSEG, /* maxsize, nsegments */ + segsize, 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ - BUS_DMA_ALLOCNOW, /* flags */ - &sc->amr_parent_dmat); + 0, /* flags */ + &sc->amr_sg_dmat); if (error != 0) { - device_printf(dev, "can't allocate parent DMA tag\n"); - amr_free(sc); + device_printf(sc->amr_dev, "can't allocate scatter/gather DMA tag\n"); return(ENOMEM); } /* - * Do bus-independant initialisation. + * 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. + * + * XXX HACK ALERT: at least some controllers don't like the s/g memory being + * allocated below 0x2000. We leak some memory if we get some + * below this mark and allocate again. We should be able to + * avoid this with the tag setup, but that does't seem to work. */ - error = amr_attach(sc); - if (error != 0) { - amr_free(sc); - return(error); +retry: + error = bus_dmamem_alloc(sc->amr_sg_dmat, (void **)&sc->amr_sgtable, BUS_DMA_NOWAIT, &sc->amr_sg_dmamap); + if (error) { + device_printf(sc->amr_dev, "can't allocate s/g table\n"); + return(ENOMEM); } + bus_dmamap_load(sc->amr_sg_dmat, sc->amr_sg_dmamap, sc->amr_sgtable, segsize, amr_sglist_map_helper, sc, 0); + if (sc->amr_sgbusaddr < 0x2000) { + debug(1, "s/g table too low (0x%x), reallocating\n", sc->amr_sgbusaddr); + goto retry; + } + return(0); +} + +/******************************************************************************** + * Allocate and set up mailbox areas for the controller (sc) + * + * The basic mailbox structure should be 16-byte aligned. This means that the + * mailbox64 structure has 4 bytes hanging off the bottom. + */ +static void +amr_setup_mbox_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct amr_softc *sc = (struct amr_softc *)arg; + debug_called(1); + + /* save phsyical base of the basic mailbox structure */ + sc->amr_mailboxphys = segs->ds_addr + 16; +} + +static int +amr_setup_mbox(struct amr_softc *sc) +{ + int error; + u_int8_t *p; + + debug_called(1); + /* - * Start the controller. + * Create a single tag describing a region large enough to hold the entire + * mailbox. */ - amr_startup(sc); + error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ + 16, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + sizeof(struct amr_mailbox) + 16, 1, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + 0, /* flags */ + &sc->amr_mailbox_dmat); + if (error != 0) { + device_printf(sc->amr_dev, "can't allocate mailbox tag\n"); + return(ENOMEM); + } + + /* + * Allocate the mailbox structure and permanently map it into + * controller-visible space. + */ + error = bus_dmamem_alloc(sc->amr_mailbox_dmat, (void **)&p, BUS_DMA_NOWAIT, + &sc->amr_mailbox_dmamap); + if (error) { + device_printf(sc->amr_dev, "can't allocate mailbox memory\n"); + return(ENOMEM); + } + bus_dmamap_load(sc->amr_mailbox_dmat, sc->amr_mailbox_dmamap, p, + sizeof(struct amr_mailbox64), amr_setup_mbox_helper, sc, 0); + /* + * Conventional mailbox is inside the mailbox64 region. + */ + bzero(p, sizeof(struct amr_mailbox64)); + sc->amr_mailbox64 = (struct amr_mailbox64 *)(p + 12); + sc->amr_mailbox = (struct amr_mailbox *)(p + 16); + return(0); } diff --git a/sys/dev/amr/amr_tables.h b/sys/dev/amr/amr_tables.h new file mode 100644 index 000000000000..d6e9993ad1c6 --- /dev/null +++ b/sys/dev/amr/amr_tables.h @@ -0,0 +1,110 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Lookup table for code-to-text translations. + */ +struct amr_code_lookup { + char *string; + u_int32_t code; +}; + +extern char *amr_describe_code(struct amr_code_lookup *table, u_int32_t code); + +#ifndef AMR_DEFINE_TABLES +extern struct amr_code_lookup amr_table_qinit[]; +extern struct amr_code_lookup amr_table_sinit[]; +extern struct amr_code_lookup amr_table_drvstate[]; + +#else /* AMR_DEFINE_TABLES */ + +/******************************************************************************** + * Look up a text description of a numeric code and return a pointer to same. + */ +char * +amr_describe_code(struct amr_code_lookup *table, u_int32_t code) +{ + int i; + + for (i = 0; table[i].string != NULL; i++) + if (table[i].code == code) + return(table[i].string); + return(table[i+1].string); +} + +struct amr_code_lookup amr_table_qinit[] = { + {"init scanning drives", AMR_QINIT_SCAN}, + {"init scanning initialising", AMR_QINIT_SCANINIT}, + {"init firmware initing", AMR_QINIT_FIRMWARE}, + {"init in progress", AMR_QINIT_INPROG}, + {"init spinning drives", AMR_QINIT_SPINUP}, + {"insufficient memory", AMR_QINIT_NOMEM}, + {"init flushing cache", AMR_QINIT_CACHEFLUSH}, + {"init successfully done", AMR_QINIT_DONE}, + {NULL, 0}, + {"unknown init code", 0} +}; + +struct amr_code_lookup amr_table_sinit[] = { + {"init abnormal terminated", AMR_SINIT_ABEND}, + {"insufficient memory", AMR_SINIT_NOMEM}, + {"firmware flushing cache", AMR_SINIT_CACHEFLUSH}, + {"init in progress", AMR_SINIT_INPROG}, + {"firmware spinning drives", AMR_SINIT_SPINUP}, + {"init successfully done", AMR_SINIT_DONE}, + {NULL, 0}, + {"unknown init code", 0} +}; + +struct amr_code_lookup amr_table_drvstate[] = { + {"offline", AMR_DRV_OFFLINE}, + {"degraded", AMR_DRV_DEGRADED}, + {"optimal", AMR_DRV_OPTIMAL}, + {"online", AMR_DRV_ONLINE}, + {"failed", AMR_DRV_FAILED}, + {"rebuild", AMR_DRV_REBUILD}, + {"hot spare", AMR_DRV_HOTSPARE}, + {NULL, 0}, + {"unknown", 0} +}; + +struct amr_code_lookup amr_table_adaptertype[] = { + {"Series 431", AMR_SIG_431}, + {"Series 438", AMR_SIG_438}, + {"Series 762", AMR_SIG_762}, + {"Integrated HP NetRAID (T5)", AMR_SIG_T5}, + {"Series 466", AMR_SIG_466}, + {"Series 467", AMR_SIG_467}, + {"Integrated HP NetRAID (T7)", AMR_SIG_T7}, + {"Series 490", AMR_SIG_490}, + {NULL, 0}, + {"unknown adapter", 0} +}; + +#endif diff --git a/sys/dev/amr/amrio.h b/sys/dev/amr/amrio.h index 48e6efda467d..757e81c64b00 100644 --- a/sys/dev/amr/amrio.h +++ b/sys/dev/amr/amrio.h @@ -27,10 +27,54 @@ */ /* - * Drive status + * ioctl interface */ -#define AMRD_OFFLINE 0x0 -#define AMRD_DEGRADED 0x1 -#define AMRD_OPTIMAL 0x2 -#define AMRD_DELETED 0x3 +#include <sys/ioccom.h> + +/* + * Fetch the driver's interface version. + */ +#define AMR_IO_VERSION_NUMBER 0x01 +#define AMR_IO_VERSION _IOR('A', 0x200, int) + +/* + * Pass a command from userspace through to the adapter. + * + * Note that in order to be code-compatible with the Linux + * interface where possible, the formatting of the au_cmd field is + * somewhat Interesting. + * + * For normal commands, the layout is (fields from struct amr_mailbox_ioctl): + * + * 0 mb_command + * 1 mb_channel + * 2 mb_param + * 3 mb_pad[0] + * 4 mb_drive + * + * For SCSI passthrough commands, the layout is: + * + * 0 AMR_CMD_PASS (0x3) + * 1 reserved, 0 + * 2 cdb length + * 3 cdb data + * 3+cdb_len passthrough control byte (timeout, ars, islogical) + * 4+cdb_len reserved, 0 + * 5+cdb_len channel + * 6+cdb_len target + */ + +struct amr_user_ioctl { + unsigned char au_cmd[32]; /* command text from userspace */ + void *au_buffer; /* data buffer in userspace */ + unsigned long au_length; /* data buffer size (0 == no data) */ + int au_direction; /* data transfer direction */ +#define AMR_IO_NODATA 0 +#define AMR_IO_READ 1 +#define AMR_IO_WRITE 2 + int au_status; /* command status returned by adapter */ +}; + +#define AMR_IO_COMMAND _IOWR('A', 0x201, struct amr_user_ioctl) + diff --git a/sys/dev/amr/amrreg.h b/sys/dev/amr/amrreg.h index 3ae84b915c22..b2cf64540888 100644 --- a/sys/dev/amr/amrreg.h +++ b/sys/dev/amr/amrreg.h @@ -1,5 +1,6 @@ /*- - * Copyright (c) 1999 Michael Smith + * Copyright (c) 1999,2000 Michael Smith + * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,16 +27,65 @@ * $FreeBSD$ */ +/******************************************************************************** + ******************************************************************************** + Driver parameters + ******************************************************************************** + ********************************************************************************/ + +/* + * We could actually use all 17 segments, but using only 16 means that + * each scatter/gather map is 128 bytes in size, and thus we don't have to worry about + * maps crossing page boundaries. + * + * The AMI documentation says that the limit is 26. Unfortunately, there's no way to + * cleanly fit more than 16 entries in without a page boundary. But is this a concern, + * since we allocate the s/g maps contiguously anyway? + */ +#define AMR_NSEG 16 + +#define AMR_MAXCMD 255 /* ident = 0 not allowed */ +#define AMR_LIMITCMD 120 /* maximum count of outstanding commands */ +#define AMR_MAXLD 40 + +#define AMR_MAX_CHANNELS 4 +#define AMR_MAX_TARGETS 15 +#define AMR_MAX_LUNS 7 +#define AMR_MAX_SCSI_CMDS (15 * AMR_MAX_CHANNELS) /* one for every target? */ + +#define AMR_MAX_CDB_LEN 0x0a +#define AMR_MAX_REQ_SENSE_LEN 0x20 + +#define AMR_BLKSIZE 512 /* constant for all controllers */ + +/* + * Perform at-startup board initialisation. + * At this point in time, this code doesn't work correctly, so leave it disabled. + */ +/*#define AMR_BOARD_INIT*/ + +/******************************************************************************** + ******************************************************************************** + Interface Magic Numbers + ******************************************************************************** + ********************************************************************************/ + /* * Mailbox commands */ -#define AMR_CMD_LREAD 0x01 -#define AMR_CMD_LWRITE 0x02 -#define AMR_CMD_ENQUIRY 0x05 -#define AMR_CMD_FLUSH 0x0a -#define AMR_CMD_CONFIG 0xa1 +#define AMR_CMD_LREAD 0x01 +#define AMR_CMD_LWRITE 0x02 +#define AMR_CMD_PASS 0x03 +#define AMR_CMD_EXT_ENQUIRY 0x04 +#define AMR_CMD_ENQUIRY 0x05 +#define AMR_CMD_FLUSH 0x0a +#define AMR_CMD_EXT_ENQUIRY2 0x0c #define AMR_CONFIG_PRODINFO 0x0e -#define AMR_CONFIG_ENQ3 0x0f +#define AMR_CMD_GET_MACHINEID 0x36 +#define AMR_CMD_GET_INITIATOR 0x7d /* returns one byte */ +#define AMR_CMD_CONFIG 0xa1 +#define AMR_CONFIG_PRODUCT_INFO 0x0e +#define AMR_CONFIG_ENQ3 0x0f #define AMR_CONFIG_ENQ3_SOLICITED_NOTIFY 0x01 #define AMR_CONFIG_ENQ3_SOLICITED_FULL 0x02 #define AMR_CONFIG_ENQ3_UNSOLICITED 0x03 @@ -48,45 +98,57 @@ #define AMR_STATUS_FAILED 0x80 /* - * Quartz doorbell registers + * Physical/logical drive states */ -#define AMR_QIDB 0x20 -#define AMR_QODB 0x2c -#define AMR_QIDB_SUBMIT 0x00000001 /* mailbox ready for work */ -#define AMR_QIDB_ACK 0x00000002 /* mailbox done */ -#define AMR_QODB_READY 0x10001234 /* work ready to be processed */ +#define AMR_DRV_CURSTATE(x) ((x) & 0x0f) +#define AMR_DRV_PREVSTATE(x) (((x) >> 4) & 0x0f) +#define AMR_DRV_OFFLINE 0x00 +#define AMR_DRV_DEGRADED 0x01 +#define AMR_DRV_OPTIMAL 0x02 +#define AMR_DRV_ONLINE 0x03 +#define AMR_DRV_FAILED 0x04 +#define AMR_DRV_REBUILD 0x05 +#define AMR_DRV_HOTSPARE 0x06 /* - * Standard I/O registers + * Logical drive properties */ -#define AMR_SCMD 0x10 /* command/ack register (write) */ -#define AMR_SMBOX_BUSY 0x10 /* mailbox status (read) */ -#define AMR_STOGGLE 0x11 /* interrupt enable bit here */ -#define AMR_SMBOX_0 0x14 /* mailbox physical address low byte */ -#define AMR_SMBOX_1 0x15 -#define AMR_SMBOX_2 0x16 -#define AMR_SMBOX_3 0x17 /* high byte */ -#define AMR_SMBOX_ENABLE 0x18 /* atomic mailbox address enable */ -#define AMR_SINTR 0x1a /* interrupt status */ +#define AMR_DRV_RAID_MASK 0x0f /* RAID level 0, 1, 3, 5, etc. */ +#define AMR_DRV_WRITEBACK 0x10 /* write-back enabled */ +#define AMR_DRV_READHEAD 0x20 /* readhead policy enabled */ +#define AMR_DRV_ADAPTIVE 0x40 /* adaptive I/O policy enabled */ /* - * Standard I/O magic numbers + * Battery status */ -#define AMR_SCMD_POST 0x10 /* -> SCMD to initiate action on mailbox */ -#define AMR_SCMD_ACKINTR 0x08 /* -> SCMD to ack mailbox retrieved */ -#define AMR_STOGL_IENABLE 0xc0 /* in STOGGLE */ -#define AMR_SINTR_VALID 0x40 /* in SINTR */ -#define AMR_SMBOX_BUSYFLAG 0x10 /* in SMBOX_BUSY */ -#define AMR_SMBOX_ADDR 0x00 /* -> SMBOX_ENABLE */ +#define AMR_BATT_MODULE_MISSING 0x01 +#define AMR_BATT_LOW_VOLTAGE 0x02 +#define AMR_BATT_TEMP_HIGH 0x04 +#define AMR_BATT_PACK_MISSING 0x08 +#define AMR_BATT_CHARGE_MASK 0x30 +#define AMR_BATT_CHARGE_DONE 0x00 +#define AMR_BATT_CHARGE_INPROG 0x10 +#define AMR_BATT_CHARGE_FAIL 0x20 +#define AMR_BATT_CYCLES_EXCEEDED 0x40 + + +/******************************************************************************** + ******************************************************************************** + 8LD Firmware Interface + ******************************************************************************** + ********************************************************************************/ /* - * Old Enquiry results + * Array constraints */ #define AMR_8LD_MAXDRIVES 8 #define AMR_8LD_MAXCHAN 5 #define AMR_8LD_MAXTARG 15 #define AMR_8LD_MAXPHYSDRIVES (AMR_8LD_MAXCHAN * AMR_8LD_MAXTARG) +/* + * Adapter Info structure + */ struct amr_adapter_info { u_int8_t aa_maxio; @@ -99,9 +161,18 @@ struct amr_adapter_info u_int8_t aa_memorysize; u_int8_t aa_cacheflush; u_int8_t aa_bios[4]; - u_int8_t res1[7]; + u_int8_t aa_boardtype; + u_int8_t aa_scsisensealert; + u_int8_t aa_writeconfigcount; + u_int8_t aa_driveinsertioncount; + u_int8_t aa_inserteddrive; + u_int8_t aa_batterystatus; + u_int8_t res1; } __attribute__ ((packed)); +/* + * Logical Drive info structure + */ struct amr_logdrive_info { u_int8_t al_numdrives; @@ -111,19 +182,60 @@ struct amr_logdrive_info u_int8_t al_state[AMR_8LD_MAXDRIVES]; } __attribute__ ((packed)); +/* + * Physical Drive info structure + */ struct amr_physdrive_info { - u_int8_t ap_state[AMR_8LD_MAXPHYSDRIVES]; - u_int8_t res1; + u_int8_t ap_state[AMR_8LD_MAXPHYSDRIVES]; /* low nibble current state, high nibble previous state */ + u_int8_t ap_predictivefailure; } __attribute__ ((packed)); +/* + * Enquiry response structure for AMR_CMD_ENQUIRY, AMR_CMD_EXT_ENQUIRY and + * AMR_CMD_EXT_ENQUIRY2. + * ENQUIRY EXT_ENQUIRY EXT_ENQUIRY2 + */ struct amr_enquiry { - struct amr_adapter_info ae_adapter; - struct amr_logdrive_info ae_ldrv; - struct amr_physdrive_info ae_pdrv; + struct amr_adapter_info ae_adapter; /* X X X */ + struct amr_logdrive_info ae_ldrv; /* X X X */ + struct amr_physdrive_info ae_pdrv; /* X X X */ + u_int8_t ae_formatting[AMR_8LD_MAXDRIVES];/* X X */ + u_int8_t res1[AMR_8LD_MAXDRIVES]; /* X X */ + u_int32_t ae_extlen; /* X */ + u_int16_t ae_subsystem; /* X */ + u_int16_t ae_subvendor; /* X */ + u_int32_t ae_signature; /* X */ +#define AMR_SIG_431 0xfffe0001 +#define AMR_SIG_438 0xfffd0002 +#define AMR_SIG_762 0xfffc0003 +#define AMR_SIG_T5 0xfffb0004 +#define AMR_SIG_466 0xfffa0005 +#define AMR_SIG_467 0xfff90006 +#define AMR_SIG_T7 0xfff80007 +#define AMR_SIG_490 0xfff70008 + u_int8_t res2[844]; /* X */ } __attribute__ ((packed)); + +/******************************************************************************** + ******************************************************************************** + 40LD Firmware Interface + ******************************************************************************** + ********************************************************************************/ + +/* + * Array constraints + */ +#define AMR_40LD_MAXDRIVES 40 +#define AMR_40LD_MAXCHAN 16 +#define AMR_40LD_MAXTARG 16 +#define AMR_40LD_MAXPHYSDRIVES 256 + +/* + * Product Info structure + */ struct amr_prodinfo { u_int32_t ap_size; /* current size in bytes (not including resvd) */ @@ -144,6 +256,105 @@ struct amr_prodinfo u_int8_t ap_numnotifyctr; /* number of notify counters */ } __attribute__((packed)); +/* + * Notify structure + */ +struct amr_notify +{ + u_int32_t an_globalcounter; /* change counter */ + + u_int8_t an_paramcounter; /* parameter change counter */ + u_int8_t an_paramid; +#define AMR_PARAM_REBUILD_RATE 0x01 /* value = new rebuild rate */ +#define AMR_PARAM_FLUSH_INTERVAL 0x02 /* value = new flush interval */ +#define AMR_PARAM_SENSE_ALERT 0x03 /* value = last physical drive with check condition set */ +#define AMR_PARAM_DRIVE_INSERTED 0x04 /* value = last physical drive inserted */ +#define AMR_PARAM_BATTERY_STATUS 0x05 /* value = battery status */ + u_int16_t an_paramval; + + u_int8_t an_writeconfigcounter; /* write config occurred */ + u_int8_t res1[3]; + + u_int8_t an_ldrvopcounter; /* logical drive operation started/completed */ + u_int8_t an_ldrvopid; + u_int8_t an_ldrvopcmd; +#define AMR_LDRVOP_CHECK 0x01 +#define AMR_LDRVOP_INIT 0x02 +#define AMR_LDRVOP_REBUILD 0x03 + u_int8_t an_ldrvopstatus; +#define AMR_LDRVOP_SUCCESS 0x00 +#define AMR_LDRVOP_FAILED 0x01 +#define AMR_LDRVOP_ABORTED 0x02 +#define AMR_LDRVOP_CORRECTED 0x03 +#define AMR_LDRVOP_STARTED 0x04 + + u_int8_t an_ldrvstatecounter; /* logical drive state change occurred */ + u_int8_t an_ldrvstateid; + u_int8_t an_ldrvstatenew; + u_int8_t an_ldrvstateold; + + u_int8_t an_pdrvstatecounter; /* physical drive state change occurred */ + u_int8_t an_pdrvstateid; + u_int8_t an_pdrvstatenew; + u_int8_t an_pdrvstateold; + + u_int8_t an_pdrvfmtcounter; + u_int8_t an_pdrvfmtid; + u_int8_t an_pdrvfmtval; +#define AMR_FORMAT_START 0x01 +#define AMR_FORMAT_COMPLETE 0x02 + u_int8_t res2; + + u_int8_t an_targxfercounter; /* scsi xfer rate change */ + u_int8_t an_targxferid; + u_int8_t an_targxferval; + u_int8_t res3; + + u_int8_t an_fcloopidcounter; /* FC/AL loop ID changed */ + u_int8_t an_fcloopidpdrvid; + u_int8_t an_fcloopid0; + u_int8_t an_fcloopid1; + + u_int8_t an_fcloopstatecounter; /* FC/AL loop status changed */ + u_int8_t an_fcloopstate0; + u_int8_t an_fcloopstate1; + u_int8_t res4; +} __attribute__((packed)); + +/* + * Enquiry3 structure + */ +struct amr_enquiry3 +{ + u_int32_t ae_datasize; /* valid data size in this structure */ + union { /* event notify structure */ + struct amr_notify n; + u_int8_t pad[0x80]; + } ae_notify; + u_int8_t ae_rebuildrate; /* current rebuild rate in % */ + u_int8_t ae_cacheflush; /* flush interval in seconds */ + u_int8_t ae_sensealert; + u_int8_t ae_driveinsertcount; /* count of inserted drives */ + u_int8_t ae_batterystatus; + u_int8_t ae_numldrives; + u_int8_t ae_reconstate[AMR_40LD_MAXDRIVES / 8]; /* reconstruction state */ + u_int16_t ae_opstatus[AMR_40LD_MAXDRIVES / 8]; /* operation status per drive */ + u_int32_t ae_drivesize[AMR_40LD_MAXDRIVES]; /* logical drive size */ + u_int8_t ae_driveprop[AMR_40LD_MAXDRIVES]; /* logical drive properties */ + u_int8_t ae_drivestate[AMR_40LD_MAXDRIVES]; /* physical drive state */ + u_int16_t ae_driveformat[AMR_40LD_MAXPHYSDRIVES]; + u_int8_t ae_targxfer[80]; /* physical drive transfer rates */ + + u_int8_t res1[263]; /* pad to 1024 bytes */ +} __attribute__ ((packed)); + + +/******************************************************************************** + ******************************************************************************** + Mailbox and Command Structures + ******************************************************************************** + ********************************************************************************/ + #define AMR_MBOX_CMDSIZE 0x10 /* portion worth copying for controller */ struct amr_mailbox @@ -177,17 +388,17 @@ struct amr_mailbox_ioctl u_int8_t mb_ident; u_int8_t mb_channel; u_int8_t mb_param; - u_int8_t res1[4]; + u_int8_t mb_pad[4]; u_int32_t mb_physaddr; u_int8_t mb_drive; u_int8_t mb_nsgelem; - u_int8_t res2; + u_int8_t res1; u_int8_t mb_busy; u_int8_t mb_nstatus; u_int8_t mb_completed[46]; u_int8_t mb_poll; u_int8_t mb_ack; - u_int8_t res3[16]; + u_int8_t res4[16]; } __attribute__ ((packed)); struct amr_sgentry @@ -196,4 +407,138 @@ struct amr_sgentry u_int32_t sg_count; } __attribute__ ((packed)); +struct amr_passthrough +{ + u_int8_t ap_timeout:3; + u_int8_t ap_ars:1; + u_int8_t ap_dummy:3; + u_int8_t ap_islogical:1; + u_int8_t ap_logical_drive_no; + u_int8_t ap_channel; + u_int8_t ap_scsi_id; + u_int8_t ap_queue_tag; + u_int8_t ap_queue_action; + u_int8_t ap_cdb[AMR_MAX_CDB_LEN]; + u_int8_t ap_cdb_length; + u_int8_t ap_request_sense_length; + u_int8_t ap_request_sense_area[AMR_MAX_REQ_SENSE_LEN]; + u_int8_t ap_no_sg_elements; + u_int8_t ap_scsi_status; + u_int32_t ap_data_transfer_address; + u_int32_t ap_data_transfer_length; +} __attribute__ ((packed)); + +#ifdef _KERNEL +/******************************************************************************** + ******************************************************************************** + "Quartz" i960 PCI bridge interface + ******************************************************************************** + ********************************************************************************/ + +#define AMR_CFG_SIG 0xa0 /* PCI config register for signature */ +#define AMR_SIGNATURE 0x3344 /* signature for Quartz adapters */ + +/* + * Doorbell registers + */ +#define AMR_QIDB 0x20 +#define AMR_QODB 0x2c +#define AMR_QIDB_SUBMIT 0x00000001 /* mailbox ready for work */ +#define AMR_QIDB_ACK 0x00000002 /* mailbox done */ +#define AMR_QODB_READY 0x10001234 /* work ready to be processed */ + +/* + * Initialisation status + */ +#define AMR_QINIT_SCAN 0x01 /* init scanning drives */ +#define AMR_QINIT_SCANINIT 0x02 /* init scanning initialising */ +#define AMR_QINIT_FIRMWARE 0x03 /* init firmware initing */ +#define AMR_QINIT_INPROG 0xdc /* init in progress */ +#define AMR_QINIT_SPINUP 0x2c /* init spinning drives */ +#define AMR_QINIT_NOMEM 0xac /* insufficient memory */ +#define AMR_QINIT_CACHEFLUSH 0xbc /* init flushing cache */ +#define AMR_QINIT_DONE 0x9c /* init successfully done */ + +/* + * I/O primitives + */ +#define AMR_QPUT_IDB(sc, val) bus_space_write_4(sc->amr_btag, sc->amr_bhandle, AMR_QIDB, val) +#define AMR_QGET_IDB(sc) bus_space_read_4 (sc->amr_btag, sc->amr_bhandle, AMR_QIDB) +#define AMR_QPUT_ODB(sc, val) bus_space_write_4(sc->amr_btag, sc->amr_bhandle, AMR_QODB, val) +#define AMR_QGET_ODB(sc) bus_space_read_4 (sc->amr_btag, sc->amr_bhandle, AMR_QODB) + +#ifdef AMR_BOARD_INIT +#define AMR_QRESET(sc) \ + do { \ + pci_write_config((sc)->amr_dev, 0x40, pci_read_config((sc)->amr_dev, 0x40, 1) | 0x20, 1); \ + pci_write_config((sc)->amr_dev, 0x64, 0x1122, 1); \ + } while (0) +#define AMR_QGET_INITSTATUS(sc) pci_read_config((sc)->amr_dev, 0x9c, 1) +#define AMR_QGET_INITCHAN(sc) pci_read_config((sc)->amr_dev, 0x9f, 1) +#define AMR_QGET_INITTARG(sc) pci_read_config((sc)->amr_dev, 0x9e, 1) +#endif + +/******************************************************************************** + ******************************************************************************** + "Standard" old-style ASIC bridge interface + ******************************************************************************** + ********************************************************************************/ + +/* + * I/O registers + */ +#define AMR_SCMD 0x10 /* command/ack register (write) */ +#define AMR_SMBOX_BUSY 0x10 /* mailbox status (read) */ +#define AMR_STOGGLE 0x11 /* interrupt enable bit here */ +#define AMR_SMBOX_0 0x14 /* mailbox physical address low byte */ +#define AMR_SMBOX_1 0x15 +#define AMR_SMBOX_2 0x16 +#define AMR_SMBOX_3 0x17 /* high byte */ +#define AMR_SMBOX_ENABLE 0x18 /* atomic mailbox address enable */ +#define AMR_SINTR 0x1a /* interrupt status */ + +/* + * I/O magic numbers + */ +#define AMR_SCMD_POST 0x10 /* -> SCMD to initiate action on mailbox */ +#define AMR_SCMD_ACKINTR 0x08 /* -> SCMD to ack mailbox retrieved */ +#define AMR_STOGL_IENABLE 0xc0 /* in STOGGLE */ +#define AMR_SINTR_VALID 0x40 /* in SINTR */ +#define AMR_SMBOX_BUSYFLAG 0x10 /* in SMBOX_BUSY */ +#define AMR_SMBOX_ADDR 0x00 /* -> SMBOX_ENABLE */ + +/* + * Initialisation status + */ +#define AMR_SINIT_ABEND 0xee /* init abnormal terminated */ +#define AMR_SINIT_NOMEM 0xca /* insufficient memory */ +#define AMR_SINIT_CACHEFLUSH 0xbb /* firmware flushing cache */ +#define AMR_SINIT_INPROG 0x11 /* init in progress */ +#define AMR_SINIT_SPINUP 0x22 /* firmware spinning drives */ +#define AMR_SINIT_DONE 0x99 /* init successfully done */ + +/* + * I/O primitives + */ +#define AMR_SPUT_ISTAT(sc, val) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_SINTR, val) +#define AMR_SGET_ISTAT(sc) bus_space_read_1 (sc->amr_btag, sc->amr_bhandle, AMR_SINTR) +#define AMR_SACK_INTERRUPT(sc) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_SCMD, AMR_SCMD_ACKINTR) +#define AMR_SPOST_COMMAND(sc) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_SCMD, AMR_SCMD_POST) +#define AMR_SGET_MBSTAT(sc) bus_space_read_1 (sc->amr_btag, sc->amr_bhandle, AMR_SMBOX_BUSY) +#define AMR_SENABLE_INTR(sc) \ + bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE, \ + bus_space_read_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE) | AMR_STOGL_IENABLE) +#define AMR_SDISABLE_INTR(sc) \ + bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE, \ + bus_space_read_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE) & ~AMR_STOGL_IENABLE) +#define AMR_SBYTE_SET(sc, reg, val) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, reg, val) + +#ifdef AMR_BOARD_INIT +#define AMR_SRESET(sc) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, 0, 0x80) +#define AMR_SGET_INITSTATUS(sc) bus_space_read_1 (sc->amr_btag, sc->amr_bhandle, AMR_SMBOX_ENABLE) +#define AMR_SGET_FAILDRIVE(sc) bus_space_read_1 (sc->amr_btag, sc->amr_bhandle, AMR_SMBOX_ENABLE + 1) +#define AMR_SGET_INITCHAN(sc) bus_space_read_1 (sc->amr_btag, sc->amr_bhandle, AMR_SMBOX_ENABLE + 2) +#define AMR_SGET_INITTARG(sc) bus_space_read_1 (sc->amr_btag, sc->amr_bhandle, AMR_SMBOX_ENABLE + 3) +#endif +#endif _KERNEL diff --git a/sys/dev/amr/amrvar.h b/sys/dev/amr/amrvar.h index 5edfb1fb93f0..159a4e8231ad 100644 --- a/sys/dev/amr/amrvar.h +++ b/sys/dev/amr/amrvar.h @@ -1,5 +1,6 @@ /*- - * Copyright (c) 1999 Michael Smith + * Copyright (c) 1999,2000 Michael Smith + * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,24 +27,18 @@ * $FreeBSD$ */ -/* - * We could actually use all 17 segments, but using only 16 means that - * each scatter/gather map is 128 bytes in size, and thus we don't have to worry about - * maps crossing page boundaries. - */ -#define AMR_NSEG 16 - -#define AMR_CFG_BASE 0x10 -#define AMR_CFG_SIG 0xa0 -#define AMR_SIGNATURE 0x3344 - -#define AMR_MAXCMD 255 /* ident = 0 not allowed */ -#define AMR_LIMITCMD 120 /* maximum count of outstanding commands */ -#define AMR_MAXLD 40 - -#define AMR_BLKSIZE 512 +#if __FreeBSD_version >= 500005 +# include <sys/taskqueue.h> +#endif -struct amr_softc; +#ifdef AMR_DEBUG +# define debug(level, fmt, args...) do {if (level <= AMR_DEBUG) printf("%s: " fmt "\n", __FUNCTION__ , ##args);} while(0) +# define debug_called(level) do {if (level <= AMR_DEBUG) printf("%s: called\n", __FUNCTION__);} while(0) +#else +# define debug(level, fmt, args...) +# define debug_called(level) +#endif +#define xdebug(fmt, args...) printf("%s: " fmt "\n", __FUNCTION__ , ##args) /* * Per-logical-drive datastructure @@ -63,6 +58,16 @@ struct amr_logdrive device_t al_disk; }; +/* + * Due to the difficulty of using the zone allocator to create a new + * zone from within a module, we use our own clustering to reduce + * memory wastage due to allocating lots of these small structures. + * + * 16k gives us a little under 200 command structures, which should + * normally be plenty. We will grab more if we need them. + */ + +#define AMR_CMD_CLUSTERSIZE (16 * 1024) /* * Per-command control structure. @@ -73,28 +78,45 @@ struct amr_command struct amr_softc *ac_sc; u_int8_t ac_slot; - int ac_status; -#define AMR_STATUS_BUSY 0xffff -#define AMR_STATUS_WEDGED 0xdead -#define AMR_STATUS_LATE 0xdeed + int ac_status; /* command completion status */ struct amr_mailbox ac_mailbox; - u_int32_t ac_sgphys; - int ac_nsgent; int ac_flags; #define AMR_CMD_DATAIN (1<<0) #define AMR_CMD_DATAOUT (1<<1) -#define AMR_CMD_PRIORITY (1<<2) - time_t ac_stamp; +#define AMR_CMD_CCB_DATAIN (1<<2) +#define AMR_CMD_CCB_DATAOUT (1<<3) +#define AMR_CMD_PRIORITY (1<<4) +#define AMR_CMD_MAPPED (1<<5) +#define AMR_CMD_SLEEP (1<<6) +#define AMR_CMD_BUSY (1<<7) + + struct bio *ac_bio; void *ac_data; size_t ac_length; bus_dmamap_t ac_dmamap; u_int32_t ac_dataphys; + void *ac_ccb_data; + size_t ac_ccb_length; + bus_dmamap_t ac_ccb_dmamap; + u_int32_t ac_ccb_dataphys; + void (* ac_complete)(struct amr_command *ac); - void *ac_private; }; +struct amr_command_cluster +{ + TAILQ_ENTRY(amr_command_cluster) acc_link; + struct amr_command acc_command[0]; +}; + +#define AMR_CMD_CLUSTERCOUNT ((AMR_CMD_CLUSTERSIZE - sizeof(struct amr_command_cluster)) / \ + sizeof(struct amr_command)) + +/* + * Per-controller-instance data + */ struct amr_softc { /* bus attachments */ @@ -123,101 +145,74 @@ struct amr_softc /* controller limits and features */ int amr_maxio; /* maximum number of I/O transactions */ int amr_maxdrives; /* max number of logical drives */ + int amr_maxchan; /* count of SCSI channels */ /* connected logical drives */ struct amr_logdrive amr_drive[AMR_MAXLD]; - /* controller status */ + /* controller state */ int amr_state; #define AMR_STATE_OPEN (1<<0) #define AMR_STATE_SUSPEND (1<<1) #define AMR_STATE_INTEN (1<<2) #define AMR_STATE_SHUTDOWN (1<<3) - struct callout_handle amr_timeout; /* periodic status check */ /* per-controller queues */ - struct bio_queue_head amr_bioq; /* pending I/O */ - int amr_waitbufs; + struct bio_queue_head amr_bioq; /* pending I/O with no commands */ + TAILQ_HEAD(,amr_command) amr_ready; /* commands ready to be submitted */ struct amr_command *amr_busycmd[AMR_MAXCMD]; - int amr_busycmdcount; - TAILQ_HEAD(,amr_command) amr_work; - int amr_workcount; + int amr_busyslots; + TAILQ_HEAD(,amr_command) amr_completed; TAILQ_HEAD(,amr_command) amr_freecmds; + TAILQ_HEAD(,amr_command_cluster) amr_cmd_clusters; + + /* CAM attachments for passthrough */ + struct cam_sim *amr_cam_sim[AMR_MAX_CHANNELS]; + TAILQ_HEAD(, ccb_hdr) amr_cam_ccbq; - int amr_locks; /* reentrancy avoidance */ + /* control device */ + dev_t amr_dev_t; /* controller type-specific support */ int amr_type; -#define AMR_TYPE_STD 0 -#define AMR_TYPE_QUARTZ 1 - void (* amr_submit_command)(struct amr_softc *sc); +#define AMR_TYPE_QUARTZ (1<<0) +#define AMR_IS_QUARTZ(sc) ((sc)->amr_type & AMR_TYPE_QUARTZ) +#define AMR_TYPE_40LD (1<<1) +#define AMR_IS_40LD(sc) ((sc)->amr_type & AMR_TYPE_40LD) + int (* amr_submit_command)(struct amr_softc *sc); int (* amr_get_work)(struct amr_softc *sc, struct amr_mailbox *mbsave); - void (* amr_attach_mailbox)(struct amr_softc *sc); + + /* misc glue */ + struct intr_config_hook amr_ich; /* wait-for-interrupts probe hook */ + struct callout_handle amr_timeout; /* periodic status check */ +#if __FreeBSD_version >= 500005 + struct task amr_task_complete; /* deferred-completion task */ +#endif }; /* - * Simple (stupid) locks. - * - * Note that these are designed to avoid reentrancy, not concurrency, and will - * need to be replaced with something better. + * Interface between bus connections and driver core. */ -#define AMR_LOCK_COMPLETING (1<<0) -#define AMR_LOCK_STARTING (1<<1) - -static __inline int -amr_lock_tas(struct amr_softc *sc, int lock) -{ - if ((sc)->amr_locks & (lock)) - return(1); - atomic_set_int(&sc->amr_locks, lock); - return(0); -} +extern int amr_attach(struct amr_softc *sc); +extern void amr_free(struct amr_softc *sc); +extern int amr_flush(struct amr_softc *sc); +extern int amr_done(struct amr_softc *sc); +extern void amr_startio(struct amr_softc *sc); -static __inline void -amr_lock_clr(struct amr_softc *sc, int lock) -{ - atomic_clear_int(&sc->amr_locks, lock); -} +extern devclass_t amr_devclass; /* - * I/O primitives + * Command buffer allocation. */ -/* Quartz */ -#define AMR_QPUT_IDB(sc, val) bus_space_write_4(sc->amr_btag, sc->amr_bhandle, AMR_QIDB, val) -#define AMR_QGET_IDB(sc) bus_space_read_4 (sc->amr_btag, sc->amr_bhandle, AMR_QIDB) -#define AMR_QPUT_ODB(sc, val) bus_space_write_4(sc->amr_btag, sc->amr_bhandle, AMR_QODB, val) -#define AMR_QGET_ODB(sc) bus_space_read_4 (sc->amr_btag, sc->amr_bhandle, AMR_QODB) - -/* Standard */ -#define AMR_SPUT_ISTAT(sc, val) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_SINTR, val) -#define AMR_SGET_ISTAT(sc) bus_space_read_1 (sc->amr_btag, sc->amr_bhandle, AMR_SINTR) -#define AMR_SACK_INTERRUPT(sc) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_SCMD, AMR_SCMD_ACKINTR) -#define AMR_SPOST_COMMAND(sc) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_SCMD, AMR_SCMD_POST) -#define AMR_SGET_MBSTAT(sc) bus_space_read_1 (sc->amr_btag, sc->amr_bhandle, AMR_SMBOX_BUSY) -#define AMR_SENABLE_INTR(sc) \ - bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE, \ - bus_space_read_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE) | AMR_STOGL_IENABLE) -#define AMR_SDISABLE_INTR(sc) \ - bus_space_write_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE, \ - bus_space_read_1(sc->amr_btag, sc->amr_bhandle, AMR_STOGGLE) & ~AMR_STOGL_IENABLE) -#define AMR_SBYTE_SET(sc, reg, val) bus_space_write_1(sc->amr_btag, sc->amr_bhandle, reg, val) +extern struct amr_command *amr_alloccmd(struct amr_softc *sc); +extern void amr_releasecmd(struct amr_command *ac); /* - * Interface between bus connections and driver core. + * CAM interface */ -extern void amr_free(struct amr_softc *sc); -extern int amr_attach(struct amr_softc *sc); -extern void amr_startup(struct amr_softc *sc); -extern void amr_intr(void *data); -extern int amr_detach(device_t dev); -extern int amr_shutdown(device_t dev); -extern int amr_suspend(device_t dev); -extern int amr_resume(device_t dev); -extern d_open_t amr_open; -extern d_close_t amr_close; -extern d_ioctl_t amr_ioctl; - -extern devclass_t amr_devclass; +extern int amr_cam_attach(struct amr_softc *sc); +extern void amr_cam_detach(struct amr_softc *sc); +extern int amr_cam_command(struct amr_softc *sc, struct amr_command **acp); /* * MegaRAID logical disk driver @@ -233,15 +228,116 @@ struct amrd_softc struct disklabel amrd_label; int amrd_unit; int amrd_flags; -#define AMRD_OPEN (1<<0) /* drive is open (can't shut down) */ +#define AMRD_OPEN (1<<0) /* drive is open (can't detach) */ }; /* * Interface between driver core and disk driver (should be using a bus?) */ -extern int amr_submit_buf(struct amr_softc *sc, struct bio *bp); -extern int amr_submit_ioctl(struct amr_softc *sc, struct amr_logdrive *drive, u_long cmd, - caddr_t addr, int32_t flag, struct proc *p); +extern int amr_submit_bio(struct amr_softc *sc, struct bio *bio); extern void amrd_intr(void *data); -extern void amr_report(void); +/******************************************************************************** + * Enqueue/dequeue functions + */ +static __inline void +amr_enqueue_bio(struct amr_softc *sc, struct bio *bio) +{ + int s; + + s = splbio(); + bioq_insert_tail(&sc->amr_bioq, bio); + splx(s); +} + +static __inline struct bio * +amr_dequeue_bio(struct amr_softc *sc) +{ + struct bio *bio; + int s; + + s = splbio(); + if ((bio = bioq_first(&sc->amr_bioq)) != NULL) + bioq_remove(&sc->amr_bioq, bio); + splx(s); + return(bio); +} + +static __inline void +amr_enqueue_ready(struct amr_command *ac) +{ + int s; + + s = splbio(); + TAILQ_INSERT_TAIL(&ac->ac_sc->amr_ready, ac, ac_link); + splx(s); +} + +static __inline void +amr_requeue_ready(struct amr_command *ac) +{ + int s; + + s = splbio(); + TAILQ_INSERT_HEAD(&ac->ac_sc->amr_ready, ac, ac_link); + splx(s); +} + +static __inline struct amr_command * +amr_dequeue_ready(struct amr_softc *sc) +{ + struct amr_command *ac; + int s; + + s = splbio(); + if ((ac = TAILQ_FIRST(&sc->amr_ready)) != NULL) + TAILQ_REMOVE(&sc->amr_ready, ac, ac_link); + splx(s); + return(ac); +} + +static __inline void +amr_enqueue_completed(struct amr_command *ac) +{ + int s; + + s = splbio(); + TAILQ_INSERT_TAIL(&ac->ac_sc->amr_completed, ac, ac_link); + splx(s); +} + +static __inline struct amr_command * +amr_dequeue_completed(struct amr_softc *sc) +{ + struct amr_command *ac; + int s; + + s = splbio(); + if ((ac = TAILQ_FIRST(&sc->amr_completed)) != NULL) + TAILQ_REMOVE(&sc->amr_completed, ac, ac_link); + splx(s); + return(ac); +} + +static __inline void +amr_enqueue_free(struct amr_command *ac) +{ + int s; + + s = splbio(); + TAILQ_INSERT_TAIL(&ac->ac_sc->amr_freecmds, ac, ac_link); + splx(s); +} + +static __inline struct amr_command * +amr_dequeue_free(struct amr_softc *sc) +{ + struct amr_command *ac; + int s; + + s = splbio(); + if ((ac = TAILQ_FIRST(&sc->amr_freecmds)) != NULL) + TAILQ_REMOVE(&sc->amr_freecmds, ac, ac_link); + splx(s); + return(ac); +} diff --git a/sys/modules/amr/Makefile b/sys/modules/amr/Makefile index 58335744f7e7..d2fd3885f2fd 100644 --- a/sys/modules/amr/Makefile +++ b/sys/modules/amr/Makefile @@ -2,6 +2,16 @@ .PATH: ${.CURDIR}/../../dev/amr KMOD = amr -SRCS = amr.c amr_pci.c amr_disk.c device_if.h bus_if.h pci_if.h +SRCS = amr.c amr_pci.c amr_disk.c device_if.h bus_if.h pci_if.h + +# SCSI passthrough support for non-disk devices +#CFLAGS += -DAMR_SCSI_PASSTHROUGH +#SRCS += amr_cam.c opt_cam.h opt_scsi.h + +# Enable a questionable optimisation for newer adapters +#CFLAGS += -DAMR_QUARTZ_GOFASTER + +# Debugging +#CFLAGS += -DAMR_DEBUG=3 .include <bsd.kmod.mk> |