diff options
Diffstat (limited to 'sys/dev/twe/twe_freebsd.c')
-rw-r--r-- | sys/dev/twe/twe_freebsd.c | 971 |
1 files changed, 971 insertions, 0 deletions
diff --git a/sys/dev/twe/twe_freebsd.c b/sys/dev/twe/twe_freebsd.c new file mode 100644 index 0000000..761117d --- /dev/null +++ b/sys/dev/twe/twe_freebsd.c @@ -0,0 +1,971 @@ +/*- + * 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$ + */ + +/* + * FreeBSD-specific code. + */ + +#include <dev/twe/twe_compat.h> +#include <dev/twe/twereg.h> +#include <dev/twe/tweio.h> +#include <dev/twe/twevar.h> +#include <dev/twe/twe_tables.h> + +#include <sys/devicestat.h> + +static devclass_t twe_devclass; + +/******************************************************************************** + ******************************************************************************** + Control device interface + ******************************************************************************** + ********************************************************************************/ + +static d_open_t twe_open; +static d_close_t twe_close; +static d_ioctl_t twe_ioctl_wrapper; + +#define TWE_CDEV_MAJOR 146 + +static struct cdevsw twe_cdevsw = { + twe_open, + twe_close, + noread, + nowrite, + twe_ioctl_wrapper, + nopoll, + nommap, + nostrategy, + "twe", + TWE_CDEV_MAJOR, + nodump, + nopsize, + 0, + -1 +}; + +/******************************************************************************** + * Accept an open operation on the control device. + */ +static int +twe_open(dev_t dev, int flags, int fmt, struct proc *p) +{ + int unit = minor(dev); + struct twe_softc *sc = devclass_get_softc(twe_devclass, unit); + + sc->twe_state |= TWE_STATE_OPEN; + return(0); +} + +/******************************************************************************** + * Accept the last close on the control device. + */ +static int +twe_close(dev_t dev, int flags, int fmt, struct proc *p) +{ + int unit = minor(dev); + struct twe_softc *sc = devclass_get_softc(twe_devclass, unit); + + sc->twe_state &= ~TWE_STATE_OPEN; + return (0); +} + +/******************************************************************************** + * Handle controller-specific control operations. + */ +static int +twe_ioctl_wrapper(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) +{ + struct twe_softc *sc = (struct twe_softc *)dev->si_drv1; + + return(twe_ioctl(sc, cmd, addr)); +} + +/******************************************************************************** + ******************************************************************************** + PCI device interface + ******************************************************************************** + ********************************************************************************/ + +static int twe_probe(device_t dev); +static int twe_attach(device_t dev); +static void twe_free(struct twe_softc *sc); +static int twe_detach(device_t dev); +static int twe_shutdown(device_t dev); +static int twe_suspend(device_t dev); +static int twe_resume(device_t dev); +static void twe_pci_intr(void *arg); +static void twe_intrhook(void *arg); + +static device_method_t twe_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, twe_probe), + DEVMETHOD(device_attach, twe_attach), + DEVMETHOD(device_detach, twe_detach), + DEVMETHOD(device_shutdown, twe_shutdown), + DEVMETHOD(device_suspend, twe_suspend), + DEVMETHOD(device_resume, twe_resume), + + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + { 0, 0 } +}; + +static driver_t twe_pci_driver = { + "twe", + twe_methods, + sizeof(struct twe_softc) +}; + +#ifdef TWE_OVERRIDE +DRIVER_MODULE(Xtwe, pci, twe_pci_driver, twe_devclass, 0, 0); +#else +DRIVER_MODULE(twe, pci, twe_pci_driver, twe_devclass, 0, 0); +#endif + +/******************************************************************************** + * Match a 3ware Escalade ATA RAID controller. + */ +static int +twe_probe(device_t dev) +{ + + debug_called(4); + + if ((pci_get_vendor(dev) == TWE_VENDOR_ID) && + (pci_get_device(dev) == TWE_DEVICE_ID)) { + device_set_desc(dev, TWE_DEVICE_NAME); +#ifdef TWE_OVERRIDE + return(0); +#else + return(-10); +#endif + } + return(ENXIO); +} + +/******************************************************************************** + * Allocate resources, initialise the controller. + */ +static int +twe_attach(device_t dev) +{ + struct twe_softc *sc; + int rid, error; + u_int32_t command; + + debug_called(4); + + /* + * Initialise the softc structure. + */ + sc = device_get_softc(dev); + sc->twe_dev = dev; + + /* + * Make sure we are going to be able to talk to this board. + */ + command = pci_read_config(dev, PCIR_COMMAND, 2); + if ((command & PCIM_CMD_PORTEN) == 0) { + twe_printf(sc, "register window not available\n"); + return(ENXIO); + } + /* + * Force the busmaster enable bit on, in case the BIOS forgot. + */ + command |= PCIM_CMD_BUSMASTEREN; + pci_write_config(dev, PCIR_COMMAND, command, 2); + + /* + * Allocate the PCI register window. + */ + rid = TWE_IO_CONFIG_REG; + if ((sc->twe_io = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE)) == NULL) { + twe_printf(sc, "can't allocate register window\n"); + twe_free(sc); + return(ENXIO); + } + sc->twe_btag = rman_get_bustag(sc->twe_io); + sc->twe_bhandle = rman_get_bushandle(sc->twe_io); + + /* + * Allocate the parent bus DMA tag appropriate for PCI. + */ + 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, TWE_MAX_SGL_LENGTH, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + &sc->twe_parent_dmat)) { + twe_printf(sc, "can't allocate parent DMA tag\n"); + twe_free(sc); + return(ENOMEM); + } + + /* + * Allocate and connect our interrupt. + */ + rid = 0; + if ((sc->twe_irq = bus_alloc_resource(sc->twe_dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE)) == NULL) { + twe_printf(sc, "can't allocate interrupt\n"); + twe_free(sc); + return(ENXIO); + } + if (bus_setup_intr(sc->twe_dev, sc->twe_irq, INTR_TYPE_BIO, twe_pci_intr, sc, &sc->twe_intr)) { + twe_printf(sc, "can't set up interrupt\n"); + twe_free(sc); + return(ENXIO); + } + + /* + * Create DMA tag for mapping objects into controller-addressable space. + */ + if (bus_dma_tag_create(sc->twe_parent_dmat, /* parent */ + 1, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MAXBSIZE, TWE_MAX_SGL_LENGTH, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + 0, /* flags */ + &sc->twe_buffer_dmat)) { + twe_printf(sc, "can't allocate data buffer DMA tag\n"); + twe_free(sc); + return(ENOMEM); + } + + /* + * Initialise the controller and driver core. + */ + if ((error = twe_setup(sc))) + return(error); + + /* + * Print some information about the controller and configuration. + */ + twe_describe_controller(sc); + + /* + * Create the control device. + */ + sc->twe_dev_t = make_dev(&twe_cdevsw, device_get_unit(sc->twe_dev), UID_ROOT, GID_OPERATOR, + S_IRUSR | S_IWUSR, "twe%d", device_get_unit(sc->twe_dev)); + sc->twe_dev_t->si_drv1 = sc; + /* + * Schedule ourselves to bring the controller up once interrupts are available. + * This isn't strictly necessary, since we disable interrupts while probing the + * controller, but it is more in keeping with common practice for other disk + * devices. + */ + sc->twe_ich.ich_func = twe_intrhook; + sc->twe_ich.ich_arg = sc; + if (config_intrhook_establish(&sc->twe_ich) != 0) { + twe_printf(sc, "can't establish configuration hook\n"); + twe_free(sc); + return(ENXIO); + } + + return(0); +} + +/******************************************************************************** + * Free all of the resources associated with (sc). + * + * Should not be called if the controller is active. + */ +static void +twe_free(struct twe_softc *sc) +{ + struct twe_request *tr; + + debug_called(4); + + /* throw away any command buffers */ + while ((tr = twe_dequeue_free(sc)) != NULL) + twe_free_request(tr); + + /* destroy the data-transfer DMA tag */ + if (sc->twe_buffer_dmat) + bus_dma_tag_destroy(sc->twe_buffer_dmat); + + /* disconnect the interrupt handler */ + if (sc->twe_intr) + bus_teardown_intr(sc->twe_dev, sc->twe_irq, sc->twe_intr); + if (sc->twe_irq != NULL) + bus_release_resource(sc->twe_dev, SYS_RES_IRQ, 0, sc->twe_irq); + + /* destroy the parent DMA tag */ + if (sc->twe_parent_dmat) + bus_dma_tag_destroy(sc->twe_parent_dmat); + + /* release the register window mapping */ + if (sc->twe_io != NULL) + bus_release_resource(sc->twe_dev, SYS_RES_IOPORT, TWE_IO_CONFIG_REG, sc->twe_io); + + /* destroy control device */ + if (sc->twe_dev_t != (dev_t)NULL) + destroy_dev(sc->twe_dev_t); +} + +/******************************************************************************** + * Disconnect from the controller completely, in preparation for unload. + */ +static int +twe_detach(device_t dev) +{ + struct twe_softc *sc = device_get_softc(dev); + int s, error; + + debug_called(4); + + error = EBUSY; + s = splbio(); + if (sc->twe_state & TWE_STATE_OPEN) + goto out; + + /* + * Shut the controller down. + */ + if ((error = twe_shutdown(dev))) + goto out; + + twe_free(sc); + + error = 0; + out: + splx(s); + return(error); +} + +/******************************************************************************** + * Bring the controller down to a dormant state and detach all child devices. + * + * 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 +twe_shutdown(device_t dev) +{ + struct twe_softc *sc = device_get_softc(dev); + int i, s, error; + + debug_called(4); + + s = splbio(); + error = 0; + + /* + * Delete all our child devices. + */ + for (i = 0; i < TWE_MAX_UNITS; i++) { + if (sc->twe_drive[i].td_disk != 0) { + if ((error = device_delete_child(sc->twe_dev, sc->twe_drive[i].td_disk)) != 0) + goto out; + sc->twe_drive[i].td_disk = 0; + } + } + + /* + * Bring the controller down. + */ + twe_deinit(sc); + + out: + splx(s); + return(error); +} + +/******************************************************************************** + * Bring the controller to a quiescent state, ready for system suspend. + */ +static int +twe_suspend(device_t dev) +{ + struct twe_softc *sc = device_get_softc(dev); + int s; + + debug_called(4); + + s = splbio(); + sc->twe_state |= TWE_STATE_SUSPEND; + + twe_disable_interrupts(sc); + splx(s); + + return(0); +} + +/******************************************************************************** + * Bring the controller back to a state ready for operation. + */ +static int +twe_resume(device_t dev) +{ + struct twe_softc *sc = device_get_softc(dev); + + debug_called(4); + + sc->twe_state &= ~TWE_STATE_SUSPEND; + twe_enable_interrupts(sc); + + return(0); +} + +/******************************************************************************* + * Take an interrupt, or be poked by other code to look for interrupt-worthy + * status. + */ +static void +twe_pci_intr(void *arg) +{ + twe_intr((struct twe_softc *)arg); +} + +/******************************************************************************** + * Delayed-startup hook + */ +static void +twe_intrhook(void *arg) +{ + struct twe_softc *sc = (struct twe_softc *)arg; + + /* pull ourselves off the intrhook chain */ + config_intrhook_disestablish(&sc->twe_ich); + + /* call core startup routine */ + twe_init(sc); +} + +/******************************************************************************** + * Given a detected drive, attach it to the bio interface. + * + * This is called from twe_init. + */ +void +twe_attach_drive(struct twe_softc *sc, struct twe_drive *dr) +{ + char buf[80]; + int error; + + dr->td_disk = device_add_child(sc->twe_dev, NULL, -1); + if (dr->td_disk == NULL) { + twe_printf(sc, "device_add_child failed\n"); + return; + } + device_set_ivars(dr->td_disk, dr); + + /* + * XXX It would make sense to test the online/initialising bits, but they seem to be + * always set... + */ + sprintf(buf, "%s, %s", twe_describe_code(twe_table_unittype, dr->td_type), + twe_describe_code(twe_table_unitstate, dr->td_state & TWE_PARAM_UNITSTATUS_MASK)); + device_set_desc_copy(dr->td_disk, buf); + + if ((error = bus_generic_attach(sc->twe_dev)) != 0) + twe_printf(sc, "bus_generic_attach returned %d\n", error); +} + +/******************************************************************************** + ******************************************************************************** + Disk device + ******************************************************************************** + ********************************************************************************/ + +/* + * Disk device softc + */ +struct twed_softc +{ + device_t twed_dev; + dev_t twed_dev_t; + struct twe_softc *twed_controller; /* parent device softc */ + struct twe_drive *twed_drive; /* drive data in parent softc */ + struct disk twed_disk; /* generic disk handle */ + struct devstat twed_stats; /* accounting */ + struct disklabel twed_label; /* synthetic label */ + int twed_flags; +#define TWED_OPEN (1<<0) /* drive is open (can't shut down) */ +}; + +/* + * Disk device bus interface + */ +static int twed_probe(device_t dev); +static int twed_attach(device_t dev); +static int twed_detach(device_t dev); + +static device_method_t twed_methods[] = { + DEVMETHOD(device_probe, twed_probe), + DEVMETHOD(device_attach, twed_attach), + DEVMETHOD(device_detach, twed_detach), + { 0, 0 } +}; + +static driver_t twed_driver = { + "twed", + twed_methods, + sizeof(struct twed_softc) +}; + +static devclass_t twed_devclass; +#ifdef TWE_OVERRIDE +DRIVER_MODULE(Xtwed, Xtwe, twed_driver, twed_devclass, 0, 0); +#else +DRIVER_MODULE(twed, twe, twed_driver, twed_devclass, 0, 0); +#endif + +/* + * Disk device control interface. + */ +static d_open_t twed_open; +static d_close_t twed_close; +static d_strategy_t twed_strategy; + +#define TWED_CDEV_MAJOR 147 + +static struct cdevsw twed_cdevsw = { + twed_open, + twed_close, + physread, + physwrite, + noioctl, + nopoll, + nommap, + twed_strategy, + "twed", + TWED_CDEV_MAJOR, + nodump, + nopsize, + D_DISK, + -1 +}; + +static struct cdevsw tweddisk_cdevsw; +#ifdef FREEBSD_4 +static int disks_registered = 0; +#endif + +/******************************************************************************** + * Handle open from generic layer. + * + * Note that this is typically only called by the diskslice code, and not + * for opens on subdevices (eg. slices, partitions). + */ +static int +twed_open(dev_t dev, int flags, int fmt, struct proc *p) +{ + struct twed_softc *sc = (struct twed_softc *)dev->si_drv1; + struct disklabel *label; + + debug_called(4); + + if (sc == NULL) + return (ENXIO); + + /* check that the controller is up and running */ + if (sc->twed_controller->twe_state & TWE_STATE_SHUTDOWN) + return(ENXIO); + + /* build synthetic label */ + label = &sc->twed_disk.d_label; + bzero(label, sizeof(*label)); + label->d_type = DTYPE_ESDI; + label->d_secsize = TWE_BLOCK_SIZE; + label->d_nsectors = sc->twed_drive->td_sectors; + label->d_ntracks = sc->twed_drive->td_heads; + label->d_ncylinders = sc->twed_drive->td_cylinders; + label->d_secpercyl = sc->twed_drive->td_sectors * sc->twed_drive->td_heads; + label->d_secperunit = sc->twed_drive->td_size; + + sc->twed_flags |= TWED_OPEN; + return (0); +} + +/******************************************************************************** + * Handle last close of the disk device. + */ +static int +twed_close(dev_t dev, int flags, int fmt, struct proc *p) +{ + struct twed_softc *sc = (struct twed_softc *)dev->si_drv1; + + debug_called(4); + + if (sc == NULL) + return (ENXIO); + + sc->twed_flags &= ~TWED_OPEN; + return (0); +} + +/******************************************************************************** + * Handle an I/O request. + */ +static void +twed_strategy(twe_bio *bp) +{ + struct twed_softc *sc = (struct twed_softc *)TWE_BIO_SOFTC(bp); + + debug_called(4); + + /* bogus disk? */ + if (sc == NULL) { + TWE_BIO_SET_ERROR(bp, EINVAL); + return; + } + + /* do-nothing operation? */ + if (TWE_BIO_LENGTH(bp) == 0) { + TWE_BIO_RESID(bp) = 0; + TWE_BIO_DONE(bp); + return; + } + + /* perform accounting */ + TWE_BIO_STATS_START(bp); + + /* pass the bio to the controller - it can work out who we are */ + twe_submit_bio(sc->twed_controller, bp); + return; +} + +/******************************************************************************** + * Handle completion of an I/O request. + */ +void +twed_intr(twe_bio *bp) +{ + debug_called(4); + + /* if no error, transfer completed */ + if (!TWE_BIO_HAS_ERROR(bp)) + TWE_BIO_RESID(bp) = 0; + + TWE_BIO_STATS_END(bp); + TWE_BIO_DONE(bp); +} + +/******************************************************************************** + * Default probe stub. + */ +static int +twed_probe(device_t dev) +{ + return (0); +} + +/******************************************************************************** + * Attach a unit to the controller. + */ +static int +twed_attach(device_t dev) +{ + struct twed_softc *sc; + device_t parent; + dev_t dsk; + + debug_called(4); + + /* initialise our softc */ + sc = device_get_softc(dev); + parent = device_get_parent(dev); + sc->twed_controller = (struct twe_softc *)device_get_softc(parent); + sc->twed_drive = device_get_ivars(dev); + sc->twed_dev = dev; + + /* report the drive */ + twed_printf(sc, "%uMB (%u sectors)\n", + sc->twed_drive->td_size / ((1024 * 1024) / TWE_BLOCK_SIZE), + sc->twed_drive->td_size); + + devstat_add_entry(&sc->twed_stats, "twed", device_get_unit(dev), TWE_BLOCK_SIZE, + DEVSTAT_NO_ORDERED_TAGS, + DEVSTAT_TYPE_STORARRAY | DEVSTAT_TYPE_IF_OTHER, + DEVSTAT_PRIORITY_ARRAY); + + /* attach a generic disk device to ourselves */ + dsk = disk_create(device_get_unit(dev), &sc->twed_disk, 0, &twed_cdevsw, &tweddisk_cdevsw); + dsk->si_drv1 = sc; + dsk->si_drv2 = &sc->twed_drive->td_unit; + sc->twed_dev_t = dsk; +#ifdef FREEBSD_4 + disks_registered++; +#endif + + /* set the maximum I/O size to the theoretical maximum allowed by the S/G list size */ + dsk->si_iosize_max = (TWE_MAX_SGL_LENGTH - 1) * PAGE_SIZE; + + return (0); +} + +/******************************************************************************** + * Disconnect ourselves from the system. + */ +static int +twed_detach(device_t dev) +{ + struct twed_softc *sc = (struct twed_softc *)device_get_softc(dev); + + debug_called(4); + + if (sc->twed_flags & TWED_OPEN) + return(EBUSY); + + devstat_remove_entry(&sc->twed_stats); +#ifdef FREEBSD_4 + if (--disks_registered == 0) + cdevsw_remove(&tweddisk_cdevsw); +#else + disk_destroy(sc->twed_dev_t); +#endif + + return(0); +} + +/******************************************************************************** + ******************************************************************************** + Misc + ******************************************************************************** + ********************************************************************************/ + +static void twe_setup_data_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error); +static void twe_setup_request_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error); + +/******************************************************************************** + * Allocate a command buffer + */ +MALLOC_DEFINE(TWE_MALLOC_CLASS, "twe commands", "twe commands"); + +struct twe_request * +twe_allocate_request(struct twe_softc *sc) +{ + struct twe_request *tr; + + if ((tr = malloc(sizeof(struct twe_request), TWE_MALLOC_CLASS, M_NOWAIT)) == NULL) + return(NULL); + bzero(tr, sizeof(*tr)); + tr->tr_sc = sc; + if (bus_dmamap_create(sc->twe_buffer_dmat, 0, &tr->tr_cmdmap)) { + twe_free_request(tr); + return(NULL); + } + if (bus_dmamap_create(sc->twe_buffer_dmat, 0, &tr->tr_dmamap)) { + bus_dmamap_destroy(sc->twe_buffer_dmat, tr->tr_cmdmap); + twe_free_request(tr); + return(NULL); + } + return(tr); +} + +/******************************************************************************** + * Permanently discard a command buffer. + */ +void +twe_free_request(struct twe_request *tr) +{ + struct twe_softc *sc = tr->tr_sc; + + debug_called(4); + + bus_dmamap_destroy(sc->twe_buffer_dmat, tr->tr_cmdmap); + bus_dmamap_destroy(sc->twe_buffer_dmat, tr->tr_dmamap); + free(tr, TWE_MALLOC_CLASS); +} + +/******************************************************************************** + * Map/unmap (tr)'s command and data in the controller's addressable space. + * + * These routines ensure that the data which the controller is going to try to + * access is actually visible to the controller, in a machine-independant + * fasion. Due to a hardware limitation, I/O buffers must be 512-byte aligned + * and we take care of that here as well. + */ +static void +twe_setup_data_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error) +{ + struct twe_request *tr = (struct twe_request *)arg; + TWE_Command *cmd = &tr->tr_command; + int i; + + debug_called(4); + + /* save base of first segment in command (applicable if there only one segment) */ + tr->tr_dataphys = segs[0].ds_addr; + + /* correct command size for s/g list size */ + tr->tr_command.generic.size += 2 * nsegments; + + /* + * Due to the fact that parameter and I/O commands have the scatter/gather list in + * different places, we need to determine which sort of command this actually is + * before we can populate it correctly. + */ + switch(cmd->generic.opcode) { + case TWE_OP_GET_PARAM: + case TWE_OP_SET_PARAM: + cmd->generic.sgl_offset = 2; + for (i = 0; i < nsegments; i++) { + cmd->param.sgl[i].address = segs[i].ds_addr; + cmd->param.sgl[i].length = segs[i].ds_len; + } + for (; i < TWE_MAX_SGL_LENGTH; i++) { /* XXX necessary? */ + cmd->param.sgl[i].address = 0; + cmd->param.sgl[i].length = 0; + } + break; + case TWE_OP_READ: + case TWE_OP_WRITE: + cmd->generic.sgl_offset = 3; + for (i = 0; i < nsegments; i++) { + cmd->io.sgl[i].address = segs[i].ds_addr; + cmd->io.sgl[i].length = segs[i].ds_len; + } + for (; i < TWE_MAX_SGL_LENGTH; i++) { /* XXX necessary? */ + cmd->io.sgl[i].address = 0; + cmd->io.sgl[i].length = 0; + } + break; + default: + /* no s/g list, nothing to do */ + } +} + +static void +twe_setup_request_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error) +{ + struct twe_request *tr = (struct twe_request *)arg; + + debug_called(4); + + /* command can't cross a page boundary */ + tr->tr_cmdphys = segs[0].ds_addr; +} + +void +twe_map_request(struct twe_request *tr) +{ + struct twe_softc *sc = tr->tr_sc; + + debug_called(4); + + + /* + * Map the command into bus space. + */ + bus_dmamap_load(sc->twe_buffer_dmat, tr->tr_cmdmap, &tr->tr_command, sizeof(tr->tr_command), + twe_setup_request_dmamap, tr, 0); + bus_dmamap_sync(sc->twe_buffer_dmat, tr->tr_cmdmap, BUS_DMASYNC_PREWRITE); + + /* + * If the command involves data, map that too. + */ + if (tr->tr_data != NULL) { + + /* + * Data must be 64-byte aligned; allocate a fixup buffer if it's not. + */ + if (((vm_offset_t)tr->tr_data % TWE_ALIGNMENT) != 0) { + tr->tr_realdata = tr->tr_data; /* save pointer to 'real' data */ + tr->tr_flags |= TWE_CMD_ALIGNBUF; + tr->tr_data = malloc(tr->tr_length, TWE_MALLOC_CLASS, M_NOWAIT); /* XXX check result here */ + } + + /* + * Map the data buffer into bus space and build the s/g list. + */ + bus_dmamap_load(sc->twe_buffer_dmat, tr->tr_dmamap, tr->tr_data, tr->tr_length, + twe_setup_data_dmamap, tr, 0); + if (tr->tr_flags & TWE_CMD_DATAIN) + bus_dmamap_sync(sc->twe_buffer_dmat, tr->tr_dmamap, BUS_DMASYNC_PREREAD); + if (tr->tr_flags & TWE_CMD_DATAOUT) { + /* if we're using an alignment buffer, and we're writing data, copy the real data out */ + if (tr->tr_flags & TWE_CMD_ALIGNBUF) + bcopy(tr->tr_realdata, tr->tr_data, tr->tr_length); + bus_dmamap_sync(sc->twe_buffer_dmat, tr->tr_dmamap, BUS_DMASYNC_PREWRITE); + } + } +} + +void +twe_unmap_request(struct twe_request *tr) +{ + struct twe_softc *sc = tr->tr_sc; + + debug_called(4); + + /* + * Unmap the command from bus space. + */ + bus_dmamap_sync(sc->twe_buffer_dmat, tr->tr_cmdmap, BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->twe_buffer_dmat, tr->tr_cmdmap); + + /* + * If the command involved data, unmap that too. + */ + if (tr->tr_data != NULL) { + + if (tr->tr_flags & TWE_CMD_DATAIN) { + bus_dmamap_sync(sc->twe_buffer_dmat, tr->tr_dmamap, BUS_DMASYNC_POSTREAD); + /* if we're using an alignment buffer, and we're reading data, copy the real data in */ + if (tr->tr_flags & TWE_CMD_ALIGNBUF) + bcopy(tr->tr_data, tr->tr_realdata, tr->tr_length); + } + if (tr->tr_flags & TWE_CMD_DATAOUT) + bus_dmamap_sync(sc->twe_buffer_dmat, tr->tr_dmamap, BUS_DMASYNC_POSTWRITE); + + bus_dmamap_unload(sc->twe_buffer_dmat, tr->tr_dmamap); + } + + /* free alignment buffer if it was used */ + if (tr->tr_flags & TWE_CMD_ALIGNBUF) { + free(tr->tr_data, TWE_MALLOC_CLASS); + tr->tr_data = tr->tr_realdata; /* restore 'real' data pointer */ + } +} + +#ifdef TWE_DEBUG +/******************************************************************************** + * Print current controller status, call from DDB. + */ +void +twe_report(void) +{ + struct twe_softc *sc; + int i, s; + + s = splbio(); + for (i = 0; (sc = devclass_get_softc(twe_devclass, i)) != NULL; i++) + twe_print_controller(sc); + splx(s); +} +#endif |