From owner-svn-src-head@FreeBSD.ORG Tue Jul 21 12:32:47 2009 Return-Path: Delivered-To: svn-src-head@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 18F641065679; Tue, 21 Jul 2009 12:32:47 +0000 (UTC) (envelope-from mav@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id 03FC68FC19; Tue, 21 Jul 2009 12:32:47 +0000 (UTC) (envelope-from mav@FreeBSD.org) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.3/8.14.3) with ESMTP id n6LCWlp9011343; Tue, 21 Jul 2009 12:32:47 GMT (envelope-from mav@svn.freebsd.org) Received: (from mav@localhost) by svn.freebsd.org (8.14.3/8.14.3/Submit) id n6LCWkP1011337; Tue, 21 Jul 2009 12:32:46 GMT (envelope-from mav@svn.freebsd.org) Message-Id: <200907211232.n6LCWkP1011337@svn.freebsd.org> From: Alexander Motin Date: Tue, 21 Jul 2009 12:32:46 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r195801 - in head: share/man/man4 sys/conf sys/dev/siis sys/modules sys/modules/siis X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 21 Jul 2009 12:32:47 -0000 Author: mav Date: Tue Jul 21 12:32:46 2009 New Revision: 195801 URL: http://svn.freebsd.org/changeset/base/195801 Log: 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) Added: head/share/man/man4/siis.4 (contents, props changed) head/sys/dev/siis/ head/sys/dev/siis/siis.c (contents, props changed) head/sys/dev/siis/siis.h (contents, props changed) head/sys/modules/siis/ head/sys/modules/siis/Makefile (contents, props changed) Modified: head/share/man/man4/Makefile head/sys/conf/files head/sys/modules/Makefile Modified: head/share/man/man4/Makefile ============================================================================== --- head/share/man/man4/Makefile Tue Jul 21 09:54:04 2009 (r195800) +++ head/share/man/man4/Makefile Tue Jul 21 12:32:46 2009 (r195801) @@ -332,6 +332,7 @@ MAN= aac.4 \ sf.4 \ si.4 \ sio.4 \ + siis.4 \ sis.4 \ sk.4 \ smb.4 \ Added: head/share/man/man4/siis.4 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/share/man/man4/siis.4 Tue Jul 21 12:32:46 2009 (r195801) @@ -0,0 +1,122 @@ +.\" Copyright (c) 2009 Alexander Motin +.\" 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. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" 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$ +.\" +.Dd July 18, 2009 +.Dt SIIS 4 +.Os +.Sh NAME +.Nm siis +.Nd SiliconImage Serial ATA Host Controller driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device pci" +.Cd "device scbus" +.Cd "device siis" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +siis_load="YES" +.Ed +.Pp +The following tunables are settable from the loader: +.Bl -ohang +.It Va hint.siis.X.msi +controls Message Signaled Interrupts (MSI) usage by the specified controller. +.It Va hint.siisch.X.pm_level +controls SATA interface Power Management for specified channel, +allowing some power to be saved at the cost of additional command +latency. +Possible values: +.Bl -tag -compact +.It 0 +interface Power Management is disabled (default); +.It 1 +device is allowed to initiate PM state change, host is passive. +.El +Note that interface Power Management is not compatible with +device presence detection. +You will have to reset bus manually on device hot-plug. +.It Va hint.siisch.X.sata_rev +setting to nonzero value limits maximum SATA revision (speed). +Values 1, 2 and 3 are respectively 1.5, 3 and 6Gbps. +.El +.Sh DESCRIPTION +This driver provides the CAM subsystem native access to the +.Tn SATA +ports of controller. +Each SATA port is represented to CAM as a separate bus with 16 targets. +Most of the bus-management details are handled by the SATA-specific +transport of CAM. +Connected ATA disks are handled by the ATA protocol disk peripheral driver +.Xr ada 4 . +ATAPI devices are handled by the SCSI protocol peripheral drivers +.Xr cd 4 , +.Xr da 4 , +.Xr sa 4 , +etc. +.Pp +Driver features include support for Serial ATA and ATAPI devices, +Port Multipliers (including FIS-based switching), hardware command queues +(31 command per port), Native Command Queuing, SATA interface Power Management, +device hot-plug and Message Signaled Interrupts. +.Pp +Same hardware is also supported by atasiliconimage driver from +.Xr ata 4 +subsystem. If both drivers are loaded at the same time, this one will be +given precedence as the more functional of the two. +.Sh HARDWARE +The +.Nm +driver supports following controllers: +.Bl -bullet -compact +.It +SiI3124 +.It +SiI3132 +.It +SiI3531 +.El +.Sh SEE ALSO +.Xr ada 4 , +.Xr cd 4 , +.Xr da 4 , +.Xr sa 4 , +.Xr scsi 4 , +.Xr ata 4 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 8.0 . +.Sh AUTHORS +.An Alexander Motin Aq mav@FreeBSD.org . Modified: head/sys/conf/files ============================================================================== --- head/sys/conf/files Tue Jul 21 09:54:04 2009 (r195800) +++ head/sys/conf/files Tue Jul 21 12:32:46 2009 (r195801) @@ -1391,6 +1391,7 @@ dev/si/si3_t225.c optional si dev/si/si_eisa.c optional si eisa dev/si/si_isa.c optional si isa dev/si/si_pci.c optional si pci +dev/siis/siis.c optional siis pci dev/sis/if_sis.c optional sis pci dev/sk/if_sk.c optional sk pci inet dev/smbus/smb.c optional smb Added: head/sys/dev/siis/siis.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/dev/siis/siis.c Tue Jul 21 12:32:46 2009 (r195801) @@ -0,0 +1,1572 @@ +/*- + * Copyright (c) 2009 Alexander Motin + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "siis.h" + +#include +#include +#include +#include +#include +#include + +/* 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) +{ *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***