diff options
author | mav <mav@FreeBSD.org> | 2009-07-21 12:32:46 +0000 |
---|---|---|
committer | mav <mav@FreeBSD.org> | 2009-07-21 12:32:46 +0000 |
commit | cbc212df17ec09c15616d874d1941f88e7a7e284 (patch) | |
tree | 72833423bf0f867330cba1275070a8231a4b6ea9 /sys/dev/siis | |
parent | 178f9ce8d28a332ea9e50d983f095644b29c1a9c (diff) | |
download | FreeBSD-src-cbc212df17ec09c15616d874d1941f88e7a7e284.zip FreeBSD-src-cbc212df17ec09c15616d874d1941f88e7a7e284.tar.gz |
Add `siis` CAM driver for SiliconImage SiI3124/3132/3531 SATA2 controllers.
Driver supports Serial ATA and ATAPI devices, Port Multipliers
(including FIS-based switching), hardware command queues (31 command
per port) and Native Command Queuing. This is probably the second on
popularity, after AHCI, type of SATA2 controllers, that benefits from
using CAM, because of hardware command queuing support.
Approved by: re (kib)
Diffstat (limited to 'sys/dev/siis')
-rw-r--r-- | sys/dev/siis/siis.c | 1572 | ||||
-rw-r--r-- | sys/dev/siis/siis.h | 445 |
2 files changed, 2017 insertions, 0 deletions
diff --git a/sys/dev/siis/siis.c b/sys/dev/siis/siis.c new file mode 100644 index 0000000..92b8b70 --- /dev/null +++ b/sys/dev/siis/siis.c @@ -0,0 +1,1572 @@ +/*- + * Copyright (c) 2009 Alexander Motin <mav@FreeBSD.org> + * 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, + * without modification, immediately at the beginning of the file. + * 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 ``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 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/module.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/ata.h> +#include <sys/bus.h> +#include <sys/endian.h> +#include <sys/malloc.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/sema.h> +#include <sys/taskqueue.h> +#include <vm/uma.h> +#include <machine/stdarg.h> +#include <machine/resource.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcireg.h> +#include "siis.h" + +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_sim.h> +#include <cam/cam_xpt_sim.h> +#include <cam/cam_xpt_periph.h> +#include <cam/cam_debug.h> + +/* local prototypes */ +static int siis_setup_interrupt(device_t dev); +static void siis_intr(void *data); +static int siis_suspend(device_t dev); +static int siis_resume(device_t dev); +static int siis_ch_suspend(device_t dev); +static int siis_ch_resume(device_t dev); +static void siis_ch_intr_locked(void *data); +static void siis_ch_intr(void *data); +static void siis_begin_transaction(device_t dev, union ccb *ccb); +static void siis_dmasetprd(void *arg, bus_dma_segment_t *segs, int nsegs, int error); +static void siis_execute_transaction(struct siis_slot *slot); +static void siis_timeout(struct siis_slot *slot); +static void siis_end_transaction(struct siis_slot *slot, enum siis_err_type et); +static int siis_setup_fis(struct siis_cmd *ctp, union ccb *ccb, int tag); +static void siis_dmainit(device_t dev); +static void siis_dmasetupc_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int error); +static void siis_dmafini(device_t dev); +static void siis_slotsalloc(device_t dev); +static void siis_slotsfree(device_t dev); +static void siis_reset(device_t dev); +static void siis_portinit(device_t dev); +static int siis_wait_ready(device_t dev, int t); + +static int siis_sata_connect(struct siis_channel *ch); +static int siis_sata_phy_reset(device_t dev); + +static void siis_issue_read_log(device_t dev); +static void siis_process_read_log(device_t dev, union ccb *ccb); + +static void siisaction(struct cam_sim *sim, union ccb *ccb); +static void siispoll(struct cam_sim *sim); + +MALLOC_DEFINE(M_SIIS, "SIIS driver", "SIIS driver data buffers"); + +static int +siis_probe(device_t dev) +{ + uint32_t devid = pci_get_devid(dev); + + if (devid == SIIS_SII3124) { + device_set_desc_copy(dev, "SiI3124 SATA2 controller"); + } else if (devid == SIIS_SII3132 || + devid == SIIS_SII3132_1 || + devid == SIIS_SII3132_2) { + device_set_desc_copy(dev, "SiI3132 SATA2 controller"); + } else if (devid == SIIS_SII3531) { + device_set_desc_copy(dev, "SiI3531 SATA2 controller"); + } else { + return (ENXIO); + } + + return (BUS_PROBE_VENDOR); +} + +static int +siis_attach(device_t dev) +{ + struct siis_controller *ctlr = device_get_softc(dev); + uint32_t devid = pci_get_devid(dev); + device_t child; + int error, unit; + + ctlr->dev = dev; + /* Global memory */ + ctlr->r_grid = PCIR_BAR(0); + if (!(ctlr->r_gmem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &ctlr->r_grid, RF_ACTIVE))) + return (ENXIO); + /* Channels memory */ + ctlr->r_rid = PCIR_BAR(2); + if (!(ctlr->r_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &ctlr->r_rid, RF_ACTIVE))) + return (ENXIO); + /* Setup our own memory management for channels. */ + ctlr->sc_iomem.rm_type = RMAN_ARRAY; + ctlr->sc_iomem.rm_descr = "I/O memory addresses"; + if ((error = rman_init(&ctlr->sc_iomem)) != 0) { + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_grid, ctlr->r_gmem); + return (error); + } + if ((error = rman_manage_region(&ctlr->sc_iomem, + rman_get_start(ctlr->r_mem), rman_get_end(ctlr->r_mem))) != 0) { + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_grid, ctlr->r_gmem); + rman_fini(&ctlr->sc_iomem); + return (error); + } + /* Reset controller */ + siis_resume(dev); + /* Number of HW channels */ + ctlr->channels = (devid == SIIS_SII3124) ? 4 : + (devid == SIIS_SII3531 ? 1 : 2); + /* Setup interrupts. */ + if (siis_setup_interrupt(dev)) { + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_grid, ctlr->r_gmem); + rman_fini(&ctlr->sc_iomem); + return ENXIO; + } + /* Attach all channels on this controller */ + for (unit = 0; unit < ctlr->channels; unit++) { + child = device_add_child(dev, "siisch", -1); + if (child == NULL) + device_printf(dev, "failed to add channel device\n"); + else + device_set_ivars(child, (void *)(intptr_t)unit); + } + bus_generic_attach(dev); + return 0; +} + +static int +siis_detach(device_t dev) +{ + struct siis_controller *ctlr = device_get_softc(dev); + device_t *children; + int nchildren, i; + + /* Detach & delete all children */ + if (!device_get_children(dev, &children, &nchildren)) { + for (i = 0; i < nchildren; i++) + device_delete_child(dev, children[i]); + free(children, M_TEMP); + } + /* Free interrupts. */ + if (ctlr->irq.r_irq) { + bus_teardown_intr(dev, ctlr->irq.r_irq, + ctlr->irq.handle); + bus_release_resource(dev, SYS_RES_IRQ, + ctlr->irq.r_irq_rid, ctlr->irq.r_irq); + } + pci_release_msi(dev); + /* Free memory. */ + rman_fini(&ctlr->sc_iomem); + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_grid, ctlr->r_gmem); + return (0); +} + +static int +siis_suspend(device_t dev) +{ + struct siis_controller *ctlr = device_get_softc(dev); + + bus_generic_suspend(dev); + /* Put controller into reset state. */ + ATA_OUTL(ctlr->r_gmem, SIIS_GCTL, SIIS_GCTL_GRESET); + return 0; +} + +static int +siis_resume(device_t dev) +{ + struct siis_controller *ctlr = device_get_softc(dev); + + /* Put controller into reset state. */ + ATA_OUTL(ctlr->r_gmem, SIIS_GCTL, SIIS_GCTL_GRESET); + DELAY(10000); + /* Get controller out of reset state and enable port interrupts. */ + ATA_OUTL(ctlr->r_gmem, SIIS_GCTL, 0x0000000f); + return (bus_generic_resume(dev)); +} + +static int +siis_setup_interrupt(device_t dev) +{ + struct siis_controller *ctlr = device_get_softc(dev); + int msi = 0; + + /* Process hints. */ + resource_int_value(device_get_name(dev), + device_get_unit(dev), "msi", &msi); + if (msi < 0) + msi = 0; + else if (msi > 0) + msi = min(1, pci_msi_count(dev)); + /* Allocate MSI if needed/present. */ + if (msi && pci_alloc_msi(dev, &msi) != 0) + msi = 0; + /* Allocate all IRQs. */ + ctlr->irq.r_irq_rid = msi ? 1 : 0; + if (!(ctlr->irq.r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, + &ctlr->irq.r_irq_rid, RF_SHAREABLE | RF_ACTIVE))) { + device_printf(dev, "unable to map interrupt\n"); + return ENXIO; + } + if ((bus_setup_intr(dev, ctlr->irq.r_irq, ATA_INTR_FLAGS, NULL, + siis_intr, ctlr, &ctlr->irq.handle))) { + /* SOS XXX release r_irq */ + device_printf(dev, "unable to setup interrupt\n"); + return ENXIO; + } + return (0); +} + +/* + * Common case interrupt handler. + */ +static void +siis_intr(void *data) +{ + struct siis_controller *ctlr = (struct siis_controller *)data; + u_int32_t is; + void *arg; + int unit; + + is = ATA_INL(ctlr->r_gmem, SIIS_IS); + for (unit = 0; unit < ctlr->channels; unit++) { + if ((is & SIIS_IS_PORT(unit)) != 0 && + (arg = ctlr->interrupt[unit].argument)) { + ctlr->interrupt[unit].function(arg); + } + } +} + +static struct resource * +siis_alloc_resource(device_t dev, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + struct siis_controller *ctlr = device_get_softc(dev); + int unit = ((struct siis_channel *)device_get_softc(child))->unit; + struct resource *res = NULL; + int offset = unit << 13; + long st; + + switch (type) { + case SYS_RES_MEMORY: + st = rman_get_start(ctlr->r_mem); + res = rman_reserve_resource(&ctlr->sc_iomem, st + offset, + st + offset + 0x2000, 0x2000, RF_ACTIVE, child); + if (res) { + bus_space_handle_t bsh; + bus_space_tag_t bst; + bsh = rman_get_bushandle(ctlr->r_mem); + bst = rman_get_bustag(ctlr->r_mem); + bus_space_subregion(bst, bsh, offset, 0x2000, &bsh); + rman_set_bushandle(res, bsh); + rman_set_bustag(res, bst); + } + break; + case SYS_RES_IRQ: + if (*rid == ATA_IRQ_RID) + res = ctlr->irq.r_irq; + break; + } + return (res); +} + +static int +siis_release_resource(device_t dev, device_t child, int type, int rid, + struct resource *r) +{ + + switch (type) { + case SYS_RES_MEMORY: + rman_release_resource(r); + return (0); + case SYS_RES_IRQ: + if (rid != ATA_IRQ_RID) + return ENOENT; + return (0); + } + return (EINVAL); +} + +static int +siis_setup_intr(device_t dev, device_t child, struct resource *irq, + int flags, driver_filter_t *filter, driver_intr_t *function, + void *argument, void **cookiep) +{ + struct siis_controller *ctlr = device_get_softc(dev); + int unit = (intptr_t)device_get_ivars(child); + + if (filter != NULL) { + printf("siis.c: we cannot use a filter here\n"); + return (EINVAL); + } + ctlr->interrupt[unit].function = function; + ctlr->interrupt[unit].argument = argument; + return (0); +} + +static int +siis_teardown_intr(device_t dev, device_t child, struct resource *irq, + void *cookie) +{ + struct siis_controller *ctlr = device_get_softc(dev); + int unit = (intptr_t)device_get_ivars(child); + + ctlr->interrupt[unit].function = NULL; + ctlr->interrupt[unit].argument = NULL; + return (0); +} + +static int +siis_print_child(device_t dev, device_t child) +{ + int retval; + + retval = bus_print_child_header(dev, child); + retval += printf(" at channel %d", + (int)(intptr_t)device_get_ivars(child)); + retval += bus_print_child_footer(dev, child); + + return (retval); +} + +devclass_t siis_devclass; +static device_method_t siis_methods[] = { + DEVMETHOD(device_probe, siis_probe), + DEVMETHOD(device_attach, siis_attach), + DEVMETHOD(device_detach, siis_detach), + DEVMETHOD(device_suspend, siis_suspend), + DEVMETHOD(device_resume, siis_resume), + DEVMETHOD(bus_print_child, siis_print_child), + DEVMETHOD(bus_alloc_resource, siis_alloc_resource), + DEVMETHOD(bus_release_resource, siis_release_resource), + DEVMETHOD(bus_setup_intr, siis_setup_intr), + DEVMETHOD(bus_teardown_intr,siis_teardown_intr), + { 0, 0 } +}; +static driver_t siis_driver = { + "siis", + siis_methods, + sizeof(struct siis_controller) +}; +DRIVER_MODULE(siis, pci, siis_driver, siis_devclass, 0, 0); +MODULE_VERSION(siis, 1); +MODULE_DEPEND(siis, cam, 1, 1, 1); + +static int +siis_ch_probe(device_t dev) +{ + + device_set_desc_copy(dev, "SIIS channel"); + return (0); +} + +static int +siis_ch_attach(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + struct cam_devq *devq; + int rid, error; + + ch->dev = dev; + ch->unit = (intptr_t)device_get_ivars(dev); + resource_int_value(device_get_name(dev), + device_get_unit(dev), "pm_level", &ch->pm_level); + resource_int_value(device_get_name(dev), + device_get_unit(dev), "sata_rev", &ch->sata_rev); + mtx_init(&ch->mtx, "SIIS channel lock", NULL, MTX_DEF); + rid = ch->unit; + if (!(ch->r_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &rid, RF_ACTIVE))) + return (ENXIO); + siis_dmainit(dev); + siis_slotsalloc(dev); + siis_ch_resume(dev); + mtx_lock(&ch->mtx); + rid = ATA_IRQ_RID; + if (!(ch->r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, + &rid, RF_SHAREABLE | RF_ACTIVE))) { + bus_release_resource(dev, SYS_RES_MEMORY, ch->unit, ch->r_mem); + device_printf(dev, "Unable to map interrupt\n"); + return (ENXIO); + } + if ((bus_setup_intr(dev, ch->r_irq, ATA_INTR_FLAGS, NULL, + siis_ch_intr_locked, dev, &ch->ih))) { + device_printf(dev, "Unable to setup interrupt\n"); + error = ENXIO; + goto err1; + } + /* Create the device queue for our SIM. */ + devq = cam_simq_alloc(SIIS_MAX_SLOTS); + if (devq == NULL) { + device_printf(dev, "Unable to allocate simq\n"); + error = ENOMEM; + goto err1; + } + /* Construct SIM entry */ + ch->sim = cam_sim_alloc(siisaction, siispoll, "siisch", ch, + device_get_unit(dev), &ch->mtx, SIIS_MAX_SLOTS, 0, devq); + if (ch->sim == NULL) { + device_printf(dev, "unable to allocate sim\n"); + error = ENOMEM; + goto err2; + } + if (xpt_bus_register(ch->sim, dev, 0) != CAM_SUCCESS) { + device_printf(dev, "unable to register xpt bus\n"); + error = ENXIO; + goto err2; + } + if (xpt_create_path(&ch->path, /*periph*/NULL, cam_sim_path(ch->sim), + CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { + device_printf(dev, "unable to create path\n"); + error = ENXIO; + goto err3; + } + mtx_unlock(&ch->mtx); + return (0); + +err3: + xpt_bus_deregister(cam_sim_path(ch->sim)); +err2: + cam_sim_free(ch->sim, /*free_devq*/TRUE); +err1: + bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq); + bus_release_resource(dev, SYS_RES_MEMORY, ch->unit, ch->r_mem); + mtx_unlock(&ch->mtx); + return (error); +} + +static int +siis_ch_detach(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + + mtx_lock(&ch->mtx); + xpt_async(AC_LOST_DEVICE, ch->path, NULL); + xpt_free_path(ch->path); + xpt_bus_deregister(cam_sim_path(ch->sim)); + cam_sim_free(ch->sim, /*free_devq*/TRUE); + mtx_unlock(&ch->mtx); + + bus_teardown_intr(dev, ch->r_irq, ch->ih); + bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq); + + siis_ch_suspend(dev); + siis_slotsfree(dev); + siis_dmafini(dev); + + bus_release_resource(dev, SYS_RES_MEMORY, ch->unit, ch->r_mem); + mtx_destroy(&ch->mtx); + return (0); +} + +static int +siis_ch_suspend(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + + /* Put port into reset state. */ + ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_PORT_RESET); + return (0); +} + +static int +siis_ch_resume(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + + /* Get port out of reset state. */ + ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_PORT_RESET); + ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_32BIT); + ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_PME); + /* Enable port interrupts */ + ATA_OUTL(ch->r_mem, SIIS_P_IESET, SIIS_P_IX_ENABLED); + return (0); +} + +devclass_t siisch_devclass; +static device_method_t siisch_methods[] = { + DEVMETHOD(device_probe, siis_ch_probe), + DEVMETHOD(device_attach, siis_ch_attach), + DEVMETHOD(device_detach, siis_ch_detach), + DEVMETHOD(device_suspend, siis_ch_suspend), + DEVMETHOD(device_resume, siis_ch_resume), + { 0, 0 } +}; +static driver_t siisch_driver = { + "siisch", + siisch_methods, + sizeof(struct siis_channel) +}; +DRIVER_MODULE(siisch, siis, siisch_driver, siis_devclass, 0, 0); + +struct siis_dc_cb_args { + bus_addr_t maddr; + int error; +}; + +static void +siis_dmainit(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + struct siis_dc_cb_args dcba; + + /* Command area. */ + if (bus_dma_tag_create(bus_get_dma_tag(dev), 1024, 0, + BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, + NULL, NULL, SIIS_WORK_SIZE, 1, SIIS_WORK_SIZE, + 0, NULL, NULL, &ch->dma.work_tag)) + goto error; + if (bus_dmamem_alloc(ch->dma.work_tag, (void **)&ch->dma.work, 0, + &ch->dma.work_map)) + goto error; + if (bus_dmamap_load(ch->dma.work_tag, ch->dma.work_map, ch->dma.work, + SIIS_WORK_SIZE, siis_dmasetupc_cb, &dcba, 0) || dcba.error) { + bus_dmamem_free(ch->dma.work_tag, ch->dma.work, ch->dma.work_map); + goto error; + } + ch->dma.work_bus = dcba.maddr; + /* Data area. */ + if (bus_dma_tag_create(bus_get_dma_tag(dev), 2, 0, + BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, + NULL, NULL, + SIIS_SG_ENTRIES * PAGE_SIZE * SIIS_MAX_SLOTS, + SIIS_SG_ENTRIES, 0xFFFFFFFF, + 0, busdma_lock_mutex, &ch->mtx, &ch->dma.data_tag)) { + goto error; + } + return; + +error: + device_printf(dev, "WARNING - DMA initialization failed\n"); + siis_dmafini(dev); +} + +static void +siis_dmasetupc_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int error) +{ + struct siis_dc_cb_args *dcba = (struct siis_dc_cb_args *)xsc; + + if (!(dcba->error = error)) + dcba->maddr = segs[0].ds_addr; +} + +static void +siis_dmafini(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + + if (ch->dma.data_tag) { + bus_dma_tag_destroy(ch->dma.data_tag); + ch->dma.data_tag = NULL; + } + if (ch->dma.work_bus) { + bus_dmamap_unload(ch->dma.work_tag, ch->dma.work_map); + bus_dmamem_free(ch->dma.work_tag, ch->dma.work, ch->dma.work_map); + ch->dma.work_bus = 0; + ch->dma.work_map = NULL; + ch->dma.work = NULL; + } + if (ch->dma.work_tag) { + bus_dma_tag_destroy(ch->dma.work_tag); + ch->dma.work_tag = NULL; + } +} + +static void +siis_slotsalloc(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + int i; + + /* Alloc and setup command/dma slots */ + bzero(ch->slot, sizeof(ch->slot)); + for (i = 0; i < SIIS_MAX_SLOTS; i++) { + struct siis_slot *slot = &ch->slot[i]; + + slot->dev = dev; + slot->slot = i; + slot->state = SIIS_SLOT_EMPTY; + slot->ccb = NULL; + callout_init_mtx(&slot->timeout, &ch->mtx, 0); + + if (bus_dmamap_create(ch->dma.data_tag, 0, &slot->dma.data_map)) + device_printf(ch->dev, "FAILURE - create data_map\n"); + } +} + +static void +siis_slotsfree(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + int i; + + /* Free all dma slots */ + for (i = 0; i < SIIS_MAX_SLOTS; i++) { + struct siis_slot *slot = &ch->slot[i]; + + if (slot->dma.data_map) { + bus_dmamap_destroy(ch->dma.data_tag, slot->dma.data_map); + slot->dma.data_map = NULL; + } + } +} + +static void +siis_phy_check_events(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + + /* If we have a connection event, deal with it */ + if (ch->pm_level == 0) { + u_int32_t status = ATA_INL(ch->r_mem, SIIS_P_SSTS); + if (((status & ATA_SS_DET_MASK) == ATA_SS_DET_PHY_ONLINE) && + ((status & ATA_SS_SPD_MASK) != ATA_SS_SPD_NO_SPEED) && + ((status & ATA_SS_IPM_MASK) == ATA_SS_IPM_ACTIVE)) { + if (bootverbose) + device_printf(dev, "CONNECT requested\n"); + siis_reset(dev); + } else { + if (bootverbose) + device_printf(dev, "DISCONNECT requested\n"); + ch->devices = 0; + } + } +} + +static void +siis_ch_intr_locked(void *data) +{ + device_t dev = (device_t)data; + struct siis_channel *ch = device_get_softc(dev); + + mtx_lock(&ch->mtx); + siis_ch_intr(data); + mtx_unlock(&ch->mtx); +} + +static void +siis_ch_intr(void *data) +{ + device_t dev = (device_t)data; + struct siis_channel *ch = device_get_softc(dev); + uint32_t istatus, sstatus, ctx, estatus, ok, err = 0; + enum siis_err_type et; + int i, ccs, port, tslots; + + mtx_assert(&ch->mtx, MA_OWNED); + /* Read command statuses. */ + sstatus = ATA_INL(ch->r_mem, SIIS_P_SS); + ok = ch->rslots & ~sstatus; + /* Complete all successfull commands. */ + for (i = 0; i < SIIS_MAX_SLOTS; i++) { + if ((ok >> i) & 1) + siis_end_transaction(&ch->slot[i], SIIS_ERR_NONE); + } + /* Do we have any other events? */ + if ((sstatus & SIIS_P_SS_ATTN) == 0) + return; + /* Read and clear interrupt statuses. */ + istatus = ATA_INL(ch->r_mem, SIIS_P_IS) & + (0xFFFF & ~SIIS_P_IX_COMMCOMP); + ATA_OUTL(ch->r_mem, SIIS_P_IS, istatus); + /* Process PHY events */ + if (istatus & SIIS_P_IX_PHYRDYCHG) + siis_phy_check_events(dev); + /* Process command errors */ + if (istatus & SIIS_P_IX_COMMERR) { + estatus = ATA_INL(ch->r_mem, SIIS_P_CMDERR); + ctx = ATA_INL(ch->r_mem, SIIS_P_CTX); + ccs = (ctx & SIIS_P_CTX_SLOT) >> SIIS_P_CTX_SLOT_SHIFT; + port = (ctx & SIIS_P_CTX_PMP) >> SIIS_P_CTX_PMP_SHIFT; + err = ch->rslots & sstatus; +//device_printf(dev, "%s ERROR ss %08x is %08x rs %08x es %d act %d port %d serr %08x\n", +// __func__, sstatus, istatus, ch->rslots, estatus, ccs, port, +// ATA_INL(ch->r_mem, SIIS_P_SERR)); + + if (!ch->readlog && !ch->recovery) { + xpt_freeze_simq(ch->sim, ch->numrslots); + ch->recovery = 1; + } + if (ch->frozen) { + union ccb *fccb = ch->frozen; + ch->frozen = NULL; + fccb->ccb_h.status = CAM_REQUEUE_REQ | CAM_RELEASE_SIMQ; + xpt_done(fccb); + } + if (estatus == SIIS_P_CMDERR_DEV || + estatus == SIIS_P_CMDERR_SDB || + estatus == SIIS_P_CMDERR_DATAFIS) { + tslots = ch->numtslots[port]; + for (i = 0; i < SIIS_MAX_SLOTS; i++) { + /* XXX: reqests in loading state. */ + if (((ch->rslots >> i) & 1) == 0) + continue; + if (ch->slot[i].ccb->ccb_h.target_id != port) + continue; + if (tslots == 0) { + /* Untagged operation. */ + if (i == ccs) + et = SIIS_ERR_TFE; + else + et = SIIS_ERR_INNOCENT; + } else { + /* Tagged operation. */ + et = SIIS_ERR_NCQ; + } + siis_end_transaction(&ch->slot[i], et); + } + /* + * We can't reinit port if there are some other + * commands active, use resume to complete them. + */ + if (ch->rslots != 0) + ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_RESUME); + } else { + if (estatus == SIIS_P_CMDERR_SENDFIS || + estatus == SIIS_P_CMDERR_INCSTATE || + estatus == SIIS_P_CMDERR_PPE || + estatus == SIIS_P_CMDERR_SERVICE) { + et = SIIS_ERR_SATA; + } else + et = SIIS_ERR_INVALID; + for (i = 0; i < SIIS_MAX_SLOTS; i++) { + /* XXX: reqests in loading state. */ + if (((ch->rslots >> i) & 1) == 0) + continue; + siis_end_transaction(&ch->slot[i], et); + } + } + } +} + +/* Must be called with channel locked. */ +static int +siis_check_collision(device_t dev, union ccb *ccb) +{ + struct siis_channel *ch = device_get_softc(dev); + + mtx_assert(&ch->mtx, MA_OWNED); + if ((ccb->ccb_h.func_code == XPT_ATA_IO) && + (ccb->ataio.cmd.flags & (CAM_ATAIO_CONTROL | CAM_ATAIO_NEEDRESULT))) { + /* Atomic command while anything active. */ + if (ch->numrslots != 0) + return (1); + } + /* We have some atomic command running. */ + if (ch->aslots != 0) + return (1); + return (0); +} + +/* Must be called with channel locked. */ +static void +siis_begin_transaction(device_t dev, union ccb *ccb) +{ + struct siis_channel *ch = device_get_softc(dev); + struct siis_slot *slot; + int tag; + + mtx_assert(&ch->mtx, MA_OWNED); + /* Choose empty slot. */ + tag = ch->lastslot; + do { + tag++; + if (tag >= SIIS_MAX_SLOTS) + tag = 0; + if (ch->slot[tag].state == SIIS_SLOT_EMPTY) + break; + } while (tag != ch->lastslot); + if (ch->slot[tag].state != SIIS_SLOT_EMPTY) + device_printf(ch->dev, "ALL SLOTS BUSY!\n"); + ch->lastslot = tag; + /* Occupy chosen slot. */ + slot = &ch->slot[tag]; + slot->ccb = ccb; + /* Update channel stats. */ + ch->numrslots++; + if ((ccb->ccb_h.func_code == XPT_ATA_IO) && + (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) { + ch->numtslots[ccb->ccb_h.target_id]++; + } + if ((ccb->ccb_h.func_code == XPT_ATA_IO) && + (ccb->ataio.cmd.flags & (CAM_ATAIO_CONTROL | CAM_ATAIO_NEEDRESULT))) + ch->aslots |= (1 << slot->slot); + slot->dma.nsegs = 0; + /* If request moves data, setup and load SG list */ + if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { + void *buf; + bus_size_t size; + + slot->state = SIIS_SLOT_LOADING; + if (ccb->ccb_h.func_code == XPT_ATA_IO) { + buf = ccb->ataio.data_ptr; + size = ccb->ataio.dxfer_len; + } else { + buf = ccb->csio.data_ptr; + size = ccb->csio.dxfer_len; + } + bus_dmamap_load(ch->dma.data_tag, slot->dma.data_map, + buf, size, siis_dmasetprd, slot, 0); + } else + siis_execute_transaction(slot); +} + +/* Locked by busdma engine. */ +static void +siis_dmasetprd(void *arg, bus_dma_segment_t *segs, int nsegs, int error) +{ + struct siis_slot *slot = arg; + struct siis_channel *ch = device_get_softc(slot->dev); + struct siis_cmd *ctp; + struct siis_dma_prd *prd; + int i; + + mtx_assert(&ch->mtx, MA_OWNED); + if (error) { + device_printf(slot->dev, "DMA load error\n"); + if (!ch->readlog) + xpt_freeze_simq(ch->sim, 1); + siis_end_transaction(slot, SIIS_ERR_INVALID); + return; + } + KASSERT(nsegs <= SIIS_SG_ENTRIES, ("too many DMA segment entries\n")); + /* Get a piece of the workspace for this request */ + ctp = (struct siis_cmd *) + (ch->dma.work + SIIS_CT_OFFSET + (SIIS_CT_SIZE * slot->slot)); + /* Fill S/G table */ + if (slot->ccb->ccb_h.func_code == XPT_ATA_IO) + prd = &ctp->u.ata.prd[0]; + else + prd = &ctp->u.atapi.prd[0]; + for (i = 0; i < nsegs; i++) { + prd[i].dba = htole64(segs[i].ds_addr); + prd[i].dbc = htole32(segs[i].ds_len); + prd[i].control = 0; + } + prd[nsegs - 1].control = htole32(SIIS_PRD_TRM); + slot->dma.nsegs = nsegs; + bus_dmamap_sync(ch->dma.data_tag, slot->dma.data_map, + ((slot->ccb->ccb_h.flags & CAM_DIR_IN) ? + BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE)); + siis_execute_transaction(slot); +} + +/* Must be called with channel locked. */ +static void +siis_execute_transaction(struct siis_slot *slot) +{ + device_t dev = slot->dev; + struct siis_channel *ch = device_get_softc(dev); + struct siis_cmd *ctp; + union ccb *ccb = slot->ccb; + u_int64_t prb_bus; + + mtx_assert(&ch->mtx, MA_OWNED); + /* Get a piece of the workspace for this request */ + ctp = (struct siis_cmd *) + (ch->dma.work + SIIS_CT_OFFSET + (SIIS_CT_SIZE * slot->slot)); + ctp->control = 0; + ctp->protocol_override = 0; + ctp->transfer_count = 0; + /* Special handling for Soft Reset command. */ + if ((ccb->ccb_h.func_code == XPT_ATA_IO) && + (ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL)) { + ctp->control |= htole16(SIIS_PRB_SOFT_RESET); + } else if (ccb->ccb_h.func_code == XPT_SCSI_IO) { + if (ccb->ccb_h.flags & CAM_DIR_IN) + ctp->control |= htole16(SIIS_PRB_PACKET_READ); + if (ccb->ccb_h.flags & CAM_DIR_OUT) + ctp->control |= htole16(SIIS_PRB_PACKET_WRITE); + } + /* Setup the FIS for this request */ + if (!siis_setup_fis(ctp, ccb, slot->slot)) { + device_printf(ch->dev, "Setting up SATA FIS failed\n"); + if (!ch->readlog) + xpt_freeze_simq(ch->sim, 1); + siis_end_transaction(slot, SIIS_ERR_INVALID); + return; + } + bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, + BUS_DMASYNC_PREWRITE); + /* Issue command to the controller. */ + slot->state = SIIS_SLOT_RUNNING; + ch->rslots |= (1 << slot->slot); + prb_bus = ch->dma.work_bus + + SIIS_CT_OFFSET + (SIIS_CT_SIZE * slot->slot); + ATA_OUTL(ch->r_mem, SIIS_P_CACTL(slot->slot), prb_bus); + ATA_OUTL(ch->r_mem, SIIS_P_CACTH(slot->slot), prb_bus >> 32); + /* Start command execution timeout */ + callout_reset(&slot->timeout, (int)ccb->ccb_h.timeout * hz / 1000, + (timeout_t*)siis_timeout, slot); + return; +} + +/* Locked by callout mechanism. */ +static void +siis_timeout(struct siis_slot *slot) +{ + device_t dev = slot->dev; + struct siis_channel *ch = device_get_softc(dev); + int i; + + mtx_assert(&ch->mtx, MA_OWNED); + device_printf(dev, "Timeout on slot %d\n", slot->slot); +device_printf(dev, "%s is %08x ss %08x rs %08x es %08x sts %08x serr %08x\n", + __func__, ATA_INL(ch->r_mem, SIIS_P_IS), ATA_INL(ch->r_mem, SIIS_P_SS), ch->rslots, + ATA_INL(ch->r_mem, SIIS_P_CMDERR), ATA_INL(ch->r_mem, SIIS_P_STS), + ATA_INL(ch->r_mem, SIIS_P_SERR)); + /* Kick controller into sane state. */ + siis_portinit(ch->dev); + + if (!ch->readlog) + xpt_freeze_simq(ch->sim, ch->numrslots); + /* Handle command with timeout. */ + siis_end_transaction(&ch->slot[slot->slot], SIIS_ERR_TIMEOUT); + /* Handle the rest of commands. */ + if (ch->frozen) { + union ccb *fccb = ch->frozen; + ch->frozen = NULL; + fccb->ccb_h.status = CAM_REQUEUE_REQ | CAM_RELEASE_SIMQ; + xpt_done(fccb); + } + for (i = 0; i < SIIS_MAX_SLOTS; i++) { + /* Do we have a running request on slot? */ + if (ch->slot[i].state < SIIS_SLOT_RUNNING) + continue; + siis_end_transaction(&ch->slot[i], SIIS_ERR_INNOCENT); + } +} + +/* Must be called with channel locked. */ +static void +siis_end_transaction(struct siis_slot *slot, enum siis_err_type et) +{ + device_t dev = slot->dev; + struct siis_channel *ch = device_get_softc(dev); + union ccb *ccb = slot->ccb; + + mtx_assert(&ch->mtx, MA_OWNED); + /* Cancel command execution timeout */ + callout_stop(&slot->timeout); + bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, + BUS_DMASYNC_POSTWRITE); + /* Read result registers to the result struct + * May be incorrect if several commands finished same time, + * so read only when sure or have to. + */ + if (ccb->ccb_h.func_code == XPT_ATA_IO) { + struct ata_res *res = &ccb->ataio.res; + if ((et == SIIS_ERR_TFE) || + (ccb->ataio.cmd.flags & CAM_ATAIO_NEEDRESULT)) { + int offs = SIIS_P_LRAM_SLOT(slot->slot) + 8; + + res->status = ATA_INB(ch->r_mem, offs + 2); + res->error = ATA_INB(ch->r_mem, offs + 3); + res->lba_low = ATA_INB(ch->r_mem, offs + 4); + res->lba_mid = ATA_INB(ch->r_mem, offs + 5); + res->lba_high = ATA_INB(ch->r_mem, offs + 6); + res->device = ATA_INB(ch->r_mem, offs + 7); + res->lba_low_exp = ATA_INB(ch->r_mem, offs + 8); + res->lba_mid_exp = ATA_INB(ch->r_mem, offs + 9); + res->lba_high_exp = ATA_INB(ch->r_mem, offs + 10); + res->sector_count = ATA_INB(ch->r_mem, offs + 12); + res->sector_count_exp = ATA_INB(ch->r_mem, offs + 13); + } else + bzero(res, sizeof(*res)); + } + if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { + bus_dmamap_sync(ch->dma.data_tag, slot->dma.data_map, + (ccb->ccb_h.flags & CAM_DIR_IN) ? + BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(ch->dma.data_tag, slot->dma.data_map); + } + /* Set proper result status. */ + ccb->ccb_h.status &= ~CAM_STATUS_MASK; + if (et != SIIS_ERR_NONE || ch->recovery) { + ch->eslots |= (1 << slot->slot); + ccb->ccb_h.status |= CAM_RELEASE_SIMQ; + } + switch (et) { + case SIIS_ERR_NONE: + ccb->ccb_h.status |= CAM_REQ_CMP; + if (ccb->ccb_h.func_code == XPT_SCSI_IO) + ccb->csio.scsi_status = SCSI_STATUS_OK; + break; + case SIIS_ERR_INVALID: + ccb->ccb_h.status |= CAM_REQ_INVALID; + break; + case SIIS_ERR_INNOCENT: + ccb->ccb_h.status |= CAM_REQUEUE_REQ; + break; + case SIIS_ERR_TFE: + if (ccb->ccb_h.func_code == XPT_SCSI_IO) { + ccb->ccb_h.status |= CAM_SCSI_STATUS_ERROR; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + } else { + ccb->ccb_h.status |= CAM_ATA_STATUS_ERROR; + } + break; + case SIIS_ERR_SATA: + ccb->ccb_h.status |= CAM_UNCOR_PARITY; + break; + case SIIS_ERR_TIMEOUT: + ccb->ccb_h.status |= CAM_CMD_TIMEOUT; + break; + case SIIS_ERR_NCQ: + ccb->ccb_h.status |= CAM_ATA_STATUS_ERROR; + break; + default: + ccb->ccb_h.status |= CAM_REQ_CMP_ERR; + } + /* Free slot. */ + ch->rslots &= ~(1 << slot->slot); + ch->aslots &= ~(1 << slot->slot); + slot->state = SIIS_SLOT_EMPTY; + slot->ccb = NULL; + /* Update channel stats. */ + ch->numrslots--; + if ((ccb->ccb_h.func_code == XPT_ATA_IO) && + (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) { + ch->numtslots[ccb->ccb_h.target_id]--; + } + /* If it was NCQ command error, put result on hold. */ + if (et == SIIS_ERR_NCQ) { + ch->hold[slot->slot] = ccb; + ch->numhslots++; + } else if (ch->readlog) /* If it was our READ LOG command - process it. */ + siis_process_read_log(dev, ccb); + else + xpt_done(ccb); + /* Unfreeze frozen command. */ + if (ch->frozen && ch->numrslots == 0) { + union ccb *fccb = ch->frozen; + ch->frozen = NULL; + siis_begin_transaction(dev, fccb); + xpt_release_simq(ch->sim, TRUE); + } + /* If we have no other active commands, ... */ + if (ch->rslots == 0) { + /* if we have slots in error, we can reinit port. */ + if (ch->eslots != 0) + siis_portinit(dev); + /* if there commands on hold, we can do READ LOG. */ + if (!ch->readlog && ch->numhslots) + siis_issue_read_log(dev); + } +} + +static void +siis_issue_read_log(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + union ccb *ccb; + struct ccb_ataio *ataio; + int i; + + /* Find some holden command. */ + for (i = 0; i < SIIS_MAX_SLOTS; i++) { + if (ch->hold[i]) + break; + } + if (i == SIIS_MAX_SLOTS) + return; + ch->readlog = 1; + ccb = xpt_alloc_ccb_nowait(); + if (ccb == NULL) { + device_printf(dev, "Unable allocate READ LOG command"); + return; /* XXX */ + } + ccb->ccb_h = ch->hold[i]->ccb_h; /* Reuse old header. */ + ccb->ccb_h.func_code = XPT_ATA_IO; + ccb->ccb_h.flags = CAM_DIR_IN; + ccb->ccb_h.timeout = 1000; /* 1s should be enough. */ + ataio = &ccb->ataio; + ataio->data_ptr = malloc(512, M_SIIS, M_NOWAIT); + if (ataio->data_ptr == NULL) { + device_printf(dev, "Unable allocate memory for READ LOG command"); + return; /* XXX */ + } + ataio->dxfer_len = 512; + bzero(&ataio->cmd, sizeof(ataio->cmd)); + ataio->cmd.flags = CAM_ATAIO_48BIT; + ataio->cmd.command = 0x2F; /* READ LOG EXT */ + ataio->cmd.sector_count = 1; + ataio->cmd.sector_count_exp = 0; + ataio->cmd.lba_low = 0x10; + ataio->cmd.lba_mid = 0; + ataio->cmd.lba_mid_exp = 0; + siis_begin_transaction(dev, ccb); +} + +static void +siis_process_read_log(device_t dev, union ccb *ccb) +{ + struct siis_channel *ch = device_get_softc(dev); + uint8_t *data; + struct ata_res *res; + int i; + + ch->readlog = 0; + data = ccb->ataio.data_ptr; + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP && + (data[0] & 0x80) == 0) { + for (i = 0; i < SIIS_MAX_SLOTS; i++) { + if (!ch->hold[i]) + continue; + if (ch->hold[i]->ccb_h.target_id != ccb->ccb_h.target_id) + continue; + if ((data[0] & 0x1F) == i) { + res = &ch->hold[i]->ataio.res; + res->status = data[2]; + res->error = data[3]; + res->lba_low = data[4]; + res->lba_mid = data[5]; + res->lba_high = data[6]; + res->device = data[7]; + res->lba_low_exp = data[8]; + res->lba_mid_exp = data[9]; + res->lba_high_exp = data[10]; + res->sector_count = data[12]; + res->sector_count_exp = data[13]; + } else { + ch->hold[i]->ccb_h.status &= ~CAM_STATUS_MASK; + ch->hold[i]->ccb_h.status |= CAM_REQUEUE_REQ; + } + xpt_done(ch->hold[i]); + ch->hold[i] = NULL; + ch->numhslots--; + } + } else { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) + device_printf(dev, "Error while READ LOG EXT\n"); + else if ((data[0] & 0x80) == 0) { + device_printf(dev, "Non-queued command error in READ LOG EXT\n"); + } + for (i = 0; i < SIIS_MAX_SLOTS; i++) { + if (!ch->hold[i]) + continue; + if (ch->hold[i]->ccb_h.target_id != ccb->ccb_h.target_id) + continue; + xpt_done(ch->hold[i]); + ch->hold[i] = NULL; + ch->numhslots--; + } + } + free(ccb->ataio.data_ptr, M_SIIS); + xpt_free_ccb(ccb); +} + +static void +siis_portinit(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + int i; + + ch->eslots = 0; + ch->recovery = 0; + ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_RESUME); + for (i = 0; i < 16; i++) { + ATA_OUTL(ch->r_mem, SIIS_P_PMPSTS(i), 0), + ATA_OUTL(ch->r_mem, SIIS_P_PMPQACT(i), 0); + } + ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_PORT_INIT); + siis_wait_ready(dev, 1000); +} + +#if 0 +static void +siis_devreset(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + + ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_DEV_RESET); + siis_wait_ready(dev, 1000); +} +#endif + +static int +siis_wait_ready(device_t dev, int t) +{ + struct siis_channel *ch = device_get_softc(dev); + int timeout = 0; + uint32_t val; + + while (((val = ATA_INL(ch->r_mem, SIIS_P_STS)) & + SIIS_P_CTL_READY) == 0) { + DELAY(1000); + if (timeout++ > t) { + device_printf(dev, "port is not ready (timeout %dms) " + "status = %08x\n", t, val); + return (EBUSY); + } + } + if (bootverbose) + device_printf(dev, "ready wait time=%dms\n", timeout); + return (0); +} + +static void +siis_reset(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + int i; + + if (bootverbose) + device_printf(dev, "SIIS reset...\n"); + xpt_freeze_simq(ch->sim, ch->numrslots); + /* Requeue freezed command. */ + if (ch->frozen) { + union ccb *fccb = ch->frozen; + ch->frozen = NULL; + fccb->ccb_h.status = CAM_REQUEUE_REQ | CAM_RELEASE_SIMQ; + xpt_done(fccb); + } + /* Disable port interrupts */ + ATA_OUTL(ch->r_mem, SIIS_P_IECLR, 0x0000FFFF); + /* Kill the engine and requeue all running commands. */ + siis_portinit(dev); + for (i = 0; i < SIIS_MAX_SLOTS; i++) { + /* Do we have a running request on slot? */ + if (ch->slot[i].state < SIIS_SLOT_RUNNING) + continue; + /* XXX; Commands in loading state. */ + siis_end_transaction(&ch->slot[i], SIIS_ERR_INNOCENT); + } + ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_PME); + /* Reset and reconnect PHY, */ + if (!siis_sata_phy_reset(dev)) { + ch->devices = 0; + /* Enable port interrupts */ + ATA_OUTL(ch->r_mem, SIIS_P_IESET, SIIS_P_IX_ENABLED); + if (bootverbose) + device_printf(dev, + "SIIS reset done: phy reset found no device\n"); + /* Tell the XPT about the event */ + xpt_async(AC_BUS_RESET, ch->path, NULL); + return; + } + /* Wait for clearing busy status. */ + if (siis_wait_ready(dev, 10000)) { + device_printf(dev, "device ready timeout\n"); + } + ch->devices = 1; + /* Enable port interrupts */ + ATA_OUTL(ch->r_mem, SIIS_P_IS, 0xFFFFFFFF); + ATA_OUTL(ch->r_mem, SIIS_P_IESET, SIIS_P_IX_ENABLED); + if (bootverbose) + device_printf(dev, "SIIS reset done: devices=%08x\n", ch->devices); + /* Tell the XPT about the event */ + xpt_async(AC_BUS_RESET, ch->path, NULL); +} + +static int +siis_setup_fis(struct siis_cmd *ctp, union ccb *ccb, int tag) +{ + u_int8_t *fis = &ctp->fis[0]; + + bzero(fis, 24); + fis[0] = 0x27; /* host to device */ + fis[1] = (ccb->ccb_h.target_id & 0x0f); + if (ccb->ccb_h.func_code == XPT_SCSI_IO) { + fis[1] |= 0x80; + fis[2] = ATA_PACKET_CMD; + if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) + fis[3] = ATA_F_DMA; + else { + fis[5] = ccb->csio.dxfer_len; + fis[6] = ccb->csio.dxfer_len >> 8; + } + fis[7] = ATA_D_LBA; + fis[15] = ATA_A_4BIT; + bzero(ctp->u.atapi.ccb, 16); + bcopy((ccb->ccb_h.flags & CAM_CDB_POINTER) ? + ccb->csio.cdb_io.cdb_ptr : ccb->csio.cdb_io.cdb_bytes, + ctp->u.atapi.ccb, ccb->csio.cdb_len); + } else if ((ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL) == 0) { + fis[1] |= 0x80; + fis[2] = ccb->ataio.cmd.command; + fis[3] = ccb->ataio.cmd.features; + fis[4] = ccb->ataio.cmd.lba_low; + fis[5] = ccb->ataio.cmd.lba_mid; + fis[6] = ccb->ataio.cmd.lba_high; + fis[7] = ccb->ataio.cmd.device; + fis[8] = ccb->ataio.cmd.lba_low_exp; + fis[9] = ccb->ataio.cmd.lba_mid_exp; + fis[10] = ccb->ataio.cmd.lba_high_exp; + fis[11] = ccb->ataio.cmd.features_exp; + if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) { + fis[12] = tag << 3; + fis[13] = 0; + } else { + fis[12] = ccb->ataio.cmd.sector_count; + fis[13] = ccb->ataio.cmd.sector_count_exp; + } + fis[15] = ATA_A_4BIT; + } else { + /* Soft reset. */ + } + return (20); +} + +static int +siis_sata_connect(struct siis_channel *ch) +{ + u_int32_t status; + int timeout; + + /* Wait up to 100ms for "connect well" */ + for (timeout = 0; timeout < 100 ; timeout++) { + status = ATA_INL(ch->r_mem, SIIS_P_SSTS); + if (((status & ATA_SS_DET_MASK) == ATA_SS_DET_PHY_ONLINE) && + ((status & ATA_SS_SPD_MASK) != ATA_SS_SPD_NO_SPEED) && + ((status & ATA_SS_IPM_MASK) == ATA_SS_IPM_ACTIVE)) + break; + DELAY(1000); + } + if (timeout >= 100) { + if (bootverbose) { + device_printf(ch->dev, "SATA connect timeout status=%08x\n", + status); + } + return (0); + } + if (bootverbose) { + device_printf(ch->dev, "SATA connect time=%dms status=%08x\n", + timeout, status); + } + /* Clear SATA error register */ + ATA_OUTL(ch->r_mem, SIIS_P_SERR, 0xffffffff); + return (1); +} + +static int +siis_sata_phy_reset(device_t dev) +{ + struct siis_channel *ch = device_get_softc(dev); + uint32_t val; + + if (bootverbose) + device_printf(dev, "hardware reset ...\n"); + ATA_OUTL(ch->r_mem, SIIS_P_SCTL, ATA_SC_IPM_DIS_PARTIAL | + ATA_SC_IPM_DIS_SLUMBER | ATA_SC_DET_RESET); + DELAY(50000); + if (ch->sata_rev == 1) + val = ATA_SC_SPD_SPEED_GEN1; + else if (ch->sata_rev == 2) + val = ATA_SC_SPD_SPEED_GEN2; + else if (ch->sata_rev == 3) + val = ATA_SC_SPD_SPEED_GEN3; + else + val = 0; + ATA_OUTL(ch->r_mem, SIIS_P_SCTL, + ATA_SC_DET_IDLE | val | ((ch->pm_level > 0) ? 0 : + (ATA_SC_IPM_DIS_PARTIAL | ATA_SC_IPM_DIS_SLUMBER))); + DELAY(50000); + return (siis_sata_connect(ch)); +} + +static void +siisaction(struct cam_sim *sim, union ccb *ccb) +{ + device_t dev; + struct siis_channel *ch; + + CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("siisaction func_code=%x\n", + ccb->ccb_h.func_code)); + + ch = (struct siis_channel *)cam_sim_softc(sim); + dev = ch->dev; + mtx_assert(&ch->mtx, MA_OWNED); + switch (ccb->ccb_h.func_code) { + /* Common cases first */ + case XPT_ATA_IO: /* Execute the requested I/O operation */ + case XPT_SCSI_IO: + if (ch->devices == 0) { + ccb->ccb_h.status = CAM_SEL_TIMEOUT; + xpt_done(ccb); + break; + } + /* Check for command collision. */ + if (siis_check_collision(dev, ccb)) { + /* Freeze command. */ + ch->frozen = ccb; + /* We have only one frozen slot, so freeze simq also. */ + xpt_freeze_simq(ch->sim, 1); + return; + } + siis_begin_transaction(dev, ccb); + break; + case XPT_EN_LUN: /* Enable LUN as a target */ + case XPT_TARGET_IO: /* Execute target I/O request */ + case XPT_ACCEPT_TARGET_IO: /* Accept Host Target Mode CDB */ + case XPT_CONT_TARGET_IO: /* Continue Host Target I/O Connection*/ + case XPT_ABORT: /* Abort the specified CCB */ + /* XXX Implement */ + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + case XPT_SET_TRAN_SETTINGS: + { + struct ccb_trans_settings *cts = &ccb->cts; + + if (cts->xport_specific.sata.valid & CTS_SATA_VALID_PM) { + if (cts->xport_specific.sata.pm_present) + ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_PME); + else + ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_PME); + } + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_GET_TRAN_SETTINGS: + /* Get default/user set transfer settings for the target */ + { + struct ccb_trans_settings *cts = &ccb->cts; + uint32_t status; + + cts->protocol = PROTO_ATA; + cts->protocol_version = SCSI_REV_2; + cts->transport = XPORT_SATA; + cts->transport_version = 2; + cts->proto_specific.valid = 0; + cts->xport_specific.sata.valid = 0; + if (cts->type == CTS_TYPE_CURRENT_SETTINGS) + status = ATA_INL(ch->r_mem, SIIS_P_SSTS) & ATA_SS_SPD_MASK; + else + status = ATA_INL(ch->r_mem, SIIS_P_SCTL) & ATA_SC_SPD_MASK; + if (status & ATA_SS_SPD_GEN3) { + cts->xport_specific.sata.bitrate = 600000; + cts->xport_specific.sata.valid |= CTS_SATA_VALID_SPEED; + } else if (status & ATA_SS_SPD_GEN2) { + cts->xport_specific.sata.bitrate = 300000; + cts->xport_specific.sata.valid |= CTS_SATA_VALID_SPEED; + } else if (status & ATA_SS_SPD_GEN1) { + cts->xport_specific.sata.bitrate = 150000; + cts->xport_specific.sata.valid |= CTS_SATA_VALID_SPEED; + } + cts->xport_specific.sata.pm_present = + (ATA_INL(ch->r_mem, SIIS_P_STS) & SIIS_P_CTL_PME) ? + 1 : 0; + cts->xport_specific.sata.valid |= CTS_SATA_VALID_PM; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } +#if 0 + case XPT_CALC_GEOMETRY: + { + struct ccb_calc_geometry *ccg; + uint32_t size_mb; + uint32_t secs_per_cylinder; + + ccg = &ccb->ccg; + size_mb = ccg->volume_size + / ((1024L * 1024L) / ccg->block_size); + if (size_mb >= 1024 && (aha->extended_trans != 0)) { + if (size_mb >= 2048) { + ccg->heads = 255; + ccg->secs_per_track = 63; + } else { + ccg->heads = 128; + ccg->secs_per_track = 32; + } + } 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; + xpt_done(ccb); + break; + } +#endif + case XPT_RESET_BUS: /* Reset the specified SCSI bus */ + case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ + siis_reset(dev); + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + case XPT_TERM_IO: /* Terminate the I/O process */ + /* XXX Implement */ + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + case XPT_PATH_INQ: /* Path routing inquiry */ + { + struct ccb_pathinq *cpi = &ccb->cpi; + + cpi->version_num = 1; /* XXX??? */ + cpi->hba_inquiry = PI_SDTR_ABLE | PI_TAG_ABLE; + cpi->hba_inquiry |= PI_SATAPM; + cpi->target_sprt = 0; + cpi->hba_misc = PIM_SEQSCAN; + cpi->hba_eng_cnt = 0; + cpi->max_target = 14; + cpi->max_lun = 0; + cpi->initiator_id = 0; + cpi->bus_id = cam_sim_bus(sim); + cpi->base_transfer_speed = 150000; + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "SIIS", HBA_IDLEN); + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->transport = XPORT_SATA; + cpi->transport_version = 2; + cpi->protocol = PROTO_ATA; + cpi->protocol_version = SCSI_REV_2; + cpi->ccb_h.status = CAM_REQ_CMP; + cpi->maxio = MAXPHYS; + xpt_done(ccb); + break; + } + default: + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } +} + +static void +siispoll(struct cam_sim *sim) +{ + struct siis_channel *ch = (struct siis_channel *)cam_sim_softc(sim); + + siis_ch_intr(ch->dev); +} diff --git a/sys/dev/siis/siis.h b/sys/dev/siis/siis.h new file mode 100644 index 0000000..87a1d5d --- /dev/null +++ b/sys/dev/siis/siis.h @@ -0,0 +1,445 @@ +/*- + * Copyright (c) 2009 Alexander Motin <mav@FreeBSD.org> + * 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, + * without modification, immediately at the beginning of the file. + * 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 ``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 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$ + */ + +/* ATA register defines */ +#define ATA_DATA 0 /* (RW) data */ + +#define ATA_FEATURE 1 /* (W) feature */ +#define ATA_F_DMA 0x01 /* enable DMA */ +#define ATA_F_OVL 0x02 /* enable overlap */ + +#define ATA_COUNT 2 /* (W) sector count */ + +#define ATA_SECTOR 3 /* (RW) sector # */ +#define ATA_CYL_LSB 4 /* (RW) cylinder# LSB */ +#define ATA_CYL_MSB 5 /* (RW) cylinder# MSB */ +#define ATA_DRIVE 6 /* (W) Sector/Drive/Head */ +#define ATA_D_LBA 0x40 /* use LBA addressing */ +#define ATA_D_IBM 0xa0 /* 512 byte sectors, ECC */ + +#define ATA_COMMAND 7 /* (W) command */ + +#define ATA_ERROR 8 /* (R) error */ +#define ATA_E_ILI 0x01 /* illegal length */ +#define ATA_E_NM 0x02 /* no media */ +#define ATA_E_ABORT 0x04 /* command aborted */ +#define ATA_E_MCR 0x08 /* media change request */ +#define ATA_E_IDNF 0x10 /* ID not found */ +#define ATA_E_MC 0x20 /* media changed */ +#define ATA_E_UNC 0x40 /* uncorrectable data */ +#define ATA_E_ICRC 0x80 /* UDMA crc error */ +#define ATA_E_ATAPI_SENSE_MASK 0xf0 /* ATAPI sense key mask */ + +#define ATA_IREASON 9 /* (R) interrupt reason */ +#define ATA_I_CMD 0x01 /* cmd (1) | data (0) */ +#define ATA_I_IN 0x02 /* read (1) | write (0) */ +#define ATA_I_RELEASE 0x04 /* released bus (1) */ +#define ATA_I_TAGMASK 0xf8 /* tag mask */ + +#define ATA_STATUS 10 /* (R) status */ +#define ATA_ALTSTAT 11 /* (R) alternate status */ +#define ATA_S_ERROR 0x01 /* error */ +#define ATA_S_INDEX 0x02 /* index */ +#define ATA_S_CORR 0x04 /* data corrected */ +#define ATA_S_DRQ 0x08 /* data request */ +#define ATA_S_DSC 0x10 /* drive seek completed */ +#define ATA_S_SERVICE 0x10 /* drive needs service */ +#define ATA_S_DWF 0x20 /* drive write fault */ +#define ATA_S_DMA 0x20 /* DMA ready */ +#define ATA_S_READY 0x40 /* drive ready */ +#define ATA_S_BUSY 0x80 /* busy */ + +#define ATA_CONTROL 12 /* (W) control */ +#define ATA_A_IDS 0x02 /* disable interrupts */ +#define ATA_A_RESET 0x04 /* RESET controller */ +#define ATA_A_4BIT 0x08 /* 4 head bits */ +#define ATA_A_HOB 0x80 /* High Order Byte enable */ + +/* SATA register defines */ +#define ATA_SSTATUS 13 +#define ATA_SS_DET_MASK 0x0000000f +#define ATA_SS_DET_NO_DEVICE 0x00000000 +#define ATA_SS_DET_DEV_PRESENT 0x00000001 +#define ATA_SS_DET_PHY_ONLINE 0x00000003 +#define ATA_SS_DET_PHY_OFFLINE 0x00000004 + +#define ATA_SS_SPD_MASK 0x000000f0 +#define ATA_SS_SPD_NO_SPEED 0x00000000 +#define ATA_SS_SPD_GEN1 0x00000010 +#define ATA_SS_SPD_GEN2 0x00000020 +#define ATA_SS_SPD_GEN3 0x00000040 + +#define ATA_SS_IPM_MASK 0x00000f00 +#define ATA_SS_IPM_NO_DEVICE 0x00000000 +#define ATA_SS_IPM_ACTIVE 0x00000100 +#define ATA_SS_IPM_PARTIAL 0x00000200 +#define ATA_SS_IPM_SLUMBER 0x00000600 + +#define ATA_SERROR 14 +#define ATA_SE_DATA_CORRECTED 0x00000001 +#define ATA_SE_COMM_CORRECTED 0x00000002 +#define ATA_SE_DATA_ERR 0x00000100 +#define ATA_SE_COMM_ERR 0x00000200 +#define ATA_SE_PROT_ERR 0x00000400 +#define ATA_SE_HOST_ERR 0x00000800 +#define ATA_SE_PHY_CHANGED 0x00010000 +#define ATA_SE_PHY_IERROR 0x00020000 +#define ATA_SE_COMM_WAKE 0x00040000 +#define ATA_SE_DECODE_ERR 0x00080000 +#define ATA_SE_PARITY_ERR 0x00100000 +#define ATA_SE_CRC_ERR 0x00200000 +#define ATA_SE_HANDSHAKE_ERR 0x00400000 +#define ATA_SE_LINKSEQ_ERR 0x00800000 +#define ATA_SE_TRANSPORT_ERR 0x01000000 +#define ATA_SE_UNKNOWN_FIS 0x02000000 + +#define ATA_SCONTROL 15 +#define ATA_SC_DET_MASK 0x0000000f +#define ATA_SC_DET_IDLE 0x00000000 +#define ATA_SC_DET_RESET 0x00000001 +#define ATA_SC_DET_DISABLE 0x00000004 + +#define ATA_SC_SPD_MASK 0x000000f0 +#define ATA_SC_SPD_NO_SPEED 0x00000000 +#define ATA_SC_SPD_SPEED_GEN1 0x00000010 +#define ATA_SC_SPD_SPEED_GEN2 0x00000020 +#define ATA_SC_SPD_SPEED_GEN3 0x00000040 + +#define ATA_SC_IPM_MASK 0x00000f00 +#define ATA_SC_IPM_NONE 0x00000000 +#define ATA_SC_IPM_DIS_PARTIAL 0x00000100 +#define ATA_SC_IPM_DIS_SLUMBER 0x00000200 + +#define ATA_SACTIVE 16 + +#define SIIS_SII3124 0x31241095 +#define SIIS_SII3132 0x31321095 +#define SIIS_SII3132_1 0x02421095 +#define SIIS_SII3132_2 0x02441095 +#define SIIS_SII3531 0x35311095 + +/* + * Global registers + */ +#define SIIS_GCTL 0x0040 /* Global Control */ +#define SIIS_GCTL_GRESET 0x80000000 /* Global Reset */ +#define SIIS_GCTL_MSIACK 0x40000000 /* MSI Ack */ +#define SIIS_GCTL_I2C_IE 0x20000000 /* I2C int enable */ +#define SIIS_GCTL_300CAP 0x01000000 /* 3Gb/s capable (R) */ +#define SIIS_GCTL_PIE(n) (1 << (n)) /* Port int enable */ +#define SIIS_IS 0x0044 /* Interrupt Status */ +#define SIIS_IS_I2C 0x20000000 /* I2C Int Status */ +#define SIIS_IS_PORT(n) (1 << (n)) /* Port interrupt stat */ +#define SIIS_PHYCONF 0x0048 /* PHY Configuration */ +#define SIIS_BIST_CTL 0x0050 +#define SIIS_BIST_PATTERN 0x0054 /* 32 bit pattern */ +#define SIIS_BIST_STATUS 0x0058 +#define SIIS_I2C_CTL 0x0060 +#define SIIS_I2C_STS 0x0064 +#define SIIS_I2C_SADDR 0x0068 +#define SIIS_I2C_DATA 0x006C +#define SIIS_FLASH_ADDR 0x0070 +#define SIIS_GPIO 0x0074 + +/* + * Port registers + */ + +#define SIIS_P_LRAM 0x0000 +#define SIIS_P_LRAM_SLOT(i) (SIIS_P_LRAM + i * 128) +#define SIIS_P_PMPSTS(i) (0x0F80 + i * 8) +#define SIIS_P_PMPQACT(i) (0x0F80 + i * 8 + 4) +#define SIIS_P_STS 0x1000 +#define SIIS_P_CTLSET 0x1000 +#define SIIS_P_CTLCLR 0x1004 +#define SIIS_P_CTL_READY 0x80000000 +#define SIIS_P_CTL_OOBB 0x02000000 +#define SIIS_P_CTL_ACT 0x001F0000 +#define SIIS_P_CTL_ACT_SHIFT 16 +#define SIIS_P_CTL_LED_ON 0x00008000 +#define SIIS_P_CTL_AIA 0x00004000 +#define SIIS_P_CTL_PME 0x00002000 +#define SIIS_P_CTL_IA 0x00001000 +#define SIIS_P_CTL_IR 0x00000800 +#define SIIS_P_CTL_32BIT 0x00000400 +#define SIIS_P_CTL_SCR_DIS 0x00000200 +#define SIIS_P_CTL_CONT_DIS 0x00000100 +#define SIIS_P_CTL_TBIST 0x00000080 +#define SIIS_P_CTL_RESUME 0x00000040 +#define SIIS_P_CTL_PLENGTH 0x00000020 +#define SIIS_P_CTL_LED_DIS 0x00000010 +#define SIIS_P_CTL_INT_NCOR 0x00000008 +#define SIIS_P_CTL_PORT_INIT 0x00000004 +#define SIIS_P_CTL_DEV_RESET 0x00000002 +#define SIIS_P_CTL_PORT_RESET 0x00000001 +#define SIIS_P_IS 0x1008 +#define SIIS_P_IX_SDBN 0x00000800 +#define SIIS_P_IX_HS_ET 0x00000400 +#define SIIS_P_IX_CRC_ET 0x00000200 +#define SIIS_P_IX_8_10_ET 0x00000100 +#define SIIS_P_IX_DEX 0x00000080 +#define SIIS_P_IX_UNRECFIS 0x00000040 +#define SIIS_P_IX_COMWAKE 0x00000020 +#define SIIS_P_IX_PHYRDYCHG 0x00000010 +#define SIIS_P_IX_PMCHG 0x00000008 +#define SIIS_P_IX_READY 0x00000004 +#define SIIS_P_IX_COMMERR 0x00000002 +#define SIIS_P_IX_COMMCOMP 0x00000001 +#define SIIS_P_IX_ENABLED SIIS_P_IX_COMMCOMP | SIIS_P_IX_COMMERR | \ + SIIS_P_IX_PHYRDYCHG | SIIS_P_IX_SDBN +#define SIIS_P_IESET 0x1010 +#define SIIS_P_IECLR 0x1014 +#define SIIS_P_CACTU 0x101C +#define SIIS_P_CMDEFIFO 0x1020 +#define SIIS_P_CMDERR 0x1024 +#define SIIS_P_CMDERR_DEV 1 +#define SIIS_P_CMDERR_SDB 2 +#define SIIS_P_CMDERR_DATAFIS 3 +#define SIIS_P_CMDERR_SENDFIS 4 +#define SIIS_P_CMDERR_INCSTATE 5 +#define SIIS_P_CMDERR_DIRECTION 6 +#define SIIS_P_CMDERR_UNDERRUN 7 +#define SIIS_P_CMDERR_OVERRUN 8 +#define SIIS_P_CMDERR_LLOVERRUN 9 +#define SIIS_P_CMDERR_PPE 11 +#define SIIS_P_CMDERR_SGTALIGN 16 +#define SIIS_P_CMDERR_PCITASGT 17 +#define SIIS_P_CMDERR_OCIMASGT 18 +#define SIIS_P_CMDERR_PCIPESGT 19 +#define SIIS_P_CMDERR_PRBALIGN 24 +#define SIIS_P_CMDERR_PCITAPRB 25 +#define SIIS_P_CMDERR_PCIMAPRB 26 +#define SIIS_P_CMDERR_PCIPEPRB 27 +#define SIIS_P_CMDERR_PCITADATA 33 +#define SIIS_P_CMDERR_PCIMADATA 34 +#define SIIS_P_CMDERR_PCIPEDATA 35 +#define SIIS_P_CMDERR_SERVICE 36 +#define SIIS_P_FISCFG 0x1028 +#define SIIS_P_PCIEFIFOTH 0x102C +#define SIIS_P_8_10_DEC_ERR 0x1040 +#define SIIS_P_CRC_ERR 0x1044 +#define SIIS_P_HS_ERR 0x1048 +#define SIIS_P_PHYCFG 0x1050 +#define SIIS_P_SS 0x1800 +#define SIIS_P_SS_ATTN 0x80000000 +#define SIIS_P_CACTL(i) (0x1C00 + i * 8) +#define SIIS_P_CACTH(i) (0x1C00 + i * 8 + 4) +#define SIIS_P_CTX 0x1E04 +#define SIIS_P_CTX_SLOT 0x0000001F +#define SIIS_P_CTX_SLOT_SHIFT 0 +#define SIIS_P_CTX_PMP 0x000001E0 +#define SIIS_P_CTX_PMP_SHIFT 5 + +#define SIIS_P_SCTL 0x1F00 +#define SIIS_P_SSTS 0x1F04 +#define SIIS_P_SERR 0x1F08 +#define SIIS_P_SACT 0x1F0C +#define SIIS_P_SNTF 0x1F10 + +#define SIIS_MAX_PORTS 4 +#define SIIS_MAX_SLOTS 31 + +#define SIIS_OFFSET 0x100 +#define SIIS_STEP 0x80 + +/* Just to be sure, if building as module. */ +#if MAXPHYS < 512 * 1024 +#undef MAXPHYS +#define MAXPHYS 512 * 1024 +#endif +/* Pessimistic prognosis on number of required S/G entries */ +#define SIIS_SG_ENTRIES (roundup(btoc(MAXPHYS), 4) + 1) +/* Command tables. Up to 32 commands, Each, 128byte aligned. */ +#define SIIS_CT_OFFSET 0 +#define SIIS_CT_SIZE (32 + 16 + SIIS_SG_ENTRIES * 16) +/* Total main work area. */ +#define SIIS_WORK_SIZE (SIIS_CT_OFFSET + SIIS_CT_SIZE * SIIS_MAX_SLOTS) + +struct siis_dma_prd { + u_int64_t dba; + u_int32_t dbc; + u_int32_t control; +#define SIIS_PRD_TRM 0x80000000 +#define SIIS_PRD_LNK 0x40000000 +#define SIIS_PRD_DRD 0x20000000 +#define SIIS_PRD_XCF 0x10000000 +} __packed; + +struct siis_cmd_ata { + struct siis_dma_prd prd[1 + SIIS_SG_ENTRIES]; +} __packed; + +struct siis_cmd_atapi { + u_int8_t ccb[16]; + struct siis_dma_prd prd[SIIS_SG_ENTRIES]; +} __packed; + +struct siis_cmd { + u_int16_t control; +#define SIIS_PRB_PROTOCOL_OVERRIDE 0x0001 +#define SIIS_PRB_RETRANSMIT 0x0002 +#define SIIS_PRB_EXTERNAL_COMMAND 0x0004 +#define SIIS_PRB_RECEIVE 0x0008 +#define SIIS_PRB_PACKET_READ 0x0010 +#define SIIS_PRB_PACKET_WRITE 0x0020 +#define SIIS_PRB_INTERRUPT_MASK 0x0040 +#define SIIS_PRB_SOFT_RESET 0x0080 + u_int16_t protocol_override; + u_int32_t transfer_count; + u_int8_t fis[24]; + union { + struct siis_cmd_ata ata; + struct siis_cmd_atapi atapi; + } u; +} __packed; + +/* misc defines */ +#define ATA_IRQ_RID 0 +#define ATA_INTR_FLAGS (INTR_MPSAFE|INTR_TYPE_BIO|INTR_ENTROPY) + +struct ata_dmaslot { + bus_dmamap_t data_map; /* data DMA map */ + int nsegs; /* Number of segs loaded */ +}; + +/* structure holding DMA related information */ +struct ata_dma { + bus_dma_tag_t work_tag; /* workspace DMA tag */ + bus_dmamap_t work_map; /* workspace DMA map */ + uint8_t *work; /* workspace */ + bus_addr_t work_bus; /* bus address of work */ + bus_dma_tag_t data_tag; /* data DMA tag */ +}; + +enum siis_slot_states { + SIIS_SLOT_EMPTY, + SIIS_SLOT_LOADING, + SIIS_SLOT_RUNNING, + SIIS_SLOT_WAITING +}; + +struct siis_slot { + device_t dev; /* Device handle */ + u_int8_t slot; /* Number of this slot */ + enum siis_slot_states state; /* Slot state */ + union ccb *ccb; /* CCB occupying slot */ + struct ata_dmaslot dma; /* DMA data of this slot */ + struct callout timeout; /* Execution timeout */ +}; + +/* structure describing an ATA channel */ +struct siis_channel { + device_t dev; /* Device handle */ + int unit; /* Physical channel */ + struct resource *r_mem; /* Memory of this channel */ + struct resource *r_irq; /* Interrupt of this channel */ + void *ih; /* Interrupt handle */ + struct ata_dma dma; /* DMA data */ + struct cam_sim *sim; + struct cam_path *path; + int pm_level; /* power management level */ + int sata_rev; /* Maximum allowed SATA generation */ + + struct siis_slot slot[SIIS_MAX_SLOTS]; + union ccb *hold[SIIS_MAX_SLOTS]; + struct mtx mtx; /* state lock */ + int devices; /* What is present */ + int pm_present; /* PM presence reported */ + uint32_t rslots; /* Running slots */ + uint32_t aslots; /* Slots with atomic commands */ + uint32_t eslots; /* Slots in error */ + int numrslots; /* Number of running slots */ + int numtslots[SIIS_MAX_SLOTS]; /* Number of tagged slots */ + int numhslots; /* Number of holden slots */ + int readlog; /* Our READ LOG active */ + int recovery; /* Some slots are in error */ + int lastslot; /* Last used slot */ + int taggedtarget; /* Last tagged target */ + union ccb *frozen; /* Frozen command */ +}; + +/* structure describing a SIIS controller */ +struct siis_controller { + device_t dev; + int r_grid; + struct resource *r_gmem; + int r_rid; + struct resource *r_mem; + struct rman sc_iomem; + struct siis_controller_irq { + struct resource *r_irq; + void *handle; + int r_irq_rid; + } irq; + int channels; + struct { + void (*function)(void *); + void *argument; + } interrupt[SIIS_MAX_PORTS]; +}; + +enum siis_err_type { + SIIS_ERR_NONE, /* No error */ + SIIS_ERR_INVALID, /* Error detected by us before submitting. */ + SIIS_ERR_INNOCENT, /* Innocent victim. */ + SIIS_ERR_TFE, /* Task File Error. */ + SIIS_ERR_SATA, /* SATA error. */ + SIIS_ERR_TIMEOUT, /* Command execution timeout. */ + SIIS_ERR_NCQ, /* NCQ command error. CCB should be put on hold + * until READ LOG executed to reveal error. */ +}; + +/* macros to hide busspace uglyness */ +#define ATA_INB(res, offset) \ + bus_read_1((res), (offset)) +#define ATA_INW(res, offset) \ + bus_read_2((res), (offset)) +#define ATA_INL(res, offset) \ + bus_read_4((res), (offset)) +#define ATA_INSW(res, offset, addr, count) \ + bus_read_multi_2((res), (offset), (addr), (count)) +#define ATA_INSW_STRM(res, offset, addr, count) \ + bus_read_multi_stream_2((res), (offset), (addr), (count)) +#define ATA_INSL(res, offset, addr, count) \ + bus_read_multi_4((res), (offset), (addr), (count)) +#define ATA_INSL_STRM(res, offset, addr, count) \ + bus_read_multi_stream_4((res), (offset), (addr), (count)) +#define ATA_OUTB(res, offset, value) \ + bus_write_1((res), (offset), (value)) +#define ATA_OUTW(res, offset, value) \ + bus_write_2((res), (offset), (value)) +#define ATA_OUTL(res, offset, value) \ + bus_write_4((res), (offset), (value)) +#define ATA_OUTSW(res, offset, addr, count) \ + bus_write_multi_2((res), (offset), (addr), (count)) +#define ATA_OUTSW_STRM(res, offset, addr, count) \ + bus_write_multi_stream_2((res), (offset), (addr), (count)) +#define ATA_OUTSL(res, offset, addr, count) \ + bus_write_multi_4((res), (offset), (addr), (count)) +#define ATA_OUTSL_STRM(res, offset, addr, count) \ + bus_write_multi_stream_4((res), (offset), (addr), (count)) |