--- /dev/null
+# Native AHCI driver, ported from OpenBSD
+#
+KMOD= sili
+
+.PATH: ${.CURDIR}
+
+SRCS= sili_dragonfly.c sili_attach.c sili_cam.c sili_pm.c sili.c \
+ bus_if.h device_if.h pci_if.h opt_scsi.h opt_cam.h
+
+.include <bsd.kmod.mk>
--- /dev/null
+/*
+ * Copyright (c) 2007 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $OpenBSD: atascsi.h,v 1.33 2009/02/16 21:19:06 miod Exp $
+ */
+
+struct atascsi;
+struct scsi_link;
+
+/*
+ * ATA commands
+ */
+
+#define ATA_C_SATA_FEATURE_ENA 0x10
+#define ATA_C_READDMA_EXT 0x25
+#define ATA_C_READ_LOG_EXT 0x2f
+#define ATA_C_WRITEDMA_EXT 0x35
+#define ATA_C_READ_FPDMA 0x60
+#define ATA_C_WRITE_FPDMA 0x61
+#define ATA_C_SATA_FEATURE_DIS 0x90
+#define ATA_C_PACKET 0xa0
+#define ATA_C_ATAPI_IDENTIFY 0xa1
+#define ATA_C_READDMA 0xc8
+#define ATA_C_WRITEDMA 0xca
+#define ATA_C_READ_PM 0xe4
+#define ATA_C_WRITE_PM 0xe8
+#define ATA_C_FLUSH_CACHE 0xe7
+#define ATA_C_FLUSH_CACHE_EXT 0xea /* lba48 */
+#define ATA_C_IDENTIFY 0xec
+#define ATA_C_SET_FEATURES 0xef
+#define ATA_C_SEC_FREEZE_LOCK 0xf5
+
+/*
+ * ATA SATA FEATURES subcommands
+ */
+#define ATA_SATAFT_NONZDMA 0x01 /* DMA non-zero buffer offset */
+#define ATA_SATAFT_DMAAAOPT 0x02 /* DMA AA optimization */
+#define ATA_SATAFT_DEVIPS 0x03 /* Device-initiated pwr state*/
+#define ATA_SATAFT_INORDER 0x04 /* in-order data delivery */
+#define ATA_SATAFT_ASYNCNOTIFY 0x05 /* Async notification */
+
+/*
+ * ATA SET FEATURES subcommands
+ */
+#define ATA_SF_WRITECACHE_EN 0x02
+#define ATA_SF_LOOKAHEAD_EN 0xaa
+
+struct ata_identify {
+ u_int16_t config; /* 0 */
+ u_int16_t ncyls; /* 1 */
+ u_int16_t reserved1; /* 2 */
+ u_int16_t nheads; /* 3 */
+ u_int16_t track_size; /* 4 */
+ u_int16_t sector_size; /* 5 */
+ u_int16_t nsectors; /* 6 */
+ u_int16_t reserved2[3]; /* 7 vendor unique */
+ u_int8_t serial[20]; /* 10 */
+ u_int16_t buffer_type; /* 20 */
+ u_int16_t buffer_size; /* 21 */
+ u_int16_t ecc; /* 22 */
+ u_int8_t firmware[8]; /* 23 */
+ u_int8_t model[40]; /* 27 */
+ u_int16_t multi; /* 47 */
+ u_int16_t dwcap; /* 48 */
+ u_int16_t cap; /* 49 */
+ u_int16_t reserved3; /* 50 */
+ u_int16_t piomode; /* 51 */
+ u_int16_t dmamode; /* 52 */
+ u_int16_t validinfo; /* 53 */
+ u_int16_t curcyls; /* 54 */
+ u_int16_t curheads; /* 55 */
+ u_int16_t cursectrk; /* 56 */
+ u_int16_t curseccp[2]; /* 57 */
+ u_int16_t mult2; /* 59 */
+ u_int16_t addrsec[2]; /* 60 */
+ u_int16_t worddma; /* 62 */
+ u_int16_t dworddma; /* 63 */
+ u_int16_t advpiomode; /* 64 */
+ u_int16_t minmwdma; /* 65 */
+ u_int16_t recmwdma; /* 66 */
+ u_int16_t minpio; /* 67 */
+ u_int16_t minpioflow; /* 68 */
+ u_int16_t reserved4[2]; /* 69 */
+ u_int16_t typtime[2]; /* 71 */
+ u_int16_t reserved5[2]; /* 73 */
+ u_int16_t qdepth; /* 75 */
+ u_int16_t satacap; /* 76 */
+ u_int16_t reserved6; /* 77 */
+ u_int16_t satafsup; /* 78 */
+ u_int16_t satafen; /* 79 */
+ u_int16_t majver; /* 80 */
+ u_int16_t minver; /* 81 */
+ u_int16_t cmdset82; /* 82 */
+ u_int16_t cmdset83; /* 83 */
+ u_int16_t cmdset84; /* 84 */
+ u_int16_t features85; /* 85 */
+ u_int16_t features86; /* 86 */
+ u_int16_t features87; /* 87 */
+#define ATA_ID_F87_WWN (1<<8)
+ u_int16_t ultradma; /* 88 */
+ u_int16_t erasetime; /* 89 */
+ u_int16_t erasetimex; /* 90 */
+ u_int16_t apm; /* 91 */
+ u_int16_t masterpw; /* 92 */
+ u_int16_t hwreset; /* 93 */
+ u_int16_t acoustic; /* 94 */
+ u_int16_t stream_min; /* 95 */
+ u_int16_t stream_xfer_d; /* 96 */
+ u_int16_t stream_lat; /* 97 */
+ u_int16_t streamperf[2]; /* 98 */
+ u_int16_t addrsecxt[4]; /* 100 */
+ u_int16_t stream_xfer_p; /* 104 */
+ u_int16_t padding1; /* 105 */
+ u_int16_t phys_sect_sz; /* 106 */
+ u_int16_t seek_delay; /* 107 */
+ u_int16_t naa_ieee_oui; /* 108 */
+ u_int16_t ieee_oui_uid; /* 109 */
+ u_int16_t uid_mid; /* 110 */
+ u_int16_t uid_low; /* 111 */
+ u_int16_t resv_wwn[4]; /* 112 */
+ u_int16_t incits; /* 116 */
+ u_int16_t words_lsec[2]; /* 117 */
+ u_int16_t cmdset119; /* 119 */
+ u_int16_t features120; /* 120 */
+ u_int16_t padding2[6];
+ u_int16_t rmsn; /* 127 */
+ u_int16_t securestatus; /* 128 */
+#define ATA_SECURE_LOCKED (1<<2)
+#define ATA_SECURE_FROZEN (1<<3)
+ u_int16_t vendor[31]; /* 129 */
+ u_int16_t padding3[16]; /* 160 */
+ u_int16_t curmedser[30]; /* 176 */
+ u_int16_t sctsupport; /* 206 */
+ u_int16_t padding4[48]; /* 207 */
+ u_int16_t integrity; /* 255 */
+} __packed;
+
+/*
+ * IDENTIFY DEVICE data
+ */
+#define ATA_IDENTIFY_SECURITY (1 << 1)
+#define ATA_IDENTIFY_WRITECACHE (1 << 5)
+#define ATA_IDENTIFY_LOOKAHEAD (1 << 6)
+
+/*
+ * Frame Information Structures
+ */
+
+#define ATA_FIS_LENGTH 20
+
+struct ata_fis_h2d {
+ u_int8_t type;
+#define ATA_FIS_TYPE_H2D 0x27
+ u_int8_t flags;
+#define ATA_H2D_FLAGS_CMD (1<<7)
+ u_int8_t command;
+ u_int8_t features;
+#define ATA_H2D_FEATURES_DMA (1<<0)
+#define ATA_H2D_FEATURES_DIR (1<<2)
+#define ATA_H2D_FEATURES_DIR_READ (1<<2)
+#define ATA_H2D_FEATURES_DIR_WRITE (0<<2)
+
+ u_int8_t lba_low;
+ u_int8_t lba_mid;
+ u_int8_t lba_high;
+ u_int8_t device;
+#define ATA_H2D_DEVICE_LBA 0x40
+
+ u_int8_t lba_low_exp;
+ u_int8_t lba_mid_exp;
+ u_int8_t lba_high_exp;
+ u_int8_t features_exp;
+
+ u_int8_t sector_count;
+ u_int8_t sector_count_exp;
+ u_int8_t reserved0;
+ u_int8_t control;
+#define ATA_FIS_CONTROL_SRST 0x04
+#define ATA_FIS_CONTROL_4BIT 0x08
+
+ u_int8_t reserved1;
+ u_int8_t reserved2;
+ u_int8_t reserved3;
+ u_int8_t reserved4;
+} __packed;
+
+struct ata_fis_d2h {
+ u_int8_t type;
+#define ATA_FIS_TYPE_D2H 0x34
+ u_int8_t flags;
+#define ATA_D2H_FLAGS_INTR (1<<6)
+ u_int8_t status;
+ u_int8_t error;
+
+ u_int8_t lba_low;
+ u_int8_t lba_mid;
+ u_int8_t lba_high;
+ u_int8_t device;
+
+ u_int8_t lba_low_exp;
+ u_int8_t lba_mid_exp;
+ u_int8_t lba_high_exp;
+ u_int8_t reserved0;
+
+ u_int8_t sector_count;
+ u_int8_t sector_count_exp;
+ u_int8_t reserved1;
+ u_int8_t reserved2;
+
+ u_int8_t reserved3;
+ u_int8_t reserved4;
+ u_int8_t reserved5;
+ u_int8_t reserved6;
+} __packed;
+
+/*
+ * SATA log page 10h -
+ * looks like a D2H FIS, with errored tag number in first byte.
+ */
+struct ata_log_page_10h {
+ struct ata_fis_d2h err_regs;
+#define ATA_LOG_10H_TYPE_NOTQUEUED 0x80
+#define ATA_LOG_10H_TYPE_TAG_MASK 0x1f
+ u_int8_t reserved[256 - sizeof(struct ata_fis_d2h)];
+ u_int8_t vendor_specific[255];
+ u_int8_t checksum;
+} __packed;
+
+/*
+ * SATA registers
+ */
+
+#define SATA_SStatus_DET 0x00f
+#define SATA_SStatus_DET_NODEV 0x000
+#define SATA_SStatus_DET_NOPHY 0x001
+#define SATA_SStatus_DET_DEV 0x003
+#define SATA_SStatus_DET_OFFLINE 0x008
+
+#define SATA_SStatus_SPD 0x0f0
+#define SATA_SStatus_SPD_NONE 0x000
+#define SATA_SStatus_SPD_1_5 0x010
+#define SATA_SStatus_SPD_3_0 0x020
+
+#define SATA_SStatus_IPM 0xf00
+#define SATA_SStatus_IPM_NODEV 0x000
+#define SATA_SStatus_IPM_ACTIVE 0x100
+#define SATA_SStatus_IPM_PARTIAL 0x200
+#define SATA_SStatus_IPM_SLUMBER 0x600
+
+#define SATA_SIGNATURE_PORT_MULTIPLIER 0x96690101
+#define SATA_SIGNATURE_ATAPI 0xeb140101
+#define SATA_SIGNATURE_DISK 0x00000101
+
+/*
+ * ATA interface
+ */
+
+struct sili_port;
+
+struct ata_port {
+ struct ata_identify at_identify; /* only if ATA_PORT_T_DISK */
+ struct sili_port *at_sili_port;
+ int at_type;
+#define ATA_PORT_T_NONE 0
+#define ATA_PORT_T_DISK 1
+#define ATA_PORT_T_ATAPI 2
+#define ATA_PORT_T_PM 3
+ int at_features;
+#define ATA_PORT_F_WCACHE (1 << 0)
+#define ATA_PORT_F_RAHEAD (1 << 1)
+#define ATA_PORT_F_FRZLCK (1 << 2)
+#define ATA_PORT_F_RESCAN (1 << 3) /* re-check on bus scan */
+#define ATA_PORT_F_READLOG (1 << 4) /* must read log page */
+ int at_probe;
+#define ATA_PROBE_NEED_INIT 0
+#define ATA_PROBE_NEED_HARD_RESET 1
+#define ATA_PROBE_NEED_SOFT_RESET 2
+#define ATA_PROBE_NEED_IDENT 3
+#define ATA_PROBE_GOOD 4
+#define ATA_PROBE_FAILED 7
+ int at_ncqdepth;
+ u_int64_t at_capacity; /* only if ATA_PORT_T_DISK */
+ int at_target; /* port multiplier port */
+ char at_name[16];
+};
+
+struct ata_xfer {
+ struct ata_fis_h2d *fis;
+ struct ata_fis_d2h *rfis;
+ u_int8_t *packetcmd;
+ u_int8_t tag;
+
+ void *data;
+ size_t datalen;
+ size_t resid;
+
+ void (*complete)(struct ata_xfer *);
+ u_int timeout;
+
+ int flags;
+#define ATA_F_READ (1<<0)
+#define ATA_F_WRITE (1<<1)
+#define ATA_F_NOWAIT (1<<2)
+#define ATA_F_POLL (1<<3)
+#define ATA_F_PIO (1<<4)
+#define ATA_F_PACKET (1<<5)
+#define ATA_F_NCQ (1<<6)
+#define ATA_F_TIMEOUT_RUNNING (1<<7)
+#define ATA_F_TIMEOUT_DESIRED (1<<8)
+#define ATA_F_TIMEOUT_EXPIRED (1<<9)
+#define ATA_FMT_FLAGS "\020" \
+ "\012EXPIRED" \
+ "\011DESIRED" "\010TRUNNING" \
+ "\007NCQ" "\006PACKET" \
+ "\005PIO" "\004POLL" "\003NOWAIT" \
+ "\002WRITE" "\001READ"
+
+ volatile int state;
+#define ATA_S_SETUP 0
+#define ATA_S_PENDING 1
+#define ATA_S_COMPLETE 2
+#define ATA_S_ERROR 3
+#define ATA_S_TIMEOUT 4
+#define ATA_S_ONCHIP 5
+#define ATA_S_PUT 6
+
+ void *atascsi_private;
+ struct ata_port *at; /* NULL if direct-attached */
+};
--- /dev/null
+#!/bin/csh
+#
+
+foreach i ( *.c *.h )
+ sed -e 's/AHCI/SILI/g' < $i > $i.new
+ mv $i.new $i
+end
--- /dev/null
+/*
+ * Copyright (c) 2009 The DragonFly Project. All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ *
+ * 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. Neither the name of The DragonFly Project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific, prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * SATA port multiplier registers
+ */
+#define SATA_PMREG_SSTS 0 /* use SATA_PM_SSTS_ bit defs */
+#define SATA_PMREG_SERR 1 /* use SATA_PM_SERR_ bit defs */
+#define SATA_PMREG_SCTL 2 /* use SATA_PM_SCTL_ bit defs */
+#define SATA_PMREG_SACT 3 /* (not implemented on PM) */
+
+#define SATA_PM_SSTS_DET 0xf /* Device Detection */
+#define SATA_PM_SSTS_DET_NONE 0x0
+#define SATA_PM_SSTS_DET_DEV_NE 0x1
+#define SATA_PM_SSTS_DET_DEV 0x3
+#define SATA_PM_SSTS_DET_PHYOFFLINE 0x4
+#define SATA_PM_SSTS_SPD 0xf0 /* Current Interface Speed */
+#define SATA_PM_SSTS_SPD_NONE 0x00
+#define SATA_PM_SSTS_SPD_GEN1 0x10
+#define SATA_PM_SSTS_SPD_GEN2 0x20
+#define SATA_PM_SSTS_IPM 0xf00 /* Interface Power Management */
+#define SATA_PM_SSTS_IPM_NONE 0x000
+#define SATA_PM_SSTS_IPM_ACTIVE 0x100
+#define SATA_PM_SSTS_IPM_PARTIAL 0x200
+#define SATA_PM_SSTS_IPM_SLUMBER 0x600
+
+#define SATA_PM_SCTL_DET 0xf /* Device Detection */
+#define SATA_PM_SCTL_DET_NONE 0x0
+#define SATA_PM_SCTL_DET_INIT 0x1
+#define SATA_PM_SCTL_DET_DISABLE 0x4
+#define SATA_PM_SCTL_SPD 0xf0 /* Speed Allowed */
+#define SATA_PM_SCTL_SPD_ANY 0x00
+#define SATA_PM_SCTL_SPD_GEN1 0x10
+#define SATA_PM_SCTL_SPD_GEN2 0x20
+#define SATA_PM_SCTL_IPM 0xf00 /* Interface Power Management */
+#define SATA_PM_SCTL_IPM_NONE 0x000
+#define SATA_PM_SCTL_IPM_NOPARTIAL 0x100
+#define SATA_PM_SCTL_IPM_NOSLUMBER 0x200
+#define SATA_PM_SCTL_IPM_DISABLED 0x300
+#define SATA_PM_SCTL_SPM 0xf000 /* Select Power Management */
+#define SATA_PM_SCTL_SPM_NONE 0x0000
+#define SATA_PM_SCTL_SPM_NOPARTIAL 0x1000
+#define SATA_PM_SCTL_SPM_NOSLUMBER 0x2000
+#define SATA_PM_SCTL_SPM_DISABLED 0x3000
+#define SATA_PM_SCTL_PMP 0xf0000 /* Set PM port for xmit FISes */
+#define SATA_PM_SCTL_PMP_SHIFT 16
+
+#define SATA_PM_SERR_ERR_I (1<<0) /* Recovered Data Integrity */
+#define SATA_PM_SERR_ERR_M (1<<1) /* Recovered Communications */
+#define SATA_PM_SERR_ERR_T (1<<8) /* Transient Data Integrity */
+#define SATA_PM_SERR_ERR_C (1<<9) /* Persistent Comm/Data */
+#define SATA_PM_SERR_ERR_P (1<<10) /* Protocol */
+#define SATA_PM_SERR_ERR_E (1<<11) /* Internal */
+#define SATA_PM_SERR_DIAG_N (1<<16) /* PhyRdy Change */
+#define SATA_PM_SERR_DIAG_I (1<<17) /* Phy Internal Error */
+#define SATA_PM_SERR_DIAG_W (1<<18) /* Comm Wake */
+#define SATA_PM_SERR_DIAG_B (1<<19) /* 10B to 8B Decode Error */
+#define SATA_PM_SERR_DIAG_D (1<<20) /* Disparity Error */
+#define SATA_PM_SERR_DIAG_C (1<<21) /* CRC Error */
+#define SATA_PM_SERR_DIAG_H (1<<22) /* Handshake Error */
+#define SATA_PM_SERR_DIAG_S (1<<23) /* Link Sequence Error */
+#define SATA_PM_SERR_DIAG_T (1<<24) /* Transport State Trans Err */
+#define SATA_PM_SERR_DIAG_F (1<<25) /* Unknown FIS Type */
+#define SATA_PM_SERR_DIAG_X (1<<26) /* Exchanged */
+
+#define SATA_PFMT_SERR "\020" \
+ "\033DIAG.X" "\032DIAG.F" "\031DIAG.T" "\030DIAG.S" \
+ "\027DIAG.H" "\026DIAG.C" "\025DIAG.D" "\024DIAG.B" \
+ "\023DIAG.W" "\022DIAG.I" "\021DIAG.N" \
+ "\014ERR.E" "\013ERR.P" "\012ERR.C" "\011ERR.T" \
+ "\002ERR.M" "\001ERR.I"
+
+/*
+ * AHCI port multiplier revision information SCR[1] (see ahci_pm_read)
+ *
+ * Rev 1.1 is the one that should support async notification.
+ */
+#define SATA_PMREV_PM1_0 0x00000002
+#define SATA_PMREV_PM1_1 0x00000004
+#define SATA_PFMT_PM_REV "\20" "\003PM1.1" "\002PM1.0"
+
+/*
+ * GSCR[64] and GSCR[96] - Port Multiplier features available and features
+ * enabled.
+ */
+#define SATA_PMREG_FEA 64
+#define SATA_PMREG_FEAEN 96 /* (features enabled) */
+#define SATA_PMFEA_BIST 0x00000001 /* BIST Support */
+#define SATA_PMFEA_PMREQ 0x00000002 /* Can issue PMREQp to host */
+#define SATA_PMFEA_DYNSSC 0x00000004 /* Dynamic SSC transmit enab */
+#define SATA_PMFEA_ASYNCNOTIFY 0x00000008 /* Async notification */
+
+#define SATA_PFMT_PM_FEA "\20" \
+ "\004AsyncNotify" \
+ "\003DynamicSSC" \
+ "\002PMREQ" \
+ "\001BIST"
+
+/*
+ * Enable generation of async notify events for individual targets
+ * via the PMEENA register. Each bit in PMEINFO is a wire-or of all
+ * SERROR bits for that target. To enable a new notification event
+ * the SERROR bits in PMSERROR_REGNO must be cleared.
+ */
+#define SATA_PMREG_EINFO 32 /* error info 16 ports */
+#define SATA_PMREG_EEENA 33 /* error info enable 16 ports */
--- /dev/null
+/*
+ * Copyright (c) 2009 The DragonFly Project. All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ *
+ * 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. Neither the name of The DragonFly Project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific, prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *
+ * Copyright (c) 2006 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ *
+ *
+ * $OpenBSD: sili.c,v 1.147 2009/02/16 21:19:07 miod Exp $
+ */
+
+#include "sili.h"
+
+void sili_port_interrupt_enable(struct sili_port *ap);
+void sili_port_interrupt_redisable(struct sili_port *ap);
+void sili_port_interrupt_reenable(struct sili_port *ap);
+
+int sili_load_prb(struct sili_ccb *);
+void sili_unload_prb(struct sili_ccb *);
+static void sili_load_prb_callback(void *info, bus_dma_segment_t *segs,
+ int nsegs, int error);
+void sili_start(struct sili_ccb *);
+int sili_port_softreset(struct sili_port *ap);
+int sili_port_pmprobe(struct sili_port *ap);
+int sili_port_hardreset(struct sili_port *ap, int hard);
+void sili_port_hardstop(struct sili_port *ap);
+void sili_port_listen(struct sili_port *ap);
+
+static void sili_ata_cmd_timeout_unserialized(void *);
+static int sili_core_timeout(struct sili_ccb *ccb);
+void sili_quick_timeout(struct sili_ccb *ccb);
+void sili_check_active_timeouts(struct sili_port *ap);
+
+void sili_beg_exclusive_access(struct sili_port *ap, struct ata_port *at);
+void sili_end_exclusive_access(struct sili_port *ap, struct ata_port *at);
+void sili_issue_pending_commands(struct sili_port *ap, struct sili_ccb *ccb);
+
+int sili_port_read_ncq_error(struct sili_port *, int);
+
+struct sili_dmamem *sili_dmamem_alloc(struct sili_softc *, bus_dma_tag_t tag);
+void sili_dmamem_free(struct sili_softc *, struct sili_dmamem *);
+static void sili_dmamem_saveseg(void *info, bus_dma_segment_t *segs, int nsegs, int error);
+
+static void sili_dummy_done(struct ata_xfer *xa);
+static void sili_empty_done(struct sili_ccb *ccb);
+static void sili_ata_cmd_done(struct sili_ccb *ccb);
+
+/* Wait for all bits in _b to be cleared */
+#define sili_pwait_clr(_ap, _r, _b) \
+ sili_pwait_eq((_ap), SILI_PWAIT_TIMEOUT, (_r), (_b), 0)
+#define sili_pwait_clr_to(_ap, _to, _r, _b) \
+ sili_pwait_eq((_ap), _to, (_r), (_b), 0)
+
+/* Wait for all bits in _b to be set */
+#define sili_pwait_set(_ap, _r, _b) \
+ sili_pwait_eq((_ap), SILI_PWAIT_TIMEOUT, (_r), (_b), (_b))
+#define sili_pwait_set_to(_ap, _to, _r, _b) \
+ sili_pwait_eq((_ap), _to, (_r), (_b), (_b))
+
+#define SILI_PWAIT_TIMEOUT 1000
+
+/*
+ * Initialize the global SILI hardware. This code does not set up any of
+ * its ports.
+ */
+int
+sili_init(struct sili_softc *sc)
+{
+ DPRINTF(SILI_D_VERBOSE, " GHC 0x%b",
+ sili_read(sc, SILI_REG_GHC), SILI_FMT_GHC);
+
+ /*
+ * Reset the entire chip. This also resets all ports.
+ *
+ * The spec doesn't say anything about how long we have to
+ * wait, so wait 10ms.
+ */
+ sili_write(sc, SILI_REG_GCTL, SILI_REG_GCTL_GRESET);
+ sili_os_sleep(10);
+ sili_write(sc, SILI_REG_GCTL, 0);
+ sili_os_sleep(10);
+
+ return (0);
+}
+
+/*
+ * Allocate and initialize an SILI port.
+ */
+int
+sili_port_alloc(struct sili_softc *sc, u_int port)
+{
+ struct sili_port *ap;
+ struct ata_port *at;
+ struct sili_ccb *ccb;
+ int rc = ENOMEM;
+ int error;
+ int i;
+
+ ap = kmalloc(sizeof(*ap), M_DEVBUF, M_WAITOK | M_ZERO);
+
+ ksnprintf(ap->ap_name, sizeof(ap->ap_name), "%s%d.%d",
+ device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev),
+ port);
+ sc->sc_ports[port] = ap;
+ kprintf("%s: allocate port\n", PORTNAME(ap));
+
+ /*
+ * Allocate enough so we never have to reallocate, it makes
+ * it easier.
+ *
+ * ap_pmcount will be reduced by the scan if we encounter the
+ * port multiplier port prior to target 15.
+ */
+ if (ap->ap_ata == NULL) {
+ ap->ap_ata = kmalloc(sizeof(*ap->ap_ata) * SILI_MAX_PMPORTS,
+ M_DEVBUF, M_INTWAIT | M_ZERO);
+ for (i = 0; i < SILI_MAX_PMPORTS; ++i) {
+ at = &ap->ap_ata[i];
+ at->at_sili_port = ap;
+ at->at_target = i;
+ at->at_probe = ATA_PROBE_NEED_INIT;
+ at->at_features |= ATA_PORT_F_RESCAN;
+ ksnprintf(at->at_name, sizeof(at->at_name),
+ "%s.%d", ap->ap_name, i);
+ }
+ }
+ if (bus_space_subregion(sc->sc_piot, sc->sc_pioh,
+ SILI_PORT_REGION(port), SILI_PORT_SIZE,
+ &ap->ap_ioh) != 0) {
+ device_printf(sc->sc_dev,
+ "unable to create register window for port %d\n",
+ port);
+ goto freeport;
+ }
+
+ ap->ap_sc = sc;
+ ap->ap_num = port;
+ ap->ap_probe = ATA_PROBE_NEED_INIT;
+ TAILQ_INIT(&ap->ap_ccb_free);
+ TAILQ_INIT(&ap->ap_ccb_pending);
+ lockinit(&ap->ap_ccb_lock, "silipo", 0, 0);
+
+ /* Disable port interrupts */
+ sili_pwrite(ap, SILI_PREG_INT_DISABLE, SILI_PREG_INT_MASK);
+
+ /*
+ * Reset the port. This is similar to a Device Reset but far
+ * more invasive. We use Device Reset in our hardreset function.
+ * This function also does the same OOB initialization sequence
+ * that Device Reset does.
+ *
+ * NOTE: SILI_PREG_STATUS_READY will not be asserted unless and until
+ * a device is connected to the port, so we can't use it to
+ * verify that the port exists.
+ */
+ sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_RESET);
+ if (sili_pread(ap, SILI_PREG_STATUS) & SILI_PREG_STATUS_READY) {
+ device_printf(sc->sc_dev,
+ "Port %d will not go into reset\n", port);
+ goto freeport;
+ }
+ sili_os_sleep(10);
+ sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESUME);
+ sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESET);
+
+ /*
+ * Allocate the SGE Table
+ */
+ ap->ap_dmamem_sget = sili_dmamem_alloc(sc, sc->sc_tag_sget);
+ if (ap->ap_dmamem_sget == NULL) {
+ kprintf("%s: NOSGET\n", PORTNAME(ap));
+ goto freeport;
+ }
+
+ /*
+ * Set up the SGE table base address
+ */
+ ap->ap_sget = (struct sili_sge *)SILI_DMA_KVA(ap->ap_dmamem_sget);
+
+ /*
+ * Allocate a CCB for each command slot
+ */
+ ap->ap_ccbs = kmalloc(sizeof(struct sili_ccb) * sc->sc_ncmds, M_DEVBUF,
+ M_WAITOK | M_ZERO);
+ if (ap->ap_ccbs == NULL) {
+ device_printf(sc->sc_dev,
+ "unable to allocate command list for port %d\n",
+ port);
+ goto freeport;
+ }
+
+ /*
+ * Port control register setup.
+ */
+ sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_NOAUTOCC);
+ sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_32BITDMA |
+ SILI_PREG_CTL_PMA |
+ SILI_PREG_CTL_NOAUTOCC);
+
+ /*
+ * Most structures are in the port BAR. Assign convenient
+ * pointers in the CCBs
+ */
+
+ for (i = 0; i < sc->sc_ncmds; i++) {
+ ccb = &ap->ap_ccbs[i];
+
+ error = bus_dmamap_create(sc->sc_tag_data, BUS_DMA_ALLOCNOW,
+ &ccb->ccb_dmamap);
+ if (error) {
+ device_printf(sc->sc_dev,
+ "unable to create dmamap for port %d "
+ "ccb %d\n", port, i);
+ goto freeport;
+ }
+
+ /*
+ * NOTE: fis and rfis overload the same structure.
+ */
+ callout_init(&ccb->ccb_timeout);
+ ccb->ccb_slot = i;
+ ccb->ccb_port = ap;
+ ccb->ccb_prb = bus_space_kva(sc->sc_piot, ap->ap_ioh,
+ SILI_PREG_LRAM_SLOT(i));
+ ccb->ccb_sge = &ap->ap_sget[i * SILI_MAX_SGET];
+ ccb->ccb_sge_paddr = SILI_DMA_DVA(ap->ap_dmamem_sget) +
+ sizeof(*ccb->ccb_sge) * SILI_MAX_SGET * i;
+ ccb->ccb_xa.fis = &ccb->ccb_prb->prb_h2d;
+ ccb->ccb_xa.rfis = &ccb->ccb_prb->prb_d2h;
+ ccb->ccb_xa.packetcmd = prb_packet(ccb->ccb_prb);
+ ccb->ccb_xa.tag = i;
+
+ ccb->ccb_xa.state = ATA_S_COMPLETE;
+
+ /*
+ * Reserve CCB[1] as the error CCB. It doesn't matter
+ * which one we use for the Sili controllers.
+ */
+ if (i == 1)
+ ap->ap_err_ccb = ccb;
+ else
+ sili_put_ccb(ccb);
+ }
+ kprintf("%s: start port\n", PORTNAME(ap));
+ sili_os_start_port(ap);
+ return(0);
+freeport:
+ sili_port_free(sc, port);
+ return (rc);
+}
+
+/*
+ * [re]initialize an idle port. No CCBs should be active.
+ *
+ * If at is NULL we are initializing a directly connected port, otherwise
+ * we are indirectly initializing a port multiplier port.
+ *
+ * This function is called during the initial port allocation sequence
+ * and is also called on hot-plug insertion. We take no chances and
+ * use a hardreset instead of a softreset.
+ *
+ * This function is the only way to move a failed port back to active
+ * status.
+ *
+ * Returns 0 if a device is successfully detected.
+ */
+int
+sili_port_init(struct sili_port *ap, struct ata_port *atx)
+{
+ u_int32_t data;
+ int rc;
+
+ /*
+ * Clear all notification bits
+ */
+ if (atx == NULL && (ap->ap_sc->sc_flags & SILI_F_SSNTF))
+ sili_pwrite(ap, SILI_PREG_SNTF, -1);
+
+ /*
+ * Make sure the port is out of continuous COMRESET mode.
+ */
+ data = SILI_PREG_SCTL_SPM_NONE |
+ SILI_PREG_SCTL_IPM_NONE |
+ SILI_PREG_SCTL_SPD_NONE |
+ SILI_PREG_SCTL_DET_NONE;
+ if (SiliForceGen1 & (1 << ap->ap_num)) {
+ data &= ~SILI_PREG_SCTL_SPD_NONE;
+ data |= SILI_PREG_SCTL_SPD_GEN1;
+ }
+ sili_pwrite(ap, SILI_PREG_SCTL, data);
+
+ /*
+ * Hard-reset the port. If a device is detected but it is busy
+ * we try a second time, this time cycling the phy as well.
+ *
+ * XXX note: hard reset mode 2 (cycling the PHY) is not reliable.
+ */
+ if (atx)
+ atx->at_probe = ATA_PROBE_NEED_HARD_RESET;
+ else
+ ap->ap_probe = ATA_PROBE_NEED_HARD_RESET;
+
+ rc = sili_port_reset(ap, atx, 1);
+#if 0
+ rc = sili_port_reset(ap, atx, 1);
+ if (rc == EBUSY) {
+ rc = sili_port_reset(ap, atx, 2);
+ }
+#endif
+
+ switch (rc) {
+ case ENODEV:
+ /*
+ * We had problems talking to the device on the port.
+ */
+ if (atx) {
+ sili_pm_read(ap, atx->at_target,
+ SATA_PMREG_SSTS, &data);
+
+ switch(data & SATA_PM_SSTS_DET) {
+ case SATA_PM_SSTS_DET_DEV_NE:
+ kprintf("%s: Device not communicating\n",
+ ATANAME(ap, atx));
+ break;
+ case SATA_PM_SSTS_DET_PHYOFFLINE:
+ kprintf("%s: PHY offline\n",
+ ATANAME(ap, atx));
+ break;
+ default:
+ kprintf("%s: No device detected\n",
+ ATANAME(ap, atx));
+ break;
+ }
+ } else {
+ data = sili_pread(ap, SILI_PREG_SSTS);
+
+ switch(data & SATA_PM_SSTS_DET) {
+ case SILI_PREG_SSTS_DET_DEV_NE:
+ kprintf("%s: Device not communicating\n",
+ ATANAME(ap, atx));
+ break;
+ case SILI_PREG_SSTS_DET_OFFLINE:
+ kprintf("%s: PHY offline\n",
+ ATANAME(ap, atx));
+ break;
+ default:
+ kprintf("%s: No device detected\n",
+ ATANAME(ap, atx));
+ break;
+ }
+ }
+ break;
+
+ case EBUSY:
+ /*
+ * The device on the port is still telling us its busy,
+ * which means that it is not properly handling a SATA
+ * port COMRESET.
+ *
+ * It may be possible to softreset the device using CLO
+ * and a device reset command.
+ */
+ if (atx) {
+ kprintf("%s: Device on port is bricked, giving up\n",
+ ATANAME(ap, atx));
+ } else {
+ kprintf("%s: Device on port is bricked, "
+ "trying softreset\n", PORTNAME(ap));
+
+ rc = sili_port_reset(ap, atx, 0);
+ if (rc) {
+ kprintf("%s: Unable unbrick device\n",
+ PORTNAME(ap));
+ } else {
+ kprintf("%s: Successfully unbricked\n",
+ PORTNAME(ap));
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * Command transfers can only be enabled if a device was successfully
+ * detected.
+ *
+ * Allocate or deallocate the ap_ata array here too.
+ */
+ if (atx == NULL) {
+ switch(ap->ap_type) {
+ case ATA_PORT_T_NONE:
+ ap->ap_pmcount = 0;
+ break;
+ case ATA_PORT_T_PM:
+ /* already set */
+ break;
+ default:
+ ap->ap_pmcount = 1;
+ break;
+ }
+ }
+
+ /*
+ * Flush interrupts on the port. XXX
+ *
+ * Enable interrupts on the port whether a device is sitting on
+ * it or not, to handle hot-plug events.
+ */
+ if (atx == NULL) {
+#if 0
+ sili_pwrite(ap, SILI_PREG_IS, sili_pread(ap, SILI_PREG_IS));
+ sili_write(ap->ap_sc, SILI_REG_IS, 1 << ap->ap_num);
+#endif
+ sili_port_interrupt_enable(ap);
+ }
+ return(rc);
+}
+
+/*
+ * Handle an errored port. This routine is called when the only
+ * commands left on the queue are expired, meaning we can safely
+ * go through a port init to clear its state.
+ *
+ * We complete the expired CCBs and then restart the queue.
+ */
+static
+void
+sili_port_reinit(struct sili_port *ap)
+{
+ struct sili_ccb *ccb;
+ struct ata_port *at;
+ int slot;
+ int target;
+ u_int32_t data;
+ int reentrant;
+
+ reentrant = (ap->ap_flags & AP_F_ERR_CCB_RESERVED) ? 1 : 0;
+
+ kprintf("%s: PORT REINIT AFTER ERROR reentrant=%d\n",
+ PORTNAME(ap), reentrant);
+
+ /*
+ * Clear port resume, clear bits 16:13 in the port device status
+ * register. This is from the data sheet.
+ *
+ * Data sheet does not specify a delay but it seems prudent.
+ */
+ sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESUME);
+ sili_os_sleep(10);
+ for (target = 0; target < SILI_MAX_PMPORTS; ++target) {
+ data = sili_pread(ap, SILI_PREG_PM_STATUS(target));
+ data &= ~(SILI_PREG_PM_STATUS_SERVICE |
+ SILI_PREG_PM_STATUS_LEGACY |
+ SILI_PREG_PM_STATUS_NATIVE |
+ SILI_PREG_PM_STATUS_VBSY);
+ sili_pwrite(ap, SILI_PREG_PM_STATUS(target), data);
+ sili_pwrite(ap, SILI_PREG_PM_QACTIVE(target), 0);
+ }
+
+ /*
+ * Issue a Port Initialize and wait for it to clear. This flushes
+ * commands but does not reset the port. Then wait for port ready.
+ */
+ sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_INIT);
+ if (sili_pwait_clr(ap, SILI_PREG_STATUS, SILI_PREG_CTL_INIT)) {
+ kprintf("%s: Unable to reinit, port failed\n",
+ PORTNAME(ap));
+ }
+ if (sili_pwait_set(ap, SILI_PREG_STATUS, SILI_PREG_STATUS_READY)) {
+ kprintf("%s: Unable to reinit, port will not come ready\n",
+ PORTNAME(ap));
+ }
+
+ /*
+ * Read the LOG ERROR page for targets that returned a specific
+ * D2H FIS with ERR set.
+ */
+ if (reentrant == 0) {
+ for (target = 0; target < SILI_MAX_PMPORTS; ++target) {
+ at = &ap->ap_ata[target];
+ if (at->at_features & ATA_PORT_F_READLOG) {
+ kprintf("%s: READ LOG ERROR PAGE\n",
+ ATANAME(ap, at));
+ at->at_features &= ~ATA_PORT_F_READLOG;
+ sili_port_read_ncq_error(ap, target);
+ }
+ }
+ }
+
+ /*
+ * Finally clean out the expired commands, we've probed the error
+ * status (or hopefully probed the error status). Well, ok,
+ * we probably didn't XXX.
+ */
+ while (ap->ap_expired) {
+ slot = ffs(ap->ap_expired) - 1;
+ ap->ap_expired &= ~(1 << slot);
+ KKASSERT(ap->ap_active & (1 << slot));
+ ap->ap_active &= ~(1 << slot);
+ --ap->ap_active_cnt;
+ ccb = &ap->ap_ccbs[slot];
+ ccb->ccb_xa.state = ATA_S_TIMEOUT;
+ kprintf("%s: reinit: kill slot %d\n",
+ ATANAME(ap, ccb->ccb_xa.at), ccb->ccb_slot);
+ ccb->ccb_done(ccb);
+ ccb->ccb_xa.complete(&ccb->ccb_xa);
+ }
+
+ /*
+ * Wow. All done. We can get the port moving again.
+ */
+ if (reentrant) {
+ kprintf("%s: reinit called reentrantly, skip end\n",
+ PORTNAME(ap));
+ } else if (ap->ap_probe == ATA_PROBE_FAILED) {
+ kprintf("%s: reinit failed, port is dead\n", PORTNAME(ap));
+ while ((ccb = TAILQ_FIRST(&ap->ap_ccb_pending)) != NULL) {
+ TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry);
+ ccb->ccb_xa.flags &= ~ATA_F_TIMEOUT_DESIRED;
+ ccb->ccb_xa.state = ATA_S_TIMEOUT;
+ ccb->ccb_done(ccb);
+ ccb->ccb_xa.complete(&ccb->ccb_xa);
+ }
+ } else {
+ kprintf("%s: reinit succeeded probe=%d type=%d\n", PORTNAME(ap), ap->ap_probe, ap->ap_type);
+ sili_issue_pending_commands(ap, NULL);
+ }
+ return;
+}
+
+/*
+ * Enable or re-enable interrupts on a port.
+ *
+ * This routine is called from the port initialization code or from the
+ * helper thread as the real interrupt may be forced to turn off certain
+ * interrupt sources.
+ */
+void
+sili_port_interrupt_enable(struct sili_port *ap)
+{
+ u_int32_t data;
+
+ data = SILI_PREG_INT_CCOMPLETE | SILI_PREG_INT_CERROR |
+ SILI_PREG_INT_PHYRDYCHG | SILI_PREG_INT_DEVEXCHG |
+ SILI_PREG_INT_DECODE | SILI_PREG_INT_CRC |
+ SILI_PREG_INT_HANDSHK | SILI_PREG_INT_PMCHANGE;
+ if (ap->ap_sc->sc_flags & SILI_F_SSNTF)
+ data |= SILI_PREG_INT_SDB;
+ sili_pwrite(ap, SILI_PREG_INT_ENABLE, data);
+}
+
+void
+sili_port_interrupt_redisable(struct sili_port *ap)
+{
+ u_int32_t data;
+
+ data = sili_read(ap->ap_sc, SILI_REG_GCTL);
+ data &= SILI_REG_GINT_PORTMASK;
+ data &= ~(1 << ap->ap_num);
+ sili_write(ap->ap_sc, SILI_REG_GCTL, data);
+}
+
+void
+sili_port_interrupt_reenable(struct sili_port *ap)
+{
+ u_int32_t data;
+
+ data = sili_read(ap->ap_sc, SILI_REG_GCTL);
+ data &= SILI_REG_GINT_PORTMASK;
+ data |= (1 << ap->ap_num);
+ sili_write(ap->ap_sc, SILI_REG_GCTL, data);
+}
+
+/*
+ * Run the port / target state machine from a main context.
+ *
+ * The state machine for the port is always run.
+ *
+ * If atx is non-NULL run the state machine for a particular target.
+ * If atx is NULL run the state machine for all targets.
+ */
+void
+sili_port_state_machine(struct sili_port *ap, int initial)
+{
+ struct ata_port *at;
+ u_int32_t data;
+ int target;
+ int didsleep;
+ int loop;
+
+ /*
+ * State machine for port. Note that CAM is not yet associated
+ * during the initial parallel probe and the port's probe state
+ * will not get past ATA_PROBE_NEED_IDENT.
+ */
+ {
+ if (initial == 0 && ap->ap_probe <= ATA_PROBE_NEED_HARD_RESET) {
+ kprintf("%s: Waiting 10 seconds on insertion\n",
+ PORTNAME(ap));
+ sili_os_sleep(10000);
+ initial = 1;
+ }
+ if (ap->ap_probe == ATA_PROBE_NEED_INIT)
+ sili_port_init(ap, NULL);
+ if (ap->ap_probe == ATA_PROBE_NEED_HARD_RESET)
+ sili_port_reset(ap, NULL, 1);
+ if (ap->ap_probe == ATA_PROBE_NEED_SOFT_RESET)
+ sili_port_reset(ap, NULL, 0);
+ if (ap->ap_probe == ATA_PROBE_NEED_IDENT)
+ sili_cam_probe(ap, NULL);
+ }
+ if (ap->ap_type != ATA_PORT_T_PM) {
+ if (ap->ap_probe == ATA_PROBE_FAILED) {
+ sili_cam_changed(ap, NULL, 0);
+ } else if (ap->ap_probe >= ATA_PROBE_NEED_IDENT) {
+ sili_cam_changed(ap, NULL, 1);
+ }
+ return;
+ }
+
+ /*
+ * Port Multiplier state machine.
+ *
+ * Get a mask of changed targets and combine with any runnable
+ * states already present.
+ */
+ for (loop = 0; ;++loop) {
+ if (sili_pm_read(ap, 15, SATA_PMREG_EINFO, &data)) {
+ kprintf("%s: PM unable to read hot-plug bitmap\n",
+ PORTNAME(ap));
+ break;
+ }
+
+ /*
+ * Do at least one loop, then stop if no more state changes
+ * have occured. The PM might not generate a new
+ * notification until we clear the entire bitmap.
+ */
+ if (loop && data == 0)
+ break;
+
+ /*
+ * New devices showing up in the bitmap require some spin-up
+ * time before we start probing them. Reset didsleep. The
+ * first new device we detect will sleep before probing.
+ *
+ * This only applies to devices whos change bit is set in
+ * the data, and does not apply to the initial boot-time
+ * probe.
+ */
+ didsleep = 0;
+
+ for (target = 0; target < ap->ap_pmcount; ++target) {
+ at = &ap->ap_ata[target];
+
+ /*
+ * Check the target state for targets behind the PM
+ * which have changed state. This will adjust
+ * at_probe and set ATA_PORT_F_RESCAN
+ *
+ * We want to wait at least 10 seconds before probing
+ * a newly inserted device. If the check status
+ * indicates a device is present and in need of a
+ * hard reset, we make sure we have slept before
+ * continuing.
+ *
+ * We also need to wait at least 1 second for the
+ * PHY state to change after insertion, if we
+ * haven't already waited the 10 seconds.
+ *
+ * NOTE: When pm_check_good finds a good port it
+ * typically starts us in probe state
+ * NEED_HARD_RESET rather than INIT.
+ */
+ if (data & (1 << target)) {
+ if (initial == 0 && didsleep == 0)
+ sili_os_sleep(1000);
+ sili_pm_check_good(ap, target);
+ if (initial == 0 && didsleep == 0 &&
+ at->at_probe <= ATA_PROBE_NEED_HARD_RESET
+ ) {
+ didsleep = 1;
+ kprintf("%s: Waiting 10 seconds on insertion\n", PORTNAME(ap));
+ sili_os_sleep(10000);
+ }
+ }
+
+ /*
+ * Report hot-plug events before the probe state
+ * really gets hot. Only actual events are reported
+ * here to reduce spew.
+ */
+ if (data & (1 << target)) {
+ kprintf("%s: HOTPLUG (PM) - ", ATANAME(ap, at));
+ switch(at->at_probe) {
+ case ATA_PROBE_NEED_INIT:
+ case ATA_PROBE_NEED_HARD_RESET:
+ kprintf("Device inserted\n");
+ break;
+ case ATA_PROBE_FAILED:
+ kprintf("Device removed\n");
+ break;
+ default:
+ kprintf("Device probe in progress\n");
+ break;
+ }
+ }
+
+ /*
+ * Run through the state machine as necessary if
+ * the port is not marked failed.
+ *
+ * The state machine may stop at NEED_IDENT if
+ * CAM is not yet attached.
+ *
+ * Acquire exclusive access to the port while we
+ * are doing this. This prevents command-completion
+ * from queueing commands for non-polled targets
+ * inbetween our probe steps. We need to do this
+ * because the reset probes can generate severe PHY
+ * and protocol errors and soft-brick the port.
+ */
+ if (at->at_probe != ATA_PROBE_FAILED &&
+ at->at_probe != ATA_PROBE_GOOD) {
+ sili_beg_exclusive_access(ap, at);
+ if (at->at_probe == ATA_PROBE_NEED_INIT)
+ sili_port_init(ap, at);
+ if (at->at_probe == ATA_PROBE_NEED_HARD_RESET)
+ sili_port_reset(ap, at, 1);
+ if (at->at_probe == ATA_PROBE_NEED_SOFT_RESET)
+ sili_port_reset(ap, at, 0);
+ if (at->at_probe == ATA_PROBE_NEED_IDENT)
+ sili_cam_probe(ap, at);
+ sili_end_exclusive_access(ap, at);
+ }
+
+ /*
+ * Add or remove from CAM
+ */
+ if (at->at_features & ATA_PORT_F_RESCAN) {
+ at->at_features &= ~ATA_PORT_F_RESCAN;
+ if (at->at_probe == ATA_PROBE_FAILED) {
+ sili_cam_changed(ap, at, 0);
+ } else if (at->at_probe >= ATA_PROBE_NEED_IDENT) {
+ sili_cam_changed(ap, at, 1);
+ }
+ }
+ data &= ~(1 << target);
+ }
+ if (data) {
+ kprintf("%s: WARNING (PM): extra bits set in "
+ "EINFO: %08x\n", PORTNAME(ap), data);
+ while (target < SILI_MAX_PMPORTS) {
+ sili_pm_check_good(ap, target);
+ ++target;
+ }
+ }
+ }
+}
+
+/*
+ * De-initialize and detach a port.
+ */
+void
+sili_port_free(struct sili_softc *sc, u_int port)
+{
+ struct sili_port *ap = sc->sc_ports[port];
+ struct sili_ccb *ccb;
+
+ /*
+ * Ensure port is disabled and its interrupts are all flushed.
+ */
+ if (ap->ap_sc) {
+ sili_os_stop_port(ap);
+ sili_pwrite(ap, SILI_PREG_INT_DISABLE, SILI_PREG_INT_MASK);
+ sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_RESET);
+ sili_write(ap->ap_sc, SILI_REG_GCTL,
+ sili_read(ap->ap_sc, SILI_REG_GCTL) &
+ ~SILI_REG_GINT_PORTST(ap->ap_num));
+ }
+
+ if (ap->ap_ccbs) {
+ while ((ccb = sili_get_ccb(ap)) != NULL) {
+ if (ccb->ccb_dmamap) {
+ bus_dmamap_destroy(sc->sc_tag_data,
+ ccb->ccb_dmamap);
+ ccb->ccb_dmamap = NULL;
+ }
+ }
+ if ((ccb = ap->ap_err_ccb) != NULL) {
+ if (ccb->ccb_dmamap) {
+ bus_dmamap_destroy(sc->sc_tag_data,
+ ccb->ccb_dmamap);
+ ccb->ccb_dmamap = NULL;
+ }
+ ap->ap_err_ccb = NULL;
+ }
+ kfree(ap->ap_ccbs, M_DEVBUF);
+ ap->ap_ccbs = NULL;
+ }
+
+ if (ap->ap_dmamem_sget) {
+ sili_dmamem_free(sc, ap->ap_dmamem_sget);
+ ap->ap_dmamem_sget = NULL;
+ }
+ if (ap->ap_ata) {
+ kfree(ap->ap_ata, M_DEVBUF);
+ ap->ap_ata = NULL;
+ }
+
+ /* bus_space(9) says we dont free the subregions handle */
+
+ kfree(ap, M_DEVBUF);
+ sc->sc_ports[port] = NULL;
+}
+
+/*
+ * Reset a port.
+ *
+ * If hard is 0 perform a softreset of the port.
+ * If hard is 1 perform a hard reset of the port.
+ * If hard is 2 perform a hard reset of the port and cycle the phy.
+ *
+ * If at is non-NULL an indirect port via a port-multiplier is being
+ * reset, otherwise a direct port is being reset.
+ *
+ * NOTE: Indirect ports can only be soft-reset.
+ */
+int
+sili_port_reset(struct sili_port *ap, struct ata_port *at, int hard)
+{
+ int rc;
+
+ if (hard) {
+ if (at)
+ rc = sili_pm_hardreset(ap, at->at_target, hard);
+ else
+ rc = sili_port_hardreset(ap, hard);
+ } else {
+ if (at)
+ rc = sili_pm_softreset(ap, at->at_target);
+ else
+ rc = sili_port_softreset(ap);
+ }
+ return(rc);
+}
+
+/*
+ * SILI soft reset, Section 10.4.1
+ *
+ * (at) will be NULL when soft-resetting a directly-attached device, and
+ * non-NULL when soft-resetting a device through a port multiplier.
+ *
+ * This function keeps port communications intact and attempts to generate
+ * a reset to the connected device using device commands.
+ */
+int
+sili_port_softreset(struct sili_port *ap)
+{
+ struct sili_ccb *ccb = NULL;
+ struct sili_prb *prb;
+ int error;
+ u_int32_t sig;
+
+ error = EIO;
+
+ kprintf("%s: START SOFTRESET\n", PORTNAME(ap));
+
+ DPRINTF(SILI_D_VERBOSE, "%s: soft reset\n", PORTNAME(ap));
+
+ crit_enter();
+ ap->ap_state = AP_S_NORMAL;
+
+ /*
+ * Prep the special soft-reset SII command.
+ */
+ ccb = sili_get_err_ccb(ap);
+ ccb->ccb_done = sili_empty_done;
+ ccb->ccb_xa.flags = ATA_F_POLL;
+ ccb->ccb_xa.complete = sili_dummy_done;
+ ccb->ccb_xa.at = NULL;
+
+ prb = ccb->ccb_prb;
+ bzero(&prb->prb_h2d, sizeof(prb->prb_h2d));
+ prb->prb_h2d.flags = 0;
+ prb->prb_control = SILI_PRB_CTRL_SOFTRESET;
+ prb->prb_override = 0;
+
+ ccb->ccb_xa.state = ATA_S_PENDING;
+ ccb->ccb_xa.flags = 0;
+
+ /* XXX */
+ if (sili_poll(ccb, 8000, sili_quick_timeout) != ATA_S_COMPLETE) {
+ kprintf("%s: First FIS failed\n", PORTNAME(ap));
+ goto err;
+ }
+
+ sig = (prb->prb_d2h.lba_high << 24) |
+ (prb->prb_d2h.lba_mid << 16) |
+ (prb->prb_d2h.lba_low << 8) |
+ (prb->prb_d2h.sector_count);
+ kprintf("%s: SOFTRESET SIGNATURE %08x\n", PORTNAME(ap), sig);
+
+ /*
+ * If the softreset is trying to clear a BSY condition after a
+ * normal portreset we assign the port type.
+ *
+ * If the softreset is being run first as part of the ccb error
+ * processing code then report if the device signature changed
+ * unexpectedly.
+ */
+ if (ap->ap_type == ATA_PORT_T_NONE) {
+ ap->ap_type = sili_port_signature(ap, NULL, sig);
+ } else {
+ if (sili_port_signature(ap, NULL, sig) != ap->ap_type) {
+ kprintf("%s: device signature unexpectedly "
+ "changed\n", PORTNAME(ap));
+ error = EBUSY; /* XXX */
+ }
+ }
+ error = 0;
+err:
+ if (ccb != NULL) {
+ sili_put_err_ccb(ccb);
+ }
+
+ /*
+ * If we failed to softreset make the port quiescent, otherwise
+ * make sure the port's start/stop state matches what it was on
+ * entry.
+ *
+ * Don't kill the port if the softreset is on a port multiplier
+ * target, that would kill all the targets!
+ */
+ kprintf("%s: END SOFTRESET %d prob=%d state=%d\n", PORTNAME(ap), error, ap->ap_probe, ap->ap_state);
+ if (error) {
+ sili_port_hardstop(ap);
+ /* ap_probe set to failed */
+ } else {
+ ap->ap_probe = ATA_PROBE_NEED_IDENT;
+ }
+ crit_exit();
+
+ if (bootverbose)
+ kprintf("%s: END SOFTRESET\n", PORTNAME(ap));
+
+ return (error);
+}
+
+/*
+ * SILI port reset, Section 10.4.2
+ *
+ * This function does a hard reset of the port. Note that the device
+ * connected to the port could still end-up hung.
+ */
+int
+sili_port_hardreset(struct sili_port *ap, int hard)
+{
+ u_int32_t r;
+ int error;
+ int loop;
+
+ DPRINTF(SILI_D_VERBOSE, "%s: port reset\n", PORTNAME(ap));
+
+ ap->ap_state = AP_S_NORMAL;
+ error = 0;
+
+ /*
+ * Issue Device Reset.
+ *
+ * NOTE: Unlike Port Reset, the port ready signal will not
+ * go active unless a device is established to be on
+ * the port.
+ */
+ sili_pwrite(ap, SILI_PREG_SERR, -1);
+ sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_PMA);
+ sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESUME);
+ sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_DEVRESET);
+ if (sili_pwait_clr(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_DEVRESET)) {
+ kprintf("%s: hardreset failed to clear\n", PORTNAME(ap));
+ }
+
+ /*
+ * Try to determine if there is a device on the port.
+ *
+ * Give the device 3/10 second to at least be detected.
+ */
+ loop = 300;
+ while (loop > 0) {
+ r = sili_pread(ap, SILI_PREG_SSTS);
+ if (r & SILI_PREG_SSTS_DET)
+ break;
+ loop -= sili_os_softsleep();
+ }
+ if (loop <= 0) {
+ if (bootverbose) {
+ kprintf("%s: Port appears to be unplugged\n",
+ PORTNAME(ap));
+ }
+ error = ENODEV;
+ }
+
+ /*
+ * There is something on the port. Give the device 3 seconds
+ * to fully negotiate.
+ */
+ if (error == 0 &&
+ sili_pwait_eq(ap, 3000, SILI_PREG_SSTS,
+ SILI_PREG_SSTS_DET, SILI_PREG_SSTS_DET_DEV)) {
+ if (bootverbose) {
+ kprintf("%s: Device may be powered down\n",
+ PORTNAME(ap));
+ }
+ error = ENODEV;
+ }
+
+ /*
+ * Wait for the port to become ready.
+ *
+ * This can take more then a second, give it 3 seconds. If we
+ * succeed give the device another 3ms after that.
+ *
+ * NOTE: Port multipliers can do two things here. First they can
+ * return device-ready if a device is on target 0 and also
+ * return the signature for that device. If there is no
+ * device on target 0 then BSY/DRQ is never cleared and
+ * it never comes ready.
+ */
+ if (error == 0 && sili_pwait_set_to(ap, 3000, SILI_PREG_STATUS,
+ SILI_PREG_STATUS_READY)) {
+ /*
+ * The device is bricked or its a port multiplier and will
+ * not unbusy until we do the pmprobe CLO softreset sequence.
+ */
+ error = sili_port_pmprobe(ap);
+ if (error) {
+ kprintf("%s: Device will not come ready\n",
+ PORTNAME(ap));
+ } else {
+ ap->ap_type = ATA_PORT_T_PM;
+ }
+ } else if (error == 0) {
+ /*
+ * The sili's hardreset doesn't return a signature (does it)?
+ * In anycase, set the type so the signature gets set by
+ * the softreset stage.
+ */
+ error = sili_port_pmprobe(ap);
+ if (error) {
+ ap->ap_type = ATA_PORT_T_NONE;
+ error = 0;
+ } else {
+ ap->ap_type = ATA_PORT_T_PM;
+ kprintf("%s: Port multiplier detected\n",
+ PORTNAME(ap));
+ }
+ }
+
+ /*
+ * hard-stop the port if we failed. This will set ap_probe
+ * to FAILED.
+ */
+ if (error) {
+ sili_port_hardstop(ap);
+ /* ap_probe set to failed */
+ } else {
+ if (ap->ap_type == ATA_PORT_T_PM)
+ ap->ap_probe = ATA_PROBE_GOOD;
+ else
+ ap->ap_probe = ATA_PROBE_NEED_SOFT_RESET;
+ }
+ return (error);
+}
+
+/*
+ * SILI port multiplier probe. This routine is run by the hardreset code
+ * if it gets past the device detect.
+ *
+ * All we do here is call sili_pm_softreset(). The Sili chip does all the
+ * hard work for us.
+ *
+ * Return 0 on success, non-zero on failure.
+ */
+int
+sili_port_pmprobe(struct sili_port *ap)
+{
+ struct ata_port *at;
+ int error;
+ int i;
+
+ /*
+ * If we don't support port multipliers don't try to detect one.
+ */
+ if ((ap->ap_sc->sc_flags & SILI_F_SPM) == 0)
+ return (ENODEV);
+
+ /*
+ * The port may be unhappy from its hardreset if there's a PM
+ * but no device at target 0. If we try to shove the softreset
+ * for target 15 down its throat it will pop a gasket.
+ *
+ * Reiniting the port.. kind of a soft reset of its command
+ * processor which otherwise does not effect the port registers,
+ * seems to fix the problem.
+ */
+ sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_PMA);
+ sili_port_reinit(ap);
+ ap->ap_state = AP_S_NORMAL;
+ error = sili_pm_softreset(ap, 15);
+ if (error == 0) {
+ ap->ap_ata[15].at_probe = ATA_PROBE_GOOD;
+ } else {
+ error = EBUSY;
+ }
+
+ kprintf("PMPROBE3 %d\n", error);
+
+ if (error == 0 && sili_pm_identify(ap)) {
+ kprintf("%s: PM - cannot identify port multiplier\n",
+ PORTNAME(ap));
+ error = EBUSY;
+ }
+ kprintf("PMPROBE3 %d %d %d\n", error, ap->ap_probe, ap->ap_state);
+
+ /*
+ * If we probed the PM reset the state for the targets behind
+ * it so they get probed by the state machine.
+ */
+ if (error == 0) {
+ for (i = 0; i < SILI_MAX_PMPORTS; ++i) {
+ at = &ap->ap_ata[i];
+ at->at_probe = ATA_PROBE_NEED_INIT;
+ at->at_features |= ATA_PORT_F_RESCAN;
+ at->at_features &= ~ATA_PORT_F_READLOG;
+ }
+ }
+
+ /*
+ * If we failed turn off PMA, otherwise identify the port multiplier.
+ * CAM will iterate the devices.
+ */
+ if (error)
+ sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_PMA);
+ return(error);
+}
+
+/*
+ * Hard-stop on hot-swap device removal. See 10.10.1
+ *
+ * Place the port in a mode that will allow it to detect hot-swap insertions.
+ * This is a bit imprecise because just setting-up SCTL to DET_INIT doesn't
+ * seem to do the job.
+ */
+void
+sili_port_hardstop(struct sili_port *ap)
+{
+ struct sili_ccb *ccb;
+ struct ata_port *at;
+ int i;
+ int slot;
+
+ ap->ap_state = AP_S_FATAL_ERROR;
+ ap->ap_probe = ATA_PROBE_FAILED;
+ ap->ap_type = ATA_PORT_T_NONE;
+
+ /*
+ * Clean up AT sub-ports on SATA port.
+ */
+ for (i = 0; ap->ap_ata && i < SILI_MAX_PMPORTS; ++i) {
+ at = &ap->ap_ata[i];
+ at->at_type = ATA_PORT_T_NONE;
+ at->at_probe = ATA_PROBE_FAILED;
+ at->at_features &= ~ATA_PORT_F_READLOG;
+ }
+
+ /*
+ * Kill the port. Don't bother waiting for it to transition
+ * back up.
+ */
+ sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_RESET);
+ if (sili_pread(ap, SILI_PREG_STATUS) & SILI_PREG_STATUS_READY) {
+ kprintf("%s: Port will not go into reset\n",
+ PORTNAME(ap));
+ }
+ sili_os_sleep(10);
+ sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESUME);
+ sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESET);
+
+ /*
+ * Turn off port-multiplier control bit
+ */
+ sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_PMA);
+
+ /*
+ * Clean up the command list.
+ */
+ while (ap->ap_active) {
+ slot = ffs(ap->ap_active) - 1;
+ ap->ap_active &= ~(1 << slot);
+ ap->ap_expired &= ~(1 << slot);
+ --ap->ap_active_cnt;
+ ccb = &ap->ap_ccbs[slot];
+ if (ccb->ccb_xa.flags & ATA_F_TIMEOUT_RUNNING) {
+ callout_stop(&ccb->ccb_timeout);
+ ccb->ccb_xa.flags &= ~ATA_F_TIMEOUT_RUNNING;
+ }
+ ccb->ccb_xa.flags &= ~(ATA_F_TIMEOUT_DESIRED |
+ ATA_F_TIMEOUT_EXPIRED);
+ ccb->ccb_xa.state = ATA_S_TIMEOUT;
+ ccb->ccb_done(ccb);
+ ccb->ccb_xa.complete(&ccb->ccb_xa);
+ }
+ while ((ccb = TAILQ_FIRST(&ap->ap_ccb_pending)) != NULL) {
+ TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry);
+ ccb->ccb_xa.state = ATA_S_TIMEOUT;
+ ccb->ccb_xa.flags &= ~ATA_F_TIMEOUT_DESIRED;
+ ccb->ccb_done(ccb);
+ ccb->ccb_xa.complete(&ccb->ccb_xa);
+ }
+ KKASSERT(ap->ap_active_cnt == 0);
+
+ /*
+ * Put the port into a listen mode, we want to get insertion/removal
+ * events.
+ */
+ sili_port_listen(ap);
+}
+
+/*
+ * Place port into a listen mode for hotplug events only. The port has
+ * already been reset and the command processor may not be ready due
+ * to the lack of a device.
+ */
+void
+sili_port_listen(struct sili_port *ap)
+{
+ u_int32_t data;
+
+#if 1
+ data = SILI_PREG_SCTL_SPM_NONE |
+ SILI_PREG_SCTL_IPM_NONE |
+ SILI_PREG_SCTL_SPD_NONE |
+ SILI_PREG_SCTL_DET_INIT;
+ if (SiliForceGen1 & (1 << ap->ap_num)) {
+ data &= ~SILI_PREG_SCTL_SPD_NONE;
+ data |= SILI_PREG_SCTL_SPD_GEN1;
+ }
+#endif
+ sili_pwrite(ap, SILI_PREG_SERR, -1);
+ sili_pwrite(ap, SILI_PREG_INT_ENABLE, SILI_PREG_INT_PHYRDYCHG |
+ SILI_PREG_INT_DEVEXCHG);
+}
+
+/*
+ * Figure out what type of device is connected to the port, ATAPI or
+ * DISK.
+ */
+int
+sili_port_signature(struct sili_port *ap, struct ata_port *at, u_int32_t sig)
+{
+ if (bootverbose)
+ kprintf("%s: sig %08x\n", ATANAME(ap, at), sig);
+ if ((sig & 0xffff0000) == (SATA_SIGNATURE_ATAPI & 0xffff0000)) {
+ return(ATA_PORT_T_ATAPI);
+ } else if ((sig & 0xffff0000) ==
+ (SATA_SIGNATURE_PORT_MULTIPLIER & 0xffff0000)) {
+ return(ATA_PORT_T_PM);
+ } else {
+ return(ATA_PORT_T_DISK);
+ }
+}
+
+/*
+ * Load the DMA descriptor table for a CCB's buffer.
+ *
+ * NOTE: ATA_F_PIO is auto-selected by sili part.
+ */
+int
+sili_load_prb(struct sili_ccb *ccb)
+{
+ struct sili_port *ap = ccb->ccb_port;
+ struct sili_softc *sc = ap->ap_sc;
+ struct ata_xfer *xa = &ccb->ccb_xa;
+ struct sili_prb *prb = ccb->ccb_prb;
+ struct sili_sge *sge;
+ bus_dmamap_t dmap = ccb->ccb_dmamap;
+ int error;
+
+ /*
+ * Set up the PRB. Set up the SGE that links to our
+ * SGE array. We do not use the limited number of SGE's
+ * in the Sili's LRAM.
+ */
+ prb->prb_xfer_count = 0;
+ prb->prb_control = 0;
+ prb->prb_override = 0;
+ sge = (ccb->ccb_xa.flags & ATA_F_PACKET) ?
+ &prb->prb_sge_packet : &prb->prb_sge_normal;
+ if (xa->datalen == 0) {
+ sge->sge_flags = SILI_SGE_FLAGS_TRM | SILI_SGE_FLAGS_DRD;
+ sge->sge_count = 0;
+ return (0);
+ }
+
+ if (ccb->ccb_xa.flags & ATA_F_READ)
+ prb->prb_control |= SILI_PRB_CTRL_READ;
+ if (ccb->ccb_xa.flags & ATA_F_WRITE)
+ prb->prb_control |= SILI_PRB_CTRL_WRITE;
+ sge->sge_flags = SILI_SGE_FLAGS_LNK;
+ sge->sge_count = 0;
+ sge->sge_paddr = ccb->ccb_sge_paddr;
+
+ /*
+ * Load our external sge array.
+ */
+ error = bus_dmamap_load(sc->sc_tag_data, dmap,
+ xa->data, xa->datalen,
+ sili_load_prb_callback,
+ ccb,
+ ((xa->flags & ATA_F_NOWAIT) ?
+ BUS_DMA_NOWAIT : BUS_DMA_WAITOK));
+ if (error != 0) {
+ kprintf("%s: error %d loading dmamap\n", PORTNAME(ap), error);
+ return (1);
+ }
+
+ bus_dmamap_sync(sc->sc_tag_data, dmap,
+ (xa->flags & ATA_F_READ) ?
+ BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE);
+
+ return (0);
+
+#ifdef DIAGNOSTIC
+diagerr:
+ bus_dmamap_unload(sc->sc_tag_data, dmap);
+ return (1);
+#endif
+}
+
+/*
+ * Callback from BUSDMA system to load the segment list.
+ *
+ * The scatter/gather table is loaded by the sili chip in blocks of
+ * four SGE's. If a continuance is required the last entry in each
+ * block must point to the next block.
+ */
+static
+void
+sili_load_prb_callback(void *info, bus_dma_segment_t *segs, int nsegs,
+ int error)
+{
+ struct sili_ccb *ccb = info;
+ struct sili_sge *sge;
+ int sgi;
+
+ KKASSERT(nsegs <= SILI_MAX_SGET);
+
+ sgi = 0;
+ sge = ccb->ccb_sge;
+ while (nsegs) {
+ if ((sgi & 3) == 3) {
+ sge->sge_paddr = htole64(ccb->ccb_sge_paddr +
+ sizeof(*sge) * (sgi + 1));
+ sge->sge_count = 0;
+ sge->sge_flags = SILI_SGE_FLAGS_LNK;
+ } else {
+ sge->sge_paddr = htole64(segs->ds_addr);
+ sge->sge_count = htole32(segs->ds_len);
+ sge->sge_flags = 0;
+ --nsegs;
+ ++segs;
+ }
+ ++sge;
+ ++sgi;
+ }
+ --sge;
+ sge->sge_flags |= SILI_SGE_FLAGS_TRM;
+}
+
+void
+sili_unload_prb(struct sili_ccb *ccb)
+{
+ struct sili_port *ap = ccb->ccb_port;
+ struct sili_softc *sc = ap->ap_sc;
+ struct ata_xfer *xa = &ccb->ccb_xa;
+ bus_dmamap_t dmap = ccb->ccb_dmamap;
+
+ if (xa->datalen != 0) {
+ bus_dmamap_sync(sc->sc_tag_data, dmap,
+ (xa->flags & ATA_F_READ) ?
+ BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
+
+ bus_dmamap_unload(sc->sc_tag_data, dmap);
+
+ if (ccb->ccb_xa.flags & ATA_F_NCQ)
+ xa->resid = 0;
+ else
+ xa->resid = xa->datalen -
+ le32toh(ccb->ccb_prb->prb_xfer_count);
+ }
+}
+
+/*
+ * Start a command and poll for completion.
+ *
+ * timeout is in ms and only counts once the command gets on-chip.
+ *
+ * Returns ATA_S_* state, compare against ATA_S_COMPLETE to determine
+ * that no error occured.
+ *
+ * NOTE: If the caller specifies a NULL timeout function the caller is
+ * responsible for clearing hardware state on failure, but we will
+ * deal with removing the ccb from any pending queue.
+ *
+ * NOTE: NCQ should never be used with this function.
+ *
+ * NOTE: If the port is in a failed state and stopped we do not try
+ * to activate the ccb.
+ */
+int
+sili_poll(struct sili_ccb *ccb, int timeout,
+ void (*timeout_fn)(struct sili_ccb *))
+{
+ struct sili_port *ap = ccb->ccb_port;
+
+ if (ccb->ccb_port->ap_state == AP_S_FATAL_ERROR) {
+ ccb->ccb_xa.state = ATA_S_ERROR;
+ return(ccb->ccb_xa.state);
+ }
+
+ sili_start(ccb);
+
+ do {
+ sili_port_intr(ap, 1);
+ switch(ccb->ccb_xa.state) {
+ case ATA_S_ONCHIP:
+ timeout -= sili_os_softsleep();
+ break;
+ case ATA_S_PENDING:
+ /*
+ * The packet can get stuck on the pending queue
+ * if the port refuses to come ready.
+ */
+ if (AP_F_EXCLUSIVE_ACCESS)
+ timeout -= sili_os_softsleep();
+ else
+ sili_os_softsleep();
+ sili_check_active_timeouts(ap);
+ break;
+ default:
+ return (ccb->ccb_xa.state);
+ }
+ } while (timeout > 0);
+
+ kprintf("%s: Poll timeout slot %d\n",
+ ATANAME(ap, ccb->ccb_xa.at),
+ ccb->ccb_slot);
+
+ timeout_fn(ccb);
+
+ return(ccb->ccb_xa.state);
+}
+
+/*
+ * When polling we have to check if the currently active CCB(s)
+ * have timed out as the callout will be deadlocked while we
+ * hold the port lock.
+ */
+void
+sili_check_active_timeouts(struct sili_port *ap)
+{
+ struct sili_ccb *ccb;
+ u_int32_t mask;
+ int tag;
+
+ mask = ap->ap_active;
+ while (mask) {
+ tag = ffs(mask) - 1;
+ mask &= ~(1 << tag);
+ ccb = &ap->ap_ccbs[tag];
+ if (ccb->ccb_xa.flags & ATA_F_TIMEOUT_EXPIRED) {
+ sili_core_timeout(ccb);
+ }
+ }
+}
+
+static
+__inline
+void
+sili_start_timeout(struct sili_ccb *ccb)
+{
+ if (ccb->ccb_xa.flags & ATA_F_TIMEOUT_DESIRED) {
+ ccb->ccb_xa.flags |= ATA_F_TIMEOUT_RUNNING;
+ callout_reset(&ccb->ccb_timeout,
+ (ccb->ccb_xa.timeout * hz + 999) / 1000,
+ sili_ata_cmd_timeout_unserialized, ccb);
+ }
+}
+
+void
+sili_start(struct sili_ccb *ccb)
+{
+ struct sili_port *ap = ccb->ccb_port;
+#if 0
+ struct sili_softc *sc = ap->ap_sc;
+#endif
+
+ KKASSERT(ccb->ccb_xa.state == ATA_S_PENDING);
+
+ /*
+ * Sync our SGE table and PRB
+ */
+ bus_dmamap_sync(ap->ap_dmamem_sget->adm_tag,
+ ap->ap_dmamem_sget->adm_map,
+ BUS_DMASYNC_PREWRITE);
+
+ /*
+ * XXX dmamap for PRB XXX BUS_DMASYNC_PREWRITE
+ */
+
+ /*
+ * Controller will update shared memory!
+ * XXX bus_dmamap_sync ... BUS_DMASYNC_PREREAD ...
+ */
+ /* Prepare RFIS area for write by controller */
+
+ /*
+ * There's no point trying to optimize this, it only shaves a few
+ * nanoseconds so just queue the command and call our generic issue.
+ */
+ sili_issue_pending_commands(ap, ccb);
+}
+
+/*
+ * While holding the port lock acquire exclusive access to the port.
+ *
+ * This is used when running the state machine to initialize and identify
+ * targets over a port multiplier. Setting exclusive access prevents
+ * sili_port_intr() from activating any requests sitting on the pending
+ * queue.
+ */
+void
+sili_beg_exclusive_access(struct sili_port *ap, struct ata_port *at)
+{
+ KKASSERT((ap->ap_flags & AP_F_EXCLUSIVE_ACCESS) == 0);
+ ap->ap_flags |= AP_F_EXCLUSIVE_ACCESS;
+ while (ap->ap_active) {
+ sili_port_intr(ap, 1);
+ sili_os_softsleep();
+ }
+}
+
+void
+sili_end_exclusive_access(struct sili_port *ap, struct ata_port *at)
+{
+ KKASSERT((ap->ap_flags & AP_F_EXCLUSIVE_ACCESS) != 0);
+ ap->ap_flags &= ~AP_F_EXCLUSIVE_ACCESS;
+ sili_issue_pending_commands(ap, NULL);
+}
+
+/*
+ * If ccb is not NULL enqueue and/or issue it.
+ *
+ * If ccb is NULL issue whatever we can from the queue. However, nothing
+ * new is issued if the exclusive access flag is set or expired ccb's are
+ * present.
+ *
+ * If existing commands are still active (ap_active) we can only
+ * issue matching new commands.
+ */
+void
+sili_issue_pending_commands(struct sili_port *ap, struct sili_ccb *ccb)
+{
+ /*
+ * Enqueue the ccb.
+ *
+ * If just running the queue and in exclusive access mode we
+ * just return. Also in this case if there are any expired ccb's
+ * we want to clear the queue so the port can be safely stopped.
+ *
+ * XXX sili chip - expiration needs to be per-target if PM supports
+ * FBSS?
+ */
+ if (ccb) {
+ TAILQ_INSERT_TAIL(&ap->ap_ccb_pending, ccb, ccb_entry);
+ } else if ((ap->ap_flags & AP_F_EXCLUSIVE_ACCESS) || ap->ap_expired) {
+ return;
+ }
+
+ /*
+ * Pull the next ccb off the queue and run it if possible.
+ * If the port is not ready to accept commands enable the
+ * ready interrupt instead of starting a new command.
+ *
+ * XXX limit ncqdepth for attached devices behind PM
+ */
+ while ((ccb = TAILQ_FIRST(&ap->ap_ccb_pending)) != NULL) {
+ if ((sili_pread(ap, SILI_PREG_STATUS) &
+ SILI_PREG_STATUS_READY) == 0) {
+ kprintf("%s: slot %d NOT READY\n",
+ ATANAME(ap, ccb->ccb_xa.at), ccb->ccb_slot);
+ sili_pwrite(ap, SILI_PREG_INT_ENABLE,
+ SILI_PREG_INT_READY);
+ break;
+ }
+ TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry);
+ ccb->ccb_xa.state = ATA_S_ONCHIP;
+ ap->ap_active |= 1 << ccb->ccb_slot;
+ ap->ap_active_cnt++;
+ sili_pwrite(ap, SILI_PREG_CMD_FIFO, ccb->ccb_slot);
+ sili_start_timeout(ccb);
+ }
+}
+
+void
+sili_intr(void *arg)
+{
+ struct sili_softc *sc = arg;
+ struct sili_port *ap;
+ u_int32_t gint;
+ int port;
+
+ /*
+ * Check if the master enable is up, and whether any interrupts are
+ * pending.
+ *
+ * Clear the ints we got.
+ */
+ if ((sc->sc_flags & SILI_F_INT_GOOD) == 0)
+ return;
+ gint = sili_read(sc, SILI_REG_GINT);
+ if (gint == 0 || gint == 0xffffffff)
+ return;
+ sili_write(sc, SILI_REG_GINT, gint);
+
+ /*
+ * Process interrupts for each port in a non-blocking fashion.
+ */
+ while (gint & SILI_REG_GINT_PORTMASK) {
+ port = ffs(gint) - 1;
+ ap = sc->sc_ports[port];
+ if (ap) {
+ if (sili_os_lock_port_nb(ap) == 0) {
+ sili_port_intr(ap, 0);
+ sili_os_unlock_port(ap);
+ } else {
+ sili_port_interrupt_redisable(ap);
+ sili_os_signal_port_thread(ap, AP_SIGF_PORTINT);
+ }
+ }
+ gint &= ~(1 << port);
+ }
+}
+
+/*
+ * Core called from helper thread.
+ */
+void
+sili_port_thread_core(struct sili_port *ap, int mask)
+{
+ /*
+ * Process any expired timedouts.
+ */
+ sili_os_lock_port(ap);
+ if (mask & AP_SIGF_TIMEOUT) {
+ sili_check_active_timeouts(ap);
+ }
+
+ /*
+ * Process port interrupts which require a higher level of
+ * intervention.
+ */
+ if (mask & AP_SIGF_PORTINT) {
+ sili_port_intr(ap, 1);
+ sili_port_interrupt_reenable(ap);
+ sili_os_unlock_port(ap);
+ } else {
+ sili_os_unlock_port(ap);
+ }
+}
+
+/*
+ * Core per-port interrupt handler.
+ *
+ * If blockable is 0 we cannot call sili_os_sleep() at all and we can only
+ * deal with normal command completions which do not require blocking.
+ */
+void
+sili_port_intr(struct sili_port *ap, int blockable)
+{
+ struct sili_softc *sc = ap->ap_sc;
+ u_int32_t is;
+ int slot;
+ struct sili_ccb *ccb = NULL;
+ struct ata_port *ccb_at = NULL;
+#ifdef DIAGNOSTIC
+ u_int32_t tmp;
+#endif
+ u_int32_t active;
+ const u_int32_t blockable_mask = SILI_PREG_IST_PHYRDYCHG |
+ SILI_PREG_IST_DEVEXCHG |
+ SILI_PREG_IST_CERROR |
+ SILI_PREG_IST_DECODE |
+ SILI_PREG_IST_CRC |
+ SILI_PREG_IST_HANDSHK;
+ const u_int32_t fatal_mask = SILI_PREG_IST_PHYRDYCHG |
+ SILI_PREG_IST_DEVEXCHG |
+ SILI_PREG_IST_DECODE |
+ SILI_PREG_IST_CRC |
+ SILI_PREG_IST_HANDSHK;
+
+ enum { NEED_NOTHING, NEED_HOTPLUG_INSERT,
+ NEED_HOTPLUG_REMOVE } need = NEED_NOTHING;
+
+ /*
+ * NOTE: CCOMPLETE was automatically cleared when we read INT_STATUS.
+ */
+ is = sili_pread(ap, SILI_PREG_INT_STATUS);
+ is &= SILI_PREG_IST_MASK;
+ if (is & SILI_PREG_IST_CCOMPLETE)
+ sili_pwrite(ap, SILI_PREG_INT_STATUS, SILI_PREG_IST_CCOMPLETE);
+
+ /*
+ * If we can't block then we can't handle these here. Disable
+ * the interrupts in question so we don't live-lock, the helper
+ * thread will re-enable them.
+ *
+ * If the port is in a completely failed state we do not want
+ * to drop through to failed-command-processing if blockable is 0,
+ * just let the thread deal with it all.
+ *
+ * Otherwise we fall through and still handle DHRS and any commands
+ * which completed normally. Even if we are errored we haven't
+ * stopped the port yet so CI/SACT are still good.
+ */
+ if (blockable == 0) {
+ if (ap->ap_state == AP_S_FATAL_ERROR) {
+ sili_port_interrupt_redisable(ap);
+ sili_os_signal_port_thread(ap, AP_SIGF_PORTINT);
+ /*is &= ~blockable_mask;*/
+ return;
+ }
+ if (is & blockable_mask) {
+ sili_port_interrupt_redisable(ap);
+ sili_os_signal_port_thread(ap, AP_SIGF_PORTINT);
+ /*is &= ~blockable_mask;*/
+ return;
+ }
+ }
+
+ if (is & SILI_PREG_IST_CERROR) {
+ /*
+ * Command failed (blockable).
+ *
+ * This stops command processing. We can extract the PM
+ * target from the PMP field in SILI_PREG_CONTEXT. The
+ * tag is not necessarily valid so don't use that.
+ *
+ * We must then expire all CCB's for that target and resume
+ * processing if any other targets have active commands.
+ * Particular error codes can be recovered by reading the LOG
+ * page.
+ *
+ * The expire handling code will do the rest, which is
+ * basically to reset the port once the only active
+ * commands remaining are all expired.
+ */
+ u_int32_t error;
+ int target;
+ int resume = 1;
+
+ target = (sili_pread(ap, SILI_PREG_CONTEXT) >>
+ SILI_PREG_CONTEXT_PMPORT_SHIFT) &
+ SILI_PREG_CONTEXT_PMPORT_MASK;
+ sili_pwrite(ap, SILI_PREG_INT_STATUS, SILI_PREG_IST_CERROR);
+ active = ap->ap_active & ~ap->ap_expired;
+ error = sili_pread(ap, SILI_PREG_CERROR);
+ kprintf("%s.%d target error %d active=%08x hactive=%08x "
+ "SERR=%b\n",
+ PORTNAME(ap), target, error,
+ active, sili_pread(ap, SILI_PREG_SLOTST),
+ sili_pread(ap, SILI_PREG_SERR), SILI_PFMT_SERR);
+
+ while (active) {
+ slot = ffs(active) - 1;
+ ccb = &ap->ap_ccbs[slot];
+ if ((ccb_at = ccb->ccb_xa.at) == NULL)
+ ccb_at = &ap->ap_ata[0];
+ if (target == ccb_at->at_target) {
+ kprintf("%s kill ccb slot %d\n",
+ ATANAME(ap, ccb->ccb_xa.at), slot);
+ if (ccb->ccb_xa.flags & ATA_F_NCQ &&
+ (error == SILI_PREG_CERROR_DEVICE ||
+ error == SILI_PREG_CERROR_SDBERROR)) {
+ ccb_at->at_features |= ATA_PORT_F_READLOG;
+ }
+ if (sili_core_timeout(ccb) == 0)
+ resume = 0;
+ }
+ active &= ~(1 << slot);
+ }
+
+ /*
+ * Resume will be 0 if the timeout reinited and restarted
+ * the port. Otherwise we resume the port to allow other
+ * commands to complete.
+ */
+ kprintf("%s.%d remain=%08x resume=%d\n",
+ PORTNAME(ap), target,
+ ap->ap_active & ~ap->ap_expired, resume);
+ if (resume)
+ sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_RESUME);
+ }
+
+ /*
+ * Device notification to us (non-blocking)
+ *
+ * This is interrupt status SILIPREG_IST_SDB
+ *
+ * NOTE! On some parts notification bits can get set without
+ * generating an interrupt. It is unclear whether this is
+ * a bug in the PM (sending a DTOH device setbits with 'N' set
+ * and 'I' not set), or a bug in the host controller.
+ *
+ * It only seems to occur under load.
+ */
+ if (sc->sc_flags & SILI_F_SSNTF) {
+ u_int32_t data;
+ const char *xstr;
+
+ data = sili_pread(ap, SILI_PREG_SNTF);
+ if (is & SILI_PREG_IST_SDB) {
+ sili_pwrite(ap, SILI_PREG_INT_STATUS,
+ SILI_PREG_IST_SDB);
+ is &= ~SILI_PREG_IST_SDB;
+ xstr = " (no SDBS!)";
+ } else {
+ xstr = "";
+ }
+ if (data) {
+ kprintf("%s: NOTIFY %08x%s\n",
+ PORTNAME(ap), data, xstr);
+ sili_pwrite(ap, SILI_PREG_SNTF, data);
+ sili_cam_changed(ap, NULL, -1);
+ }
+ }
+
+ /*
+ * Port change (hot-plug) (blockable).
+ *
+ * A PCS interrupt will occur on hot-plug once communication is
+ * established.
+ *
+ * A PRCS interrupt will occur on hot-unplug (and possibly also
+ * on hot-plug).
+ *
+ * XXX We can then check the CPS (Cold Presence State) bit, if
+ * supported, to determine if a device is plugged in or not and do
+ * the right thing.
+ *
+ * WARNING: A PCS interrupt is cleared by clearing DIAG_X, and
+ * can also occur if an unsolicited COMINIT is received.
+ * If this occurs command processing is automatically
+ * stopped (CR goes inactive) and the port must be stopped
+ * and restarted.
+ */
+ if (is & (SILI_PREG_IST_PHYRDYCHG | SILI_PREG_IST_DEVEXCHG)) {
+ /* XXX */
+ sili_pwrite(ap, SILI_PREG_SERR,
+ (SILI_PREG_SERR_DIAG_N | SILI_PREG_SERR_DIAG_X));
+ sili_pwrite(ap, SILI_PREG_INT_STATUS,
+ is & (SILI_PREG_IST_PHYRDYCHG | SILI_PREG_IST_DEVEXCHG));
+
+ is &= ~(SILI_PREG_IST_PHYRDYCHG | SILI_PREG_IST_DEVEXCHG);
+ kprintf("%s: Port change\n", PORTNAME(ap));
+
+ switch (sili_pread(ap, SILI_PREG_SSTS) & SILI_PREG_SSTS_DET) {
+ case SILI_PREG_SSTS_DET_DEV:
+ if (ap->ap_type == ATA_PORT_T_NONE &&
+ ap->ap_probe == ATA_PROBE_FAILED) {
+ need = NEED_HOTPLUG_INSERT;
+ goto fatal;
+ }
+ break;
+ default:
+ kprintf("%s: Device lost\n", PORTNAME(ap));
+ if (ap->ap_type != ATA_PORT_T_NONE) {
+ need = NEED_HOTPLUG_REMOVE;
+ goto fatal;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Check for remaining errors - they are fatal. (blockable)
+ */
+ if (is & fatal_mask) {
+ u_int32_t serr;
+
+ sili_pwrite(ap, SILI_PREG_INT_STATUS, is & fatal_mask);
+
+ serr = sili_pread(ap, SILI_PREG_SERR);
+ kprintf("%s: Unrecoverable errors (IS: %b, SERR: %b), "
+ "disabling port.\n",
+ PORTNAME(ap),
+ is, SILI_PFMT_INT_STATUS,
+ serr, SILI_PFMT_SERR
+ );
+ is &= ~fatal_mask;
+ /* XXX try recovery first */
+ goto fatal;
+ }
+
+ /*
+ * Fail all outstanding commands if we know the port won't recover.
+ *
+ * We may have a ccb_at if the failed command is known and was
+ * being sent to a device over a port multiplier (PM). In this
+ * case if the port itself has not completely failed we fail just
+ * the commands related to that target.
+ */
+ if (ap->ap_state == AP_S_FATAL_ERROR && ap->ap_active) {
+fatal:
+ kprintf("%s: Interrupt, fatal error\n", PORTNAME(ap));
+ ap->ap_state = AP_S_FATAL_ERROR;
+/*failall:*/
+ /*
+ * Error all the active slots. If running across a PM
+ * try to error out just the slots related to the target.
+ */
+ active = ap->ap_active & ~ap->ap_expired;
+
+ while (active) {
+ slot = ffs(active) - 1;
+ kprintf("%s: Killing slot %d\n", PORTNAME(ap), slot);
+ active &= ~(1 << slot);
+ ccb = &ap->ap_ccbs[slot];
+ sili_core_timeout(ccb);
+ }
+ }
+
+ /*
+ * CCB completion (non blocking).
+ *
+ * CCB completion is detected by noticing the slot bit in
+ * the port slot status register has cleared while the bit
+ * is still set in our ap_active variable.
+ *
+ * When completing expired events we must remember to reinit
+ * the port once everything is clear.
+ */
+ active = ap->ap_active & ~sili_pread(ap, SILI_PREG_SLOTST);
+
+ while (active) {
+ slot = ffs(active) - 1;
+ ccb = &ap->ap_ccbs[slot];
+
+ DPRINTF(SILI_D_INTR, "%s: slot %d is complete%s\n",
+ PORTNAME(ap), slot, ccb->ccb_xa.state == ATA_S_ERROR ?
+ " (error)" : "");
+
+ active &= ~(1 << slot);
+
+ /*
+ * XXX sync POSTREAD for return data?
+ */
+ ap->ap_active &= ~(1 << ccb->ccb_slot);
+ --ap->ap_active_cnt;
+
+ /*
+ * Complete the ccb. If the ccb was marked expired it
+ * may or may not have been cleared from the port,
+ * make sure we mark it as having timed out.
+ */
+ if (ap->ap_expired & (1 << ccb->ccb_slot)) {
+ ap->ap_expired &= ~(1 << ccb->ccb_slot);
+ ccb->ccb_xa.state = ATA_S_TIMEOUT;
+ ccb->ccb_done(ccb);
+ ccb->ccb_xa.complete(&ccb->ccb_xa);
+ } else {
+ if (ccb->ccb_xa.state == ATA_S_ONCHIP)
+ ccb->ccb_xa.state = ATA_S_COMPLETE;
+ ccb->ccb_done(ccb);
+ }
+ }
+ if (is & SILI_PREG_IST_READY) {
+ is &= ~SILI_PREG_IST_READY;
+ sili_pwrite(ap, SILI_PREG_INT_DISABLE, SILI_PREG_INT_READY);
+ sili_pwrite(ap, SILI_PREG_INT_STATUS, SILI_PREG_IST_READY);
+ }
+
+ /*
+ * If we had expired commands and were waiting for
+ * remaining commands to complete, and they have now
+ * completed, we can reinit the port.
+ *
+ * This will also clean out the expired commands.
+ * The timeout code also calls sili_port_reinit() if
+ * the only commands remaining after a timeout are all
+ * now expired commands.
+ *
+ * Otherwise just reissue.
+ */
+ if (ap->ap_expired && ap->ap_active == ap->ap_expired)
+ sili_port_reinit(ap);
+ else
+ sili_issue_pending_commands(ap, NULL);
+
+ /*
+ * Cleanup. Will not be set if non-blocking.
+ */
+ switch(need) {
+ case NEED_HOTPLUG_INSERT:
+ /*
+ * A hot-plug insertion event has occured and all
+ * outstanding commands have already been revoked.
+ *
+ * Don't recurse if this occurs while we are
+ * resetting the port.
+ *
+ * Place the port in a continuous COMRESET state
+ * until the INIT code gets to it.
+ */
+ kprintf("%s: HOTPLUG - Device inserted\n",
+ PORTNAME(ap));
+ ap->ap_probe = ATA_PROBE_NEED_INIT;
+ sili_cam_changed(ap, NULL, -1);
+ break;
+ case NEED_HOTPLUG_REMOVE:
+ /*
+ * A hot-plug removal event has occured and all
+ * outstanding commands have already been revoked.
+ *
+ * Don't recurse if this occurs while we are
+ * resetting the port.
+ */
+ kprintf("%s: HOTPLUG - Device removed\n",
+ PORTNAME(ap));
+ sili_port_hardstop(ap);
+ /* ap_probe set to failed */
+ sili_cam_changed(ap, NULL, -1);
+ break;
+ default:
+ break;
+ }
+}
+
+struct sili_ccb *
+sili_get_ccb(struct sili_port *ap)
+{
+ struct sili_ccb *ccb;
+
+ lockmgr(&ap->ap_ccb_lock, LK_EXCLUSIVE);
+ ccb = TAILQ_FIRST(&ap->ap_ccb_free);
+ if (ccb != NULL) {
+ KKASSERT(ccb->ccb_xa.state == ATA_S_PUT);
+ TAILQ_REMOVE(&ap->ap_ccb_free, ccb, ccb_entry);
+ ccb->ccb_xa.state = ATA_S_SETUP;
+ ccb->ccb_xa.at = NULL;
+ }
+ lockmgr(&ap->ap_ccb_lock, LK_RELEASE);
+
+ return (ccb);
+}
+
+void
+sili_put_ccb(struct sili_ccb *ccb)
+{
+ struct sili_port *ap = ccb->ccb_port;
+
+#ifdef DIAGNOSTIC
+ if (ccb->ccb_xa.state != ATA_S_COMPLETE &&
+ ccb->ccb_xa.state != ATA_S_TIMEOUT &&
+ ccb->ccb_xa.state != ATA_S_ERROR) {
+ kprintf("%s: invalid ata_xfer state %02x in sili_put_ccb, "
+ "slot %d\n",
+ PORTNAME(ccb->ccb_port), ccb->ccb_xa.state,
+ ccb->ccb_slot);
+ }
+#endif
+
+ ccb->ccb_xa.state = ATA_S_PUT;
+ lockmgr(&ap->ap_ccb_lock, LK_EXCLUSIVE);
+ TAILQ_INSERT_TAIL(&ap->ap_ccb_free, ccb, ccb_entry);
+ lockmgr(&ap->ap_ccb_lock, LK_RELEASE);
+}
+
+struct sili_ccb *
+sili_get_err_ccb(struct sili_port *ap)
+{
+ struct sili_ccb *err_ccb;
+
+ KKASSERT(sili_pread(ap, SILI_PREG_CI) == 0);
+ KKASSERT((ap->ap_flags & AP_F_ERR_CCB_RESERVED) == 0);
+ ap->ap_flags |= AP_F_ERR_CCB_RESERVED;
+
+#ifdef DIAGNOSTIC
+ KKASSERT(ap->ap_err_busy == 0);
+ ap->ap_err_busy = 1;
+#endif
+ /*
+ * Grab a CCB to use for error recovery. This should never fail, as
+ * we ask atascsi to reserve one for us at init time.
+ */
+ err_ccb = ap->ap_err_ccb;
+ KKASSERT(err_ccb != NULL);
+ err_ccb->ccb_xa.flags = 0;
+ err_ccb->ccb_done = sili_empty_done;
+
+ return err_ccb;
+}
+
+void
+sili_put_err_ccb(struct sili_ccb *ccb)
+{
+ struct sili_port *ap = ccb->ccb_port;
+
+#ifdef DIAGNOSTIC
+ KKASSERT(ap->ap_err_busy);
+#endif
+ KKASSERT((ap->ap_flags & AP_F_ERR_CCB_RESERVED) != 0);
+
+ KKASSERT(ccb == ap->ap_err_ccb);
+
+#ifdef DIAGNOSTIC
+ ap->ap_err_busy = 0;
+#endif
+ ap->ap_flags &= ~AP_F_ERR_CCB_RESERVED;
+}
+
+/*
+ * Read log page to get NCQ error.
+ */
+int
+sili_port_read_ncq_error(struct sili_port *ap, int target)
+{
+ struct sili_ccb *ccb;
+ struct ata_fis_h2d *fis;
+ int rc = EIO;
+
+ DPRINTF(SILI_D_VERBOSE, "%s: read log page\n", PORTNAME(ap));
+
+ /* Prep error CCB for READ LOG EXT, page 10h, 1 sector. */
+ ccb = sili_get_err_ccb(ap);
+ ccb->ccb_done = sili_empty_done;
+ ccb->ccb_xa.flags = ATA_F_NOWAIT | ATA_F_READ | ATA_F_POLL;
+ ccb->ccb_xa.data = ap->ap_err_scratch;
+ ccb->ccb_xa.datalen = 512;
+ ccb->ccb_xa.complete = sili_dummy_done;
+ ccb->ccb_xa.at = &ap->ap_ata[target];
+ fis = &ccb->ccb_prb->prb_h2d;
+ bzero(fis, sizeof(*fis));
+
+ fis->type = ATA_FIS_TYPE_H2D;
+ fis->flags = ATA_H2D_FLAGS_CMD | target;
+ fis->command = ATA_C_READ_LOG_EXT;
+ fis->lba_low = 0x10; /* queued error log page (10h) */
+ fis->sector_count = 1; /* number of sectors (1) */
+ fis->sector_count_exp = 0;
+ fis->lba_mid = 0; /* starting offset */
+ fis->lba_mid_exp = 0;
+ fis->device = 0;
+
+ if (sili_load_prb(ccb) != 0) {
+ rc = ENOMEM; /* XXX caller must abort all commands */
+ } else {
+ ccb->ccb_xa.state = ATA_S_PENDING;
+ rc = sili_poll(ccb, 1000, sili_quick_timeout);
+ }
+
+ /* Abort our command, if it failed, by stopping command DMA. */
+ if (rc) {
+ kprintf("%s: log page read failed, slot %d was still active.\n",
+ ATANAME(ap, ccb->ccb_xa.at), ccb->ccb_slot);
+ }
+
+ /* Done with the error CCB now. */
+ sili_unload_prb(ccb);
+ sili_put_err_ccb(ccb);
+
+ /* Extract failed register set and tags from the scratch space. */
+ if (rc == 0) {
+ struct ata_log_page_10h *log;
+ int err_slot;
+
+ log = (struct ata_log_page_10h *)ap->ap_err_scratch;
+ if (log->err_regs.type & ATA_LOG_10H_TYPE_NOTQUEUED) {
+ /* Not queued bit was set - wasn't an NCQ error? */
+ kprintf("%s: read NCQ error page, but not an NCQ "
+ "error?\n",
+ PORTNAME(ap));
+ rc = ESRCH;
+ } else {
+ /* Copy back the log record as a D2H register FIS. */
+ err_slot = log->err_regs.type &
+ ATA_LOG_10H_TYPE_TAG_MASK;
+ ccb = &ap->ap_ccbs[err_slot];
+ if (ap->ap_expired & (1 << ccb->ccb_slot)) {
+ kprintf("%s: read NCQ error page ok\n",
+ ATANAME(ap, ccb->ccb_xa.at));
+ memcpy(&ccb->ccb_prb->prb_d2h, &log->err_regs,
+ sizeof(struct ata_fis_d2h));
+ ccb->ccb_prb->prb_d2h.type = ATA_FIS_TYPE_D2H;
+ ccb->ccb_prb->prb_d2h.flags = 0;
+ } else {
+ kprintf("%s: error log slot %d did not match a failed ccb!\n", ATANAME(ccb->ccb_port, ccb->ccb_xa.at), err_slot);
+ }
+ }
+ }
+
+ return (rc);
+}
+
+/*
+ * Allocate memory for various structures DMAd by hardware. The maximum
+ * number of segments for these tags is 1 so the DMA memory will have a
+ * single physical base address.
+ */
+struct sili_dmamem *
+sili_dmamem_alloc(struct sili_softc *sc, bus_dma_tag_t tag)
+{
+ struct sili_dmamem *adm;
+ int error;
+
+ adm = kmalloc(sizeof(*adm), M_DEVBUF, M_INTWAIT | M_ZERO);
+
+ error = bus_dmamem_alloc(tag, (void **)&adm->adm_kva,
+ BUS_DMA_ZERO, &adm->adm_map);
+ if (error == 0) {
+ adm->adm_tag = tag;
+ error = bus_dmamap_load(tag, adm->adm_map,
+ adm->adm_kva,
+ bus_dma_tag_getmaxsize(tag),
+ sili_dmamem_saveseg, &adm->adm_busaddr,
+ 0);
+ }
+ if (error) {
+ if (adm->adm_map) {
+ bus_dmamap_destroy(tag, adm->adm_map);
+ adm->adm_map = NULL;
+ adm->adm_tag = NULL;
+ adm->adm_kva = NULL;
+ }
+ kfree(adm, M_DEVBUF);
+ adm = NULL;
+ }
+ return (adm);
+}
+
+static
+void
+sili_dmamem_saveseg(void *info, bus_dma_segment_t *segs, int nsegs, int error)
+{
+ KKASSERT(error == 0);
+ KKASSERT(nsegs == 1);
+ *(bus_addr_t *)info = segs->ds_addr;
+}
+
+
+void
+sili_dmamem_free(struct sili_softc *sc, struct sili_dmamem *adm)
+{
+ if (adm->adm_map) {
+ bus_dmamap_unload(adm->adm_tag, adm->adm_map);
+ bus_dmamap_destroy(adm->adm_tag, adm->adm_map);
+ adm->adm_map = NULL;
+ adm->adm_tag = NULL;
+ adm->adm_kva = NULL;
+ }
+ kfree(adm, M_DEVBUF);
+}
+
+u_int32_t
+sili_read(struct sili_softc *sc, bus_size_t r)
+{
+ bus_space_barrier(sc->sc_iot, sc->sc_ioh, r, 4,
+ BUS_SPACE_BARRIER_READ);
+ return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, r));
+}
+
+void
+sili_write(struct sili_softc *sc, bus_size_t r, u_int32_t v)
+{
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh, r, v);
+ bus_space_barrier(sc->sc_iot, sc->sc_ioh, r, 4,
+ BUS_SPACE_BARRIER_WRITE);
+}
+
+u_int32_t
+sili_pread(struct sili_port *ap, bus_size_t r)
+{
+ bus_space_barrier(ap->ap_sc->sc_iot, ap->ap_ioh, r, 4,
+ BUS_SPACE_BARRIER_READ);
+ return (bus_space_read_4(ap->ap_sc->sc_iot, ap->ap_ioh, r));
+}
+
+void
+sili_pwrite(struct sili_port *ap, bus_size_t r, u_int32_t v)
+{
+ bus_space_write_4(ap->ap_sc->sc_iot, ap->ap_ioh, r, v);
+ bus_space_barrier(ap->ap_sc->sc_iot, ap->ap_ioh, r, 4,
+ BUS_SPACE_BARRIER_WRITE);
+}
+
+/*
+ * Wait up to (timeout) milliseconds for the masked port register to
+ * match the target.
+ *
+ * Timeout is in milliseconds.
+ */
+int
+sili_pwait_eq(struct sili_port *ap, int timeout,
+ bus_size_t r, u_int32_t mask, u_int32_t target)
+{
+ int t;
+
+ /*
+ * Loop hard up to 100uS
+ */
+ for (t = 0; t < 100; ++t) {
+ if ((sili_pread(ap, r) & mask) == target)
+ return (0);
+ sili_os_hardsleep(1); /* us */
+ }
+
+ do {
+ timeout -= sili_os_softsleep();
+ if ((sili_pread(ap, r) & mask) == target)
+ return (0);
+ } while (timeout > 0);
+ return (1);
+}
+
+int
+sili_wait_ne(struct sili_softc *sc, bus_size_t r, u_int32_t mask,
+ u_int32_t target)
+{
+ int t;
+
+ /*
+ * Loop hard up to 100uS
+ */
+ for (t = 0; t < 100; ++t) {
+ if ((sili_read(sc, r) & mask) != target)
+ return (0);
+ sili_os_hardsleep(1); /* us */
+ }
+
+ /*
+ * And one millisecond the slow way
+ */
+ t = 1000;
+ do {
+ t -= sili_os_softsleep();
+ if ((sili_read(sc, r) & mask) != target)
+ return (0);
+ } while (t > 0);
+
+ return (1);
+}
+
+
+/*
+ * Acquire an ata transfer.
+ *
+ * Pass a NULL at for direct-attached transfers, and a non-NULL at for
+ * targets that go through the port multiplier.
+ */
+struct ata_xfer *
+sili_ata_get_xfer(struct sili_port *ap, struct ata_port *at)
+{
+ struct sili_ccb *ccb;
+
+ ccb = sili_get_ccb(ap);
+ if (ccb == NULL) {
+ DPRINTF(SILI_D_XFER, "%s: sili_ata_get_xfer: NULL ccb\n",
+ PORTNAME(ap));
+ return (NULL);
+ }
+
+ DPRINTF(SILI_D_XFER, "%s: sili_ata_get_xfer got slot %d\n",
+ PORTNAME(ap), ccb->ccb_slot);
+
+ bzero(ccb->ccb_xa.fis, sizeof(*ccb->ccb_xa.fis));
+ ccb->ccb_xa.at = at;
+ ccb->ccb_xa.fis->type = ATA_FIS_TYPE_H2D;
+
+ return (&ccb->ccb_xa);
+}
+
+void
+sili_ata_put_xfer(struct ata_xfer *xa)
+{
+ struct sili_ccb *ccb = (struct sili_ccb *)xa;
+
+ DPRINTF(SILI_D_XFER, "sili_ata_put_xfer slot %d\n", ccb->ccb_slot);
+
+ sili_put_ccb(ccb);
+}
+
+int
+sili_ata_cmd(struct ata_xfer *xa)
+{
+ struct sili_ccb *ccb = (struct sili_ccb *)xa;
+
+ KKASSERT(xa->state == ATA_S_SETUP);
+
+ if (ccb->ccb_port->ap_state == AP_S_FATAL_ERROR)
+ goto failcmd;
+#if 0
+ kprintf("%s: started std command %b ccb %d ccb_at %p %d\n",
+ ATANAME(ccb->ccb_port, ccb->ccb_xa.at),
+ sili_pread(ccb->ccb_port, SILI_PREG_CMD), SILI_PFMT_CMD,
+ ccb->ccb_slot,
+ ccb->ccb_xa.at,
+ ccb->ccb_xa.at ? ccb->ccb_xa.at->at_target : -1);
+#endif
+
+ ccb->ccb_done = sili_ata_cmd_done;
+
+ if (sili_load_prb(ccb) != 0)
+ goto failcmd;
+
+ xa->state = ATA_S_PENDING;
+
+ if (xa->flags & ATA_F_POLL)
+ return (sili_poll(ccb, xa->timeout, sili_ata_cmd_timeout));
+
+ crit_enter();
+ KKASSERT((xa->flags & ATA_F_TIMEOUT_EXPIRED) == 0);
+ xa->flags |= ATA_F_TIMEOUT_DESIRED;
+ sili_start(ccb);
+ crit_exit();
+ return (xa->state);
+
+failcmd:
+ crit_enter();
+ xa->state = ATA_S_ERROR;
+ xa->complete(xa);
+ crit_exit();
+ return (ATA_S_ERROR);
+}
+
+static void
+sili_ata_cmd_done(struct sili_ccb *ccb)
+{
+ struct ata_xfer *xa = &ccb->ccb_xa;
+
+ /*
+ * NOTE: callout does not lock port and may race us modifying
+ * the flags, so make sure its stopped.
+ */
+ if (xa->flags & ATA_F_TIMEOUT_RUNNING) {
+ callout_stop(&ccb->ccb_timeout);
+ xa->flags &= ~ATA_F_TIMEOUT_RUNNING;
+ }
+ xa->flags &= ~(ATA_F_TIMEOUT_DESIRED | ATA_F_TIMEOUT_EXPIRED);
+
+ KKASSERT(xa->state != ATA_S_ONCHIP);
+ sili_unload_prb(ccb);
+
+#ifdef DIAGNOSTIC
+ else if (xa->state != ATA_S_ERROR && xa->state != ATA_S_TIMEOUT)
+ kprintf("%s: invalid ata_xfer state %02x in sili_ata_cmd_done, "
+ "slot %d\n",
+ PORTNAME(ccb->ccb_port), xa->state, ccb->ccb_slot);
+#endif
+ if (xa->state != ATA_S_TIMEOUT)
+ xa->complete(xa);
+}
+
+/*
+ * Timeout from callout, MPSAFE - nothing can mess with the CCB's flags
+ * while the callout is runing.
+ *
+ * We can't safely get the port lock here or delay, we could block
+ * the callout thread.
+ */
+static void
+sili_ata_cmd_timeout_unserialized(void *arg)
+{
+ struct sili_ccb *ccb = arg;
+ struct sili_port *ap = ccb->ccb_port;
+
+ ccb->ccb_xa.flags &= ~ATA_F_TIMEOUT_RUNNING;
+ ccb->ccb_xa.flags |= ATA_F_TIMEOUT_EXPIRED;
+ sili_os_signal_port_thread(ap, AP_SIGF_TIMEOUT);
+}
+
+void
+sili_ata_cmd_timeout(struct sili_ccb *ccb)
+{
+ sili_core_timeout(ccb);
+}
+
+/*
+ * Timeout code, typically called when the port command processor is running.
+ *
+ * Returns 0 if all timeout processing completed, non-zero if it is still
+ * in progress.
+ */
+static
+int
+sili_core_timeout(struct sili_ccb *ccb)
+{
+ struct ata_xfer *xa = &ccb->ccb_xa;
+ struct sili_port *ap = ccb->ccb_port;
+ struct ata_port *at;
+
+ at = ccb->ccb_xa.at;
+
+ kprintf("%s: CMD TIMEOUT state=%d slot=%d\n"
+ "\t active=%08x\n"
+ "\texpired=%08x\n"
+ "\thactive=%08x\n",
+ ATANAME(ap, at),
+ ccb->ccb_xa.state, ccb->ccb_slot,
+ ap->ap_active,
+ ap->ap_expired,
+ sili_pread(ap, SILI_PREG_SLOTST)
+ );
+
+ /*
+ * NOTE: Timeout will not be running if the command was polled.
+ * If we got here at least one of these flags should be set.
+ *
+ * However, it might be running if we are called from the
+ * interrupt error handling code.
+ */
+ KKASSERT(xa->flags & (ATA_F_POLL | ATA_F_TIMEOUT_DESIRED |
+ ATA_F_TIMEOUT_RUNNING));
+ if (xa->flags & ATA_F_TIMEOUT_RUNNING) {
+ callout_stop(&ccb->ccb_timeout);
+ xa->flags &= ~ATA_F_TIMEOUT_RUNNING;
+ }
+ xa->flags &= ~ATA_F_TIMEOUT_EXPIRED;
+
+ if (ccb->ccb_xa.state == ATA_S_PENDING) {
+ TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry);
+ ccb->ccb_xa.state = ATA_S_TIMEOUT;
+ ccb->ccb_done(ccb);
+ xa->complete(xa);
+ sili_issue_pending_commands(ap, NULL);
+ return(1);
+ }
+ if (ccb->ccb_xa.state != ATA_S_ONCHIP) {
+ kprintf("%s: Unexpected state during timeout: %d\n",
+ ATANAME(ap, at), ccb->ccb_xa.state);
+ return(1);
+ }
+
+ /*
+ * We can't process timeouts while other commands are running.
+ */
+ ap->ap_expired |= 1 << ccb->ccb_slot;
+
+ if (ap->ap_active != ap->ap_expired) {
+ kprintf("%s: Deferred timeout until its safe, slot %d\n",
+ ATANAME(ap, at), ccb->ccb_slot);
+ return(1);
+ }
+
+ /*
+ * We have to issue a Port reinit. We don't read an error log
+ * page for timeouts. Reiniting the port will clear all pending
+ * commands.
+ */
+ sili_port_reinit(ap);
+ return(0);
+}
+
+/*
+ * Used by the softreset, pmprobe, and read_ncq_error only, in very
+ * specialized, controlled circumstances.
+ */
+void
+sili_quick_timeout(struct sili_ccb *ccb)
+{
+ struct sili_port *ap = ccb->ccb_port;
+
+ switch (ccb->ccb_xa.state) {
+ case ATA_S_PENDING:
+ TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry);
+ ccb->ccb_xa.state = ATA_S_TIMEOUT;
+ break;
+ case ATA_S_ONCHIP:
+ KKASSERT((ap->ap_active & ~ap->ap_expired) ==
+ (1 << ccb->ccb_slot));
+ ccb->ccb_xa.state = ATA_S_TIMEOUT;
+ ap->ap_active &= ~(1 << ccb->ccb_slot);
+ KKASSERT(ap->ap_active_cnt > 0);
+ --ap->ap_active_cnt;
+ sili_port_reinit(ap);
+ break;
+ default:
+ panic("%s: sili_quick_timeout: ccb in bad state %d",
+ ATANAME(ap, ccb->ccb_xa.at), ccb->ccb_xa.state);
+ }
+}
+
+static void
+sili_dummy_done(struct ata_xfer *xa)
+{
+}
+
+static void
+sili_empty_done(struct sili_ccb *ccb)
+{
+}
--- /dev/null
+/*
+ * Copyright (c) 2006 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $OpenBSD: sili.c,v 1.147 2009/02/16 21:19:07 miod Exp $
+ */
+
+#if defined(__DragonFly__)
+#include "sili_dragonfly.h"
+#else
+#error "build for OS unknown"
+#endif
+#include "pmreg.h"
+#include "atascsi.h"
+
+/* change to SILI_DEBUG for dmesg spam */
+#define NO_SILI_DEBUG
+
+#ifdef SILI_DEBUG
+#define DPRINTF(m, f...) do { if ((silidebug & (m)) == (m)) kprintf(f); } \
+ while (0)
+#define SILI_D_TIMEOUT 0x00
+#define SILI_D_VERBOSE 0x01
+#define SILI_D_INTR 0x02
+#define SILI_D_XFER 0x08
+int silidebug = SILI_D_VERBOSE;
+#else
+#define DPRINTF(m, f...)
+#endif
+
+/*
+ * BAR0 - Global Registers 128-byte aligned, 128-byte region
+ * BAR1 - Port Registers and LRAM 32KB-aligned
+ * BAR2 - Indirect I/O registers (we don't use this)
+ */
+
+/*
+ * Port-N Slot Status.
+ *
+ * NOTE: Mirrors SILI_PREG_SLOTST
+ */
+#define SILI_REG_SLOTST(n) (0x0000 + ((n) * 4))
+#define SILI_REG_SLOTST_ATTN 0x80000000 /* attention required */
+
+#define SILI_REG_GCTL 0x0040 /* Global Control */
+#define SILI_REG_GCTL_PORTEN(n) (1 << (n)) /* Port interrupt ena */
+#define SILI_REG_GCTL_300CAP 0x01000000 /* 3Gb/s capable (R) */
+#define SILI_REG_GCTL_I2C_IEN 0x20000000 /* I2C Int enable */
+#define SILI_REG_GCTL_MSIACK 0x40000000 /* MSI Ack W1C */
+#define SILI_REG_GCTL_GRESET 0x80000000 /* global reset */
+
+#define SILI_REG_GINT 0x0044 /* Global Interrupt Status */
+#define SILI_REG_GINT_I2C 0x20000000 /* I2C Int Status */
+#define SILI_REG_GINT_PORTST(n) (1 << (n)) /* Port interrupt stat */
+#define SILI_REG_GINT_PORTMASK 0x0000FFFF
+
+/*
+ * Most bits in phyconf should not be modified. Setting the low four bits
+ * to 1's, all channel Tx outputs spread spectrum clocking.
+ */
+#define SILI_REG_PHYCONF 0x0048 /* PHY Configuration */
+#define SILI_REG_PHYCONF_ALLSS 0x000F /* spread spectrum tx */
+
+/*
+ * BIST_CTL_TEN - Enable data paths for running data loopback BIST
+ * BIST_CTL_TPAT - Select repeating pattern (1) or pseudo-random
+ * pattern (0)
+ * BIST_CTL_RXSEL- Select the rx port for pattern comparison
+ * BIST_CTL_TXSEL- Select the tx ports that transmit loopback data
+ */
+#define SILI_REG_BIST_CTL 0x0050
+#define SILI_REG_BIST_CTL_TEN 0x80000000
+#define SILI_REG_BIST_CTL_TPAT 0x40000000
+#define SILI_REG_BIST_CTL_RXSEL(n) ((n) << 16)
+#define SILI_REG_BIST_CTL_TXSEL(n) (1 << (n))
+
+#define SILI_REG_BIST_PATTERN 0x0054 /* 32 bit pattern */
+
+/*
+ * GOOD is set to 1 on BIST initiation, and reset to 0 on the first
+ * comparison failure.
+ */
+#define SILI_REG_BIST_STATUS 0x0058
+#define SILI_REG_BIST_STATUS_CNT 0x000007FF /* pattern counter */
+#define SILI_REG_BIST_STATUS_GOOD 0x80000000 /* set to 0 on compare fail */
+
+#define SILI_REG_I2C_CTL 0x0060
+#define SILI_REG_I2C_CTL_START 0x00000001
+#define SILI_REG_I2C_CTL_STOP 0x00000002
+#define SILI_REG_I2C_CTL_NACK 0x00000004 /* send nack on data byte rx */
+#define SILI_REG_I2C_CTL_TFDATA 0x00000008 /* set to initiate txfer */
+ /* to/from data buffer */
+#define SILI_REG_I2C_CTL_MABORT 0x00000010 /* set w/STOP to send stop */
+ /* without first xfering a */
+ /* byte */
+#define SILI_REG_I2C_CTL_SCLEN 0x00000020 /* clock-en for master mode */
+#define SILI_REG_I2C_CTL_UNITEN 0x00000040 /* enable controller */
+#define SILI_REG_I2C_CTL_GCALLD 0x00000080 /* Disable detect of a */
+ /* general call address */
+#define SILI_REG_I2C_CTL_TXBEI 0x00000100 /* xmit buffer empty int en */
+#define SILI_REG_I2C_CTL_RXBFI 0x00000200 /* rx buffer full int en */
+#define SILI_REG_I2C_CTL_BERRI 0x00000400 /* bus error int en */
+#define SILI_REG_I2C_CTL_STOPI 0x00000800 /* stop detect int en */
+#define SILI_REG_I2C_CTL_ARBLI 0x00001000 /* arb loss int en */
+#define SILI_REG_I2C_CTL_ARBDI 0x00002000 /* arb detect int en */
+#define SILI_REG_I2C_CTL_UNITRS 0x00004000 /* reset I2C controller */
+#define SILI_REG_I2C_CTL_FASTM 0x00008000 /* 400kbit/s (else 100kbit/s)*/
+
+#define SILI_REG_I2C_STS 0x0064
+#define SILI_REG_I2C_STS_RW 0x00000001
+#define SILI_REG_I2C_STS_ACKSTS 0x00000002 /* ack/nack status(R) last */
+ /* ack or nack sent or rcvd */
+#define SILI_REG_I2C_STS_UNTBSY 0x00000004 /* unit busy (R) */
+#define SILI_REG_I2C_STS_BUSBSY 0x00000008 /* bus busy with activity(R) */
+ /* other then from controller*/
+#define SILI_REG_I2C_STS_STOPDT 0x00000010 /* stop detect (R/W1C) */
+#define SILI_REG_I2C_STS_ARBLD 0x00000020 /* arb loss detect (R/W1C) */
+#define SILI_REG_I2C_STS_TXBED 0x00000040 /* tx buffer empty (R) */
+#define SILI_REG_I2C_STS_RXBFD 0x00000080 /* rx buffer full (R/W1C) */
+#define SILI_REG_I2C_STS_GCALLD 0x00000100 /* Gen Call detect (R/W1C) */
+#define SILI_REG_I2C_STS_SADDRD 0x00000200 /* Slave addr detect (R/W1C) */
+#define SILI_REG_I2C_STS_BERRD 0x00000400 /* Bus error detect (R/W1C) */
+
+#define SILI_REG_I2C_SADDR 0x0068 /* our slave address */
+#define SILI_REG_I2C_SADDR_MASK 0x0000003F /* 6 bits */
+
+#define SILI_REG_I2C_DATA 0x006C /* data buffer (8 bits) */
+
+#define SILI_REG_FLASH_ADDR 0x0070 /* Flash control & addr reg */
+#define SILI_REG_FLASH_ADDR_MEMPRS 0x04000000 /* Flash memory present */
+#define SILI_REG_FLASH_ADDR_GPIOEN 0x80000000 /* use flash data pins for */
+ /* GPIO */
+#define SILI_REG_FLASH_ADDR_MEMST 0x02000000 /* Mem Access Start (R/W) */
+ /* (clears on op complete) */
+#define SILI_REG_FLASH_ADDR_MEMRD 0x01000000 /* 0=write, 1=read */
+#define SILI_REG_FLASH_ADDR_MASK 0x0003FFFF /* 18 bit memory address */
+
+/*
+ * NOTE: In order to set a GPIO pin to read the DATA bit must be written to
+ * 1and the DCTL (drain control) bit must be written to 1 as well
+ * to make it open-drain only (drive on low only).
+ */
+#define SILI_REG_GPIO 0x0074
+#define SILI_REG_GPIO_DATA_SHIFT 0 /* 8 bits Flash or GPIO data */
+#define SILI_REG_GPIO_TDET_SHIFT 8 /* 8 bits transition detect */
+#define SILI_REG_GPIO_DCTL_SHIFT 16 /* 8 bits drain control */
+
+/*
+ * Per-port registers
+ *
+ */
+
+#define SILI_PORT_REGION(port) (8192 * (port))
+#define SILI_PORT_SIZE 8192
+#define SILI_PREG_LRAM 0x0000 /* 0x0000-0x0F7F */
+#define SILI_PREG_LRAM_SLOT(n) (0x0000 + (128 * (n)))
+
+#define SILI_PREG_LRAM_SLOT_FIS 0x0000 /* Current FIS and Control */
+#define SILI_PREG_LRAM_DMA0 0x0020 /* DMA Entry 0 or ATAPI cmd */
+#define SILI_PREG_LRAM_DMA1 0x0030 /* DMA Entry 0 or ATAPI cmd */
+#define SILI_PREG_LRAM_CMDACT 0x0040 /* Cmd Act Reg (actual) 64b */
+#define SILI_PREG_LRAM_DMATAB 0x0040 /* Scatter Gather Table */
+
+/*
+ * Each port has a port status and qactive register for each target behind
+ * the port multiplier, if there is a port multiplier.
+ *
+ * SERVICE - Service received from device, service command from controller
+ * not yet acknowledged.
+ *
+ * LEGACY - One or more legacy queued commands is outstanding.
+ *
+ * NATIVE - One or more NCQ queued commands is outstanding.
+ *
+ * VBSY - A virtual device busy indicating that a command has been issued
+ * to the device and the device has not yet sent the final D2H
+ * register FIS, or that a data transfer is in progress.
+ *
+ * The PM_QACTIVE register contains a demultiplexed bitmap of slots queued
+ * to each target behind the port multiplier.
+ *
+ */
+#define SILI_PREG_PM_STATUS(n) (0x0F80 + (8 * (n)))
+#define SILI_PREG_PM_QACTIVE(n) (0x0F84 + (8 * (n)))
+
+#define SILI_PREG_PM_STATUS_SERVICE 0x00010000 /* Service pending */
+#define SILI_PREG_PM_STATUS_LEGACY 0x00008000 /* Legacy outstanding*/
+#define SILI_PREG_PM_STATUS_NATIVE 0x00004000 /* NCQ outstanding */
+#define SILI_PREG_PM_STATUS_VBSY 0x00002000 /* virtual dev busy */
+#define SILI_PREG_PM_STATUS_EXEC_SHIFT 8 /* last active slot */
+#define SILI_PREG_PM_STATUS_EXEC_MASK 0x1F
+#define SILI_PREG_PM_STATUS_PIO_MASK 0xFF /* last PIO setup */
+
+/*
+ * NOTE: SILI_PREG_STATUS uses the same address as SILI_PREG_CTL_SET,
+ * but for read.
+ */
+#define SILI_PREG_STATUS 0x1000 /* Port Control Status (R) */
+#define SILI_PREG_STATUS_READY 0x80000000 /* Port Ready (R) */
+#define SILI_PREG_STATUS_SLOT 0x001F0000 /* Active Slot (R) */
+#define SILI_PREG_STATUS_SLOT_SHIFT 16 /* Shift value */
+#define SILI_PREG_STATUS_MASK 0x0200FFFF /* see PREG_CTL_xxx */
+
+/*
+ * NOTE: Reset sequence. Set CTL_RESET, Clear CTL_RESET, then Wait for
+ * the port to become ready.
+ *
+ * NOTE: NOAUTOCC. If set to 1 a 1 must be written to the command completion
+ * bit in the port interrupt status register to clear it. When set to
+ * 0 then reading the port slot status register will automatically clear
+ * the command completion interrupt.
+ *
+ * NOTE: ATA16 controls whether a PACKET mode command is 12 or 16 bytes.
+ *
+ * NOTE: RESUME if set to 1 processing is enabled for outstanding commands
+ * to additional targets connected to a port multiplier after a command
+ * error has occured. When set the internal BUSY status will be set
+ * for the target that errored, preventing additional commands from
+ * being sent until a Port Initialize operation is performed.
+ *
+ * NOTE: 32BITDMA if 1 causes a write to the low 32 bits of a Command
+ * Activation register to copy PREG_32BIT_ACTUA to the upper 32
+ * bits and start command execution. If 0 you must write to the
+ * low 32 bits and then the high 32 bits and your write to the
+ * high 32 bits will start command execution.
+ *
+ * NOTE: OOB_BYP is set on global reset and not changed by a port reset.
+ */
+#define SILI_PREG_CTL_SET 0x1000 /* Port Control Set (W1S) */
+#define SILI_PREG_CTL_CLR 0x1004 /* Port Control Clear (W1C) */
+#define SILI_PREG_CTL_RESET 0x00000001 /* Hold port in reset */
+#define SILI_PREG_CTL_DEVRESET 0x00000002 /* Device reset */
+ /* (Self clearing) */
+#define SILI_PREG_CTL_INIT 0x00000004 /* Port initialize */
+ /* (Self clearing) */
+#define SILI_PREG_CTL_NOAUTOCC 0x00000008
+#define SILI_PREG_CTL_NOLED 0x00000010 /* Disable the LED port */
+ /* activity indicator. */
+#define SILI_PREG_CTL_ATA16 0x00000020 /* 0=12 byte 1=16 byte */
+#define SILI_PREG_CTL_RESUME 0x00000040 /* PM special error handling */
+#define SILI_PREG_CTL_TXBIST 0x00000080 /* transmit a BIST FIS */
+#define SILI_PREG_CTL_CONT_DIS 0x00000100 /* no CONT on repeat primitves*/
+#define SILI_PREG_CTL_NOSCRAM 0x00000200 /* Disable link scrambler */
+#define SILI_PREG_CTL_32BITDMA 0x00000400 /* see above */
+#define SILI_PREG_CTL_ACC_ILCK 0x00000800 /* accept interlocked FIS rx */
+ /* (Self clearing) */
+#define SILI_PREG_CTL_REJ_ILCK 0x00001000 /* reject interlocked FIS rx */
+ /* (Self clearing) */
+#define SILI_PREG_CTL_PMA 0x00002000 /* Enable PM support */
+#define SILI_PREG_CTL_AUTO_ILCK 0x00004000 /* Auto interlock accept */
+#define SILI_PREG_CTL_LEDON 0x00008000 /* LED on */
+#define SILI_PREG_CTL_OOB_BYP 0x02000000 /* Bypass OOB initialization */
+
+/*
+ * Status bits in the upper half of the register report the actual condition
+ * while the status bits in the lower half of the register are masked by
+ * the interrupt enable bits or threshold registers. Writing a 1 to either
+ * version will clear it.
+ *
+ * NOTE: The steering bits written to INT_ENABLE will not show up in the
+ * status register. The INT_ENABLE/INT_DISABLE registers are R+W1S
+ * or R+W1C and thus can be read.
+ *
+ * NOTE: PHYRDYCHG, COMWAKE, UNRECFIS, DEVEXCHG: Can be cleared by writing
+ * W1C either here or via the DIAG.xxx bit bit in SError.
+ */
+#define SILI_PREG_INT_STATUS 0x1008 /* Control clear */
+#define SILI_PREG_INT_ENABLE 0x1010 /* Interrupt Enable Set */
+#define SILI_PREG_INT_DISABLE 0x1014 /* Interrupt Enable Clear */
+
+#define SILI_PREG_INT_STEER(n) ((n) << 30) /* Port Int -> INTA...INTD */
+#define SILI_PREG_INT_CCOMPLETE 0x00000001 /* one or more cmds completed*/
+#define SILI_PREG_INT_CERROR 0x00000002 /* read port error register */
+ /* to get error */
+#define SILI_PREG_INT_READY 0x00000004 /* Port is ready for cmds */
+#define SILI_PREG_INT_PMCHANGE 0x00000008 /* Change in power mng state */
+#define SILI_PREG_INT_PHYRDYCHG 0x00000010 /* Mirrors DIAG.N in SError */
+#define SILI_PREG_INT_COMWAKE 0x00000020 /* Mirrors DIAG.W in SError */
+#define SILI_PREG_INT_UNRECFIS 0x00000040 /* Mirrors DIAG.F in SError */
+#define SILI_PREG_INT_DEVEXCHG 0x00000080 /* Mirrors DIAG.X in SError */
+#define SILI_PREG_INT_DECODE 0x00000100 /* 8b/10b dec err cnt > thr */
+#define SILI_PREG_INT_CRC 0x00000200 /* CRC err count > thr */
+#define SILI_PREG_INT_HANDSHK 0x00000400 /* Handshake err count > thr */
+#define SILI_PREG_INT_SDB 0x00000800 /* Set Device Bits (SNotify) */
+#define SILI_PREG_INT_SHIFT 16 /* shift upper bits of status*/
+
+#define SILI_PREG_IST_CCOMPLETE 0x00010000 /* one or more cmds completed*/
+#define SILI_PREG_IST_CERROR 0x00020000 /* read port error register */
+ /* to get error */
+#define SILI_PREG_IST_READY 0x00040000 /* Port is ready for cmds */
+#define SILI_PREG_IST_PMCHANGE 0x00080000 /* Change in power mng state */
+#define SILI_PREG_IST_PHYRDYCHG 0x00100000 /* Mirrors DIAG.N in SError */
+#define SILI_PREG_IST_COMWAKE 0x00200000 /* Mirrors DIAG.W in SError */
+#define SILI_PREG_IST_UNRECFIS 0x00400000 /* Mirrors DIAG.F in SError */
+#define SILI_PREG_IST_DEVEXCHG 0x00800000 /* Mirrors DIAG.X in SError */
+#define SILI_PREG_IST_DECODE 0x01000000 /* 8b/10b dec err cnt > thr */
+#define SILI_PREG_IST_CRC 0x02000000 /* CRC err count > thr */
+#define SILI_PREG_IST_HANDSHK 0x04000000 /* Handshake err count > thr */
+#define SILI_PREG_IST_SDB 0x08000000 /* Set Device Bits (SNotify) */
+
+#define SILI_PREG_INT_MASK (SILI_PREG_INT_CCOMPLETE | \
+ SILI_PREG_INT_CERROR | \
+ SILI_PREG_INT_READY | \
+ SILI_PREG_INT_PMCHANGE | \
+ SILI_PREG_INT_PHYRDYCHG | \
+ SILI_PREG_INT_COMWAKE | \
+ SILI_PREG_INT_UNRECFIS | \
+ SILI_PREG_INT_DEVEXCHG | \
+ SILI_PREG_INT_DECODE | \
+ SILI_PREG_INT_CRC | \
+ SILI_PREG_INT_HANDSHK | \
+ SILI_PREG_INT_SDB)
+#define SILI_PREG_IST_MASK (SILI_PREG_INT_MASK << 16)
+
+#define SILI_PFMT_INT_STATUS "\020" \
+ "\034SDB" \
+ "\033HANDSHK" \
+ "\032CRC" \
+ "\031DECODE" \
+ "\030DEVEXCHG" \
+ "\027UNRECFIS" \
+ "\026COMWAKE" \
+ "\025PHYRDYCHG" \
+ "\024PMCHANGE" \
+ "\023READY" \
+ "\022ERROR" \
+ "\021COMPLETE" \
+ \
+ "\014SDBm" \
+ "\013HANDSHKm" \
+ "\012CRCm" \
+ "\011DECODEm" \
+ "\010DEVEXCHGm" \
+ "\007UNRECFISm" \
+ "\006COMWAKEm" \
+ "\005PHYRDYCHGm" \
+ "\004PMCHANGEm" \
+ "\003READYm" \
+ "\002ERRORm" \
+ "\001COMPLETEm"
+
+/*
+ * 32BIT_ACTUA is only used when DMA is 32 bits. It is typically set to 0.
+ */
+#define SILI_PREG_32BIT_ACTUA 0x101C /* 32b activation upper addr */
+
+/*
+ * Writing a slot number 0-30 to CMD_FIFO starts the command from LRAM.
+ */
+#define SILI_PREG_CMD_FIFO 0x1020 /* Command execution FIFO */
+
+/*
+ * If the port is interrupted via INT_CERROR this register contains
+ * the error code.
+ *
+ * Most errors write the task file register back to the PRB slot for host
+ * scrutiny.
+ */
+#define SILI_PREG_CERROR 0x1024 /* Command error */
+#define SILI_PREG_CERROR_DEVICE 1 /* ERR set in D2H FIS */
+#define SILI_PREG_CERROR_SDBERROR 2 /* ERR set in SDB from device*/
+#define SILI_PREG_CERROR_DATAFISERR 3 /* Sil3132 error on send */
+#define SILI_PREG_CERROR_SENDFISERR 4 /* Sil3132 error on send */
+#define SILI_PREG_CERROR_BADSTATE 5 /* Sil3132 inconsistency */
+#define SILI_PREG_CERROR_DIRECTION 6 /* DMA direction mismatch */
+#define SILI_PREG_CERROR_UNDERRUN 7 /* DMA SG H2D list too small */
+#define SILI_PREG_CERROR_OVERRUN 8 /* DMA SG D2H list too small */
+#define SILI_PREG_CERROR_LLOVERUN 9 /* Too much data from device */
+#define SILI_PREG_CERROR_PKTPROTO 11 /* Packet protocol error */
+#define SILI_PREG_CERROR_BADALIGN 16 /* Bad SG list, not 8-byte */
+ /* aligned */
+#define SILI_PREG_CERROR_PCITGTABRT 17 /* PCI target abort received */
+#define SILI_PREG_CERROR_PCIMASABRT 18 /* PCI master abort received */
+#define SILI_PREG_CERROR_PCIPARABRT 19 /* PCI parity abort received */
+#define SILI_PREG_CERROR_PRBALIGN 24 /* PRB addr not 8-byte algned*/
+#define SILI_PREG_CERROR_PCITGTABRT2 25 /* During fetch of PRB */
+#define SILI_PREG_CERROR_PCIMASABRT2 26 /* During fetch of PRB */
+#define SILI_PREG_CERROR_PCIPARABRT3 33 /* During data transfer */
+#define SILI_PREG_CERROR_PCITGTABRT3 34 /* During data transfer */
+#define SILI_PREG_CERROR_PCIMASABRT3 35 /* During data transfer */
+#define SILI_PREG_CERROR_SERVICE 36 /* FIS received during tx */
+ /* phase */
+
+/*
+ * Port FIS Configuration. Fir each possible FIS type, a 2-bit code
+ * defines the desired reception behavior as follows. Bits [1:0] define
+ * the code for all other FIS types not defined by [29:2].
+ *
+ * 00 Accept FIS without interlock
+ * 01 Reject FIS without interlock
+ * 10 Interlock FIS
+ * 11 (reserved)
+ *
+ * FIS Code Name Start Default
+ * -------- ------ ------ -------
+ * ---- (reserved) 30
+ * 0x27 Register (H2D) 28 01
+ * 0x34 Register (D2H) 26 00
+ * 0x39 DMA Activate 24 00
+ * 0x41 DMA Setup 22 00
+ * 0x46 Data 20 00
+ * 0x58 BIST Activate 18 00
+ * 0x5F PIO Setup 16 00
+ * 0xA1 Set Device Bits 14 00
+ * 0xA6 (reserved) 12 01
+ * 0xB8 (reserved) 10 01
+ * 0xBF (reserved) 8 01
+ * 0xC7 (reserved) 6 01
+ * 0xD4 (reserved) 4 01
+ * 0xD9 (reserved) 2 01
+ * ALL OTHERS (reserved) 0 01
+ */
+#define SILI_PREG_FIS_CONFIG 0x1028 /* FIS configuration */
+
+/*
+ * The data FIFO is 2KBytes in each direction.
+ *
+ * When DMAing from the device the Write Request Threshold is used.
+ * When DMAing to the device the Read Request Threshold is used.
+ *
+ * The threshold can be set from 1-2040 bytes (write 0-2039), in multiples
+ * of 8 bits. The low 3 bits are hardwired to 0. A value of 0 causes a
+ * request whenever possible.
+ */
+#define SILI_PREG_FIFO_CTL 0x102C /* PCIex request FIFO thresh */
+#define SILI_PREG_FIFO_CTL_READ_SHIFT 0
+#define SILI_PREG_FIFO_CTL_WRITE_SHIFT 16
+#define SILI_PREG_FIFO_CTL_MASK 0xFF
+
+/*
+ * Error counters and thresholds. The counter occupies the low 16 bits
+ * and the threshold occupies the high 16 bits. The appropriate interrupt
+ * occurs when the counter exceeds the threshold. Clearing the interrupt
+ * clears the counter as well. A threshold of 0 disables the interrupt
+ * assertion and masks the interrupt status bit in the port interrupt status
+ * register.
+ */
+#define SILI_PREG_CTR_DECODE 0x1040 /* 8B/10B Decode Error Ctr */
+#define SILI_PREG_CTR_CRC 0x1044 /* CRC Error Counter */
+#define SILI_PREG_CTR_HANDSHK 0x1048 /* Handshake Error Counter */
+
+/*
+ * NOTE: This register is reset only by the global reset and will not be
+ * reset by a port reset.
+ *
+ * NOTE: Bits 15:5 configure the PHY and should not be changed unless you
+ * want to blow up the part.
+ *
+ * Bits 4:0 define the nominal output swing for the transmitter
+ * and are set to 0x0C by default. Generally speaking, don't mess
+ * with them.
+ */
+#define SILI_PREG_PHY_CONFIG 0x1050 /* Handshake Error Counter */
+#define SILI_PREG_PHY_CONFIG_AMP_MASK 0x1F
+
+#define SILI_PREG_SLOTST 0x1800 /* Slot Status */
+#define SILI_PREG_SLOTST_ATTN 0x80000000 /* 0-30 bit for each slot */
+
+/*
+ * Shadow command activation register, shadows low or high 32 bits
+ * of actual command activation register.
+ */
+#define SILI_PREG_CMDACT(n) (0x1C00 + (4 * (n)))
+
+/*
+ * Port Context Register. Contains the port multipler target (0-15) and
+ * the command slot (0-31) for the PM port state machine.
+ *
+ * Upon a processing halt due to a device specific error, the port multipler
+ * target is the one that returned the error status.
+ *
+ * The command slot is NOT deterministic in this case and should not be
+ * assumed valid. Use READ LOG EXTENDED to determine the tag number.
+ * However, the documentation does appear to indicate that for non-NCQ
+ * errors the command slot does contain the tag that errored (since there
+ * will be only one truely active).
+ */
+#define SILI_PREG_CONTEXT 0x1E04
+#define SILI_PREG_CONTEXT_SLOT_MASK 0x1F
+#define SILI_PREG_CONTEXT_PMPORT_MASK 0x0F
+#define SILI_PREG_CONTEXT_SLOT_SHIFT 0
+#define SILI_PREG_CONTEXT_PMPORT_SHIFT 5
+
+/*
+ * SControl register - power management, speed negotiation, and COMRESET
+ * operation.
+ */
+#define SILI_PREG_SCTL 0x1F00
+
+/*
+ * PMP: Identify the PM port for accessing the SActive register and some
+ * bit fields of the Diagnostic registers.
+ */
+#define SILI_PREG_SCTL_PMP 0x000F0000
+#define SILI_PREG_SCTL_PMP_SHIFT 16
+
+/*
+ * SPM: It is unclear from mode 4 is. "Transition from a power management
+ * state initiate (ComWake asserted)". When setting a state, the field
+ * will self-reset to 0 as soon as the action is initiated.
+ */
+#define SILI_PREG_SCTL_SPM 0x0000F000
+#define SILI_PREG_SCTL_SPM_NONE 0x00000000
+#define SILI_PREG_SCTL_SPM_PARTIAL 0x00010000
+#define SILI_PREG_SCTL_SPM_SLUMBER 0x00020000
+#define SILI_PREG_SCTL_SPM_FROM 0x00040000
+
+/*
+ * IPM: Identify interface power management states not supported (bits).
+ */
+#define SILI_PREG_SCTL_IPM 0x00000F00
+#define SILI_PREG_SCTL_IPM_NONE 0x00000000
+#define SILI_PREG_SCTL_IPM_NPARTIAL 0x00000100
+#define SILI_PREG_SCTL_IPM_NSLUMBER 0x00000200
+
+/*
+ * SPD: Limit speed negotiation (0000 for no restrictions)
+ */
+#define SILI_PREG_SCTL_SPD 0x000000F0
+#define SILI_PREG_SCTL_SPD_NONE 0x00000000
+#define SILI_PREG_SCTL_SPD_GEN1 0x00000010
+#define SILI_PREG_SCTL_SPD_GEN2 0x00000020
+
+/*
+ * DET: Control host adapter device detection and interface initialization
+ */
+#define SILI_PREG_SCTL_DET 0x0000000F
+#define SILI_PREG_SCTL_DET_NONE 0x00000000 /* nop/complete */
+#define SILI_PREG_SCTL_DET_INIT 0x00000001 /* hold in COMRESET */
+
+/*
+ * SStatus register - Probe status
+ */
+#define SILI_PREG_SSTS 0x1F04
+#define SILI_PREG_SSTS_IPM 0x00000F00
+#define SILI_PREG_SSTS_IPM_NOCOMM 0x00000000
+#define SILI_PREG_SSTS_IPM_ACTIVE 0x00000100
+#define SILI_PREG_SSTS_IPM_PARTIAL 0x00000200
+#define SILI_PREG_SSTS_IPM_SLUMBER 0x00000600
+
+#define SILI_PREG_SSTS_SPD 0x000000F0
+#define SILI_PREG_SSTS_SPD_NONE 0x00000000
+#define SILI_PREG_SSTS_SPD_GEN1 0x00000010
+#define SILI_PREG_SSTS_SPD_GEN2 0x00000020
+
+#define SILI_PREG_SSTS_DET 0x0000000F
+#define SILI_PREG_SSTS_DET_NOPHY 0x00000000 /* no dev, no phy */
+#define SILI_PREG_SSTS_DET_DEV_NE 0x00000001 /* dev, no phy */
+#define SILI_PREG_SSTS_DET_DEV 0x00000003 /* dev and phy */
+#define SILI_PREG_SSTS_DET_OFFLINE 0x00000004 /* BIST/LOOPBACK */
+
+/*
+ * These are mostly R/W1C bits. "B", "C", and "H" operate independantly
+ * and depend on the corresponding error counter register.
+ */
+#define SILI_PREG_SERR 0x1F08
+#define SILI_PREG_SERR_ERR_I (1<<0) /* Recovered Data Integrity */
+#define SILI_PREG_SERR_ERR_M (1<<1) /* Recovered Communications */
+#define SILI_PREG_SERR_ERR_T (1<<8) /* Transient Data Integrity */
+#define SILI_PREG_SERR_ERR_C (1<<9) /* Persistent Comm/Data */
+#define SILI_PREG_SERR_ERR_P (1<<10) /* Protocol */
+#define SILI_PREG_SERR_ERR_E (1<<11) /* Internal */
+#define SILI_PREG_SERR_DIAG_N (1<<16) /* PhyRdy Change */
+#define SILI_PREG_SERR_DIAG_I (1<<17) /* Phy Internal Error */
+#define SILI_PREG_SERR_DIAG_W (1<<18) /* Comm Wake */
+#define SILI_PREG_SERR_DIAG_B (1<<19) /* 10B to 8B Decode Error */
+#define SILI_PREG_SERR_DIAG_D (1<<20) /* Disparity Error */
+#define SILI_PREG_SERR_DIAG_C (1<<21) /* CRC Error */
+#define SILI_PREG_SERR_DIAG_H (1<<22) /* Handshake Error */
+#define SILI_PREG_SERR_DIAG_S (1<<23) /* Link Sequence Error */
+#define SILI_PREG_SERR_DIAG_T (1<<24) /* Transport State Trans Err */
+#define SILI_PREG_SERR_DIAG_F (1<<25) /* Unknown FIS Type */
+#define SILI_PREG_SERR_DIAG_X (1<<26) /* Exchanged */
+
+#define SILI_PFMT_SERR "\020" \
+ "\033DIAG.X" "\032DIAG.F" "\031DIAG.T" "\030DIAG.S" \
+ "\027DIAG.H" "\026DIAG.C" "\025DIAG.D" "\024DIAG.B" \
+ "\023DIAG.W" "\022DIAG.I" "\021DIAG.N" \
+ "\014ERR.E" "\013ERR.P" "\012ERR.C" "\011ERR.T" \
+ "\002ERR.M" "\001ERR.I"
+
+/*
+ * SACT provides indirect access to the Port Device QActive registers.
+ * We have direct access and do not have to use this.
+ */
+#define SILI_PREG_SACT 0x1F0C
+
+/*
+ * Indicate which devices have sent a Set Device Bits FIS with Notifcation
+ * set. R/W1C
+ */
+#define SILI_PREG_SNTF 0x1F10
+
+/*
+ * Internal register space indirect register access via the PCI I/O space.
+ * (This is for BIOSes running in 16-bit mode, we use the direct map).
+ *
+ * All offsets must be 4-byte aligned
+ */
+#define SILI_BAR2_GRO 0x0000 /* Global Register Offset */
+#define SILI_BAR2_GRD 0x0004 /* Global Register Data */
+#define SILI_BAR2_PRO 0x0008 /* Port Register Offset */
+#define SILI_BAR2_PRD 0x000C /* Port Register Data */
+
+/*
+ * SILI mapped structures
+ */
+struct sili_sge {
+ u_int64_t sge_paddr;
+ u_int32_t sge_count;
+ u_int32_t sge_flags;
+} __packed;
+
+#define SILI_SGE_FLAGS_TRM 0x80000000 /* last SGE associated w/cmd */
+#define SILI_SGE_FLAGS_LNK 0x40000000 /* link to SGE array */
+#define SILI_SGE_FLAGS_DRD 0x20000000 /* discard (ign sge_paddr) */
+#define SILI_SGE_FLAGS_XCF 0x10000000 /* external cmd fetch */
+
+/*
+ * The PRB
+ *
+ * NOTE: ATAPI PACKETS. The packet is stored in prb_sge[0] and sge[1]
+ * is the first SGE.
+ */
+struct sili_prb {
+ u_int16_t prb_control;
+ u_int16_t prb_override;
+ u_int32_t prb_xfer_count;
+ union {
+ struct ata_fis_h2d h2d;
+ struct ata_fis_d2h d2h;
+ } prb_fis;
+ u_int32_t prb_reserved1c;
+ struct sili_sge prb_sge[2];
+ /* note: command activation register is prb_ext[0].sge_paddr */
+ struct sili_sge prb_ext[4];
+} __packed;
+
+#define prb_h2d prb_fis.h2d
+#define prb_d2h prb_fis.d2h
+#define prb_activation prb_ext[0].sge_paddr
+#define prb_packet(prb) ((u_int8_t *)&(prb)->prb_sge[0])
+#define prb_sge_normal prb_sge[0]
+#define prb_sge_packet prb_sge[1]
+
+/*
+ * NOTE: override may be left 0 and the SIL3132 will decode the
+ * 8-bit ATA command and use the correct protocol.
+ */
+#define SILI_PRB_CTRL_OVERRIDE 0x0001 /* use protocol field override */
+#define SILI_PRB_CTRL_REXMIT 0x0002 /* ok to rexmit ext command */
+#define SILI_PRB_CTRL_EXTCMD 0x0004 /* FIS fetched from host memory */
+ /* (else from LRAM) */
+#define SILI_PRB_CTRL_RECEIVE 0x0008 /* Reserve cmd slot to receive */
+ /* an interlocked FIS */
+#define SILI_PRB_CTRL_READ 0x0010 /* device to host data */
+#define SILI_PRB_CTRL_WRITE 0x0020 /* host to device data */
+#define SILI_PRB_CTRL_NOINT 0x0040 /* do not post int on completion*/
+#define SILI_PRB_CTRL_SOFTRESET 0x0080 /* issue soft reset (special) */
+
+#define SILI_PRB_OVER_ATAPI 0x0001
+#define SILI_PRB_OVER_ATA 0x0002
+#define SILI_PRB_OVER_NCQ 0x0004
+#define SILI_PRB_OVER_READ 0x0008 /* device to host data */
+#define SILI_PRB_OVER_WRITE 0x0010 /* host to device data */
+#define SILI_PRB_OVER_RAW 0x0020 /* no protocol special case */
+
+/*
+ * Each sge is 16 bytes. We need to accomodate MAXPHYS (128K) which is
+ * at least 32 entries, plus one for page slop, plus one more for every
+ * 3 entries for the link entry. The command table for the Sili is
+ * built into its LRAM but it can have a separate SGE table for each
+ * command, and we use this.
+ */
+#define SILI_MAX_SGET 64
+#define SILI_MAX_PMPORTS 16
+
+#if MAXPHYS / PAGE_SIZE + 1 > SILI_MAX_SGET
+#error "SILI_MAX_SGET is not big enough"
+#endif
+
+#define SILI_MAX_PORTS 16
+#define SILI_MAX_CMDS 31 /* not 32 */
+
+struct sili_dmamem {
+ bus_dma_tag_t adm_tag;
+ bus_dmamap_t adm_map;
+ bus_dma_segment_t adm_seg;
+ bus_addr_t adm_busaddr;
+ caddr_t adm_kva;
+};
+#define SILI_DMA_MAP(_adm) ((_adm)->adm_map)
+#define SILI_DMA_DVA(_adm) ((_adm)->adm_busaddr)
+#define SILI_DMA_KVA(_adm) ((void *)(_adm)->adm_kva)
+
+struct sili_softc;
+struct sili_port;
+struct sili_device;
+
+struct sili_ccb {
+ /* ATA xfer associated with this CCB. Must be 1st struct member. */
+ struct ata_xfer ccb_xa;
+ struct callout ccb_timeout;
+
+ int ccb_slot;
+ struct sili_port *ccb_port;
+
+ bus_dmamap_t ccb_dmamap;
+ struct sili_prb *ccb_prb;
+ struct sili_sge *ccb_sge; /* linked SGE table */
+ u_int64_t ccb_sge_paddr; /* phys addr of sge table */
+
+ void (*ccb_done)(struct sili_ccb *);
+
+ TAILQ_ENTRY(sili_ccb) ccb_entry;
+};
+
+struct sili_port {
+ struct sili_softc *ap_sc;
+ bus_space_handle_t ap_ioh;
+
+ int ap_num;
+ int ap_pmcount;
+ int ap_flags;
+#define AP_F_BUS_REGISTERED 0x0001
+#define AP_F_CAM_ATTACHED 0x0002
+#define AP_F_IN_RESET 0x0004
+#define AP_F_SCAN_RUNNING 0x0008
+#define AP_F_SCAN_REQUESTED 0x0010
+#define AP_F_SCAN_COMPLETED 0x0020
+#define AP_F_IGNORE_IFS 0x0040
+#define AP_F_EXCLUSIVE_ACCESS 0x0200
+#define AP_F_ERR_CCB_RESERVED 0x0400
+ int ap_signal; /* os per-port thread sig */
+ thread_t ap_thread; /* os per-port thread */
+ struct lock ap_lock; /* os per-port lock */
+#define AP_SIGF_INIT 0x0001
+#define AP_SIGF_TIMEOUT 0x0002
+#define AP_SIGF_PORTINT 0x0004
+#define AP_SIGF_STOP 0x8000
+ struct cam_sim *ap_sim;
+
+ struct sili_sge *ap_sget;
+
+ struct sili_dmamem *ap_dmamem_sget;/* separate sge tables */
+
+ u_int32_t ap_active; /* active bmask */
+ u_int32_t ap_active_cnt; /* active count */
+ u_int32_t ap_expired; /* deferred expired bmask */
+ struct sili_ccb *ap_ccbs;
+ struct sili_ccb *ap_err_ccb; /* used to read LOG page */
+
+ TAILQ_HEAD(, sili_ccb) ap_ccb_free;
+ TAILQ_HEAD(, sili_ccb) ap_ccb_pending;
+ struct lock ap_ccb_lock;
+
+ int ap_type; /* ATA_PORT_T_xxx */
+ int ap_probe; /* ATA_PROBE_xxx */
+ struct ata_port *ap_ata;
+
+ u_int32_t ap_state;
+#define AP_S_NORMAL 0
+#define AP_S_FATAL_ERROR 1
+
+ /* For error recovery. */
+#ifdef DIAGNOSTIC
+ int ap_err_busy;
+#endif
+ u_int8_t ap_err_scratch[512];
+
+ char ap_name[16];
+};
+
+#define PORTNAME(_ap) ((_ap)->ap_name)
+#define ATANAME(_ap, _at) ((_at) ? (_at)->at_name : (_ap)->ap_name)
+
+struct sili_softc {
+ device_t sc_dev;
+ const struct sili_device *sc_ad; /* special casing */
+
+ struct resource *sc_irq; /* bus resources */
+ struct resource *sc_regs; /* bus resources */
+ struct resource *sc_pregs; /* bus resources */
+ bus_space_tag_t sc_iot; /* split from sc_regs */
+ bus_space_handle_t sc_ioh; /* split from sc_regs */
+ bus_space_tag_t sc_piot; /* split from sc_pregs */
+ bus_space_handle_t sc_pioh; /* split from sc_pregs */
+
+ int sc_rid_irq; /* saved bus RIDs */
+ int sc_rid_regs;
+ int sc_rid_pregs;
+
+ void *sc_irq_handle; /* installed irq vector */
+
+/* bus_dma_tag_t sc_tag_prb;*/
+ bus_dma_tag_t sc_tag_sget;
+ bus_dma_tag_t sc_tag_data;
+
+ int sc_flags;
+#define SILI_F_NO_NCQ 0x0001
+#define SILI_F_IGN_FR 0x0002
+#define SILI_F_INT_GOOD 0x0004
+#define SILI_F_64BIT 0x0008
+#define SILI_F_300 0x0010
+#define SILI_F_NCQ 0x0020
+#define SILI_F_SSNTF 0x0040
+#define SILI_F_SPM 0x0080
+
+ u_int sc_ncmds; /* max 31, NOT 32 */
+
+ struct sili_port *sc_ports[SILI_MAX_PORTS];
+};
+#define DEVNAME(_s) ((_s)->sc_dev.dv_xname)
+
+struct sili_device {
+ pci_vendor_id_t ad_vendor;
+ pci_product_id_t ad_product;
+ int ad_nports;
+ int (*ad_attach)(device_t dev);
+ int (*ad_detach)(device_t dev);
+ char *name;
+};
+
+const struct sili_device *sili_lookup_device(device_t dev);
+int sili_init(struct sili_softc *);
+int sili_port_init(struct sili_port *ap, struct ata_port *at);
+int sili_port_alloc(struct sili_softc *, u_int);
+void sili_port_state_machine(struct sili_port *ap, int initial);
+void sili_port_free(struct sili_softc *, u_int);
+int sili_port_reset(struct sili_port *, struct ata_port *at, int);
+
+u_int32_t sili_read(struct sili_softc *, bus_size_t);
+void sili_write(struct sili_softc *, bus_size_t, u_int32_t);
+int sili_wait_ne(struct sili_softc *, bus_size_t, u_int32_t, u_int32_t);
+u_int32_t sili_pread(struct sili_port *, bus_size_t);
+void sili_pwrite(struct sili_port *, bus_size_t, u_int32_t);
+int sili_pwait_eq(struct sili_port *, int, bus_size_t,
+ u_int32_t, u_int32_t);
+void sili_intr(void *);
+void sili_port_intr(struct sili_port *ap, int blockable);
+
+int sili_cam_attach(struct sili_port *ap);
+void sili_cam_changed(struct sili_port *ap, struct ata_port *at, int found);
+void sili_cam_detach(struct sili_port *ap);
+int sili_cam_probe(struct sili_port *ap, struct ata_port *at);
+
+struct ata_xfer *sili_ata_get_xfer(struct sili_port *ap, struct ata_port *at);
+void sili_ata_put_xfer(struct ata_xfer *xa);
+int sili_ata_cmd(struct ata_xfer *xa);
+
+int sili_pm_identify(struct sili_port *ap);
+int sili_pm_set_feature(struct sili_port *ap, int feature, int enable);
+int sili_pm_hardreset(struct sili_port *ap, int target, int hard);
+int sili_pm_softreset(struct sili_port *ap, int target);
+int sili_pm_phy_status(struct sili_port *ap, int target, u_int32_t *datap);
+int sili_pm_read(struct sili_port *ap, int target,
+ int which, u_int32_t *res);
+int sili_pm_write(struct sili_port *ap, int target,
+ int which, u_int32_t data);
+void sili_pm_check_good(struct sili_port *ap, int target);
+void sili_ata_cmd_timeout(struct sili_ccb *ccb);
+struct sili_ccb *sili_get_ccb(struct sili_port *ap);
+void sili_put_ccb(struct sili_ccb *ccb);
+struct sili_ccb *sili_get_err_ccb(struct sili_port *);
+void sili_put_err_ccb(struct sili_ccb *);
+int sili_poll(struct sili_ccb *ccb, int timeout,
+ void (*timeout_fn)(struct sili_ccb *));
+
+int sili_port_signature(struct sili_port *ap, struct ata_port *at,
+ u_int32_t sig);
+void sili_port_thread_core(struct sili_port *ap, int mask);
+
+void sili_os_sleep(int ms);
+void sili_os_hardsleep(int us);
+int sili_os_softsleep(void);
+void sili_os_start_port(struct sili_port *ap);
+void sili_os_stop_port(struct sili_port *ap);
+void sili_os_signal_port_thread(struct sili_port *ap, int mask);
+void sili_os_lock_port(struct sili_port *ap);
+int sili_os_lock_port_nb(struct sili_port *ap);
+void sili_os_unlock_port(struct sili_port *ap);
+
+extern u_int32_t SiliForceGen1;
+extern u_int32_t SiliNoFeatures;
--- /dev/null
+/*
+ * Copyright (c) 2006 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ *
+ * Copyright (c) 2009 The DragonFly Project. All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ *
+ * 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. Neither the name of The DragonFly Project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific, prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $OpenBSD: sili.c,v 1.147 2009/02/16 21:19:07 miod Exp $
+ */
+
+#include "sili.h"
+
+static int sili_pci_attach(device_t);
+static int sili_pci_detach(device_t);
+
+static const struct sili_device sili_devices[] = {
+ {
+ .ad_vendor = PCI_VENDOR_SII,
+ .ad_product = PCI_PRODUCT_SII_3132,
+ .ad_nports = 2,
+ .ad_attach = sili_pci_attach,
+ .ad_detach = sili_pci_detach,
+ .name = "SiliconImage-3132-SATA"
+ },
+ { 0, 0, 0, NULL, NULL, NULL }
+};
+
+/*
+ * Match during probe and attach. The device does not yet have a softc.
+ */
+const struct sili_device *
+sili_lookup_device(device_t dev)
+{
+ const struct sili_device *ad;
+ u_int16_t vendor = pci_get_vendor(dev);
+ u_int16_t product = pci_get_device(dev);
+#if 0
+ u_int8_t class = pci_get_class(dev);
+ u_int8_t subclass = pci_get_subclass(dev);
+ u_int8_t progif = pci_read_config(dev, PCIR_PROGIF, 1);
+#endif
+
+ for (ad = &sili_devices[0]; ad->ad_vendor; ++ad) {
+ if (ad->ad_vendor == vendor && ad->ad_product == product)
+ return (ad);
+ }
+ return (NULL);
+#if 0
+ /*
+ * Last ad is the default match if the PCI device matches SATA.
+ */
+ if (class == PCIC_STORAGE && subclass == PCIS_STORAGE_SATA &&
+ progif == PCIP_STORAGE_SATA_SILI_1_0) {
+ return (ad);
+ }
+ return (NULL);
+#endif
+}
+
+static int
+sili_pci_attach(device_t dev)
+{
+ struct sili_softc *sc = device_get_softc(dev);
+ struct sili_port *ap;
+ const char *gen;
+ u_int32_t nports, reg;
+ bus_addr_t addr;
+ int i;
+ int error;
+
+ /*
+ * Map the SILI controller's IRQ, BAR(0) (global regs),
+ * and BAR(1) (port regs and lram).
+ */
+ sc->sc_dev = dev;
+ sc->sc_rid_irq = SILI_IRQ_RID;
+ sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_rid_irq,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq == NULL) {
+ device_printf(dev, "unable to map interrupt\n");
+ sili_pci_detach(dev);
+ return (ENXIO);
+ }
+
+ /*
+ * When mapping the register window store the tag and handle
+ * separately so we can use the tag with per-port bus handle
+ * sub-spaces.
+ */
+ sc->sc_rid_regs = PCIR_BAR(0);
+ sc->sc_regs = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+ &sc->sc_rid_regs, RF_ACTIVE);
+ if (sc->sc_regs == NULL) {
+ device_printf(dev, "unable to map registers\n");
+ sili_pci_detach(dev);
+ return (ENXIO);
+ }
+ sc->sc_iot = rman_get_bustag(sc->sc_regs);
+ sc->sc_ioh = rman_get_bushandle(sc->sc_regs);
+
+ sc->sc_rid_pregs = PCIR_BAR(2);
+ sc->sc_pregs = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+ &sc->sc_rid_pregs, RF_ACTIVE);
+ if (sc->sc_pregs == NULL) {
+ device_printf(dev, "unable to map port registers\n");
+ sili_pci_detach(dev);
+ return (ENXIO);
+ }
+ sc->sc_piot = rman_get_bustag(sc->sc_pregs);
+ sc->sc_pioh = rman_get_bushandle(sc->sc_pregs);
+
+ /*
+ * Initialize the chipset and then set the interrupt vector up
+ */
+ error = sili_init(sc);
+ if (error) {
+ sili_pci_detach(dev);
+ return (ENXIO);
+ }
+
+ /*
+ * We assume at least 4 commands.
+ */
+ sc->sc_ncmds = SILI_MAX_CMDS;
+ sc->sc_flags |= SILI_F_64BIT;
+ sc->sc_flags |= SILI_F_NCQ;
+ sc->sc_flags |= SILI_F_SSNTF;
+ sc->sc_flags |= SILI_F_SPM;
+
+ addr = (sc->sc_flags & SILI_F_64BIT) ?
+ BUS_SPACE_MAXADDR : BUS_SPACE_MAXADDR_32BIT;
+
+ /*
+ * DMA tags for allocation of DMA memory buffers, lists, and so
+ * forth. These are typically per-port.
+ *
+ * The stuff is mostly built into the BAR mappings. We only need
+ * tags for our external SGE list and data.
+ */
+ error = 0;
+ error += bus_dma_tag_create(
+ NULL, /* parent tag */
+ 256, /* alignment */
+ 65536, /* boundary */
+ addr, /* loaddr? */
+ BUS_SPACE_MAXADDR, /* hiaddr */
+ NULL, /* filter */
+ NULL, /* filterarg */
+ sizeof(struct sili_sge) * SILI_MAX_SGET * SILI_MAX_CMDS,
+ /* [max]size */
+ 1, /* maxsegs */
+ sizeof(struct sili_sge) * SILI_MAX_SGET * SILI_MAX_CMDS,
+ /* maxsegsz */
+ 0, /* flags */
+ &sc->sc_tag_sget); /* return tag */
+
+ /*
+ * The data tag is used for later dmamaps and not immediately
+ * allocated.
+ */
+ error += bus_dma_tag_create(
+ NULL, /* parent tag */
+ 4, /* alignment */
+ 0, /* boundary */
+ addr, /* loaddr? */
+ BUS_SPACE_MAXADDR, /* hiaddr */
+ NULL, /* filter */
+ NULL, /* filterarg */
+ 4096 * 1024, /* maxiosize */
+ SILI_MAX_SGET, /* maxsegs */
+ 65536, /* maxsegsz */
+ 0, /* flags */
+ &sc->sc_tag_data); /* return tag */
+
+ if (error) {
+ device_printf(dev, "unable to create dma tags\n");
+ sili_pci_detach(dev);
+ return (ENXIO);
+ }
+
+ if (sili_read(sc, SILI_REG_GCTL) & SILI_REG_GCTL_300CAP) {
+ gen = "1 (1.5Gbps) and 2 (3Gbps)";
+ sc->sc_flags |= SILI_F_300;
+ } else {
+ gen = "1 (1.5Gbps)";
+ }
+
+ nports = sc->sc_ad->ad_nports;
+ KKASSERT(nports <= SILI_MAX_PORTS);
+
+ kprintf("SILI %s ports=%d\n", sc->sc_ad->name, nports);
+
+ /*
+ * Allocate per-port resources
+ *
+ * All ports are attached in parallel but the CAM scan-bus
+ * is held up until all ports are attached so we get a deterministic
+ * order.
+ */
+ for (i = 0; error == 0 && i < nports; i++) {
+ error = sili_port_alloc(sc, i);
+ }
+
+ /*
+ * Setup the interrupt vector and enable interrupts. Note that
+ * since the irq may be shared we do not set it up until we are
+ * ready to go.
+ */
+ if (error == 0) {
+ error = bus_setup_intr(dev, sc->sc_irq, 0, sili_intr, sc,
+ &sc->sc_irq_handle, NULL);
+ }
+
+ if (error) {
+ device_printf(dev, "unable to install interrupt\n");
+ sili_pci_detach(dev);
+ return (ENXIO);
+ }
+
+ /*
+ * Interrupt subsystem is good to go now, enable all port interrupts
+ */
+ crit_enter();
+ reg = sili_read(sc, SILI_REG_GCTL);
+ for (i = 0; i < nports; ++i)
+ reg |= SILI_REG_GCTL_PORTEN(i);
+ sili_write(sc, SILI_REG_GCTL, reg);
+ sc->sc_flags |= SILI_F_INT_GOOD;
+ crit_exit();
+ sili_intr(sc);
+
+ /*
+ * All ports are probing in parallel. Wait for them to finish
+ * and then issue the cam attachment and bus scan serially so
+ * the 'da' assignments are deterministic.
+ */
+ for (i = 0; i < nports; i++) {
+ if ((ap = sc->sc_ports[i]) != NULL) {
+ while (ap->ap_signal & AP_SIGF_INIT)
+ tsleep(&ap->ap_signal, 0, "ahprb1", hz);
+ sili_os_lock_port(ap);
+ if (sili_cam_attach(ap) == 0) {
+ sili_cam_changed(ap, NULL, -1);
+ sili_os_unlock_port(ap);
+ while ((ap->ap_flags & AP_F_SCAN_COMPLETED) == 0) {
+ tsleep(&ap->ap_flags, 0, "ahprb2", hz);
+ }
+ } else {
+ sili_os_unlock_port(ap);
+ }
+ }
+ }
+
+ return(0);
+}
+
+/*
+ * Device unload / detachment
+ */
+static int
+sili_pci_detach(device_t dev)
+{
+ struct sili_softc *sc = device_get_softc(dev);
+ struct sili_port *ap;
+ int i;
+
+ /*
+ * Disable the controller and de-register the interrupt, if any.
+ *
+ * XXX interlock last interrupt?
+ */
+ sc->sc_flags &= ~SILI_F_INT_GOOD;
+ if (sc->sc_regs)
+ sili_write(sc, SILI_REG_GCTL, SILI_REG_GCTL_GRESET);
+
+ if (sc->sc_irq_handle) {
+ bus_teardown_intr(dev, sc->sc_irq, sc->sc_irq_handle);
+ sc->sc_irq_handle = NULL;
+ }
+
+ /*
+ * Free port structures and DMA memory
+ */
+ for (i = 0; i < SILI_MAX_PORTS; i++) {
+ ap = sc->sc_ports[i];
+ if (ap) {
+ sili_cam_detach(ap);
+ sili_port_free(sc, i);
+ }
+ }
+
+ /*
+ * Clean up the bus space
+ */
+ if (sc->sc_irq) {
+ bus_release_resource(dev, SYS_RES_IRQ,
+ sc->sc_rid_irq, sc->sc_irq);
+ sc->sc_irq = NULL;
+ }
+ if (sc->sc_regs) {
+ bus_release_resource(dev, SYS_RES_MEMORY,
+ sc->sc_rid_regs, sc->sc_regs);
+ sc->sc_regs = NULL;
+ }
+ if (sc->sc_pregs) {
+ bus_release_resource(dev, SYS_RES_MEMORY,
+ sc->sc_rid_pregs, sc->sc_pregs);
+ sc->sc_regs = NULL;
+ }
+
+ if (sc->sc_tag_sget) {
+ bus_dma_tag_destroy(sc->sc_tag_sget);
+ sc->sc_tag_sget = NULL;
+ }
+ if (sc->sc_tag_data) {
+ bus_dma_tag_destroy(sc->sc_tag_data);
+ sc->sc_tag_data = NULL;
+ }
+
+ return (0);
+}
--- /dev/null
+/*
+ * Copyright (c) 2009 The DragonFly Project. All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ *
+ * 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. Neither the name of The DragonFly Project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific, prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *
+ * Copyright (c) 2007 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $OpenBSD: atascsi.c,v 1.64 2009/02/16 21:19:06 miod Exp $
+ * $DragonFly$
+ */
+/*
+ * Implement each SATA port as its own SCSI bus on CAM. This way we can
+ * implement future port multiplier features as individual devices on the
+ * bus.
+ *
+ * Much of the cdb<->xa conversion code was taken from OpenBSD, the rest
+ * was written natively for DragonFly.
+ */
+
+#include "sili.h"
+
+const char *ScsiTypeArray[32] = {
+ "DIRECT",
+ "SEQUENTIAL",
+ "PRINTER",
+ "PROCESSOR",
+ "WORM",
+ "CDROM",
+ "SCANNER",
+ "OPTICAL",
+ "CHANGER",
+ "COMM",
+ "ASC0",
+ "ASC1",
+ "STORARRAY",
+ "ENCLOSURE",
+ "RBC",
+ "OCRW",
+ "0x10",
+ "OSD",
+ "ADC",
+ "0x13",
+ "0x14",
+ "0x15",
+ "0x16",
+ "0x17",
+ "0x18",
+ "0x19",
+ "0x1A",
+ "0x1B",
+ "0x1C",
+ "0x1D",
+ "0x1E",
+ "NODEVICE"
+};
+
+static void sili_xpt_action(struct cam_sim *sim, union ccb *ccb);
+static void sili_xpt_poll(struct cam_sim *sim);
+static void sili_xpt_scsi_disk_io(struct sili_port *ap,
+ struct ata_port *at, union ccb *ccb);
+static void sili_xpt_scsi_atapi_io(struct sili_port *ap,
+ struct ata_port *at, union ccb *ccb);
+
+static void sili_ata_complete_disk_rw(struct ata_xfer *xa);
+static void sili_ata_complete_disk_synchronize_cache(struct ata_xfer *xa);
+static void sili_atapi_complete_cmd(struct ata_xfer *xa);
+static void sili_ata_dummy_sense(struct scsi_sense_data *sense_data);
+static void sili_ata_atapi_sense(struct ata_fis_d2h *rfis,
+ struct scsi_sense_data *sense_data);
+
+static int sili_cam_probe_disk(struct sili_port *ap, struct ata_port *at);
+static int sili_cam_probe_atapi(struct sili_port *ap, struct ata_port *at);
+static void sili_ata_dummy_done(struct ata_xfer *xa);
+static void ata_fix_identify(struct ata_identify *id);
+static void sili_cam_rescan(struct sili_port *ap);
+
+int
+sili_cam_attach(struct sili_port *ap)
+{
+ struct cam_devq *devq;
+ struct cam_sim *sim;
+ int error;
+ int unit;
+
+ kprintf("%s: CAM ATTACH\n", PORTNAME(ap));
+ /*
+ * We want at least one ccb to be available for error processing
+ * so don't let CAM use more then ncmds - 1.
+ */
+ unit = device_get_unit(ap->ap_sc->sc_dev);
+ if (ap->ap_sc->sc_ncmds > 1)
+ devq = cam_simq_alloc(ap->ap_sc->sc_ncmds - 1);
+ else
+ devq = cam_simq_alloc(ap->ap_sc->sc_ncmds);
+ if (devq == NULL) {
+ return (ENOMEM);
+ }
+ sim = cam_sim_alloc(sili_xpt_action, sili_xpt_poll, "sili",
+ (void *)ap, unit, &sim_mplock, 1, 1, devq);
+ cam_simq_release(devq);
+ if (sim == NULL) {
+ return (ENOMEM);
+ }
+ ap->ap_sim = sim;
+ sili_os_unlock_port(ap);
+ error = xpt_bus_register(ap->ap_sim, ap->ap_num);
+ sili_os_lock_port(ap);
+ if (error != CAM_SUCCESS) {
+ sili_cam_detach(ap);
+ return (EINVAL);
+ }
+ kprintf("%s: CAM ATTACH2\n", PORTNAME(ap));
+ ap->ap_flags |= AP_F_BUS_REGISTERED;
+ kprintf("%s: CAM ATTACH3 %d\n", PORTNAME(ap), ap->ap_probe);
+
+ if (ap->ap_probe == ATA_PROBE_NEED_IDENT)
+ error = sili_cam_probe(ap, NULL);
+ else
+ error = 0;
+ if (error) {
+ sili_cam_detach(ap);
+ return (EIO);
+ }
+ ap->ap_flags |= AP_F_CAM_ATTACHED;
+
+ return(0);
+}
+
+/*
+ * The state of the port has changed.
+ *
+ * If at is NULL the physical port has changed state.
+ * If at is non-NULL a particular target behind a PM has changed state.
+ *
+ * If found is -1 the target state must be queued to a non-interrupt context.
+ * (only works with at == NULL).
+ *
+ * If found is 0 the target was removed.
+ * If found is 1 the target was inserted.
+ */
+void
+sili_cam_changed(struct sili_port *ap, struct ata_port *atx, int found)
+{
+ struct cam_path *tmppath;
+ int status;
+ int target;
+
+ target = atx ? atx->at_target : CAM_TARGET_WILDCARD;
+
+ if (ap->ap_sim == NULL)
+ return;
+ if (found == CAM_TARGET_WILDCARD) {
+ status = xpt_create_path(&tmppath, NULL,
+ cam_sim_path(ap->ap_sim),
+ target, CAM_LUN_WILDCARD);
+ if (status != CAM_REQ_CMP)
+ return;
+ sili_cam_rescan(ap);
+ } else {
+ status = xpt_create_path(&tmppath, NULL,
+ cam_sim_path(ap->ap_sim),
+ target,
+ CAM_LUN_WILDCARD);
+ if (status != CAM_REQ_CMP)
+ return;
+#if 0
+ /*
+ * This confuses CAM
+ */
+ if (found)
+ xpt_async(AC_FOUND_DEVICE, tmppath, NULL);
+ else
+ xpt_async(AC_LOST_DEVICE, tmppath, NULL);
+#endif
+ }
+ xpt_free_path(tmppath);
+}
+
+void
+sili_cam_detach(struct sili_port *ap)
+{
+ int error;
+
+ if ((ap->ap_flags & AP_F_CAM_ATTACHED) == 0)
+ return;
+ get_mplock();
+ if (ap->ap_sim) {
+ xpt_freeze_simq(ap->ap_sim, 1);
+ }
+ if (ap->ap_flags & AP_F_BUS_REGISTERED) {
+ error = xpt_bus_deregister(cam_sim_path(ap->ap_sim));
+ KKASSERT(error == CAM_REQ_CMP);
+ ap->ap_flags &= ~AP_F_BUS_REGISTERED;
+ }
+ if (ap->ap_sim) {
+ cam_sim_free(ap->ap_sim);
+ ap->ap_sim = NULL;
+ }
+ rel_mplock();
+ ap->ap_flags &= ~AP_F_CAM_ATTACHED;
+}
+
+/*
+ * Once the SILI port has been attached we need to probe for a device or
+ * devices on the port and setup various options.
+ *
+ * If at is NULL we are probing the direct-attached device on the port,
+ * which may or may not be a port multiplier.
+ */
+int
+sili_cam_probe(struct sili_port *ap, struct ata_port *atx)
+{
+ struct ata_port *at;
+ struct ata_xfer *xa;
+ u_int64_t capacity;
+ u_int64_t capacity_bytes;
+ int model_len;
+ int error;
+ int devncqdepth;
+ int i;
+ const char *wcstr;
+ const char *rastr;
+ const char *scstr;
+ const char *type;
+
+ error = EIO;
+
+ /*
+ * Delayed CAM attachment for initial probe, sim may be NULL
+ */
+ if (ap->ap_sim == NULL)
+ return(0);
+
+ /*
+ * A NULL atx indicates a probe of the directly connected device.
+ * A non-NULL atx indicates a device connected via a port multiplier.
+ * We need to preserve atx for calls to sili_ata_get_xfer().
+ *
+ * at is always non-NULL. For directly connected devices we supply
+ * an (at) pointing to target 0.
+ */
+ if (atx == NULL) {
+ at = ap->ap_ata; /* direct attached - device 0 */
+ if (ap->ap_type == ATA_PORT_T_PM) {
+ kprintf("%s: Found Port Multiplier\n",
+ ATANAME(ap, atx));
+ return (0);
+ }
+ at->at_type = ap->ap_type;
+ } else {
+ at = atx;
+ if (atx->at_type == ATA_PORT_T_PM) {
+ kprintf("%s: Bogus device, reducing port count to %d\n",
+ ATANAME(ap, atx), atx->at_target);
+ if (ap->ap_pmcount > atx->at_target)
+ ap->ap_pmcount = atx->at_target;
+ goto err;
+ }
+ }
+ if (ap->ap_type == ATA_PORT_T_NONE)
+ goto err;
+ if (at->at_type == ATA_PORT_T_NONE)
+ goto err;
+
+ /*
+ * Issue identify, saving the result
+ */
+ xa = sili_ata_get_xfer(ap, atx);
+ xa->complete = sili_ata_dummy_done;
+ xa->data = &at->at_identify;
+ xa->datalen = sizeof(at->at_identify);
+ xa->fis->flags = ATA_H2D_FLAGS_CMD | at->at_target;
+
+ switch(at->at_type) {
+ case ATA_PORT_T_DISK:
+ xa->fis->command = ATA_C_IDENTIFY;
+ type = "DISK";
+ break;
+ case ATA_PORT_T_ATAPI:
+ xa->fis->command = ATA_C_ATAPI_IDENTIFY;
+ type = "ATAPI";
+ break;
+ default:
+ xa->fis->command = ATA_C_ATAPI_IDENTIFY;
+ type = "UNKNOWN(ATAPI?)";
+ break;
+ }
+ xa->fis->features = 0;
+ xa->fis->device = 0;
+ xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL;
+ xa->timeout = 1000;
+
+ if (sili_ata_cmd(xa) != ATA_S_COMPLETE) {
+ kprintf("%s: Detected %s device but unable to IDENTIFY\n",
+ ATANAME(ap, atx), type);
+ sili_ata_put_xfer(xa);
+ goto err;
+ }
+ sili_ata_put_xfer(xa);
+
+ ata_fix_identify(&at->at_identify);
+
+ /*
+ * Read capacity using SATA probe info.
+ */
+ if (le16toh(at->at_identify.cmdset83) & 0x0400) {
+ /* LBA48 feature set supported */
+ capacity = 0;
+ for (i = 3; i >= 0; --i) {
+ capacity <<= 16;
+ capacity +=
+ le16toh(at->at_identify.addrsecxt[i]);
+ }
+ } else {
+ capacity = le16toh(at->at_identify.addrsec[1]);
+ capacity <<= 16;
+ capacity += le16toh(at->at_identify.addrsec[0]);
+ }
+ at->at_capacity = capacity;
+ if (atx == NULL)
+ ap->ap_probe = ATA_PROBE_GOOD;
+
+ capacity_bytes = capacity * 512;
+
+ /*
+ * Negotiate NCQ, throw away any ata_xfer's beyond the negotiated
+ * number of slots and limit the number of CAM ccb's to one less
+ * so we always have a slot available for recovery.
+ *
+ * NCQ is not used if ap_ncqdepth is 1 or the host controller does
+ * not support it, and in that case the driver can handle extra
+ * ccb's.
+ *
+ * NCQ is currently used only with direct-attached disks. It is
+ * not used with port multipliers or direct-attached ATAPI devices.
+ *
+ * Remember at least one extra CCB needs to be reserved for the
+ * error ccb.
+ */
+ if ((ap->ap_sc->sc_flags & SILI_F_NCQ) &&
+ at->at_type == ATA_PORT_T_DISK &&
+ (le16toh(at->at_identify.satacap) & (1 << 8))) {
+ at->at_ncqdepth = (le16toh(at->at_identify.qdepth) & 0x1F) + 1;
+ devncqdepth = at->at_ncqdepth;
+ if (at->at_ncqdepth > ap->ap_sc->sc_ncmds)
+ at->at_ncqdepth = ap->ap_sc->sc_ncmds;
+ if (at->at_ncqdepth > 1) {
+ for (i = 0; i < ap->ap_sc->sc_ncmds; ++i) {
+ xa = sili_ata_get_xfer(ap, atx);
+ if (xa->tag < at->at_ncqdepth) {
+ xa->state = ATA_S_COMPLETE;
+ sili_ata_put_xfer(xa);
+ }
+ }
+ if (at->at_ncqdepth >= ap->ap_sc->sc_ncmds) {
+ cam_devq_resize(ap->ap_sim->devq,
+ at->at_ncqdepth - 1);
+ }
+ }
+ } else {
+ devncqdepth = 0;
+ }
+
+ /*
+ * Make the model string a bit more presentable
+ */
+ for (model_len = 40; model_len; --model_len) {
+ if (at->at_identify.model[model_len-1] == ' ')
+ continue;
+ if (at->at_identify.model[model_len-1] == 0)
+ continue;
+ break;
+ }
+
+ /*
+ * Generate informatiive strings.
+ *
+ * NOTE: We do not automatically set write caching, lookahead,
+ * or the security state for ATAPI devices.
+ */
+ if (at->at_identify.cmdset82 & ATA_IDENTIFY_WRITECACHE) {
+ if (at->at_identify.features85 & ATA_IDENTIFY_WRITECACHE)
+ wcstr = "enabled";
+ else if (at->at_type == ATA_PORT_T_ATAPI)
+ wcstr = "disabled";
+ else
+ wcstr = "enabling";
+ } else {
+ wcstr = "notsupp";
+ }
+
+ if (at->at_identify.cmdset82 & ATA_IDENTIFY_LOOKAHEAD) {
+ if (at->at_identify.features85 & ATA_IDENTIFY_LOOKAHEAD)
+ rastr = "enabled";
+ else if (at->at_type == ATA_PORT_T_ATAPI)
+ rastr = "disabled";
+ else
+ rastr = "enabling";
+ } else {
+ rastr = "notsupp";
+ }
+
+ if (at->at_identify.cmdset82 & ATA_IDENTIFY_SECURITY) {
+ if (at->at_identify.securestatus & ATA_SECURE_FROZEN)
+ scstr = "frozen";
+ else if (at->at_type == ATA_PORT_T_ATAPI)
+ scstr = "unfrozen";
+ else if (SiliNoFeatures & (1 << ap->ap_num))
+ scstr = "<disabled>";
+ else
+ scstr = "freezing";
+ } else {
+ scstr = "notsupp";
+ }
+
+ kprintf("%s: Found %s \"%*.*s %8.8s\" serial=\"%20.20s\"\n"
+ "%s: tags=%d/%d satacap=%04x satafea=%04x NCQ=%s "
+ "capacity=%lld.%02dMB\n",
+
+ ATANAME(ap, atx),
+ type,
+ model_len, model_len,
+ at->at_identify.model,
+ at->at_identify.firmware,
+ at->at_identify.serial,
+
+ ATANAME(ap, atx),
+ devncqdepth, ap->ap_sc->sc_ncmds,
+ at->at_identify.satacap,
+ at->at_identify.satafsup,
+ (at->at_ncqdepth > 1 ? "YES" : "NO"),
+ (long long)capacity_bytes / (1024 * 1024),
+ (int)(capacity_bytes % (1024 * 1024)) * 100 / (1024 * 1024)
+ );
+ kprintf("%s: f85=%04x f86=%04x f87=%04x WC=%s RA=%s SEC=%s\n",
+ ATANAME(ap, atx),
+ at->at_identify.features85,
+ at->at_identify.features86,
+ at->at_identify.features87,
+ wcstr,
+ rastr,
+ scstr
+ );
+
+ /*
+ * Additional type-specific probing
+ */
+ switch(at->at_type) {
+ case ATA_PORT_T_DISK:
+ error = sili_cam_probe_disk(ap, atx);
+ break;
+ case ATA_PORT_T_ATAPI:
+ error = sili_cam_probe_atapi(ap, atx);
+ break;
+ default:
+ error = EIO;
+ break;
+ }
+err:
+ kprintf("%s: CAM PROBE %d\n", ATANAME(ap, atx), error);
+ if (error) {
+ at->at_probe = ATA_PROBE_FAILED;
+ if (atx == NULL)
+ ap->ap_probe = at->at_probe;
+ } else {
+ at->at_probe = ATA_PROBE_GOOD;
+ if (atx == NULL)
+ ap->ap_probe = at->at_probe;
+ }
+ return (error);
+}
+
+/*
+ * DISK-specific probe after initial ident
+ */
+static int
+sili_cam_probe_disk(struct sili_port *ap, struct ata_port *atx)
+{
+ struct ata_port *at;
+ struct ata_xfer *xa;
+
+ at = atx ? atx : ap->ap_ata;
+
+ /*
+ * Enable write cache if supported
+ *
+ * NOTE: "WD My Book" external disk devices have a very poor
+ * daughter board between the the ESATA and the HD. Sending
+ * any ATA_C_SET_FEATURES commands will break the hardware port
+ * with a fatal protocol error. However, this device also
+ * indicates that WRITECACHE is already on and READAHEAD is
+ * not supported so we avoid the issue.
+ */
+ if ((at->at_identify.cmdset82 & ATA_IDENTIFY_WRITECACHE) &&
+ (at->at_identify.features85 & ATA_IDENTIFY_WRITECACHE) == 0) {
+ xa = sili_ata_get_xfer(ap, atx);
+ xa->complete = sili_ata_dummy_done;
+ xa->fis->command = ATA_C_SET_FEATURES;
+ /*xa->fis->features = ATA_SF_WRITECACHE_EN;*/
+ xa->fis->features = ATA_SF_LOOKAHEAD_EN;
+ xa->fis->flags = ATA_H2D_FLAGS_CMD | at->at_target;
+ xa->fis->device = 0;
+ xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL;
+ xa->timeout = 1000;
+ xa->datalen = 0;
+ if (sili_ata_cmd(xa) == ATA_S_COMPLETE)
+ at->at_features |= ATA_PORT_F_WCACHE;
+ else
+ kprintf("%s: Unable to enable write-caching\n",
+ ATANAME(ap, atx));
+ sili_ata_put_xfer(xa);
+ }
+
+ /*
+ * Enable readahead if supported
+ */
+ if ((at->at_identify.cmdset82 & ATA_IDENTIFY_LOOKAHEAD) &&
+ (at->at_identify.features85 & ATA_IDENTIFY_LOOKAHEAD) == 0) {
+ xa = sili_ata_get_xfer(ap, atx);
+ xa->complete = sili_ata_dummy_done;
+ xa->fis->command = ATA_C_SET_FEATURES;
+ xa->fis->features = ATA_SF_LOOKAHEAD_EN;
+ xa->fis->flags = ATA_H2D_FLAGS_CMD | at->at_target;
+ xa->fis->device = 0;
+ xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL;
+ xa->timeout = 1000;
+ xa->datalen = 0;
+ if (sili_ata_cmd(xa) == ATA_S_COMPLETE)
+ at->at_features |= ATA_PORT_F_RAHEAD;
+ else
+ kprintf("%s: Unable to enable read-ahead\n",
+ ATANAME(ap, atx));
+ sili_ata_put_xfer(xa);
+ }
+
+ /*
+ * FREEZE LOCK the device so malicious users can't lock it on us.
+ * As there is no harm in issuing this to devices that don't
+ * support the security feature set we just send it, and don't bother
+ * checking if the device sends a command abort to tell us it doesn't
+ * support it
+ */
+ if ((at->at_identify.cmdset82 & ATA_IDENTIFY_SECURITY) &&
+ (at->at_identify.securestatus & ATA_SECURE_FROZEN) == 0 &&
+ (SiliNoFeatures & (1 << ap->ap_num)) == 0) {
+ xa = sili_ata_get_xfer(ap, atx);
+ xa->complete = sili_ata_dummy_done;
+ xa->fis->command = ATA_C_SEC_FREEZE_LOCK;
+ xa->fis->flags = ATA_H2D_FLAGS_CMD | at->at_target;
+ xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL;
+ xa->timeout = 1000;
+ xa->datalen = 0;
+ if (sili_ata_cmd(xa) == ATA_S_COMPLETE)
+ at->at_features |= ATA_PORT_F_FRZLCK;
+ else
+ kprintf("%s: Unable to set security freeze\n",
+ ATANAME(ap, atx));
+ sili_ata_put_xfer(xa);
+ }
+
+ return (0);
+}
+
+/*
+ * ATAPI-specific probe after initial ident
+ */
+static int
+sili_cam_probe_atapi(struct sili_port *ap, struct ata_port *atx)
+{
+ return(0);
+}
+
+/*
+ * Fix byte ordering so buffers can be accessed as
+ * strings.
+ */
+static void
+ata_fix_identify(struct ata_identify *id)
+{
+ u_int16_t *swap;
+ int i;
+
+ swap = (u_int16_t *)id->serial;
+ for (i = 0; i < sizeof(id->serial) / sizeof(u_int16_t); i++)
+ swap[i] = bswap16(swap[i]);
+
+ swap = (u_int16_t *)id->firmware;
+ for (i = 0; i < sizeof(id->firmware) / sizeof(u_int16_t); i++)
+ swap[i] = bswap16(swap[i]);
+
+ swap = (u_int16_t *)id->model;
+ for (i = 0; i < sizeof(id->model) / sizeof(u_int16_t); i++)
+ swap[i] = bswap16(swap[i]);
+}
+
+/*
+ * Dummy done callback for xa.
+ */
+static void
+sili_ata_dummy_done(struct ata_xfer *xa)
+{
+}
+
+/*
+ * Use an engineering request to initiate a target scan for devices
+ * behind a port multiplier.
+ *
+ * An asynchronous bus scan is used to avoid reentrancy issues.
+ */
+static void
+sili_cam_rescan_callback(struct cam_periph *periph, union ccb *ccb)
+{
+ struct sili_port *ap = ccb->ccb_h.sim_priv.entries[0].ptr;
+
+ if (ccb->ccb_h.func_code == XPT_SCAN_BUS) {
+ kprintf("%s: CAM RESCAN CALLBACK\n", PORTNAME(ap));
+ ap->ap_flags &= ~AP_F_SCAN_RUNNING;
+ if (ap->ap_flags & AP_F_SCAN_REQUESTED) {
+ ap->ap_flags &= ~AP_F_SCAN_REQUESTED;
+ sili_cam_rescan(ap);
+ }
+ ap->ap_flags |= AP_F_SCAN_COMPLETED;
+ wakeup(&ap->ap_flags);
+ }
+ xpt_free_ccb(ccb);
+}
+
+static void
+sili_cam_rescan(struct sili_port *ap)
+{
+ struct cam_path *path;
+ union ccb *ccb;
+ int status;
+ int i;
+
+ kprintf("%s: CAM RESCAN-A\n", PORTNAME(ap));
+ if (ap->ap_flags & AP_F_SCAN_RUNNING) {
+ ap->ap_flags |= AP_F_SCAN_REQUESTED;
+ return;
+ }
+ ap->ap_flags |= AP_F_SCAN_RUNNING;
+ for (i = 0; i < SILI_MAX_PMPORTS; ++i) {
+ ap->ap_ata[i].at_features |= ATA_PORT_F_RESCAN;
+ }
+
+ status = xpt_create_path(&path, xpt_periph, cam_sim_path(ap->ap_sim),
+ CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD);
+ if (status != CAM_REQ_CMP)
+ return;
+
+ ccb = xpt_alloc_ccb();
+ xpt_setup_ccb(&ccb->ccb_h, path, 5); /* 5 = low priority */
+ ccb->ccb_h.func_code = XPT_ENG_EXEC;
+ ccb->ccb_h.cbfcnp = sili_cam_rescan_callback;
+ ccb->ccb_h.sim_priv.entries[0].ptr = ap;
+ ccb->crcn.flags = CAM_FLAG_NONE;
+ xpt_action_async(ccb);
+}
+
+static void
+sili_xpt_rescan(struct sili_port *ap)
+{
+ struct cam_path *path;
+ union ccb *ccb;
+ int status;
+
+ kprintf("%s: CAM RESCAN-B\n", PORTNAME(ap));
+ status = xpt_create_path(&path, xpt_periph, cam_sim_path(ap->ap_sim),
+ CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD);
+ if (status != CAM_REQ_CMP)
+ return;
+
+ ccb = xpt_alloc_ccb();
+ xpt_setup_ccb(&ccb->ccb_h, path, 5); /* 5 = low priority */
+ ccb->ccb_h.func_code = XPT_SCAN_BUS;
+ ccb->ccb_h.cbfcnp = sili_cam_rescan_callback;
+ ccb->ccb_h.sim_priv.entries[0].ptr = ap;
+ ccb->crcn.flags = CAM_FLAG_NONE;
+ xpt_action_async(ccb);
+}
+
+/*
+ * Action function - dispatch command
+ */
+static
+void
+sili_xpt_action(struct cam_sim *sim, union ccb *ccb)
+{
+ struct sili_port *ap;
+ struct ata_port *at, *atx;
+ struct ccb_hdr *ccbh;
+ int unit;
+
+ /* XXX lock */
+ ap = cam_sim_softc(sim);
+ at = ap->ap_ata;
+ atx = NULL;
+ KKASSERT(ap != NULL);
+ ccbh = &ccb->ccb_h;
+ unit = cam_sim_unit(sim);
+
+ /*
+ * Early failure checks. These checks do not apply to XPT_PATH_INQ,
+ * otherwise the bus rescan will not remove the dead devices when
+ * unplugging a PM.
+ *
+ * For non-wildcards we have one target (0) and one lun (0),
+ * unless we have a port multiplier.
+ *
+ * A wildcard target indicates only the general bus is being
+ * probed.
+ *
+ * Calculate at and atx. at is always non-NULL. atx is only
+ * non-NULL for direct-attached devices. It will be NULL for
+ * devices behind a port multiplier.
+ *
+ * XXX What do we do with a LUN wildcard?
+ */
+ if (ccbh->target_id != CAM_TARGET_WILDCARD &&
+ ccbh->func_code != XPT_PATH_INQ) {
+ if (ap->ap_type == ATA_PORT_T_NONE) {
+ ccbh->status = CAM_DEV_NOT_THERE;
+ xpt_done(ccb);
+ return;
+ }
+ if (ccbh->target_id < 0 || ccbh->target_id >= ap->ap_pmcount) {
+ ccbh->status = CAM_DEV_NOT_THERE;
+ xpt_done(ccb);
+ return;
+ }
+ at += ccbh->target_id;
+ if (ap->ap_type == ATA_PORT_T_PM)
+ atx = at;
+
+ if (ccbh->target_lun != CAM_LUN_WILDCARD && ccbh->target_lun) {
+ ccbh->status = CAM_DEV_NOT_THERE;
+ xpt_done(ccb);
+ return;
+ }
+ }
+
+ /*
+ * Switch on the meta XPT command
+ */
+ switch(ccbh->func_code) {
+ case XPT_ENG_EXEC:
+ /*
+ * This routine is called after a port multiplier has been
+ * probed.
+ */
+ ccbh->status = CAM_REQ_CMP;
+ sili_os_lock_port(ap);
+ sili_port_state_machine(ap, 0);
+ sili_os_unlock_port(ap);
+ xpt_done(ccb);
+ sili_xpt_rescan(ap);
+ break;
+ case XPT_PATH_INQ:
+ /*
+ * This command always succeeds, otherwise the bus scan
+ * will not detach dead devices.
+ */
+ ccb->cpi.version_num = 1;
+ ccb->cpi.hba_inquiry = 0;
+ ccb->cpi.target_sprt = 0;
+ ccb->cpi.hba_misc = PIM_SEQSCAN;
+ ccb->cpi.hba_eng_cnt = 0;
+ bzero(ccb->cpi.vuhba_flags, sizeof(ccb->cpi.vuhba_flags));
+ ccb->cpi.max_target = SILI_MAX_PMPORTS - 1;
+ ccb->cpi.max_lun = 0;
+ ccb->cpi.async_flags = 0;
+ ccb->cpi.hpath_id = 0;
+ ccb->cpi.initiator_id = SILI_MAX_PMPORTS - 1;
+ ccb->cpi.unit_number = cam_sim_unit(sim);
+ ccb->cpi.bus_id = cam_sim_bus(sim);
+ ccb->cpi.base_transfer_speed = 150000;
+ ccb->cpi.transport = XPORT_SATA;
+ ccb->cpi.transport_version = 1;
+ ccb->cpi.protocol = PROTO_SCSI;
+ ccb->cpi.protocol_version = SCSI_REV_2;
+
+ ccbh->status = CAM_REQ_CMP;
+ if (ccbh->target_id == CAM_TARGET_WILDCARD) {
+ sili_os_lock_port(ap);
+ sili_port_state_machine(ap, 0);
+ sili_os_unlock_port(ap);
+ } else {
+ switch(sili_pread(ap, SILI_PREG_SSTS) &
+ SILI_PREG_SSTS_SPD) {
+ case SILI_PREG_SSTS_SPD_GEN1:
+ ccb->cpi.base_transfer_speed = 150000;
+ break;
+ case SILI_PREG_SSTS_SPD_GEN2:
+ ccb->cpi.base_transfer_speed = 300000;
+ break;
+ default:
+ /* unknown */
+ ccb->cpi.base_transfer_speed = 1000;
+ break;
+ }
+#if 0
+ if (ap->ap_type == ATA_PORT_T_NONE)
+ ccbh->status = CAM_DEV_NOT_THERE;
+#endif
+ }
+ xpt_done(ccb);
+ break;
+ case XPT_RESET_DEV:
+ sili_os_lock_port(ap);
+ if (ap->ap_type == ATA_PORT_T_NONE) {
+ ccbh->status = CAM_DEV_NOT_THERE;
+ } else {
+ sili_port_reset(ap, atx, 0);
+ ccbh->status = CAM_REQ_CMP;
+ }
+ sili_os_unlock_port(ap);
+ xpt_done(ccb);
+ break;
+ case XPT_RESET_BUS:
+ sili_os_lock_port(ap);
+ sili_port_reset(ap, NULL, 1);
+ sili_os_unlock_port(ap);
+ ccbh->status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ break;
+ case XPT_SET_TRAN_SETTINGS:
+ ccbh->status = CAM_FUNC_NOTAVAIL;
+ xpt_done(ccb);
+ break;
+ case XPT_GET_TRAN_SETTINGS:
+ ccb->cts.protocol = PROTO_SCSI;
+ ccb->cts.protocol_version = SCSI_REV_2;
+ ccb->cts.transport = XPORT_SATA;
+ ccb->cts.transport_version = XPORT_VERSION_UNSPECIFIED;
+ ccb->cts.proto_specific.valid = 0;
+ ccb->cts.xport_specific.valid = 0;
+ ccbh->status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ break;
+ case XPT_CALC_GEOMETRY:
+ cam_calc_geometry(&ccb->ccg, 1);
+ xpt_done(ccb);
+ break;
+ case XPT_SCSI_IO:
+ /*
+ * Our parallel startup code might have only probed through
+ * to the IDENT, so do the last step if necessary.
+ */
+ if (at->at_probe == ATA_PROBE_NEED_IDENT)
+ sili_cam_probe(ap, atx);
+ if (at->at_probe != ATA_PROBE_GOOD) {
+ ccbh->status = CAM_DEV_NOT_THERE;
+ xpt_done(ccb);
+ break;
+ }
+ switch(at->at_type) {
+ case ATA_PORT_T_DISK:
+ sili_xpt_scsi_disk_io(ap, atx, ccb);
+ break;
+ case ATA_PORT_T_ATAPI:
+ sili_xpt_scsi_atapi_io(ap, atx, ccb);
+ break;
+ default:
+ ccbh->status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ break;
+ }
+ break;
+ default:
+ ccbh->status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ break;
+ }
+}
+
+/*
+ * Poll function.
+ *
+ * Generally this function gets called heavily when interrupts might be
+ * non-operational, during a halt/reboot or panic.
+ */
+static
+void
+sili_xpt_poll(struct cam_sim *sim)
+{
+ struct sili_port *ap;
+
+ ap = cam_sim_softc(sim);
+ crit_enter();
+ sili_os_lock_port(ap);
+ sili_port_intr(ap, 1);
+ sili_os_unlock_port(ap);
+ crit_exit();
+}
+
+/*
+ * Convert the SCSI command in ccb to an ata_xfer command in xa
+ * for ATA_PORT_T_DISK operations. Set the completion function
+ * to convert the response back, then dispatch to the OpenBSD SILI
+ * layer.
+ *
+ * SILI DISK commands only support a limited command set, and we
+ * fake additional commands to make it play nice with the CAM subsystem.
+ */
+static
+void
+sili_xpt_scsi_disk_io(struct sili_port *ap, struct ata_port *atx,
+ union ccb *ccb)
+{
+ struct ccb_hdr *ccbh;
+ struct ccb_scsiio *csio;
+ struct ata_xfer *xa;
+ struct ata_port *at;
+ struct ata_fis_h2d *fis;
+ scsi_cdb_t cdb;
+ union scsi_data *rdata;
+ int rdata_len;
+ u_int64_t capacity;
+ u_int64_t lba;
+ u_int32_t count;
+
+ ccbh = &ccb->csio.ccb_h;
+ csio = &ccb->csio;
+ at = atx ? atx : &ap->ap_ata[0];
+
+ /*
+ * XXX not passing NULL at for direct attach!
+ */
+ xa = sili_ata_get_xfer(ap, atx);
+ rdata = (void *)csio->data_ptr;
+ rdata_len = csio->dxfer_len;
+
+ /*
+ * Build the FIS or process the csio to completion.
+ */
+ cdb = (void *)((ccbh->flags & CAM_CDB_POINTER) ?
+ csio->cdb_io.cdb_ptr : csio->cdb_io.cdb_bytes);
+
+ switch(cdb->generic.opcode) {
+ case REQUEST_SENSE:
+ /*
+ * Auto-sense everything, so explicit sense requests
+ * return no-sense.
+ */
+ ccbh->status = CAM_SCSI_STATUS_ERROR;
+ break;
+ case INQUIRY:
+ /*
+ * Inquiry supported features
+ *
+ * [opcode, byte2, page_code, length, control]
+ */
+ if (cdb->inquiry.byte2 & SI_EVPD) {
+ switch(cdb->inquiry.page_code) {
+ case SVPD_SUPPORTED_PAGE_LIST:
+ /* XXX atascsi_disk_vpd_supported */
+ case SVPD_UNIT_SERIAL_NUMBER:
+ /* XXX atascsi_disk_vpd_serial */
+ case SVPD_UNIT_DEVID:
+ /* XXX atascsi_disk_vpd_ident */
+ default:
+ ccbh->status = CAM_FUNC_NOTAVAIL;
+ break;
+ }
+ } else {
+ bzero(rdata, rdata_len);
+ if (rdata_len < SHORT_INQUIRY_LENGTH) {
+ ccbh->status = CAM_CCB_LEN_ERR;
+ break;
+ }
+ if (rdata_len > sizeof(rdata->inquiry_data))
+ rdata_len = sizeof(rdata->inquiry_data);
+ rdata->inquiry_data.device = T_DIRECT;
+ rdata->inquiry_data.version = SCSI_REV_SPC2;
+ rdata->inquiry_data.response_format = 2;
+ rdata->inquiry_data.additional_length = 32;
+ bcopy("SATA ", rdata->inquiry_data.vendor, 8);
+ bcopy(at->at_identify.model,
+ rdata->inquiry_data.product,
+ sizeof(rdata->inquiry_data.product));
+ bcopy(at->at_identify.firmware,
+ rdata->inquiry_data.revision,
+ sizeof(rdata->inquiry_data.revision));
+ ccbh->status = CAM_REQ_CMP;
+ }
+ break;
+ case READ_CAPACITY_16:
+ if (cdb->read_capacity_16.service_action != SRC16_SERVICE_ACTION) {
+ ccbh->status = CAM_REQ_INVALID;
+ break;
+ }
+ if (rdata_len < sizeof(rdata->read_capacity_data_16)) {
+ ccbh->status = CAM_CCB_LEN_ERR;
+ break;
+ }
+ /* fall through */
+ case READ_CAPACITY:
+ if (rdata_len < sizeof(rdata->read_capacity_data)) {
+ ccbh->status = CAM_CCB_LEN_ERR;
+ break;
+ }
+
+ capacity = at->at_capacity;
+
+ bzero(rdata, rdata_len);
+ if (cdb->generic.opcode == READ_CAPACITY) {
+ rdata_len = sizeof(rdata->read_capacity_data);
+ if (capacity > 0xFFFFFFFFU)
+ capacity = 0xFFFFFFFFU;
+ bzero(&rdata->read_capacity_data, rdata_len);
+ scsi_ulto4b((u_int32_t)capacity - 1,
+ rdata->read_capacity_data.addr);
+ scsi_ulto4b(512, rdata->read_capacity_data.length);
+ } else {
+ rdata_len = sizeof(rdata->read_capacity_data_16);
+ bzero(&rdata->read_capacity_data_16, rdata_len);
+ scsi_u64to8b(capacity - 1,
+ rdata->read_capacity_data_16.addr);
+ scsi_ulto4b(512, rdata->read_capacity_data_16.length);
+ }
+ ccbh->status = CAM_REQ_CMP;
+ break;
+ case SYNCHRONIZE_CACHE:
+ /*
+ * Synchronize cache. Specification says this can take
+ * greater then 30 seconds so give it at least 45.
+ */
+ fis = xa->fis;
+ fis->flags = ATA_H2D_FLAGS_CMD;
+ fis->command = ATA_C_FLUSH_CACHE;
+ fis->device = 0;
+ if (xa->timeout < 45000)
+ xa->timeout = 45000;
+ xa->datalen = 0;
+ xa->flags = ATA_F_READ;
+ xa->complete = sili_ata_complete_disk_synchronize_cache;
+ break;
+ case TEST_UNIT_READY:
+ case START_STOP_UNIT:
+ case PREVENT_ALLOW:
+ /*
+ * Just silently return success
+ */
+ ccbh->status = CAM_REQ_CMP;
+ rdata_len = 0;
+ break;
+ case ATA_PASS_12:
+ case ATA_PASS_16:
+ /*
+ * XXX implement pass-through
+ */
+ ccbh->status = CAM_FUNC_NOTAVAIL;
+ break;
+ default:
+ switch(cdb->generic.opcode) {
+ case READ_6:
+ lba = scsi_3btoul(cdb->rw_6.addr) & 0x1FFFFF;
+ count = cdb->rw_6.length ? cdb->rw_6.length : 0x100;
+ xa->flags = ATA_F_READ;
+ break;
+ case READ_10:
+ lba = scsi_4btoul(cdb->rw_10.addr);
+ count = scsi_2btoul(cdb->rw_10.length);
+ xa->flags = ATA_F_READ;
+ break;
+ case READ_12:
+ lba = scsi_4btoul(cdb->rw_12.addr);
+ count = scsi_4btoul(cdb->rw_12.length);
+ xa->flags = ATA_F_READ;
+ break;
+ case READ_16:
+ lba = scsi_8btou64(cdb->rw_16.addr);
+ count = scsi_4btoul(cdb->rw_16.length);
+ xa->flags = ATA_F_READ;
+ break;
+ case WRITE_6:
+ lba = scsi_3btoul(cdb->rw_6.addr) & 0x1FFFFF;
+ count = cdb->rw_6.length ? cdb->rw_6.length : 0x100;
+ xa->flags = ATA_F_WRITE;
+ break;
+ case WRITE_10:
+ lba = scsi_4btoul(cdb->rw_10.addr);
+ count = scsi_2btoul(cdb->rw_10.length);
+ xa->flags = ATA_F_WRITE;
+ break;
+ case WRITE_12:
+ lba = scsi_4btoul(cdb->rw_12.addr);
+ count = scsi_4btoul(cdb->rw_12.length);
+ xa->flags = ATA_F_WRITE;
+ break;
+ case WRITE_16:
+ lba = scsi_8btou64(cdb->rw_16.addr);
+ count = scsi_4btoul(cdb->rw_16.length);
+ xa->flags = ATA_F_WRITE;
+ break;
+ default:
+ ccbh->status = CAM_REQ_INVALID;
+ break;
+ }
+ if (ccbh->status != CAM_REQ_INPROG)
+ break;
+
+ fis = xa->fis;
+ fis->flags = ATA_H2D_FLAGS_CMD;
+ fis->lba_low = (u_int8_t)lba;
+ fis->lba_mid = (u_int8_t)(lba >> 8);
+ fis->lba_high = (u_int8_t)(lba >> 16);
+ fis->device = ATA_H2D_DEVICE_LBA;
+
+ /*
+ * NCQ only for direct-attached disks, do not currently
+ * try to use NCQ with port multipliers.
+ *
+ * XXX fixme SII chip can do NCQ w/ port multipliers.
+ */
+ if (at->at_ncqdepth > 1 &&
+ at->at_type == ATA_PORT_T_DISK &&
+ (ap->ap_sc->sc_flags & SILI_F_NCQ) &&
+ (ccbh->flags & CAM_POLLED) == 0) {
+ /*
+ * Use NCQ - always uses 48 bit addressing
+ */
+ xa->flags |= ATA_F_NCQ;
+ fis->command = (xa->flags & ATA_F_WRITE) ?
+ ATA_C_WRITE_FPDMA : ATA_C_READ_FPDMA;
+ fis->lba_low_exp = (u_int8_t)(lba >> 24);
+ fis->lba_mid_exp = (u_int8_t)(lba >> 32);
+ fis->lba_high_exp = (u_int8_t)(lba >> 40);
+ fis->sector_count = xa->tag << 3;
+ fis->features = (u_int8_t)count;
+ fis->features_exp = (u_int8_t)(count >> 8);
+ } else if (count > 0x100 || lba > 0xFFFFFFFFU) {
+ /*
+ * Use LBA48
+ */
+ fis->command = (xa->flags & ATA_F_WRITE) ?
+ ATA_C_WRITEDMA_EXT : ATA_C_READDMA_EXT;
+ fis->lba_low_exp = (u_int8_t)(lba >> 24);
+ fis->lba_mid_exp = (u_int8_t)(lba >> 32);
+ fis->lba_high_exp = (u_int8_t)(lba >> 40);
+ fis->sector_count = (u_int8_t)count;
+ fis->sector_count_exp = (u_int8_t)(count >> 8);
+ } else {
+ /*
+ * Use LBA
+ *
+ * NOTE: 256 sectors is supported, stored as 0.
+ */
+ fis->command = (xa->flags & ATA_F_WRITE) ?
+ ATA_C_WRITEDMA : ATA_C_READDMA;
+ fis->device |= (u_int8_t)(lba >> 24) & 0x0F;
+ fis->sector_count = (u_int8_t)count;
+ }
+
+ xa->data = csio->data_ptr;
+ xa->datalen = csio->dxfer_len;
+ xa->complete = sili_ata_complete_disk_rw;
+ xa->timeout = ccbh->timeout; /* milliseconds */
+ if (ccbh->flags & CAM_POLLED)
+ xa->flags |= ATA_F_POLL;
+ break;
+ }
+
+ /*
+ * If the request is still in progress the xa and FIS have
+ * been set up and must be dispatched. Otherwise the request
+ * is complete.
+ */
+ if (ccbh->status == CAM_REQ_INPROG) {
+ KKASSERT(xa->complete != NULL);
+ xa->atascsi_private = ccb;
+ ccb->ccb_h.sim_priv.entries[0].ptr = ap;
+ sili_os_lock_port(ap);
+ fis->flags |= at->at_target;
+ sili_ata_cmd(xa);
+ sili_os_unlock_port(ap);
+ } else {
+ sili_ata_put_xfer(xa);
+ xpt_done(ccb);
+ }
+}
+
+/*
+ * Convert the SCSI command in ccb to an ata_xfer command in xa
+ * for ATA_PORT_T_ATAPI operations. Set the completion function
+ * to convert the response back, then dispatch to the OpenBSD SILI
+ * layer.
+ */
+static
+void
+sili_xpt_scsi_atapi_io(struct sili_port *ap, struct ata_port *atx,
+ union ccb *ccb)
+{
+ struct ccb_hdr *ccbh;
+ struct ccb_scsiio *csio;
+ struct ata_xfer *xa;
+ struct ata_fis_h2d *fis;
+ scsi_cdb_t cdbs;
+ scsi_cdb_t cdbd;
+ int flags;
+ struct ata_port *at;
+
+ ccbh = &ccb->csio.ccb_h;
+ csio = &ccb->csio;
+ at = atx ? atx : &ap->ap_ata[0];
+
+ switch (ccbh->flags & CAM_DIR_MASK) {
+ case CAM_DIR_IN:
+ flags = ATA_F_PACKET | ATA_F_READ;
+ break;
+ case CAM_DIR_OUT:
+ flags = ATA_F_PACKET | ATA_F_WRITE;
+ break;
+ case CAM_DIR_NONE:
+ flags = ATA_F_PACKET;
+ break;
+ default:
+ ccbh->status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ return;
+ /* NOT REACHED */
+ }
+
+ /*
+ * The command has to fit in the packet command buffer.
+ */
+ if (csio->cdb_len < 6 || csio->cdb_len > 16) {
+ ccbh->status = CAM_CCB_LEN_ERR;
+ xpt_done(ccb);
+ return;
+ }
+
+ /*
+ * Initialize the XA and FIS.
+ *
+ * XXX not passing NULL at for direct attach!
+ */
+ xa = sili_ata_get_xfer(ap, atx);
+ fis = xa->fis;
+
+ fis->flags = ATA_H2D_FLAGS_CMD | at->at_target;
+ fis->command = ATA_C_PACKET;
+ fis->device = 0;
+ fis->sector_count = xa->tag << 3;
+ fis->features = ATA_H2D_FEATURES_DMA |
+ ((flags & ATA_F_WRITE) ?
+ ATA_H2D_FEATURES_DIR_WRITE : ATA_H2D_FEATURES_DIR_READ);
+ fis->lba_mid = 0x00;
+ fis->lba_high = 0x20;
+
+ xa->flags = flags;
+ xa->data = csio->data_ptr;
+ xa->datalen = csio->dxfer_len;
+ xa->timeout = ccbh->timeout; /* milliseconds */
+
+ if (ccbh->flags & CAM_POLLED)
+ xa->flags |= ATA_F_POLL;
+
+ /*
+ * Copy the cdb to the packetcmd buffer in the FIS using a
+ * convenient pointer in the xa.
+ */
+ cdbs = (void *)((ccbh->flags & CAM_CDB_POINTER) ?
+ csio->cdb_io.cdb_ptr : csio->cdb_io.cdb_bytes);
+ bcopy(cdbs, xa->packetcmd, csio->cdb_len);
+
+#if 0
+ kprintf("opcode %d cdb_len %d dxfer_len %d\n",
+ cdbs->generic.opcode,
+ csio->cdb_len, csio->dxfer_len);
+#endif
+
+ /*
+ * Some ATAPI commands do not actually follow the SCSI standard.
+ */
+ cdbd = (void *)xa->packetcmd;
+
+ switch(cdbd->generic.opcode) {
+ case INQUIRY:
+ /*
+ * Some ATAPI devices can't handle SI_EVPD being set
+ * for a basic inquiry (page_code == 0).
+ *
+ * Some ATAPI devices can't handle long inquiry lengths,
+ * don't ask me why. Truncate the inquiry length.
+ */
+ if ((cdbd->inquiry.byte2 & SI_EVPD) &&
+ cdbd->inquiry.page_code == 0) {
+ cdbd->inquiry.byte2 &= ~SI_EVPD;
+ }
+ if (cdbd->inquiry.page_code == 0 &&
+ cdbd->inquiry.length > SHORT_INQUIRY_LENGTH) {
+ cdbd->inquiry.length = SHORT_INQUIRY_LENGTH;
+ }
+ break;
+ case READ_6:
+ case WRITE_6:
+ /*
+ * Convert *_6 to *_10 commands. Most ATAPI devices
+ * cannot handle the SCSI READ_6 and WRITE_6 commands.
+ */
+ cdbd->rw_10.opcode |= 0x20;
+ cdbd->rw_10.byte2 = 0;
+ cdbd->rw_10.addr[0] = cdbs->rw_6.addr[0] & 0x1F;
+ cdbd->rw_10.addr[1] = cdbs->rw_6.addr[1];
+ cdbd->rw_10.addr[2] = cdbs->rw_6.addr[2];
+ cdbd->rw_10.addr[3] = 0;
+ cdbd->rw_10.reserved = 0;
+ cdbd->rw_10.length[0] = 0;
+ cdbd->rw_10.length[1] = cdbs->rw_6.length;
+ cdbd->rw_10.control = cdbs->rw_6.control;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * And dispatch
+ */
+ xa->complete = sili_atapi_complete_cmd;
+ xa->atascsi_private = ccb;
+ ccb->ccb_h.sim_priv.entries[0].ptr = ap;
+ sili_os_lock_port(ap);
+ sili_ata_cmd(xa);
+ sili_os_unlock_port(ap);
+}
+
+/*
+ * Completion function for ATA_PORT_T_DISK cache synchronization.
+ */
+static
+void
+sili_ata_complete_disk_synchronize_cache(struct ata_xfer *xa)
+{
+ union ccb *ccb = xa->atascsi_private;
+ struct ccb_hdr *ccbh = &ccb->ccb_h;
+ struct sili_port *ap = ccb->ccb_h.sim_priv.entries[0].ptr;
+
+ switch(xa->state) {
+ case ATA_S_COMPLETE:
+ ccbh->status = CAM_REQ_CMP;
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ break;
+ case ATA_S_ERROR:
+ kprintf("%s: synchronize_cache: error\n",
+ ATANAME(ap, xa->at));
+ ccbh->status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID;
+ ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
+ sili_ata_dummy_sense(&ccb->csio.sense_data);
+ break;
+ case ATA_S_TIMEOUT:
+ kprintf("%s: synchronize_cache: timeout\n",
+ ATANAME(ap, xa->at));
+ ccbh->status = CAM_CMD_TIMEOUT;
+ break;
+ default:
+ kprintf("%s: synchronize_cache: unknown state %d\n",
+ ATANAME(ap, xa->at), xa->state);
+ ccbh->status = CAM_REQ_CMP_ERR;
+ break;
+ }
+ sili_ata_put_xfer(xa);
+ sili_os_unlock_port(ap);
+ xpt_done(ccb);
+ sili_os_lock_port(ap);
+}
+
+/*
+ * Completion function for ATA_PORT_T_DISK I/O
+ */
+static
+void
+sili_ata_complete_disk_rw(struct ata_xfer *xa)
+{
+ union ccb *ccb = xa->atascsi_private;
+ struct ccb_hdr *ccbh = &ccb->ccb_h;
+ struct sili_port *ap = ccb->ccb_h.sim_priv.entries[0].ptr;
+
+ switch(xa->state) {
+ case ATA_S_COMPLETE:
+ ccbh->status = CAM_REQ_CMP;
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ break;
+ case ATA_S_ERROR:
+ kprintf("%s: disk_rw: error\n", ATANAME(ap, xa->at));
+ ccbh->status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID;
+ ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
+ sili_ata_dummy_sense(&ccb->csio.sense_data);
+ break;
+ case ATA_S_TIMEOUT:
+ kprintf("%s: disk_rw: timeout\n", ATANAME(ap, xa->at));
+ ccbh->status = CAM_CMD_TIMEOUT;
+ ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
+ sili_ata_dummy_sense(&ccb->csio.sense_data);
+ break;
+ default:
+ kprintf("%s: disk_rw: unknown state %d\n",
+ ATANAME(ap, xa->at), xa->state);
+ ccbh->status = CAM_REQ_CMP_ERR;
+ break;
+ }
+ ccb->csio.resid = xa->resid;
+ sili_ata_put_xfer(xa);
+ sili_os_unlock_port(ap);
+ xpt_done(ccb);
+ sili_os_lock_port(ap);
+}
+
+/*
+ * Completion function for ATA_PORT_T_ATAPI I/O
+ *
+ * Sense data is returned in the rfis.
+ */
+static
+void
+sili_atapi_complete_cmd(struct ata_xfer *xa)
+{
+ union ccb *ccb = xa->atascsi_private;
+ struct ccb_hdr *ccbh = &ccb->ccb_h;
+ struct sili_port *ap = ccb->ccb_h.sim_priv.entries[0].ptr;
+ scsi_cdb_t cdb;
+
+ cdb = (void *)((ccb->ccb_h.flags & CAM_CDB_POINTER) ?
+ ccb->csio.cdb_io.cdb_ptr : ccb->csio.cdb_io.cdb_bytes);
+
+ switch(xa->state) {
+ case ATA_S_COMPLETE:
+ ccbh->status = CAM_REQ_CMP;
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ break;
+ case ATA_S_ERROR:
+ ccbh->status = CAM_SCSI_STATUS_ERROR;
+ ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
+ sili_ata_atapi_sense(xa->rfis, &ccb->csio.sense_data);
+ break;
+ case ATA_S_TIMEOUT:
+ kprintf("%s: cmd %d: timeout\n",
+ PORTNAME(ap), cdb->generic.opcode);
+ ccbh->status = CAM_CMD_TIMEOUT;
+ ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
+ sili_ata_dummy_sense(&ccb->csio.sense_data);
+ break;
+ default:
+ kprintf("%s: cmd %d: unknown state %d\n",
+ PORTNAME(ap), cdb->generic.opcode, xa->state);
+ ccbh->status = CAM_REQ_CMP_ERR;
+ break;
+ }
+ ccb->csio.resid = xa->resid;
+ sili_ata_put_xfer(xa);
+ sili_os_unlock_port(ap);
+ xpt_done(ccb);
+ sili_os_lock_port(ap);
+}
+
+/*
+ * Construct dummy sense data for errors on DISKs
+ */
+static
+void
+sili_ata_dummy_sense(struct scsi_sense_data *sense_data)
+{
+ sense_data->error_code = SSD_ERRCODE_VALID | SSD_CURRENT_ERROR;
+ sense_data->segment = 0;
+ sense_data->flags = SSD_KEY_MEDIUM_ERROR;
+ sense_data->info[0] = 0;
+ sense_data->info[1] = 0;
+ sense_data->info[2] = 0;
+ sense_data->info[3] = 0;
+ sense_data->extra_len = 0;
+}
+
+/*
+ * Construct atapi sense data for errors on ATAPI
+ *
+ * The ATAPI sense data is stored in the passed rfis and must be converted
+ * to SCSI sense data.
+ */
+static
+void
+sili_ata_atapi_sense(struct ata_fis_d2h *rfis,
+ struct scsi_sense_data *sense_data)
+{
+ sense_data->error_code = SSD_ERRCODE_VALID | SSD_CURRENT_ERROR;
+ sense_data->segment = 0;
+ sense_data->flags = (rfis->error & 0xF0) >> 4;
+ if (rfis->error & 0x04)
+ sense_data->flags |= SSD_KEY_ILLEGAL_REQUEST;
+ if (rfis->error & 0x02)
+ sense_data->flags |= SSD_EOM;
+ if (rfis->error & 0x01)
+ sense_data->flags |= SSD_ILI;
+ sense_data->info[0] = 0;
+ sense_data->info[1] = 0;
+ sense_data->info[2] = 0;
+ sense_data->info[3] = 0;
+ sense_data->extra_len = 0;
+}
--- /dev/null
+/*
+ * Copyright (c) 2009 The DragonFly Project. All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ *
+ * 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. Neither the name of The DragonFly Project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific, prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Primary device and CAM interface to OpenBSD SILI driver, for DragonFly
+ */
+
+#include "sili.h"
+
+u_int32_t SiliForceGen1 = 0;
+u_int32_t SiliNoFeatures = 0;
+
+/*
+ * Device bus methods
+ */
+
+static int sili_probe (device_t dev);
+static int sili_attach (device_t dev);
+static int sili_detach (device_t dev);
+#if 0
+static int sili_shutdown (device_t dev);
+static int sili_suspend (device_t dev);
+static int sili_resume (device_t dev);
+#endif
+
+static void sili_port_thread(void *arg);
+
+static device_method_t sili_methods[] = {
+ DEVMETHOD(device_probe, sili_probe),
+ DEVMETHOD(device_attach, sili_attach),
+ DEVMETHOD(device_detach, sili_detach),
+#if 0
+ DEVMETHOD(device_shutdown, sili_shutdown),
+ DEVMETHOD(device_suspend, sili_suspend),
+ DEVMETHOD(device_resume, sili_resume),
+#endif
+
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+ DEVMETHOD(bus_driver_added, bus_generic_driver_added),
+ {0, 0}
+};
+
+static devclass_t sili_devclass;
+
+static driver_t sili_driver = {
+ "sili",
+ sili_methods,
+ sizeof(struct sili_softc)
+};
+
+MODULE_DEPEND(sili, cam, 1, 1, 1);
+DRIVER_MODULE(sili, pci, sili_driver, sili_devclass, 0, 0);
+
+/*
+ * Device bus method procedures
+ */
+static int
+sili_probe (device_t dev)
+{
+ const struct sili_device *ad;
+
+ if (kgetenv("hint.sili.disabled"))
+ return(ENXIO);
+ if (kgetenv("hint.sili.force150"))
+ SiliForceGen1 = -1;
+ if (kgetenv("hint.sili.nofeatures"))
+ SiliNoFeatures = -1;
+
+ ad = sili_lookup_device(dev);
+ if (ad) {
+ device_set_desc(dev, ad->name);
+ return(-5); /* higher priority the NATA */
+ }
+ return(ENXIO);
+}
+
+static int
+sili_attach (device_t dev)
+{
+ struct sili_softc *sc = device_get_softc(dev);
+ int error;
+
+ sc->sc_ad = sili_lookup_device(dev);
+ if (sc->sc_ad == NULL)
+ return(ENXIO);
+ error = sc->sc_ad->ad_attach(dev);
+ return (error);
+}
+
+static int
+sili_detach (device_t dev)
+{
+ struct sili_softc *sc = device_get_softc(dev);
+ int error = 0;
+
+ if (sc->sc_ad) {
+ error = sc->sc_ad->ad_detach(dev);
+ sc->sc_ad = NULL;
+ }
+ return(error);
+}
+
+#if 0
+
+static int
+sili_shutdown (device_t dev)
+{
+ return (0);
+}
+
+static int
+sili_suspend (device_t dev)
+{
+ return (0);
+}
+
+static int
+sili_resume (device_t dev)
+{
+ return (0);
+}
+
+#endif
+
+/*
+ * Sleep (ms) milliseconds, error on the side of caution.
+ */
+void
+sili_os_sleep(int ms)
+{
+ int ticks;
+
+ ticks = hz * ms / 1000 + 1;
+ tsleep(&ticks, 0, "ahslp", ticks);
+}
+
+/*
+ * Sleep for a minimum interval and return the number of milliseconds
+ * that was. The minimum value returned is 1
+ */
+int
+sili_os_softsleep(void)
+{
+ if (hz >= 1000) {
+ tsleep(&ticks, 0, "ahslp", hz / 1000);
+ return(1);
+ } else {
+ tsleep(&ticks, 0, "ahslp", 1);
+ return(1000 / hz);
+ }
+}
+
+void
+sili_os_hardsleep(int us)
+{
+ DELAY(us);
+}
+
+/*
+ * Create the OS-specific port helper thread and per-port lock.
+ */
+void
+sili_os_start_port(struct sili_port *ap)
+{
+ atomic_set_int(&ap->ap_signal, AP_SIGF_INIT);
+ lockinit(&ap->ap_lock, "silipo", 0, 0);
+ kthread_create(sili_port_thread, ap, &ap->ap_thread,
+ "%s", PORTNAME(ap));
+}
+
+/*
+ * Stop the OS-specific port helper thread and kill the per-port lock.
+ */
+void
+sili_os_stop_port(struct sili_port *ap)
+{
+ if (ap->ap_thread) {
+ sili_os_signal_port_thread(ap, AP_SIGF_STOP);
+ sili_os_sleep(10);
+ if (ap->ap_thread) {
+ kprintf("%s: Waiting for thread to terminate\n",
+ PORTNAME(ap));
+ while (ap->ap_thread)
+ sili_os_sleep(100);
+ kprintf("%s: thread terminated\n",
+ PORTNAME(ap));
+ }
+ }
+ lockuninit(&ap->ap_lock);
+}
+
+/*
+ * Add (mask) to the set of bits being sent to the per-port thread helper
+ * and wake the helper up if necessary.
+ */
+void
+sili_os_signal_port_thread(struct sili_port *ap, int mask)
+{
+ atomic_set_int(&ap->ap_signal, mask);
+ wakeup(&ap->ap_thread);
+}
+
+/*
+ * Unconditionally lock the port structure for access.
+ */
+void
+sili_os_lock_port(struct sili_port *ap)
+{
+ lockmgr(&ap->ap_lock, LK_EXCLUSIVE);
+}
+
+/*
+ * Conditionally lock the port structure for access.
+ *
+ * Returns 0 on success, non-zero on failure.
+ */
+int
+sili_os_lock_port_nb(struct sili_port *ap)
+{
+ return (lockmgr(&ap->ap_lock, LK_EXCLUSIVE | LK_NOWAIT));
+}
+
+/*
+ * Unlock a previously locked port.
+ */
+void
+sili_os_unlock_port(struct sili_port *ap)
+{
+ lockmgr(&ap->ap_lock, LK_RELEASE);
+}
+
+/*
+ * Per-port thread helper. This helper thread is responsible for
+ * atomically retrieving and clearing the signal mask and calling
+ * the machine-independant driver core.
+ */
+static
+void
+sili_port_thread(void *arg)
+{
+ struct sili_port *ap = arg;
+ int mask;
+
+ /*
+ * The helper thread is responsible for the initial port init,
+ * so all the ports can be inited in parallel.
+ *
+ * We also run the state machine which should do all probes.
+ * Since CAM is not attached yet we will not get out-of-order
+ * SCSI attachments.
+ */
+ sili_os_lock_port(ap);
+ sili_port_init(ap, NULL);
+ sili_port_state_machine(ap, 1);
+ sili_os_unlock_port(ap);
+ atomic_clear_int(&ap->ap_signal, AP_SIGF_INIT);
+ wakeup(&ap->ap_signal);
+
+ /*
+ * Then loop on the helper core.
+ */
+ mask = ap->ap_signal;
+ while ((mask & AP_SIGF_STOP) == 0) {
+ atomic_clear_int(&ap->ap_signal, mask);
+ sili_port_thread_core(ap, mask);
+ crit_enter();
+ tsleep_interlock(&ap->ap_thread);
+ if (ap->ap_signal == 0)
+ tsleep(&ap->ap_thread, 0, "ahport", 0);
+ crit_exit();
+ mask = ap->ap_signal;
+ }
+ ap->ap_thread = NULL;
+}
--- /dev/null
+/*
+ * Copyright (c) 2009 The DragonFly Project. All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ *
+ * 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. Neither the name of The DragonFly Project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific, prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/disk.h>
+#include <sys/stat.h>
+#include <sys/devicestat.h>
+#include <sys/buf.h>
+#include <sys/proc.h>
+#include <sys/queue.h>
+#include <sys/rman.h>
+#include <sys/endian.h>
+#include <sys/sysctl.h>
+#include <sys/kthread.h>
+
+#include <bus/cam/cam.h>
+#include <bus/cam/cam_ccb.h>
+#include <bus/cam/cam_periph.h>
+#include <bus/cam/cam_sim.h>
+#include <bus/cam/cam_xpt_sim.h>
+#include <bus/cam/cam_xpt_periph.h>
+#include <bus/cam/cam_debug.h>
+#include <bus/cam/scsi/scsi_all.h>
+
+#include <machine/clock.h>
+
+#include <vm/vm.h>
+
+#include <bus/pci/pcireg.h>
+#include <bus/pci/pcivar.h>
+#include <bus/pci/pcidevs.h>
+
+#include <sys/thread2.h>
+
+#define SILI_CDEV_MAJOR 188
+
+#define SILI_IRQ_RID 0
--- /dev/null
+/*
+ * Copyright (c) 2009 The DragonFly Project. All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ *
+ * 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. Neither the name of The DragonFly Project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific, prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sili.h"
+
+static void sili_pm_dummy_done(struct ata_xfer *xa);
+static void sili_pm_empty_done(struct sili_ccb *ccb);
+
+/*
+ * Identify the port multiplier
+ */
+int
+sili_pm_identify(struct sili_port *ap)
+{
+ u_int32_t chipid;
+ u_int32_t rev;
+ u_int32_t nports;
+ u_int32_t data1;
+ u_int32_t data2;
+
+ ap->ap_pmcount = 0;
+ ap->ap_probe = ATA_PROBE_FAILED;
+ if (sili_pm_read(ap, 15, 0, &chipid))
+ goto err;
+ if (sili_pm_read(ap, 15, 1, &rev))
+ goto err;
+ if (sili_pm_read(ap, 15, 2, &nports))
+ goto err;
+ nports &= 0x0000000F; /* only the low 4 bits */
+ --nports;
+ ap->ap_probe = ATA_PROBE_GOOD;
+ kprintf("%s: Port multiplier: chip=%08x rev=0x%b nports=%d\n",
+ PORTNAME(ap),
+ chipid,
+ rev, SATA_PFMT_PM_REV,
+ nports);
+ ap->ap_pmcount = nports;
+
+ if (sili_pm_read(ap, 15, SATA_PMREG_FEA, &data1)) {
+ kprintf("%s: Port multiplier: Warning, "
+ "cannot read feature register\n", PORTNAME(ap));
+ } else {
+ kprintf("%s: Port multiplier features: 0x%b\n",
+ PORTNAME(ap),
+ data1,
+ SATA_PFMT_PM_FEA);
+ }
+ if (sili_pm_read(ap, 15, SATA_PMREG_FEAEN, &data2) == 0) {
+ kprintf("%s: Port multiplier defaults: 0x%b\n",
+ PORTNAME(ap),
+ data2,
+ SATA_PFMT_PM_FEA);
+ }
+
+ /*
+ * Turn on async notification if we support and the PM supports it.
+ * This allows the PM to forward async notification events to us and
+ * it will also generate an event for target 15 for hot-plug events
+ * (or is supposed to anyway).
+ */
+ if ((ap->ap_sc->sc_flags & SILI_F_SSNTF) &&
+ (data1 & SATA_PMFEA_ASYNCNOTIFY)) {
+ u_int32_t serr_bits = SATA_PM_SERR_DIAG_N |
+ SATA_PM_SERR_DIAG_X;
+ data2 |= SATA_PMFEA_ASYNCNOTIFY;
+ if (sili_pm_write(ap, 15, SATA_PMREG_FEAEN, data2)) {
+ kprintf("%s: Port multiplier: AsyncNotify cannot be "
+ "enabled\n", PORTNAME(ap));
+ } else if (sili_pm_write(ap, 15, SATA_PMREG_EEENA, serr_bits)) {
+ kprintf("%s: Port mulltiplier: AsyncNotify unable "
+ "to enable error info bits\n", PORTNAME(ap));
+ } else {
+ kprintf("%s: Port multiplier: AsyncNotify enabled\n",
+ PORTNAME(ap));
+ }
+ }
+
+ return (0);
+err:
+ kprintf("%s: Port multiplier cannot be identified\n", PORTNAME(ap));
+ return (EIO);
+}
+
+/*
+ * Do a COMRESET sequence on the target behind a port multiplier.
+ *
+ * If hard is 2 we also cycle the phy on the target.
+ *
+ * This must be done prior to any softreset or probe attempts on
+ * targets behind the port multiplier.
+ *
+ * Returns 0 on success or an error.
+ */
+int
+sili_pm_hardreset(struct sili_port *ap, int target, int hard)
+{
+ struct ata_port *at;
+ u_int32_t data;
+ int loop;
+ int error = EIO;
+
+ at = &ap->ap_ata[target];
+
+ /*
+ * Turn off power management and kill the phy on the target
+ * if requested. Hold state for 10ms.
+ */
+ data = SATA_PM_SCTL_IPM_DISABLED;
+ if (hard == 2)
+ data |= SATA_PM_SCTL_DET_DISABLE;
+ if (sili_pm_write(ap, target, SATA_PMREG_SERR, -1))
+ goto err;
+ if (sili_pm_write(ap, target, SATA_PMREG_SCTL, data))
+ goto err;
+ sili_os_sleep(10);
+
+ /*
+ * Start transmitting COMRESET. COMRESET must be sent for at
+ * least 1ms.
+ */
+ at->at_probe = ATA_PROBE_FAILED;
+ at->at_type = ATA_PORT_T_NONE;
+ data = SATA_PM_SCTL_IPM_DISABLED | SATA_PM_SCTL_DET_INIT;
+ if (SiliForceGen1 & (1 << ap->ap_num)) {
+ kprintf("%s.%d: Force 1.5GBits\n", PORTNAME(ap), target);
+ data |= SATA_PM_SCTL_SPD_GEN1;
+ } else {
+ data |= SATA_PM_SCTL_SPD_ANY;
+ }
+ if (sili_pm_write(ap, target, SATA_PMREG_SCTL, data))
+ goto err;
+
+ /*
+ * It takes about 100ms for the DET logic to settle down,
+ * from trial and error testing. If this is too short
+ * the softreset code will fail.
+ */
+ sili_os_sleep(100);
+
+ if (sili_pm_phy_status(ap, target, &data)) {
+ kprintf("%s: (A)Cannot clear phy status\n",
+ ATANAME(ap ,at));
+ }
+
+ /*
+ * Flush any status, then clear DET to initiate negotiation.
+ */
+ sili_pm_write(ap, target, SATA_PMREG_SERR, -1);
+ data = SATA_PM_SCTL_IPM_DISABLED | SATA_PM_SCTL_DET_NONE;
+ if (sili_pm_write(ap, target, SATA_PMREG_SCTL, data))
+ goto err;
+
+ /*
+ * Try to determine if there is a device on the port.
+ *
+ * Give the device 3/10 second to at least be detected.
+ * If we fail clear any pending status since we may have
+ * cycled the phy and probably caused another PRCS interrupt.
+ */
+ for (loop = 3; loop; --loop) {
+ if (sili_pm_read(ap, target, SATA_PMREG_SSTS, &data))
+ goto err;
+ if (data & SATA_PM_SSTS_DET)
+ break;
+ sili_os_sleep(100);
+ }
+ if (loop == 0) {
+ kprintf("%s.%d: Port appears to be unplugged\n",
+ PORTNAME(ap), target);
+ error = ENODEV;
+ goto err;
+ }
+
+ /*
+ * There is something on the port. Give the device 3 seconds
+ * to fully negotiate.
+ */
+ for (loop = 30; loop; --loop) {
+ if (sili_pm_read(ap, target, SATA_PMREG_SSTS, &data))
+ goto err;
+ if ((data & SATA_PM_SSTS_DET) == SATA_PM_SSTS_DET_DEV)
+ break;
+ sili_os_sleep(100);
+ }
+
+ /*
+ * Device not detected
+ */
+ if (loop == 0) {
+ kprintf("%s: Device may be powered down\n",
+ PORTNAME(ap));
+ error = ENODEV;
+ goto err;
+ }
+
+ /*
+ * Device detected
+ */
+ kprintf("%s.%d: Device detected data=%08x\n",
+ PORTNAME(ap), target, data);
+ /*
+ * Clear SERR on the target so we get a new NOTIFY event if a hot-plug
+ * or hot-unplug occurs.
+ */
+ sili_os_sleep(100);
+
+ error = 0;
+err:
+ at->at_probe = error ? ATA_PROBE_FAILED : ATA_PROBE_NEED_SOFT_RESET;
+ return (error);
+}
+
+/*
+ * SILI soft reset through port multiplier.
+ *
+ * This function keeps port communications intact and attempts to generate
+ * a reset to the connected device using device commands. Unlike
+ * hard-port operations we can't do fancy stop/starts or stuff like
+ * that without messing up other commands that might be running or
+ * queued.
+ *
+ * The SII chip will do the whole mess for us.
+ */
+int
+sili_pm_softreset(struct sili_port *ap, int target)
+{
+ struct ata_port *at;
+ struct sili_ccb *ccb;
+ struct sili_prb *prb;
+ int error;
+ u_int32_t data;
+ u_int32_t sig;
+
+ error = EIO;
+ at = &ap->ap_ata[target];
+
+ DPRINTF(SILI_D_VERBOSE, "%s: soft reset\n", PORTNAME(ap));
+
+ /*
+ * Prep the special soft-reset SII command.
+ */
+ ccb = sili_get_err_ccb(ap);
+ ccb->ccb_done = sili_pm_empty_done;
+ ccb->ccb_xa.flags = ATA_F_POLL;
+ ccb->ccb_xa.complete = sili_pm_dummy_done;
+ ccb->ccb_xa.at = at;
+
+ prb = ccb->ccb_prb;
+ bzero(&prb->prb_h2d, sizeof(prb->prb_h2d));
+ prb->prb_h2d.flags = at->at_target;
+ prb->prb_control = SILI_PRB_CTRL_SOFTRESET;
+ prb->prb_override = 0;
+ prb->prb_xfer_count = 0;
+
+ ccb->ccb_xa.state = ATA_S_PENDING;
+ ccb->ccb_xa.flags = 0;
+
+ if (sili_poll(ccb, 8000, sili_ata_cmd_timeout) != ATA_S_COMPLETE) {
+ kprintf("%s: (PM) Softreset FIS failed\n", ATANAME(ap, at));
+ sili_put_err_ccb(ccb);
+ goto err;
+ }
+
+ sig = (prb->prb_d2h.lba_high << 24) |
+ (prb->prb_d2h.lba_mid << 16) |
+ (prb->prb_d2h.lba_low << 8) |
+ (prb->prb_d2h.sector_count);
+ kprintf("%s: PM SOFTRESET SIGNATURE %08x\n", ATANAME(ap, at), sig);
+
+ sili_put_err_ccb(ccb);
+
+ /*
+ * Clear the phy status of the target so we can get a new event.
+ *
+ * Target 15 is the PM itself and these registers have
+ * different meanings.
+ */
+ if (target != 15) {
+ if (sili_pm_phy_status(ap, target, &data)) {
+ kprintf("%s: (C)Cannot clear phy status\n",
+ ATANAME(ap ,at));
+ }
+ sili_pm_write(ap, target, SATA_PMREG_SERR, -1);
+ }
+
+ /*
+ * If the softreset is trying to clear a BSY condition after a
+ * normal portreset we assign the port type.
+ *
+ * If the softreset is being run first as part of the ccb error
+ * processing code then report if the device signature changed
+ * unexpectedly.
+ */
+ if (at->at_type == ATA_PORT_T_NONE) {
+ at->at_type = sili_port_signature(ap, at, sig);
+ } else {
+ if (sili_port_signature(ap, at, sig) != at->at_type) {
+ kprintf("%s: device signature unexpectedly "
+ "changed\n", ATANAME(ap, at));
+ error = EBUSY; /* XXX */
+ }
+ }
+ error = 0;
+err:
+ /*
+ * Clear error status so we can detect removal.
+ *
+ * Target 15 is the PM itself and these registers have
+ * different meanings.
+ */
+ if (error == 0 && target != 15) {
+ if (sili_pm_write(ap, target, SATA_PMREG_SERR, -1)) {
+ kprintf("%s: sili_pm_softreset unable to clear SERR\n",
+ ATANAME(ap, at));
+ ap->ap_flags &= ~AP_F_IGNORE_IFS;
+ }
+ }
+
+ at->at_probe = error ? ATA_PROBE_FAILED : ATA_PROBE_NEED_IDENT;
+ return (error);
+}
+
+
+/*
+ * Return the phy status for a target behind a port multiplier and
+ * reset SATA_PMREG_SERR.
+ *
+ * Returned bits follow SILI_PREG_SSTS bits. The SILI_PREG_SSTS_SPD
+ * bits can be used to determine the link speed and will be 0 if there
+ * is no link.
+ *
+ * 0 is returned if any communications error occurs.
+ */
+int
+sili_pm_phy_status(struct sili_port *ap, int target, u_int32_t *datap)
+{
+ int error;
+
+ error = sili_pm_read(ap, target, SATA_PMREG_SSTS, datap);
+ if (error == 0)
+ error = sili_pm_write(ap, target, SATA_PMREG_SERR, -1);
+ if (error)
+ *datap = 0;
+ return(error);
+}
+
+int
+sili_pm_set_feature(struct sili_port *ap, int feature, int enable)
+{
+ struct ata_xfer *xa;
+ int error;
+
+ xa = sili_ata_get_xfer(ap, &ap->ap_ata[15]);
+
+ xa->fis->type = ATA_FIS_TYPE_H2D;
+ xa->fis->flags = ATA_H2D_FLAGS_CMD | 15;
+ xa->fis->command = enable ? ATA_C_SATA_FEATURE_ENA :
+ ATA_C_SATA_FEATURE_DIS;
+ xa->fis->sector_count = feature;
+ xa->fis->control = ATA_FIS_CONTROL_4BIT;
+
+ xa->complete = sili_pm_dummy_done;
+ xa->datalen = 0;
+ xa->flags = ATA_F_READ | ATA_F_POLL;
+ xa->timeout = 1000;
+
+ if (sili_ata_cmd(xa) == ATA_S_COMPLETE)
+ error = 0;
+ else
+ error = EIO;
+ sili_ata_put_xfer(xa);
+ return(error);
+}
+
+/*
+ * Check that a target is still good.
+ */
+void
+sili_pm_check_good(struct sili_port *ap, int target)
+{
+ struct ata_port *at;
+ u_int32_t data;
+
+ /*
+ * It looks like we might have to read the EINFO register
+ * to allow the PM to generate a new event.
+ */
+ if (sili_pm_read(ap, 15, SATA_PMREG_EINFO, &data)) {
+ kprintf("%s: Port multiplier EINFO could not be read\n",
+ PORTNAME(ap));
+ }
+
+ if (sili_pm_write(ap, target, SATA_PMREG_SERR, -1)) {
+ kprintf("%s: Port multiplier: SERR could not be cleared\n",
+ PORTNAME(ap));
+ }
+
+ if (target == CAM_TARGET_WILDCARD || target >= ap->ap_pmcount)
+ return;
+ at = &ap->ap_ata[target];
+
+ /*
+ * If the device needs an init or hard reset also make sure the
+ * PHY is turned on.
+ */
+ if (at->at_probe <= ATA_PROBE_NEED_HARD_RESET) {
+ /*kprintf("%s DOHARD\n", ATANAME(ap, at));*/
+ sili_pm_hardreset(ap, target, 1);
+ }
+
+ /*
+ * Read the detect status
+ */
+ if (sili_pm_read(ap, target, SATA_PMREG_SSTS, &data)) {
+ kprintf("%s: Unable to access PM SSTS register target %d\n",
+ PORTNAME(ap), target);
+ return;
+ }
+ if ((data & SATA_PM_SSTS_DET) != SATA_PM_SSTS_DET_DEV) {
+ /*kprintf("%s: DETECT %08x\n", ATANAME(ap, at), data);*/
+ if (at->at_probe != ATA_PROBE_FAILED) {
+ at->at_probe = ATA_PROBE_FAILED;
+ at->at_type = ATA_PORT_T_NONE;
+ at->at_features |= ATA_PORT_F_RESCAN;
+ kprintf("%s: HOTPLUG (PM) - Device removed\n",
+ ATANAME(ap, at));
+ }
+ } else {
+ if (at->at_probe == ATA_PROBE_FAILED) {
+ at->at_probe = ATA_PROBE_NEED_HARD_RESET;
+ at->at_features |= ATA_PORT_F_RESCAN;
+ kprintf("%s: HOTPLUG (PM) - Device inserted\n",
+ ATANAME(ap, at));
+ }
+ }
+}
+
+/*
+ * Read a PM register
+ */
+int
+sili_pm_read(struct sili_port *ap, int target, int which, u_int32_t *datap)
+{
+ struct ata_xfer *xa;
+ int error;
+
+ xa = sili_ata_get_xfer(ap, &ap->ap_ata[15]);
+
+ xa->fis->type = ATA_FIS_TYPE_H2D;
+ xa->fis->flags = ATA_H2D_FLAGS_CMD | 15;
+ xa->fis->command = ATA_C_READ_PM;
+ xa->fis->features = which;
+ xa->fis->device = target | ATA_H2D_DEVICE_LBA;
+ xa->fis->control = ATA_FIS_CONTROL_4BIT;
+
+ xa->complete = sili_pm_dummy_done;
+ xa->datalen = 0;
+ xa->flags = ATA_F_READ | ATA_F_POLL;
+ xa->timeout = 1000;
+
+ if (sili_ata_cmd(xa) == ATA_S_COMPLETE) {
+ *datap = xa->rfis->sector_count | (xa->rfis->lba_low << 8) |
+ (xa->rfis->lba_mid << 16) | (xa->rfis->lba_high << 24);
+ error = 0;
+ } else {
+ kprintf("%s.%d pm_read SCA[%d] failed\n",
+ PORTNAME(ap), target, which);
+ *datap = 0;
+ error = EIO;
+ }
+ sili_ata_put_xfer(xa);
+ return (error);
+}
+
+/*
+ * Write a PM register
+ */
+int
+sili_pm_write(struct sili_port *ap, int target, int which, u_int32_t data)
+{
+ struct ata_xfer *xa;
+ int error;
+
+ xa = sili_ata_get_xfer(ap, &ap->ap_ata[15]);
+
+ xa->fis->type = ATA_FIS_TYPE_H2D;
+ xa->fis->flags = ATA_H2D_FLAGS_CMD | 15;
+ xa->fis->command = ATA_C_WRITE_PM;
+ xa->fis->features = which;
+ xa->fis->device = target | ATA_H2D_DEVICE_LBA;
+ xa->fis->sector_count = (u_int8_t)data;
+ xa->fis->lba_low = (u_int8_t)(data >> 8);
+ xa->fis->lba_mid = (u_int8_t)(data >> 16);
+ xa->fis->lba_high = (u_int8_t)(data >> 24);
+ xa->fis->control = ATA_FIS_CONTROL_4BIT;
+
+ xa->complete = sili_pm_dummy_done;
+ xa->datalen = 0;
+ xa->flags = ATA_F_READ | ATA_F_POLL;
+ xa->timeout = 1000;
+
+ if (sili_ata_cmd(xa) == ATA_S_COMPLETE)
+ error = 0;
+ else
+ error = EIO;
+ sili_ata_put_xfer(xa);
+ return(error);
+}
+
+/*
+ * Dummy done callback for xa.
+ */
+static void
+sili_pm_dummy_done(struct ata_xfer *xa)
+{
+}
+
+static void
+sili_pm_empty_done(struct sili_ccb *ccb)
+{
+}