si3124.c revision 193974072f41a843678abf5f61979c748687e66b
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* SiliconImage 3124/3132 sata controller driver
*/
/*
*
*
* Few Design notes
*
*
* I. General notes
*
* Even though the driver is named as si3124, it is actually meant to
* work with both 3124 and 3132 controllers.
*
* The current file si3124.c is the main driver code. The si3124reg.h
* holds the register definitions from SiI 3124/3132 data sheets. The
* si3124var.h holds the driver specific definitions which are not
* directly derived from data sheets.
*
*
* II. Data structures
*
* si_ctl_state_t: This holds the driver private information for each
* controller instance. Each of the sata ports within a single
* controller are represented by si_port_state_t. The
* sictl_global_acc_handle and sictl_global_address map the
* controller-wide global register space and are derived from pci
* BAR 0. The sictl_port_acc_handle and sictl_port_addr map the
* per-port register space and are derived from pci BAR 1.
*
* si_port_state_t: This holds the per port information. The siport_mutex
* holds the per port mutex. The siport_pending_tags is the bit mask of
* commands posted to controller. The siport_slot_pkts[] holds the
* pending sata packets. The siport_port_type holds the device type
* connected directly to the port while the siport_portmult_state
* holds the similar information for the devices behind a port
* multiplier.
*
* si_prb_t: This contains the PRB being posted to the controller.
* The two SGE entries contained within si_prb_t itself are not
* really used to hold any scatter gather entries. The scatter gather
* list is maintained external to PRB and is linked from one
* of the contained SGEs inside the PRB. For atapi devices, the
* first contained SGE holds the PACKET and second contained
* SGE holds the link to an external SGT. For non-atapi devices,
* the first contained SGE works as link to external SGT while
* second SGE is blank.
*
* external SGT tables: The external SGT tables pointed to from
* within si_prb_t are actually abstracted as si_sgblock_t. Each
* si_sgblock_t contains SI_MAX_SGT_TABLES_PER_PRB number of
* SGT tables linked in a chain. Currently this max value of
* SGT tables per block is hard coded as 10 which translates
* to a maximum of 31 dma cookies per single dma transfer.
*
*
* III. Driver operation
*
* Command Issuing: We use the "indirect method of command issuance". The
* PRB contains the command [and atapi PACKET] and a link to the
* external SGT chain. We write the physical address of the PRB into
* command activation register. There are 31 command slots for
* each port. After posting a command, we remember the posted slot &
* the sata packet in siport_pending_tags & siport_slot_pkts[]
* respectively.
*
* Command completion: On a successful completion, intr_command_complete()
* receives the control. The slot_status register holds the outstanding
* commands. Any reading of slot_status register automatically clears
* the interrupt. By comparing the slot_status register contents with
* per port siport_pending_tags, we determine which of the previously
* posted commands have finished.
*
* Timeout handling: Every 5 seconds, the watchdog handler scans thru the
* pending packets. The satapkt->satapkt_hba_driver_private field is
* overloaded with the count of watchdog cycles a packet has survived.
* If a packet has not completed within satapkt->satapkt_time, it is
* failed with error code of SATA_PKT_TIMEOUT. There is one watchdog
* handler running for each instance of controller.
*
* Error handling: For 3124, whenever any single command has encountered
* an error, the whole port execution completely stalls; there is no
* way of canceling or aborting the particular failed command. If
* the port is connected to a port multiplier, we can however RESUME
* other non-error devices connected to the port multiplier.
* The only way to recover the failed commands is to either initialize
* the port or reset the port/device. Both port initialize and reset
* operations result in discarding any of pending commands on the port.
* All such discarded commands are sent up to framework with PKT_RESET
* satapkt_reason. The assumption is that framework [and sd] would
* retry these commands again. The failed command itself however is
* sent up with PKT_DEV_ERROR.
*
* Here is the implementation strategy based on SiliconImage email
* regarding how they handle the errors for their Windows driver:
*
* a) for DEVICEERROR:
* If the port is connected to port multiplier, then
* 1) Resume the port
* 2) Wait for all the non-failed commands to complete
* 3) Perform a Port Initialize
*
* If the port is not connected to port multiplier, issue
* a Port Initialize.
*
* b) for SDBERROR: [SDBERROR means failed command is an NCQ command]
* Handle exactly like DEVICEERROR handling.
* After the Port Initialize done, do a Read Log Extended.
*
* c) for SENDFISERROR:
* If the port is connected to port multiplier, then
* 1) Resume the port
* 2) Wait for all the non-failed commands to complete
* 3) Perform a Port Initialize
*
* If the port is not connected to port multiplier, issue
* a Device Reset.
*
* d) for DATAFISERROR:
* If the port was executing an NCQ command, issue a Device
* Reset.
*
* Otherwise, follow the same error recovery as DEVICEERROR.
*
* e) for any other error, simply issue a Device Reset.
*
* To synchronize the interactions between various control flows (e.g.
* error recovery, timeout handling, si_poll_timeout, incoming flow
* from framework etc.), the following precautions are taken care of:
* a) During mopping_in_progress, no more commands are
* accepted from the framework.
*
* b) While draining the port multiplier commands, we should
* handle the possibility of any of the other waited commands
* failing (possibly with a different error code)
*
* Atapi handling: For atapi devices, we use the first SGE within the PRB
* to fill the scsi cdb while the second SGE points to external SGT.
*
* Queuing: Queue management is achieved external to the driver inside sd.
* Based on sata_hba_tran->qdepth and IDENTIFY data, the framework
* enables or disables the queuing. The qdepth for si3124 is 31
* commands.
*
* Port Multiplier: Enumeration of port multiplier is handled during the
* controller initialization and also during the a hotplug operation.
* Current logic takes care of situation where a port multiplier
* is hotplugged into a port which had a cdisk connected previously
* and vice versa.
*
* Register poll timeouts: Currently most of poll timeouts on register
* reads is set to 0.5 seconds except for a value of 10 seconds
* while reading the device signature. [Such a big timeout values
* for device signature were found needed during cold reboots
* for devices behind port multiplier].
*
*
* IV. Known Issues
*
* 1) Currently the atapi packet length is hard coded to 12 bytes
* This is wrong. The framework should determine it just like they
* determine ad_cdb_len in legacy atapi.c. It should even reject
* init_pkt() for greater CDB lengths. See atapi.c. Revisit this
* in 2nd phase of framework project.
*
* 2) Do real REQUEST SENSE command instead of faking for ATAPI case.
*
*/
#include <sys/note.h>
#include <sys/scsi/scsi.h>
#include <sys/pci.h>
#include <sys/sata/sata_hba.h>
#include <sys/sata/adapters/si3124/si3124reg.h>
#include <sys/sata/adapters/si3124/si3124var.h>
/*
* Function prototypes for driver entry points
*/
static int si_attach(dev_info_t *, ddi_attach_cmd_t);
static int si_detach(dev_info_t *, ddi_detach_cmd_t);
static int si_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int si_power(dev_info_t *, int, int);
/*
* Function prototypes for SATA Framework interfaces
*/
static int si_register_sata_hba_tran(si_ctl_state_t *);
static int si_unregister_sata_hba_tran(si_ctl_state_t *);
static int si_tran_probe_port(dev_info_t *, sata_device_t *);
static int si_tran_start(dev_info_t *, sata_pkt_t *spkt);
static int si_tran_abort(dev_info_t *, sata_pkt_t *, int);
static int si_tran_reset_dport(dev_info_t *, sata_device_t *);
static int si_tran_hotplug_port_activate(dev_info_t *, sata_device_t *);
static int si_tran_hotplug_port_deactivate(dev_info_t *, sata_device_t *);
/*
* Local function prototypes
*/
static int si_alloc_port_state(si_ctl_state_t *, int);
static void si_dealloc_port_state(si_ctl_state_t *, int);
static int si_alloc_sgbpool(si_ctl_state_t *, int);
static void si_dealloc_sgbpool(si_ctl_state_t *, int);
static int si_alloc_prbpool(si_ctl_state_t *, int);
static void si_dealloc_prbpool(si_ctl_state_t *, int);
static void si_find_dev_signature(si_ctl_state_t *, si_port_state_t *,
int, int);
static void si_poll_cmd(si_ctl_state_t *, si_port_state_t *, int, int,
sata_pkt_t *);
static int si_claim_free_slot(si_ctl_state_t *, si_port_state_t *, int);
static int si_deliver_satapkt(si_ctl_state_t *, si_port_state_t *, int,
sata_pkt_t *);
static int si_initialize_controller(si_ctl_state_t *);
static void si_deinititalize_controller(si_ctl_state_t *);
static void si_init_port(si_ctl_state_t *, int);
static int si_enumerate_port_multiplier(si_ctl_state_t *,
si_port_state_t *, int);
static int si_read_portmult_reg(si_ctl_state_t *, si_port_state_t *,
int, int, int, uint32_t *);
static int si_write_portmult_reg(si_ctl_state_t *, si_port_state_t *,
int, int, int, uint32_t);
static void si_set_sense_data(sata_pkt_t *, int);
static uint_t si_intr(caddr_t, caddr_t);
static int si_intr_command_complete(si_ctl_state_t *,
si_port_state_t *, int);
static int si_intr_command_error(si_ctl_state_t *,
si_port_state_t *, int);
static void si_error_recovery_DEVICEERROR(si_ctl_state_t *,
si_port_state_t *, int);
static void si_error_recovery_SDBERROR(si_ctl_state_t *,
si_port_state_t *, int);
static void si_error_recovery_DATAFISERROR(si_ctl_state_t *,
si_port_state_t *, int);
static void si_error_recovery_SENDFISERROR(si_ctl_state_t *,
si_port_state_t *, int);
static void si_error_recovery_default(si_ctl_state_t *,
si_port_state_t *, int);
static uint8_t si_read_log_ext(si_ctl_state_t *,
si_port_state_t *si_portp, int);
static void si_log_error_message(si_ctl_state_t *, int, uint32_t);
static int si_intr_port_ready(si_ctl_state_t *, si_port_state_t *, int);
static int si_intr_pwr_change(si_ctl_state_t *, si_port_state_t *, int);
static int si_intr_phy_ready_change(si_ctl_state_t *, si_port_state_t *, int);
static int si_intr_comwake_rcvd(si_ctl_state_t *, si_port_state_t *, int);
static int si_intr_unrecognised_fis(si_ctl_state_t *, si_port_state_t *, int);
static int si_intr_dev_xchanged(si_ctl_state_t *, si_port_state_t *, int);
static int si_intr_decode_err_threshold(si_ctl_state_t *,
si_port_state_t *, int);
static int si_intr_crc_err_threshold(si_ctl_state_t *, si_port_state_t *, int);
static int si_intr_handshake_err_threshold(si_ctl_state_t *,
si_port_state_t *, int);
static int si_intr_set_devbits_notify(si_ctl_state_t *, si_port_state_t *, int);
static void si_handle_attention_raised(si_ctl_state_t *,
si_port_state_t *, int);
static void si_enable_port_interrupts(si_ctl_state_t *, int);
static void si_enable_all_interrupts(si_ctl_state_t *);
static void si_disable_port_interrupts(si_ctl_state_t *, int);
static void si_disable_all_interrupts(si_ctl_state_t *);
static void fill_dev_sregisters(si_ctl_state_t *, int, sata_device_t *);
static int si_add_legacy_intrs(si_ctl_state_t *);
static int si_add_msi_intrs(si_ctl_state_t *);
static void si_rem_intrs(si_ctl_state_t *);
static int si_reset_dport_wait_till_ready(si_ctl_state_t *,
si_port_state_t *, int, int);
static int si_initialize_port_wait_till_ready(si_ctl_state_t *, int);
static void si_timeout_pkts(si_ctl_state_t *, si_port_state_t *, int, uint32_t);
static void si_watchdog_handler(si_ctl_state_t *);
static void si_log(si_ctl_state_t *, uint_t, char *, ...);
static void si_copy_out_regs(sata_cmd_t *, fis_reg_h2d_t *);
/*
* DMA attributes for the data buffer
*/
static ddi_dma_attr_t buffer_dma_attr = {
DMA_ATTR_V0, /* dma_attr_version */
0, /* dma_attr_addr_lo: lowest bus address */
0xffffffffffffffffull, /* dma_attr_addr_hi: highest bus address */
0xffffffffull, /* dma_attr_count_max i.e. for one cookie */
1, /* dma_attr_align: single byte aligned */
1, /* dma_attr_burstsizes */
1, /* dma_attr_minxfer */
0xffffffffull, /* dma_attr_maxxfer i.e. includes all cookies */
0xffffffffull, /* dma_attr_seg */
SI_MAX_SGL_LENGTH, /* dma_attr_sgllen */
512, /* dma_attr_granular */
0, /* dma_attr_flags */
};
/*
* DMA attributes for incore RPB and SGT pool
*/
static ddi_dma_attr_t prb_sgt_dma_attr = {
DMA_ATTR_V0, /* dma_attr_version */
0, /* dma_attr_addr_lo: lowest bus address */
0xffffffffffffffffull, /* dma_attr_addr_hi: highest bus address */
0xffffffffull, /* dma_attr_count_max i.e. for one cookie */
8, /* dma_attr_align: quad word aligned */
1, /* dma_attr_burstsizes */
1, /* dma_attr_minxfer */
0xffffffffull, /* dma_attr_maxxfer i.e. includes all cookies */
0xffffffffull, /* dma_attr_seg */
1, /* dma_attr_sgllen */
1, /* dma_attr_granular */
0, /* dma_attr_flags */
};
/* Device access attributes */
static ddi_device_acc_attr_t accattr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
static struct dev_ops sictl_dev_ops = {
DEVO_REV, /* devo_rev */
0, /* refcnt */
si_getinfo, /* info */
nulldev, /* identify */
nulldev, /* probe */
si_attach, /* attach */
si_detach, /* detach */
nodev, /* no reset */
(struct cb_ops *)0, /* driver operations */
NULL, /* bus operations */
si_power, /* power */
ddi_quiesce_not_supported, /* devo_quiesce */
};
static sata_tran_hotplug_ops_t si_tran_hotplug_ops = {
SATA_TRAN_HOTPLUG_OPS_REV_1,
si_tran_hotplug_port_activate,
si_tran_hotplug_port_deactivate
};
static int si_watchdog_timeout = 5; /* 5 seconds */
static int si_watchdog_tick;
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops, /* driverops */
"si3124 driver",
&sictl_dev_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
NULL
};
/* The following are needed for si_log() */
static kmutex_t si_log_mutex;
static char si_log_buf[512];
uint32_t si_debug_flags = 0x0;
static int is_msi_supported = 0;
/* Opaque state pointer to be initialized by ddi_soft_state_init() */
static void *si_statep = NULL;
/*
* si3124 module initialization.
*
*/
int
_init(void)
{
int error;
error = ddi_soft_state_init(&si_statep, sizeof (si_ctl_state_t), 0);
if (error != 0) {
return (error);
}
mutex_init(&si_log_mutex, NULL, MUTEX_DRIVER, NULL);
if ((error = sata_hba_init(&modlinkage)) != 0) {
mutex_destroy(&si_log_mutex);
ddi_soft_state_fini(&si_statep);
return (error);
}
error = mod_install(&modlinkage);
if (error != 0) {
sata_hba_fini(&modlinkage);
mutex_destroy(&si_log_mutex);
ddi_soft_state_fini(&si_statep);
return (error);
}
si_watchdog_tick = drv_usectohz((clock_t)si_watchdog_timeout * 1000000);
return (error);
}
/*
* si3124 module uninitialize.
*
*/
int
_fini(void)
{
int error;
error = mod_remove(&modlinkage);
if (error != 0) {
return (error);
}
/* Remove the resources allocated in _init(). */
sata_hba_fini(&modlinkage);
mutex_destroy(&si_log_mutex);
ddi_soft_state_fini(&si_statep);
return (error);
}
/*
* _info entry point
*
*/
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*
* The attach entry point for dev_ops.
*
* We initialize the controller, initialize the soft state, register
* the interrupt handlers and then register ourselves with sata framework.
*/
static int
si_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
si_ctl_state_t *si_ctlp;
int instance;
int status;
int attach_state;
int intr_types;
sata_device_t sdevice;
SIDBG0(SIDBG_INIT|SIDBG_ENTRY, NULL, "si_attach enter");
instance = ddi_get_instance(dip);
attach_state = ATTACH_PROGRESS_NONE;
switch (cmd) {
case DDI_ATTACH:
/* Allocate si_softc. */
status = ddi_soft_state_zalloc(si_statep, instance);
if (status != DDI_SUCCESS) {
goto err_out;
}
si_ctlp = ddi_get_soft_state(si_statep, instance);
si_ctlp->sictl_devinfop = dip;
attach_state |= ATTACH_PROGRESS_STATEP_ALLOC;
/* Configure pci config space handle. */
status = pci_config_setup(dip, &si_ctlp->sictl_pci_conf_handle);
if (status != DDI_SUCCESS) {
goto err_out;
}
si_ctlp->sictl_devid =
pci_config_get16(si_ctlp->sictl_pci_conf_handle,
PCI_CONF_DEVID);
if (si_ctlp->sictl_devid == SI3132_DEV_ID) {
si_ctlp->sictl_num_ports = SI3132_MAX_PORTS;
} else {
si_ctlp->sictl_num_ports = SI3124_MAX_PORTS;
}
attach_state |= ATTACH_PROGRESS_CONF_HANDLE;
/* Now map the bar0; the bar0 contains the global registers. */
status = ddi_regs_map_setup(dip,
PCI_BAR0,
(caddr_t *)&si_ctlp->sictl_global_addr,
0,
0,
&accattr,
&si_ctlp->sictl_global_acc_handle);
if (status != DDI_SUCCESS) {
goto err_out;
}
attach_state |= ATTACH_PROGRESS_BAR0_MAP;
/* Now map bar1; the bar1 contains the port registers. */
status = ddi_regs_map_setup(dip,
PCI_BAR1,
(caddr_t *)&si_ctlp->sictl_port_addr,
0,
0,
&accattr,
&si_ctlp->sictl_port_acc_handle);
if (status != DDI_SUCCESS) {
goto err_out;
}
attach_state |= ATTACH_PROGRESS_BAR1_MAP;
/*
* Disable all the interrupts before adding interrupt
* handler(s). The interrupts shall be re-enabled selectively
* out of si_init_port().
*/
si_disable_all_interrupts(si_ctlp);
/* Get supported interrupt types. */
if (ddi_intr_get_supported_types(dip, &intr_types)
!= DDI_SUCCESS) {
SIDBG0(SIDBG_INIT, NULL,
"ddi_intr_get_supported_types failed");
goto err_out;
}
SIDBG1(SIDBG_INIT, NULL,
"ddi_intr_get_supported_types() returned: 0x%x",
intr_types);
if (is_msi_supported && (intr_types & DDI_INTR_TYPE_MSI)) {
SIDBG0(SIDBG_INIT, NULL, "Using MSI interrupt type");
/*
* Try MSI first, but fall back to legacy if MSI
* attach fails.
*/
if (si_add_msi_intrs(si_ctlp) == DDI_SUCCESS) {
si_ctlp->sictl_intr_type = DDI_INTR_TYPE_MSI;
attach_state |= ATTACH_PROGRESS_INTR_ADDED;
SIDBG0(SIDBG_INIT, NULL,
"MSI interrupt setup done");
} else {
SIDBG0(SIDBG_INIT, NULL,
"MSI registration failed "
"will try Legacy interrupts");
}
}
if (!(attach_state & ATTACH_PROGRESS_INTR_ADDED) &&
(intr_types & DDI_INTR_TYPE_FIXED)) {
/*
* Either the MSI interrupt setup has failed or only
* fixed interrupts are available on the system.
*/
SIDBG0(SIDBG_INIT, NULL, "Using Legacy interrupt type");
if (si_add_legacy_intrs(si_ctlp) == DDI_SUCCESS) {
si_ctlp->sictl_intr_type = DDI_INTR_TYPE_FIXED;
attach_state |= ATTACH_PROGRESS_INTR_ADDED;
SIDBG0(SIDBG_INIT, NULL,
"Legacy interrupt setup done");
} else {
SIDBG0(SIDBG_INIT, NULL,
"legacy interrupt setup failed");
goto err_out;
}
}
if (!(attach_state & ATTACH_PROGRESS_INTR_ADDED)) {
SIDBG0(SIDBG_INIT, NULL,
"si3124: No interrupts registered");
goto err_out;
}
/* Initialize the mutex. */
mutex_init(&si_ctlp->sictl_mutex, NULL, MUTEX_DRIVER,
(void *)(uintptr_t)si_ctlp->sictl_intr_pri);
attach_state |= ATTACH_PROGRESS_MUTEX_INIT;
/*
* Initialize the controller and driver core.
*/
si_ctlp->sictl_flags |= SI_ATTACH;
status = si_initialize_controller(si_ctlp);
si_ctlp->sictl_flags &= ~SI_ATTACH;
if (status) {
goto err_out;
}
attach_state |= ATTACH_PROGRESS_HW_INIT;
if (si_register_sata_hba_tran(si_ctlp)) {
SIDBG0(SIDBG_INIT, NULL,
"si3124: setting sata hba tran failed");
goto err_out;
}
si_ctlp->sictl_timeout_id = timeout(
(void (*)(void *))si_watchdog_handler,
(caddr_t)si_ctlp, si_watchdog_tick);
si_ctlp->sictl_power_level = PM_LEVEL_D0;
return (DDI_SUCCESS);
case DDI_RESUME:
si_ctlp = ddi_get_soft_state(si_statep, instance);
status = si_initialize_controller(si_ctlp);
if (status) {
return (DDI_FAILURE);
}
si_ctlp->sictl_timeout_id = timeout(
(void (*)(void *))si_watchdog_handler,
(caddr_t)si_ctlp, si_watchdog_tick);
(void) pm_power_has_changed(dip, 0, PM_LEVEL_D0);
/* Notify SATA framework about RESUME. */
if (sata_hba_attach(si_ctlp->sictl_devinfop,
si_ctlp->sictl_sata_hba_tran,
DDI_RESUME) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/*
* Notify the "framework" that it should reprobe ports to see
* if any device got changed while suspended.
*/
bzero((void *)&sdevice, sizeof (sata_device_t));
sata_hba_event_notify(dip, &sdevice,
SATA_EVNT_PWR_LEVEL_CHANGED);
SIDBG0(SIDBG_INIT|SIDBG_EVENT, si_ctlp,
"sending event up: SATA_EVNT_PWR_LEVEL_CHANGED");
(void) pm_idle_component(si_ctlp->sictl_devinfop, 0);
si_ctlp->sictl_power_level = PM_LEVEL_D0;
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
err_out:
if (attach_state & ATTACH_PROGRESS_HW_INIT) {
si_ctlp->sictl_flags |= SI_DETACH;
/* We want to set SI_DETACH to deallocate all memory */
si_deinititalize_controller(si_ctlp);
si_ctlp->sictl_flags &= ~SI_DETACH;
}
if (attach_state & ATTACH_PROGRESS_MUTEX_INIT) {
mutex_destroy(&si_ctlp->sictl_mutex);
}
if (attach_state & ATTACH_PROGRESS_INTR_ADDED) {
si_rem_intrs(si_ctlp);
}
if (attach_state & ATTACH_PROGRESS_BAR1_MAP) {
ddi_regs_map_free(&si_ctlp->sictl_port_acc_handle);
}
if (attach_state & ATTACH_PROGRESS_BAR0_MAP) {
ddi_regs_map_free(&si_ctlp->sictl_global_acc_handle);
}
if (attach_state & ATTACH_PROGRESS_CONF_HANDLE) {
pci_config_teardown(&si_ctlp->sictl_pci_conf_handle);
}
if (attach_state & ATTACH_PROGRESS_STATEP_ALLOC) {
ddi_soft_state_free(si_statep, instance);
}
return (DDI_FAILURE);
}
/*
* The detach entry point for dev_ops.
*
* We undo the things we did in si_attach().
*/
static int
si_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
si_ctl_state_t *si_ctlp;
int instance;
SIDBG0(SIDBG_INIT|SIDBG_ENTRY, NULL, "si_detach enter");
instance = ddi_get_instance(dip);
si_ctlp = ddi_get_soft_state(si_statep, instance);
switch (cmd) {
case DDI_DETACH:
mutex_enter(&si_ctlp->sictl_mutex);
/* disable the interrupts for an uninterrupted detach */
si_disable_all_interrupts(si_ctlp);
mutex_exit(&si_ctlp->sictl_mutex);
/* unregister from the sata framework. */
if (si_unregister_sata_hba_tran(si_ctlp) != SI_SUCCESS) {
si_enable_all_interrupts(si_ctlp);
return (DDI_FAILURE);
}
mutex_enter(&si_ctlp->sictl_mutex);
/* now cancel the timeout handler. */
si_ctlp->sictl_flags |= SI_NO_TIMEOUTS;
(void) untimeout(si_ctlp->sictl_timeout_id);
si_ctlp->sictl_flags &= ~SI_NO_TIMEOUTS;
/* deinitialize the controller. */
si_ctlp->sictl_flags |= SI_DETACH;
si_deinititalize_controller(si_ctlp);
si_ctlp->sictl_flags &= ~SI_DETACH;
/* destroy any mutexes */
mutex_exit(&si_ctlp->sictl_mutex);
mutex_destroy(&si_ctlp->sictl_mutex);
/* remove the interrupts */
si_rem_intrs(si_ctlp);
/* remove the reg maps. */
ddi_regs_map_free(&si_ctlp->sictl_port_acc_handle);
ddi_regs_map_free(&si_ctlp->sictl_global_acc_handle);
pci_config_teardown(&si_ctlp->sictl_pci_conf_handle);
/* free the soft state. */
ddi_soft_state_free(si_statep, instance);
return (DDI_SUCCESS);
case DDI_SUSPEND:
/* Inform SATA framework */
if (sata_hba_detach(dip, cmd) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
mutex_enter(&si_ctlp->sictl_mutex);
/*
* Device needs to be at full power in case it is needed to
* handle dump(9e) to save CPR state after DDI_SUSPEND
* completes. This is OK since presumably power will be
* removed anyways. No outstanding transactions should be
* on the controller since the children are already quiesed.
*
* If any ioctls/cfgadm support is added that touches
* hardware, those entry points will need to check for
* suspend and then block or return errors until resume.
*
*/
if (pm_busy_component(si_ctlp->sictl_devinfop, 0) ==
DDI_SUCCESS) {
mutex_exit(&si_ctlp->sictl_mutex);
(void) pm_raise_power(si_ctlp->sictl_devinfop, 0,
PM_LEVEL_D0);
mutex_enter(&si_ctlp->sictl_mutex);
}
si_deinititalize_controller(si_ctlp);
si_ctlp->sictl_flags |= SI_NO_TIMEOUTS;
(void) untimeout(si_ctlp->sictl_timeout_id);
si_ctlp->sictl_flags &= ~SI_NO_TIMEOUTS;
SIDBG1(SIDBG_POWER, NULL, "si3124%d: DDI_SUSPEND", instance);
mutex_exit(&si_ctlp->sictl_mutex);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
si_power(dev_info_t *dip, int component, int level)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(component))
#endif /* __lock_lint */
si_ctl_state_t *si_ctlp;
int instance = ddi_get_instance(dip);
int rval = DDI_SUCCESS;
int old_level;
sata_device_t sdevice;
si_ctlp = ddi_get_soft_state(si_statep, instance);
if (si_ctlp == NULL) {
return (DDI_FAILURE);
}
SIDBG0(SIDBG_ENTRY, NULL, "si_power enter");
mutex_enter(&si_ctlp->sictl_mutex);
old_level = si_ctlp->sictl_power_level;
switch (level) {
case PM_LEVEL_D0: /* fully on */
pci_config_put16(si_ctlp->sictl_pci_conf_handle,
PM_CSR(si_ctlp->sictl_devid), PCI_PMCSR_D0);
#ifndef __lock_lint
delay(drv_usectohz(10000));
#endif /* __lock_lint */
si_ctlp->sictl_power_level = PM_LEVEL_D0;
(void) pci_restore_config_regs(si_ctlp->sictl_devinfop);
SIDBG2(SIDBG_POWER, si_ctlp,
"si3124%d: turning power ON. old level %d",
instance, old_level);
/*
* If called from attach, just raise device power,
* restore config registers (if they were saved
* from a previous detach that lowered power),
* and exit.
*/
if (si_ctlp->sictl_flags & SI_ATTACH)
break;
mutex_exit(&si_ctlp->sictl_mutex);
(void) si_initialize_controller(si_ctlp);
mutex_enter(&si_ctlp->sictl_mutex);
si_ctlp->sictl_timeout_id = timeout(
(void (*)(void *))si_watchdog_handler,
(caddr_t)si_ctlp, si_watchdog_tick);
bzero((void *)&sdevice, sizeof (sata_device_t));
sata_hba_event_notify(
si_ctlp->sictl_sata_hba_tran->sata_tran_hba_dip,
&sdevice, SATA_EVNT_PWR_LEVEL_CHANGED);
SIDBG0(SIDBG_EVENT|SIDBG_POWER, si_ctlp,
"sending event up: PWR_LEVEL_CHANGED");
break;
case PM_LEVEL_D3: /* fully off */
if (!(si_ctlp->sictl_flags & SI_DETACH)) {
si_ctlp->sictl_flags |= SI_NO_TIMEOUTS;
(void) untimeout(si_ctlp->sictl_timeout_id);
si_ctlp->sictl_flags &= ~SI_NO_TIMEOUTS;
si_deinititalize_controller(si_ctlp);
si_ctlp->sictl_power_level = PM_LEVEL_D3;
}
(void) pci_save_config_regs(si_ctlp->sictl_devinfop);
pci_config_put16(si_ctlp->sictl_pci_conf_handle,
PM_CSR(si_ctlp->sictl_devid), PCI_PMCSR_D3HOT);
SIDBG2(SIDBG_POWER, NULL, "si3124%d: turning power OFF. "
"old level %d", instance, old_level);
break;
default:
SIDBG2(SIDBG_POWER, NULL, "si3124%d: turning power OFF. "
"old level %d", instance, old_level);
rval = DDI_FAILURE;
break;
}
mutex_exit(&si_ctlp->sictl_mutex);
return (rval);
}
/*
* The info entry point for dev_ops.
*
*/
static int
si_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd,
void *arg,
void **result)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(dip))
#endif /* __lock_lint */
si_ctl_state_t *si_ctlp;
int instance;
dev_t dev;
dev = (dev_t)arg;
instance = getminor(dev);
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
si_ctlp = ddi_get_soft_state(si_statep, instance);
if (si_ctlp != NULL) {
*result = si_ctlp->sictl_devinfop;
return (DDI_SUCCESS);
} else {
*result = NULL;
return (DDI_FAILURE);
}
case DDI_INFO_DEVT2INSTANCE:
*(int *)result = instance;
break;
default:
break;
}
return (DDI_SUCCESS);
}
/*
* Registers the si3124 with sata framework.
*/
static int
si_register_sata_hba_tran(si_ctl_state_t *si_ctlp)
{
struct sata_hba_tran *sata_hba_tran;
SIDBG0(SIDBG_INIT|SIDBG_ENTRY, si_ctlp,
"si_register_sata_hba_tran entry");
mutex_enter(&si_ctlp->sictl_mutex);
/* Allocate memory for the sata_hba_tran */
sata_hba_tran = kmem_zalloc(sizeof (sata_hba_tran_t), KM_SLEEP);
sata_hba_tran->sata_tran_hba_rev = SATA_TRAN_HBA_REV;
sata_hba_tran->sata_tran_hba_dip = si_ctlp->sictl_devinfop;
sata_hba_tran->sata_tran_hba_dma_attr = &buffer_dma_attr;
sata_hba_tran->sata_tran_hba_num_cports = si_ctlp->sictl_num_ports;
sata_hba_tran->sata_tran_hba_features_support = 0;
sata_hba_tran->sata_tran_hba_qdepth = SI_NUM_SLOTS;
sata_hba_tran->sata_tran_probe_port = si_tran_probe_port;
sata_hba_tran->sata_tran_start = si_tran_start;
sata_hba_tran->sata_tran_abort = si_tran_abort;
sata_hba_tran->sata_tran_reset_dport = si_tran_reset_dport;
sata_hba_tran->sata_tran_selftest = NULL;
sata_hba_tran->sata_tran_hotplug_ops = &si_tran_hotplug_ops;
sata_hba_tran->sata_tran_pwrmgt_ops = NULL;
sata_hba_tran->sata_tran_ioctl = NULL;
mutex_exit(&si_ctlp->sictl_mutex);
/* Attach it to SATA framework */
if (sata_hba_attach(si_ctlp->sictl_devinfop, sata_hba_tran, DDI_ATTACH)
!= DDI_SUCCESS) {
kmem_free((void *)sata_hba_tran, sizeof (sata_hba_tran_t));
return (SI_FAILURE);
}
mutex_enter(&si_ctlp->sictl_mutex);
si_ctlp->sictl_sata_hba_tran = sata_hba_tran;
mutex_exit(&si_ctlp->sictl_mutex);
return (SI_SUCCESS);
}
/*
* Unregisters the si3124 with sata framework.
*/
static int
si_unregister_sata_hba_tran(si_ctl_state_t *si_ctlp)
{
/* Detach from the SATA framework. */
if (sata_hba_detach(si_ctlp->sictl_devinfop, DDI_DETACH) !=
DDI_SUCCESS) {
return (SI_FAILURE);
}
/* Deallocate sata_hba_tran. */
kmem_free((void *)si_ctlp->sictl_sata_hba_tran,
sizeof (sata_hba_tran_t));
si_ctlp->sictl_sata_hba_tran = NULL;
return (SI_SUCCESS);
}
/*
* Called by sata framework to probe a port. We return the
* cached information from a previous hardware probe.
*
* The actual hardware probing itself was done either from within
* si_initialize_controller() during the driver attach or
* from a phy ready change interrupt handler.
*/
static int
si_tran_probe_port(dev_info_t *dip, sata_device_t *sd)
{
si_ctl_state_t *si_ctlp;
uint8_t cport = sd->satadev_addr.cport;
uint8_t pmport = sd->satadev_addr.pmport;
uint8_t qual = sd->satadev_addr.qual;
uint8_t port_type;
si_port_state_t *si_portp;
si_portmult_state_t *si_portmultp;
si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
SIDBG3(SIDBG_ENTRY, si_ctlp,
"si_tran_probe_port: cport: 0x%x, pmport: 0x%x, qual: 0x%x",
cport, pmport, qual);
if (cport >= SI_MAX_PORTS) {
sd->satadev_type = SATA_DTYPE_NONE;
sd->satadev_state = SATA_STATE_UNKNOWN; /* invalid port */
return (SATA_FAILURE);
}
mutex_enter(&si_ctlp->sictl_mutex);
si_portp = si_ctlp->sictl_ports[cport];
mutex_exit(&si_ctlp->sictl_mutex);
if (si_portp == NULL) {
sd->satadev_type = SATA_DTYPE_NONE;
sd->satadev_state = SATA_STATE_UNKNOWN;
return (SATA_FAILURE);
}
mutex_enter(&si_portp->siport_mutex);
if (qual == SATA_ADDR_PMPORT) {
if (pmport >= si_portp->siport_portmult_state.sipm_num_ports) {
sd->satadev_type = SATA_DTYPE_NONE;
sd->satadev_state = SATA_STATE_UNKNOWN;
mutex_exit(&si_portp->siport_mutex);
return (SATA_FAILURE);
} else {
si_portmultp = &si_portp->siport_portmult_state;
port_type = si_portmultp->sipm_port_type[pmport];
}
} else {
port_type = si_portp->siport_port_type;
}
switch (port_type) {
case PORT_TYPE_DISK:
sd->satadev_type = SATA_DTYPE_ATADISK;
break;
case PORT_TYPE_ATAPI:
sd->satadev_type = SATA_DTYPE_ATAPICD;
break;
case PORT_TYPE_MULTIPLIER:
sd->satadev_type = SATA_DTYPE_PMULT;
sd->satadev_add_info =
si_portp->siport_portmult_state.sipm_num_ports;
break;
case PORT_TYPE_UNKNOWN:
sd->satadev_type = SATA_DTYPE_UNKNOWN;
break;
default:
/* we don't support any other device types. */
sd->satadev_type = SATA_DTYPE_NONE;
break;
}
sd->satadev_state = SATA_STATE_READY;
if (qual == SATA_ADDR_PMPORT) {
(void) si_read_portmult_reg(si_ctlp, si_portp, cport,
pmport, PSCR_REG0, &sd->satadev_scr.sstatus);
(void) si_read_portmult_reg(si_ctlp, si_portp, cport,
pmport, PSCR_REG1, &sd->satadev_scr.serror);
(void) si_read_portmult_reg(si_ctlp, si_portp, cport,
pmport, PSCR_REG2, &sd->satadev_scr.scontrol);
(void) si_read_portmult_reg(si_ctlp, si_portp, cport,
pmport, PSCR_REG3, &sd->satadev_scr.sactive);
} else {
fill_dev_sregisters(si_ctlp, cport, sd);
if (!(si_portp->siport_active)) {
/*
* Since we are implementing the port deactivation
* in software only, we need to fake a valid value
* for sstatus when the device is in deactivated state.
*/
SSTATUS_SET_DET(sd->satadev_scr.sstatus,
SSTATUS_DET_PHYOFFLINE);
SSTATUS_SET_IPM(sd->satadev_scr.sstatus,
SSTATUS_IPM_NODEV_NOPHY);
sd->satadev_state = SATA_PSTATE_SHUTDOWN;
}
}
mutex_exit(&si_portp->siport_mutex);
return (SATA_SUCCESS);
}
/*
* Called by sata framework to transport a sata packet down stream.
*
* The actual work of building the FIS & transporting it to the hardware
* is done out of the subroutine si_deliver_satapkt().
*/
static int
si_tran_start(dev_info_t *dip, sata_pkt_t *spkt)
{
si_ctl_state_t *si_ctlp;
uint8_t cport;
si_port_state_t *si_portp;
int slot;
cport = spkt->satapkt_device.satadev_addr.cport;
si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
mutex_enter(&si_ctlp->sictl_mutex);
si_portp = si_ctlp->sictl_ports[cport];
mutex_exit(&si_ctlp->sictl_mutex);
SIDBG1(SIDBG_ENTRY, si_ctlp,
"si_tran_start entry: port: 0x%x", cport);
mutex_enter(&si_portp->siport_mutex);
if ((si_portp->siport_port_type == PORT_TYPE_NODEV) ||
!si_portp->siport_active) {
/*
* si_intr_phy_ready_change() may have rendered it to
* PORT_TYPE_NODEV. cfgadm operation may have rendered
* it inactive.
*/
spkt->satapkt_reason = SATA_PKT_PORT_ERROR;
fill_dev_sregisters(si_ctlp, cport, &spkt->satapkt_device);
mutex_exit(&si_portp->siport_mutex);
return (SATA_TRAN_PORT_ERROR);
}
if (spkt->satapkt_cmd.satacmd_flags.sata_clear_dev_reset) {
si_portp->siport_reset_in_progress = 0;
SIDBG1(SIDBG_ENTRY, si_ctlp,
"si_tran_start clearing the "
"reset_in_progress for port: 0x%x", cport);
}
if (si_portp->siport_reset_in_progress &&
! spkt->satapkt_cmd.satacmd_flags.sata_ignore_dev_reset &&
! ddi_in_panic()) {
spkt->satapkt_reason = SATA_PKT_BUSY;
SIDBG1(SIDBG_ERRS, si_ctlp,
"si_tran_start returning BUSY while "
"reset in progress: port: 0x%x", cport);
mutex_exit(&si_portp->siport_mutex);
return (SATA_TRAN_BUSY);
}
if (si_portp->mopping_in_progress) {
spkt->satapkt_reason = SATA_PKT_BUSY;
SIDBG1(SIDBG_ERRS, si_ctlp,
"si_tran_start returning BUSY while "
"mopping in progress: port: 0x%x", cport);
mutex_exit(&si_portp->siport_mutex);
return (SATA_TRAN_BUSY);
}
if ((slot = si_deliver_satapkt(si_ctlp, si_portp, cport, spkt))
== SI_FAILURE) {
spkt->satapkt_reason = SATA_PKT_QUEUE_FULL;
SIDBG1(SIDBG_ERRS, si_ctlp,
"si_tran_start returning QUEUE_FULL: port: 0x%x",
cport);
mutex_exit(&si_portp->siport_mutex);
return (SATA_TRAN_QUEUE_FULL);
}
if (spkt->satapkt_op_mode & (SATA_OPMODE_POLLING|SATA_OPMODE_SYNCH)) {
/* we need to poll now */
mutex_exit(&si_portp->siport_mutex);
si_poll_cmd(si_ctlp, si_portp, cport, slot, spkt);
mutex_enter(&si_portp->siport_mutex);
}
mutex_exit(&si_portp->siport_mutex);
return (SATA_TRAN_ACCEPTED);
}
#define SENDUP_PACKET(si_portp, satapkt, reason) \
if ((satapkt->satapkt_cmd.satacmd_cmd_reg == \
SATAC_WRITE_FPDMA_QUEUED) || \
(satapkt->satapkt_cmd.satacmd_cmd_reg == \
SATAC_READ_FPDMA_QUEUED)) { \
si_portp->siport_pending_ncq_count--; \
} \
if (satapkt) { \
satapkt->satapkt_reason = reason; \
/* \
* We set the satapkt_reason in both synch and \
* non-synch cases. \
*/ \
} \
if (satapkt && \
!(satapkt->satapkt_op_mode & SATA_OPMODE_SYNCH) && \
satapkt->satapkt_comp) { \
mutex_exit(&si_portp->siport_mutex); \
(*satapkt->satapkt_comp)(satapkt); \
mutex_enter(&si_portp->siport_mutex); \
}
/*
* Mopping is necessitated because of the si3124 hardware limitation.
* The only way to recover from errors or to abort a command is to
* reset the port/device but such a reset also results in throwing
* away all the unfinished pending commands.
*
* A port or device is reset in four scenarios:
* a) some commands failed with errors
* b) or we need to timeout some commands
* c) or we need to abort some commands
* d) or we need reset the port at the request of sata framework
*
* In all these scenarios, we need to send any pending unfinished
* commands up to sata framework.
*
* Only one mopping process at a time is allowed; this is achieved
* by using siport_mop_mutex.
*/
static void
si_mop_commands(si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
uint8_t port,
uint32_t slot_status,
uint32_t failed_tags,
uint32_t timedout_tags,
uint32_t aborting_tags,
uint32_t reset_tags)
{
uint32_t finished_tags, unfinished_tags;
int tmpslot;
sata_pkt_t *satapkt;
si_prb_t *prb;
uint32_t *prb_word_ptr;
int i;
SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
"si_mop_commands entered: slot_status: 0x%x",
slot_status);
SIDBG4(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
"si_mop_commands: failed_tags: 0x%x, timedout_tags: 0x%x"
"aborting_tags: 0x%x, reset_tags: 0x%x",
failed_tags,
timedout_tags,
aborting_tags,
reset_tags);
/*
* We could be here for four reasons: abort, reset,
* timeout or error handling. Only one such mopping
* is allowed at a time.
*
* Note that we are already holding the main per port
* mutex; all we need now is siport_mop_mutex.
*/
mutex_enter(&si_portp->siport_mop_mutex);
mutex_enter(&si_portp->siport_mutex);
si_portp->mopping_in_progress = 1;
finished_tags = si_portp->siport_pending_tags &
~slot_status & SI_SLOT_MASK;
unfinished_tags = slot_status & SI_SLOT_MASK &
~failed_tags &
~aborting_tags &
~reset_tags &
~timedout_tags;
/* Send up the finished_tags with SATA_PKT_COMPLETED. */
while (finished_tags) {
tmpslot = ddi_ffs(finished_tags) - 1;
if (tmpslot == -1) {
break;
}
satapkt = si_portp->siport_slot_pkts[tmpslot];
ASSERT(satapkt != NULL);
prb = &si_portp->siport_prbpool[tmpslot];
ASSERT(prb != NULL);
satapkt->satapkt_cmd.satacmd_status_reg =
GET_FIS_COMMAND(prb->prb_fis);
if (satapkt->satapkt_cmd.satacmd_flags.sata_special_regs)
si_copy_out_regs(&satapkt->satapkt_cmd, &prb->prb_fis);
SIDBG1(SIDBG_ERRS, si_ctlp,
"si_mop_commands sending up completed satapkt: %x",
satapkt);
SENDUP_PACKET(si_portp, satapkt, SATA_PKT_COMPLETED);
CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
CLEAR_BIT(finished_tags, tmpslot);
}
ASSERT(finished_tags == 0);
/* Send up failed_tags with SATA_PKT_DEV_ERROR. */
while (failed_tags) {
tmpslot = ddi_ffs(failed_tags) - 1;
if (tmpslot == -1) {
break;
}
SIDBG1(SIDBG_ERRS, si_ctlp, "si3124: si_mop_commands: "
"handling failed slot: 0x%x", tmpslot);
satapkt = si_portp->siport_slot_pkts[tmpslot];
ASSERT(satapkt != NULL);
if (satapkt->satapkt_device.satadev_type ==
SATA_DTYPE_ATAPICD) {
si_set_sense_data(satapkt, SATA_PKT_DEV_ERROR);
}
/*
* The LRAM contains the the modified FIS.
* Read the modified FIS to obtain the Error & Status.
*/
prb = &(si_portp->siport_prbpool[tmpslot]);
prb_word_ptr = (uint32_t *)prb;
for (i = 0; i < (sizeof (si_prb_t)/4); i++) {
prb_word_ptr[i] = ddi_get32(
si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_LRAM(si_ctlp, port,
tmpslot)+i*4));
}
satapkt->satapkt_cmd.satacmd_status_reg =
GET_FIS_COMMAND(prb->prb_fis);
satapkt->satapkt_cmd.satacmd_error_reg =
GET_FIS_FEATURES(prb->prb_fis);
satapkt->satapkt_cmd.satacmd_sec_count_lsb =
GET_FIS_SECTOR_COUNT(prb->prb_fis);
satapkt->satapkt_cmd.satacmd_lba_low_lsb =
GET_FIS_SECTOR(prb->prb_fis);
satapkt->satapkt_cmd.satacmd_lba_mid_lsb =
GET_FIS_CYL_LOW(prb->prb_fis);
satapkt->satapkt_cmd.satacmd_lba_high_lsb =
GET_FIS_CYL_HI(prb->prb_fis);
satapkt->satapkt_cmd.satacmd_device_reg =
GET_FIS_DEV_HEAD(prb->prb_fis);
if (satapkt->satapkt_cmd.satacmd_addr_type == ATA_ADDR_LBA48) {
satapkt->satapkt_cmd.satacmd_sec_count_msb =
GET_FIS_SECTOR_COUNT_EXP(prb->prb_fis);
satapkt->satapkt_cmd.satacmd_lba_low_msb =
GET_FIS_SECTOR_EXP(prb->prb_fis);
satapkt->satapkt_cmd.satacmd_lba_mid_msb =
GET_FIS_CYL_LOW_EXP(prb->prb_fis);
satapkt->satapkt_cmd.satacmd_lba_high_msb =
GET_FIS_CYL_HI_EXP(prb->prb_fis);
}
if (satapkt->satapkt_cmd.satacmd_flags.sata_special_regs)
si_copy_out_regs(&satapkt->satapkt_cmd, &prb->prb_fis);
/*
* In the case of NCQ command failures, the error is
* overwritten by the one obtained from issuing of a
* READ LOG EXTENDED command.
*/
if (si_portp->siport_err_tags_SDBERROR & (1 << tmpslot)) {
satapkt->satapkt_cmd.satacmd_error_reg =
si_read_log_ext(si_ctlp, si_portp, port);
}
SENDUP_PACKET(si_portp, satapkt, SATA_PKT_DEV_ERROR);
CLEAR_BIT(failed_tags, tmpslot);
CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
}
ASSERT(failed_tags == 0);
/* Send up timedout_tags with SATA_PKT_TIMEOUT. */
while (timedout_tags) {
tmpslot = ddi_ffs(timedout_tags) - 1;
if (tmpslot == -1) {
break;
}
satapkt = si_portp->siport_slot_pkts[tmpslot];
ASSERT(satapkt != NULL);
SIDBG1(SIDBG_ERRS, si_ctlp,
"si_mop_commands sending "
"spkt up with PKT_TIMEOUT: %x",
satapkt);
SENDUP_PACKET(si_portp, satapkt, SATA_PKT_TIMEOUT);
CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
CLEAR_BIT(timedout_tags, tmpslot);
}
ASSERT(timedout_tags == 0);
/* Send up aborting packets with SATA_PKT_ABORTED. */
while (aborting_tags) {
tmpslot = ddi_ffs(unfinished_tags) - 1;
if (tmpslot == -1) {
break;
}
satapkt = si_portp->siport_slot_pkts[tmpslot];
ASSERT(satapkt != NULL);
SIDBG1(SIDBG_ERRS, si_ctlp,
"si_mop_commands aborting spkt: %x",
satapkt);
if (satapkt->satapkt_device.satadev_type ==
SATA_DTYPE_ATAPICD) {
si_set_sense_data(satapkt, SATA_PKT_ABORTED);
}
SENDUP_PACKET(si_portp, satapkt, SATA_PKT_ABORTED);
CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
CLEAR_BIT(aborting_tags, tmpslot);
}
ASSERT(aborting_tags == 0);
/* Reset tags are sent up to framework with SATA_PKT_RESET. */
while (reset_tags) {
tmpslot = ddi_ffs(reset_tags) - 1;
if (tmpslot == -1) {
break;
}
satapkt = si_portp->siport_slot_pkts[tmpslot];
ASSERT(satapkt != NULL);
SIDBG1(SIDBG_ERRS, si_ctlp,
"si_mop_commands sending PKT_RESET for "
"reset spkt: %x",
satapkt);
SENDUP_PACKET(si_portp, satapkt, SATA_PKT_RESET);
CLEAR_BIT(reset_tags, tmpslot);
CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
}
ASSERT(reset_tags == 0);
/* Send up the unfinished_tags with SATA_PKT_BUSY. */
while (unfinished_tags) {
tmpslot = ddi_ffs(unfinished_tags) - 1;
if (tmpslot == -1) {
break;
}
satapkt = si_portp->siport_slot_pkts[tmpslot];
ASSERT(satapkt != NULL);
SIDBG1(SIDBG_ERRS, si_ctlp,
"si_mop_commands sending PKT_BUSY for "
"retry spkt: %x",
satapkt);
SENDUP_PACKET(si_portp, satapkt, SATA_PKT_BUSY);
CLEAR_BIT(unfinished_tags, tmpslot);
CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
}
ASSERT(unfinished_tags == 0);
si_portp->mopping_in_progress = 0;
mutex_exit(&si_portp->siport_mutex);
mutex_exit(&si_portp->siport_mop_mutex);
}
/*
* Called by the sata framework to abort the previously sent packet(s).
*
* We reset the device and mop the commands on the port.
*/
static int
si_tran_abort(dev_info_t *dip, sata_pkt_t *spkt, int flag)
{
uint32_t slot_status;
uint8_t port;
int tmpslot;
uint32_t aborting_tags;
uint32_t finished_tags;
si_port_state_t *si_portp;
si_ctl_state_t *si_ctlp;
port = spkt->satapkt_device.satadev_addr.cport;
si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
mutex_enter(&si_ctlp->sictl_mutex);
si_portp = si_ctlp->sictl_ports[port];
mutex_exit(&si_ctlp->sictl_mutex);
SIDBG1(SIDBG_ENTRY, si_ctlp, "si_tran_abort on port: %x", port);
mutex_enter(&si_portp->siport_mutex);
if ((si_portp->siport_port_type == PORT_TYPE_NODEV) ||
!si_portp->siport_active) {
/*
* si_intr_phy_ready_change() may have rendered it to
* PORT_TYPE_NODEV. cfgadm operation may have rendered
* it inactive.
*/
spkt->satapkt_reason = SATA_PKT_PORT_ERROR;
fill_dev_sregisters(si_ctlp, port, &spkt->satapkt_device);
mutex_exit(&si_portp->siport_mutex);
return (SATA_FAILURE);
}
if (flag == SATA_ABORT_ALL_PACKETS) {
aborting_tags = si_portp->siport_pending_tags;
} else {
/*
* Need to abort a single packet.
* Search our siport_slot_pkts[] list for matching spkt.
*/
aborting_tags = 0xffffffff; /* 0xffffffff is impossible tag */
for (tmpslot = 0; tmpslot < SI_NUM_SLOTS; tmpslot++) {
if (si_portp->siport_slot_pkts[tmpslot] == spkt) {
aborting_tags = (0x1 << tmpslot);
break;
}
}
if (aborting_tags == 0xffffffff) {
/* requested packet is not on pending list. */
fill_dev_sregisters(si_ctlp, port,
&spkt->satapkt_device);
mutex_exit(&si_portp->siport_mutex);
return (SATA_FAILURE);
}
}
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
(void) si_reset_dport_wait_till_ready(si_ctlp, si_portp,
port, SI_DEVICE_RESET);
/*
* Compute which have finished and which need to be retried.
*
* The finished tags are siport_pending_tags minus the slot_status.
* The aborting_tags have to be reduced by finished_tags since we
* can't possibly abort a tag which had finished already.
*/
finished_tags = si_portp->siport_pending_tags &
~slot_status & SI_SLOT_MASK;
aborting_tags &= ~finished_tags;
mutex_exit(&si_portp->siport_mutex);
si_mop_commands(si_ctlp,
si_portp,
port,
slot_status,
0, /* failed_tags */
0, /* timedout_tags */
aborting_tags,
0); /* reset_tags */
mutex_enter(&si_portp->siport_mutex);
fill_dev_sregisters(si_ctlp, port, &spkt->satapkt_device);
mutex_exit(&si_portp->siport_mutex);
return (SATA_SUCCESS);
}
/*
* Used to reject all the pending packets on a port during a reset
* operation.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static void
si_reject_all_reset_pkts(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
uint32_t slot_status;
uint32_t reset_tags;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG1(SIDBG_ENTRY, si_ctlp,
"si_reject_all_reset_pkts on port: %x",
port);
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
/* Compute which tags need to be sent up. */
reset_tags = slot_status & SI_SLOT_MASK;
mutex_exit(&si_portp->siport_mutex);
si_mop_commands(si_ctlp,
si_portp,
port,
slot_status,
0, /* failed_tags */
0, /* timedout_tags */
0, /* aborting_tags */
reset_tags);
mutex_enter(&si_portp->siport_mutex);
}
/*
* Called by sata framework to reset a port(s) or device.
*
*/
static int
si_tran_reset_dport(dev_info_t *dip, sata_device_t *sd)
{
si_ctl_state_t *si_ctlp;
uint8_t port = sd->satadev_addr.cport;
int i;
si_port_state_t *si_portp;
int retval = SI_SUCCESS;
si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
SIDBG1(SIDBG_ENTRY, si_ctlp,
"si_tran_reset_port entry: port: 0x%x",
port);
switch (sd->satadev_addr.qual) {
case SATA_ADDR_CPORT:
mutex_enter(&si_ctlp->sictl_mutex);
si_portp = si_ctlp->sictl_ports[port];
mutex_exit(&si_ctlp->sictl_mutex);
mutex_enter(&si_portp->siport_mutex);
retval = si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
SI_PORT_RESET);
si_reject_all_reset_pkts(si_ctlp, si_portp, port);
mutex_exit(&si_portp->siport_mutex);
break;
case SATA_ADDR_DCPORT:
mutex_enter(&si_ctlp->sictl_mutex);
si_portp = si_ctlp->sictl_ports[port];
mutex_exit(&si_ctlp->sictl_mutex);
mutex_enter(&si_portp->siport_mutex);
if ((si_portp->siport_port_type == PORT_TYPE_NODEV) ||
!si_portp->siport_active) {
mutex_exit(&si_portp->siport_mutex);
retval = SI_FAILURE;
break;
}
retval = si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
SI_DEVICE_RESET);
si_reject_all_reset_pkts(si_ctlp, si_portp, port);
mutex_exit(&si_portp->siport_mutex);
break;
case SATA_ADDR_CNTRL:
for (i = 0; i < si_ctlp->sictl_num_ports; i++) {
mutex_enter(&si_ctlp->sictl_mutex);
si_portp = si_ctlp->sictl_ports[port];
mutex_exit(&si_ctlp->sictl_mutex);
mutex_enter(&si_portp->siport_mutex);
retval = si_reset_dport_wait_till_ready(si_ctlp,
si_portp, i, SI_PORT_RESET);
if (retval) {
mutex_exit(&si_portp->siport_mutex);
break;
}
si_reject_all_reset_pkts(si_ctlp, si_portp, port);
mutex_exit(&si_portp->siport_mutex);
}
break;
case SATA_ADDR_PMPORT:
case SATA_ADDR_DPMPORT:
SIDBG0(SIDBG_VERBOSE, si_ctlp,
"port mult reset not implemented yet");
/* FALLSTHROUGH */
default:
retval = SI_FAILURE;
}
return (retval);
}
/*
* Called by sata framework to activate a port as part of hotplug.
*
* Note: Not port-mult aware.
*/
static int
si_tran_hotplug_port_activate(dev_info_t *dip, sata_device_t *satadev)
{
si_ctl_state_t *si_ctlp;
si_port_state_t *si_portp;
uint8_t port;
si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
port = satadev->satadev_addr.cport;
mutex_enter(&si_ctlp->sictl_mutex);
si_portp = si_ctlp->sictl_ports[port];
mutex_exit(&si_ctlp->sictl_mutex);
SIDBG0(SIDBG_ENTRY, si_ctlp, "si_tran_hotplug_port_activate entry");
mutex_enter(&si_portp->siport_mutex);
si_enable_port_interrupts(si_ctlp, port);
/*
* Reset the device so that a si_find_dev_signature() would trigger.
* But this reset is an internal operation; the sata framework does
* not need to know about it.
*/
(void) si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
SI_DEVICE_RESET|SI_RESET_NO_EVENTS_UP);
satadev->satadev_state = SATA_STATE_READY;
si_portp->siport_active = PORT_ACTIVE;
fill_dev_sregisters(si_ctlp, port, satadev);
mutex_exit(&si_portp->siport_mutex);
return (SATA_SUCCESS);
}
/*
* Called by sata framework to deactivate a port as part of hotplug.
*
* Note: Not port-mult aware.
*/
static int
si_tran_hotplug_port_deactivate(dev_info_t *dip, sata_device_t *satadev)
{
si_ctl_state_t *si_ctlp;
si_port_state_t *si_portp;
uint8_t port;
si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
port = satadev->satadev_addr.cport;
mutex_enter(&si_ctlp->sictl_mutex);
si_portp = si_ctlp->sictl_ports[port];
mutex_exit(&si_ctlp->sictl_mutex);
SIDBG0(SIDBG_ENTRY, NULL, "si_tran_hotplug_port_deactivate entry");
mutex_enter(&si_portp->siport_mutex);
if (si_portp->siport_pending_tags & SI_SLOT_MASK) {
/*
* There are pending commands on this port.
* Fail the deactivate request.
*/
satadev->satadev_state = SATA_STATE_READY;
mutex_exit(&si_portp->siport_mutex);
return (SATA_FAILURE);
}
/* mark the device as not accessible any more. */
si_portp->siport_active = PORT_INACTIVE;
/* disable the interrupts on the port. */
si_disable_port_interrupts(si_ctlp, port);
satadev->satadev_state = SATA_PSTATE_SHUTDOWN;
fill_dev_sregisters(si_ctlp, port, satadev);
/*
* Since we are implementing the port deactivation in software only,
* we need to fake a valid value for sstatus.
*/
SSTATUS_SET_DET(satadev->satadev_scr.sstatus, SSTATUS_DET_PHYOFFLINE);
SSTATUS_SET_IPM(satadev->satadev_scr.sstatus, SSTATUS_IPM_NODEV_NOPHY);
mutex_exit(&si_portp->siport_mutex);
return (SATA_SUCCESS);
}
/*
* Allocates the si_port_state_t.
*/
static int
si_alloc_port_state(si_ctl_state_t *si_ctlp, int port)
{
si_port_state_t *si_portp;
si_ctlp->sictl_ports[port] = (si_port_state_t *)kmem_zalloc(
sizeof (si_port_state_t), KM_SLEEP);
si_portp = si_ctlp->sictl_ports[port];
mutex_init(&si_portp->siport_mutex, NULL, MUTEX_DRIVER,
(void *)(uintptr_t)si_ctlp->sictl_intr_pri);
mutex_init(&si_portp->siport_mop_mutex, NULL, MUTEX_DRIVER,
(void *)(uintptr_t)si_ctlp->sictl_intr_pri);
mutex_enter(&si_portp->siport_mutex);
/* allocate prb & sgt pkts for this port. */
if (si_alloc_prbpool(si_ctlp, port)) {
mutex_exit(&si_portp->siport_mutex);
kmem_free(si_ctlp->sictl_ports[port], sizeof (si_port_state_t));
return (SI_FAILURE);
}
if (si_alloc_sgbpool(si_ctlp, port)) {
si_dealloc_prbpool(si_ctlp, port);
mutex_exit(&si_portp->siport_mutex);
kmem_free(si_ctlp->sictl_ports[port], sizeof (si_port_state_t));
return (SI_FAILURE);
}
si_portp->siport_active = PORT_ACTIVE;
mutex_exit(&si_portp->siport_mutex);
return (SI_SUCCESS);
}
/*
* Deallocates the si_port_state_t.
*/
static void
si_dealloc_port_state(si_ctl_state_t *si_ctlp, int port)
{
si_port_state_t *si_portp;
si_portp = si_ctlp->sictl_ports[port];
mutex_enter(&si_portp->siport_mutex);
si_dealloc_sgbpool(si_ctlp, port);
si_dealloc_prbpool(si_ctlp, port);
mutex_exit(&si_portp->siport_mutex);
mutex_destroy(&si_portp->siport_mutex);
mutex_destroy(&si_portp->siport_mop_mutex);
kmem_free(si_ctlp->sictl_ports[port], sizeof (si_port_state_t));
}
/*
* Allocates the SGB (Scatter Gather Block) incore buffer.
*/
static int
si_alloc_sgbpool(si_ctl_state_t *si_ctlp, int port)
{
si_port_state_t *si_portp;
uint_t cookie_count;
size_t incore_sgbpool_size = SI_NUM_SLOTS * sizeof (si_sgblock_t);
size_t ret_len;
ddi_dma_cookie_t sgbpool_dma_cookie;
si_portp = si_ctlp->sictl_ports[port];
/* allocate sgbpool dma handle. */
if (ddi_dma_alloc_handle(si_ctlp->sictl_devinfop,
&prb_sgt_dma_attr,
DDI_DMA_SLEEP,
NULL,
&si_portp->siport_sgbpool_dma_handle) !=
DDI_SUCCESS) {
return (SI_FAILURE);
}
/* allocate the memory for sgbpool. */
if (ddi_dma_mem_alloc(si_portp->siport_sgbpool_dma_handle,
incore_sgbpool_size,
&accattr,
DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP,
NULL,
(caddr_t *)&si_portp->siport_sgbpool,
&ret_len,
&si_portp->siport_sgbpool_acc_handle) != NULL) {
/* error.. free the dma handle. */
ddi_dma_free_handle(&si_portp->siport_sgbpool_dma_handle);
return (SI_FAILURE);
}
/* now bind it */
if (ddi_dma_addr_bind_handle(si_portp->siport_sgbpool_dma_handle,
NULL,
(caddr_t)si_portp->siport_sgbpool,
incore_sgbpool_size,
DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP,
NULL,
&sgbpool_dma_cookie,
&cookie_count) != DDI_DMA_MAPPED) {
/* error.. free the dma handle & free the memory. */
ddi_dma_mem_free(&si_portp->siport_sgbpool_acc_handle);
ddi_dma_free_handle(&si_portp->siport_sgbpool_dma_handle);
return (SI_FAILURE);
}
si_portp->siport_sgbpool_physaddr = sgbpool_dma_cookie.dmac_laddress;
return (SI_SUCCESS);
}
/*
* Deallocates the SGB (Scatter Gather Block) incore buffer.
*/
static void
si_dealloc_sgbpool(si_ctl_state_t *si_ctlp, int port)
{
si_port_state_t *si_portp = si_ctlp->sictl_ports[port];
/* Unbind the dma handle first. */
(void) ddi_dma_unbind_handle(si_portp->siport_sgbpool_dma_handle);
/* Then free the underlying memory. */
ddi_dma_mem_free(&si_portp->siport_sgbpool_acc_handle);
/* Now free the handle itself. */
ddi_dma_free_handle(&si_portp->siport_sgbpool_dma_handle);
}
/*
* Allocates the PRB (Port Request Block) incore packets.
*/
static int
si_alloc_prbpool(si_ctl_state_t *si_ctlp, int port)
{
si_port_state_t *si_portp;
uint_t cookie_count;
size_t incore_pkt_size = SI_NUM_SLOTS * sizeof (si_prb_t);
size_t ret_len;
ddi_dma_cookie_t prbpool_dma_cookie;
si_portp = si_ctlp->sictl_ports[port];
/* allocate prb pkts. */
if (ddi_dma_alloc_handle(si_ctlp->sictl_devinfop,
&prb_sgt_dma_attr,
DDI_DMA_SLEEP,
NULL,
&si_portp->siport_prbpool_dma_handle) !=
DDI_SUCCESS) {
return (SI_FAILURE);
}
if (ddi_dma_mem_alloc(si_portp->siport_prbpool_dma_handle,
incore_pkt_size,
&accattr,
DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP,
NULL,
(caddr_t *)&si_portp->siport_prbpool,
&ret_len,
&si_portp->siport_prbpool_acc_handle) != NULL) {
/* error.. free the dma handle. */
ddi_dma_free_handle(&si_portp->siport_prbpool_dma_handle);
return (SI_FAILURE);
}
if (ddi_dma_addr_bind_handle(si_portp->siport_prbpool_dma_handle,
NULL,
(caddr_t)si_portp->siport_prbpool,
incore_pkt_size,
DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP,
NULL,
&prbpool_dma_cookie,
&cookie_count) != DDI_DMA_MAPPED) {
/* error.. free the dma handle & free the memory. */
ddi_dma_mem_free(&si_portp->siport_prbpool_acc_handle);
ddi_dma_free_handle(&si_portp->siport_prbpool_dma_handle);
return (SI_FAILURE);
}
si_portp->siport_prbpool_physaddr =
prbpool_dma_cookie.dmac_laddress;
return (SI_SUCCESS);
}
/*
* Deallocates the PRB (Port Request Block) incore packets.
*/
static void
si_dealloc_prbpool(si_ctl_state_t *si_ctlp, int port)
{
si_port_state_t *si_portp = si_ctlp->sictl_ports[port];
/* Unbind the prb dma handle first. */
(void) ddi_dma_unbind_handle(si_portp->siport_prbpool_dma_handle);
/* Then free the underlying memory. */
ddi_dma_mem_free(&si_portp->siport_prbpool_acc_handle);
/* Now free the handle itself. */
ddi_dma_free_handle(&si_portp->siport_prbpool_dma_handle);
}
/*
* Soft-reset the port to find the signature of the device connected to
* the port.
*/
static void
si_find_dev_signature(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port,
int pmp)
{
si_prb_t *prb;
uint32_t slot_status, signature;
int slot, loop_count;
SIDBG2(SIDBG_ENTRY|SIDBG_INIT, si_ctlp,
"si_find_dev_signature enter: port: %x, pmp: %x",
port, pmp);
/* Build a Soft Reset PRB in host memory. */
mutex_enter(&si_portp->siport_mutex);
slot = si_claim_free_slot(si_ctlp, si_portp, port);
if (slot == -1) {
/* Empty slot could not be found. */
if (pmp != PORTMULT_CONTROL_PORT) {
/* We are behind port multiplier. */
si_portp->siport_portmult_state.sipm_port_type[pmp] =
PORT_TYPE_NODEV;
} else {
si_portp->siport_port_type = PORT_TYPE_NODEV;
}
mutex_exit(&si_portp->siport_mutex);
return;
}
prb = &si_portp->siport_prbpool[slot];
bzero((void *)prb, sizeof (si_prb_t));
SET_FIS_PMP(prb->prb_fis, pmp);
SET_PRB_CONTROL_SOFT_RESET(prb);
#if SI_DEBUG
if (si_debug_flags & SIDBG_DUMP_PRB) {
char *ptr;
int j;
ptr = (char *)prb;
cmn_err(CE_WARN, "si_find_dev_signature, prb: ");
for (j = 0; j < (sizeof (si_prb_t)); j++) {
if (j%4 == 0) {
cmn_err(CE_WARN, "----");
}
cmn_err(CE_WARN, "%x ", ptr[j]);
}
}
#endif /* SI_DEBUG */
/* deliver soft reset prb to empty slot. */
POST_PRB_ADDR(si_ctlp, si_portp, port, slot);
loop_count = 0;
/* Loop till the soft reset is finished. */
do {
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
if (loop_count++ > SI_POLLRATE_SOFT_RESET) {
/* We are effectively timing out after 10 sec. */
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (slot_status & SI_SLOT_MASK & (0x1 << slot));
SIDBG2(SIDBG_POLL_LOOP, si_ctlp,
"si_find_dev_signature: loop count: %d, slot_status: 0x%x",
loop_count, slot_status);
CLEAR_BIT(si_portp->siport_pending_tags, slot);
/* Read device signature from command slot. */
signature = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SIGNATURE_MSB(si_ctlp, port, slot)));
signature <<= 8;
signature |= (0xff & ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SIGNATURE_LSB(si_ctlp,
port, slot))));
SIDBG1(SIDBG_INIT, si_ctlp, "Device signature: 0x%x", signature);
if (signature == SI_SIGNATURE_PORT_MULTIPLIER) {
SIDBG2(SIDBG_INIT, si_ctlp,
"Found multiplier at cport: 0x%d, pmport: 0x%x",
port, pmp);
if (pmp != PORTMULT_CONTROL_PORT) {
/*
* It is wrong to chain a port multiplier behind
* another port multiplier.
*/
si_portp->siport_portmult_state.sipm_port_type[pmp] =
PORT_TYPE_NODEV;
} else {
si_portp->siport_port_type = PORT_TYPE_MULTIPLIER;
mutex_exit(&si_portp->siport_mutex);
(void) si_enumerate_port_multiplier(si_ctlp,
si_portp, port);
mutex_enter(&si_portp->siport_mutex);
}
si_init_port(si_ctlp, port);
} else if (signature == SI_SIGNATURE_ATAPI) {
if (pmp != PORTMULT_CONTROL_PORT) {
/* We are behind port multiplier. */
si_portp->siport_portmult_state.sipm_port_type[pmp] =
PORT_TYPE_ATAPI;
} else {
si_portp->siport_port_type = PORT_TYPE_ATAPI;
si_init_port(si_ctlp, port);
}
SIDBG2(SIDBG_INIT, si_ctlp,
"Found atapi at : cport: %x, pmport: %x",
port, pmp);
} else if (signature == SI_SIGNATURE_DISK) {
if (pmp != PORTMULT_CONTROL_PORT) {
/* We are behind port multiplier. */
si_portp->siport_portmult_state.sipm_port_type[pmp] =
PORT_TYPE_DISK;
} else {
si_portp->siport_port_type = PORT_TYPE_DISK;
si_init_port(si_ctlp, port);
}
SIDBG2(SIDBG_INIT, si_ctlp,
"found disk at : cport: %x, pmport: %x",
port, pmp);
} else {
if (pmp != PORTMULT_CONTROL_PORT) {
/* We are behind port multiplier. */
si_portp->siport_portmult_state.sipm_port_type[pmp] =
PORT_TYPE_UNKNOWN;
} else {
si_portp->siport_port_type = PORT_TYPE_UNKNOWN;
}
SIDBG3(SIDBG_INIT, si_ctlp,
"Found unknown signature 0x%x at: port: %x, pmp: %x",
signature, port, pmp);
}
mutex_exit(&si_portp->siport_mutex);
}
/*
* Polls for the completion of the command. This is safe with both
* interrupts enabled or disabled.
*/
static void
si_poll_cmd(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port,
int slot,
sata_pkt_t *satapkt)
{
uint32_t slot_status;
int pkt_timeout_ticks;
uint32_t port_intr_status;
int in_panic = ddi_in_panic();
SIDBG1(SIDBG_ENTRY, si_ctlp, "si_poll_cmd entered: port: 0x%x", port);
pkt_timeout_ticks = drv_usectohz((clock_t)satapkt->satapkt_time *
1000000);
mutex_enter(&si_portp->siport_mutex);
/* we start out with SATA_PKT_COMPLETED as the satapkt_reason */
satapkt->satapkt_reason = SATA_PKT_COMPLETED;
do {
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
if (slot_status & SI_SLOT_MASK & (0x1 << slot)) {
if (in_panic) {
/*
* If we are in panic, we can't rely on
* timers; so, busy wait instead of delay().
*/
mutex_exit(&si_portp->siport_mutex);
drv_usecwait(SI_1MS_USECS);
mutex_enter(&si_portp->siport_mutex);
} else {
mutex_exit(&si_portp->siport_mutex);
#ifndef __lock_lint
delay(SI_1MS_TICKS);
#endif /* __lock_lint */
mutex_enter(&si_portp->siport_mutex);
}
} else {
break;
}
pkt_timeout_ticks -= SI_1MS_TICKS;
} while (pkt_timeout_ticks > 0);
if (satapkt->satapkt_reason != SATA_PKT_COMPLETED) {
/* The si_mop_command() got to our packet before us */
goto poll_done;
}
/*
* Interrupts and timers may not be working properly in a crash dump
* situation; we may need to handle all the three conditions here:
* successful completion, packet failure and packet timeout.
*/
if (IS_ATTENTION_RAISED(slot_status)) { /* error seen on port */
port_intr_status = ddi_get32(si_ctlp->sictl_global_acc_handle,
(uint32_t *)PORT_INTERRUPT_STATUS(si_ctlp, port));
SIDBG2(SIDBG_VERBOSE, si_ctlp,
"si_poll_cmd: port_intr_status: 0x%x, port: %x",
port_intr_status, port);
if (port_intr_status & INTR_COMMAND_ERROR) {
mutex_exit(&si_portp->siport_mutex);
(void) si_intr_command_error(si_ctlp, si_portp, port);
mutex_enter(&si_portp->siport_mutex);
goto poll_done;
/*
* Why do we need to call si_intr_command_error() ?
*
* Answer: Even if the current packet is not the
* offending command, we need to restart the stalled
* port; (may be, the interrupts are not working well
* in panic condition). The call to routine
* si_intr_command_error() will achieve that.
*
* What if the interrupts are working fine and the
* si_intr_command_error() gets called once more from
* interrupt context ?
*
* Answer: The second instance of routine
* si_intr_command_error() will not mop anything
* since the first error handler has already blown
* away the hardware pending queues through reset.
*
* Will the si_intr_command_error() hurt current
* packet ?
*
* Answer: No.
*/
} else {
/* Ignore any non-error interrupts at this stage */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_INTERRUPT_STATUS(si_ctlp,
port)),
port_intr_status & INTR_MASK);
}
} else if (slot_status & SI_SLOT_MASK & (0x1 << slot)) {
satapkt->satapkt_reason = SATA_PKT_TIMEOUT;
} /* else: the command completed successfully */
if ((satapkt->satapkt_cmd.satacmd_cmd_reg ==
SATAC_WRITE_FPDMA_QUEUED) ||
(satapkt->satapkt_cmd.satacmd_cmd_reg ==
SATAC_READ_FPDMA_QUEUED)) {
si_portp->siport_pending_ncq_count--;
}
CLEAR_BIT(si_portp->siport_pending_tags, slot);
poll_done:
mutex_exit(&si_portp->siport_mutex);
/*
* tidbit: What is the interaction of abort with polling ?
* What happens if the current polled pkt is aborted in parallel ?
*
* Answer: Assuming that the si_mop_commands() completes ahead
* of polling, all it does is to set the satapkt_reason to
* SPKT_PKT_ABORTED. That would be fine with us.
*
* The same logic applies to reset interacting with polling.
*/
}
/*
* Searches for and claims a free slot.
*
* Returns: SI_FAILURE if no slots found
* claimed slot number if successful
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
/*ARGSUSED*/
static int
si_claim_free_slot(si_ctl_state_t *si_ctlp, si_port_state_t *si_portp, int port)
{
uint32_t free_slots;
int slot;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG1(SIDBG_ENTRY, si_ctlp,
"si_claim_free_slot entry: siport_pending_tags: %x",
si_portp->siport_pending_tags);
free_slots = (~si_portp->siport_pending_tags) & SI_SLOT_MASK;
slot = ddi_ffs(free_slots) - 1;
if (slot == -1) {
SIDBG0(SIDBG_VERBOSE, si_ctlp,
"si_claim_free_slot: no empty slots");
return (SI_FAILURE);
}
si_portp->siport_pending_tags |= (0x1 << slot);
SIDBG1(SIDBG_VERBOSE, si_ctlp, "si_claim_free_slot: found slot: 0x%x",
slot);
return (slot);
}
/*
* Builds the PRB for the sata packet and delivers it to controller.
*
* Returns:
* slot number if we can obtain a slot successfully
* otherwise, return SI_FAILURE
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static int
si_deliver_satapkt(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port,
sata_pkt_t *spkt)
{
int slot;
si_prb_t *prb;
sata_cmd_t *cmd;
si_sge_t *sgep; /* scatter gather entry pointer */
si_sgt_t *sgtp; /* scatter gather table pointer */
si_sgblock_t *sgbp; /* scatter gather block pointer */
int i, j, cookie_index;
int ncookies;
int is_atapi = 0;
ddi_dma_cookie_t cookie;
_NOTE(ASSUMING_PROTECTED(si_portp))
slot = si_claim_free_slot(si_ctlp, si_portp, port);
if (slot == -1) {
return (SI_FAILURE);
}
if (spkt->satapkt_device.satadev_type == SATA_DTYPE_ATAPICD) {
is_atapi = 1;
}
if ((si_portp->siport_port_type == PORT_TYPE_NODEV) ||
!si_portp->siport_active) {
/*
* si_intr_phy_ready_change() may have rendered it to
* PORT_TYPE_NODEV. cfgadm operation may have rendered
* it inactive.
*/
spkt->satapkt_reason = SATA_PKT_PORT_ERROR;
fill_dev_sregisters(si_ctlp, port, &spkt->satapkt_device);
return (SI_FAILURE);
}
prb = &(si_portp->siport_prbpool[slot]);
bzero((void *)prb, sizeof (si_prb_t));
cmd = &spkt->satapkt_cmd;
SIDBG4(SIDBG_ENTRY, si_ctlp,
"si_deliver_satpkt entry: cmd_reg: 0x%x, slot: 0x%x, \
port: %x, satapkt: %x",
cmd->satacmd_cmd_reg, slot, port, (uint32_t)(intptr_t)spkt);
/* Now fill the prb. */
if (is_atapi) {
if (spkt->satapkt_cmd.satacmd_flags.sata_data_direction ==
SATA_DIR_READ) {
SET_PRB_CONTROL_PKT_READ(prb);
} else if (spkt->satapkt_cmd.satacmd_flags.sata_data_direction
== SATA_DIR_WRITE) {
SET_PRB_CONTROL_PKT_WRITE(prb);
}
}
SET_FIS_TYPE(prb->prb_fis, REGISTER_FIS_H2D);
if ((spkt->satapkt_device.satadev_addr.qual == SATA_ADDR_PMPORT) ||
(spkt->satapkt_device.satadev_addr.qual == SATA_ADDR_DPMPORT)) {
SET_FIS_PMP(prb->prb_fis,
spkt->satapkt_device.satadev_addr.pmport);
}
SET_FIS_CDMDEVCTL(prb->prb_fis, 1);
SET_FIS_COMMAND(prb->prb_fis, cmd->satacmd_cmd_reg);
SET_FIS_FEATURES(prb->prb_fis, cmd->satacmd_features_reg);
SET_FIS_SECTOR_COUNT(prb->prb_fis, cmd->satacmd_sec_count_lsb);
switch (cmd->satacmd_addr_type) {
case ATA_ADDR_LBA:
/* fallthru */
case ATA_ADDR_LBA28:
/* LBA[7:0] */
SET_FIS_SECTOR(prb->prb_fis, cmd->satacmd_lba_low_lsb);
/* LBA[15:8] */
SET_FIS_CYL_LOW(prb->prb_fis, cmd->satacmd_lba_mid_lsb);
/* LBA[23:16] */
SET_FIS_CYL_HI(prb->prb_fis, cmd->satacmd_lba_high_lsb);
/* LBA [27:24] (also called dev_head) */
SET_FIS_DEV_HEAD(prb->prb_fis, cmd->satacmd_device_reg);
break;
case ATA_ADDR_LBA48:
/* LBA[7:0] */
SET_FIS_SECTOR(prb->prb_fis, cmd->satacmd_lba_low_lsb);
/* LBA[15:8] */
SET_FIS_CYL_LOW(prb->prb_fis, cmd->satacmd_lba_mid_lsb);
/* LBA[23:16] */
SET_FIS_CYL_HI(prb->prb_fis, cmd->satacmd_lba_high_lsb);
/* LBA [31:24] */
SET_FIS_SECTOR_EXP(prb->prb_fis, cmd->satacmd_lba_low_msb);
/* LBA [39:32] */
SET_FIS_CYL_LOW_EXP(prb->prb_fis, cmd->satacmd_lba_mid_msb);
/* LBA [47:40] */
SET_FIS_CYL_HI_EXP(prb->prb_fis, cmd->satacmd_lba_high_msb);
/* Set dev_head */
SET_FIS_DEV_HEAD(prb->prb_fis, cmd->satacmd_device_reg);
/* Set the extended sector count and features */
SET_FIS_SECTOR_COUNT_EXP(prb->prb_fis,
cmd->satacmd_sec_count_msb);
SET_FIS_FEATURES_EXP(prb->prb_fis,
cmd->satacmd_features_reg_ext);
break;
}
if (cmd->satacmd_flags.sata_queued) {
/*
* For queued commands, the TAG for the sector count lsb is
* generated from current slot number.
*/
SET_FIS_SECTOR_COUNT(prb->prb_fis, slot << 3);
}
if ((cmd->satacmd_cmd_reg == SATAC_WRITE_FPDMA_QUEUED) ||
(cmd->satacmd_cmd_reg == SATAC_READ_FPDMA_QUEUED)) {
si_portp->siport_pending_ncq_count++;
}
/* *** now fill the scatter gather list ******* */
if (is_atapi) { /* It is an ATAPI drive */
/* atapi command goes into sge0 */
bcopy(cmd->satacmd_acdb, &prb->prb_sge0, sizeof (si_sge_t));
/* Now fill sge1 with pointer to external SGT. */
if (spkt->satapkt_cmd.satacmd_num_dma_cookies) {
prb->prb_sge1.sge_addr =
si_portp->siport_sgbpool_physaddr +
slot*sizeof (si_sgblock_t);
SET_SGE_LNK(prb->prb_sge1);
} else {
SET_SGE_TRM(prb->prb_sge1);
}
} else {
/* Fill the sge0 */
if (spkt->satapkt_cmd.satacmd_num_dma_cookies) {
prb->prb_sge0.sge_addr =
si_portp->siport_sgbpool_physaddr +
slot*sizeof (si_sgblock_t);
SET_SGE_LNK(prb->prb_sge0);
} else {
SET_SGE_TRM(prb->prb_sge0);
}
/* sge1 is left empty in non-ATAPI case */
}
bzero(&si_portp->siport_sgbpool[slot], sizeof (si_sgblock_t));
ncookies = spkt->satapkt_cmd.satacmd_num_dma_cookies;
ASSERT(ncookies <= SI_MAX_SGL_LENGTH);
SIDBG1(SIDBG_COOKIES, si_ctlp, "total ncookies: %d", ncookies);
if (ncookies == 0) {
sgbp = &si_portp->siport_sgbpool[slot];
sgtp = &sgbp->sgb_sgt[0];
sgep = &sgtp->sgt_sge[0];
/* No cookies. Terminate the chain. */
SIDBG0(SIDBG_COOKIES, si_ctlp, "empty cookies: terminating.");
sgep->sge_addr_low = 0;
sgep->sge_addr_high = 0;
sgep->sge_data_count = 0;
SET_SGE_TRM((*sgep));
goto sgl_fill_done;
}
for (i = 0, cookie_index = 0, sgbp = &si_portp->siport_sgbpool[slot];
i < SI_MAX_SGT_TABLES_PER_PRB; i++) {
sgtp = &sgbp->sgb_sgt[i];
/* Now fill the first 3 entries of SGT in the loop below. */
for (j = 0, sgep = &sgtp->sgt_sge[0];
((j < 3) && (cookie_index < ncookies-1));
j++, cookie_index++, sgep++) {
ASSERT(cookie_index < ncookies);
SIDBG2(SIDBG_COOKIES, si_ctlp,
"inner loop: cookie_index: %d, ncookies: %d",
cookie_index,
ncookies);
cookie = spkt->satapkt_cmd.
satacmd_dma_cookie_list[cookie_index];
sgep->sge_addr_low = cookie._dmu._dmac_la[0];
sgep->sge_addr_high = cookie._dmu._dmac_la[1];
sgep->sge_data_count = cookie.dmac_size;
}
/*
* If this happens to be the last cookie, we terminate it here.
* Otherwise, we link to next SGT.
*/
if (cookie_index == ncookies-1) {
/* This is the last cookie. Terminate the chain. */
SIDBG2(SIDBG_COOKIES, si_ctlp,
"filling the last: cookie_index: %d, "
"ncookies: %d",
cookie_index,
ncookies);
cookie = spkt->satapkt_cmd.
satacmd_dma_cookie_list[cookie_index];
sgep->sge_addr_low = cookie._dmu._dmac_la[0];
sgep->sge_addr_high = cookie._dmu._dmac_la[1];
sgep->sge_data_count = cookie.dmac_size;
SET_SGE_TRM((*sgep));
break; /* we break the loop */
} else {
/* This is not the last one. So link it. */
SIDBG2(SIDBG_COOKIES, si_ctlp,
"linking SGT: cookie_index: %d, ncookies: %d",
cookie_index,
ncookies);
sgep->sge_addr = si_portp->siport_sgbpool_physaddr +
slot * sizeof (si_sgblock_t) +
(i+1) * sizeof (si_sgt_t);
SET_SGE_LNK((*sgep));
}
}
/* *** finished filling the scatter gather list ******* */
sgl_fill_done:
/* Now remember the sata packet in siport_slot_pkts[]. */
si_portp->siport_slot_pkts[slot] = spkt;
/*
* We are overloading satapkt_hba_driver_private with
* watched_cycle count.
*/
spkt->satapkt_hba_driver_private = (void *)(intptr_t)0;
if (is_atapi) {
/* program the packet_lenth if it is atapi device. */
#ifdef ATAPI_2nd_PHASE
/*
* Framework needs to calculate the acdb_len based on
* identify packet data. This needs to be accomplished
* in second phase of the project.
*/
ASSERT((cmd->satacmd_acdb_len == 12) ||
(cmd->satacmd_acdb_len == 16));
SIDBG1(SIDBG_VERBOSE, si_ctlp, "deliver: acdb_len: %d",
cmd->satacmd_acdb_len);
if (cmd->satacmd_acdb_len == 16) {
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
PORT_CONTROL_SET_BITS_PACKET_LEN);
} else {
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
PORT_CONTROL_CLEAR_BITS_PACKET_LEN);
}
#else /* ATAPI_2nd_PHASE */
/* hard coding for now to 12 bytes */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
PORT_CONTROL_CLEAR_BITS_PACKET_LEN);
#endif /* ATAPI_2nd_PHASE */
}
#if SI_DEBUG
if (si_debug_flags & SIDBG_DUMP_PRB) {
if (!(is_atapi && (prb->prb_sge0.sge_addr_low == 0))) {
/*
* Do not dump the atapi Test-Unit-Ready commands.
* The sd_media_watch spews too many of these.
*/
int *ptr;
si_sge_t *tmpsgep;
int j;
ptr = (int *)prb;
cmn_err(CE_WARN, "si_deliver_satpkt prb: ");
for (j = 0; j < (sizeof (si_prb_t)/4); j++) {
cmn_err(CE_WARN, "%x ", ptr[j]);
}
cmn_err(CE_WARN,
"si_deliver_satpkt sgt: low, high, count link");
for (j = 0,
tmpsgep = (si_sge_t *)
&si_portp->siport_sgbpool[slot];
j < (sizeof (si_sgblock_t)/ sizeof (si_sge_t));
j++, tmpsgep++) {
ptr = (int *)tmpsgep;
cmn_err(CE_WARN, "%x %x %x %x",
ptr[0],
ptr[1],
ptr[2],
ptr[3]);
if (IS_SGE_TRM_SET((*tmpsgep))) {
break;
}
}
}
}
#endif /* SI_DEBUG */
/* Deliver PRB */
POST_PRB_ADDR(si_ctlp, si_portp, port, slot);
return (slot);
}
/*
* Initialize the controller and set up driver data structures.
*
* This routine can be called from three separate cases: DDI_ATTACH, PM_LEVEL_D0
* and DDI_RESUME. The DDI_ATTACH case is different from other two cases; the
* memory allocation & device signature probing are attempted only during
* DDI_ATTACH case. In the case of PM_LEVEL_D0 & DDI_RESUME, we are starting
* from a previously initialized state; so there is no need to allocate memory
* or to attempt probing the device signatures.
*/
static int
si_initialize_controller(si_ctl_state_t *si_ctlp)
{
uint32_t port_status;
uint32_t SStatus;
uint32_t SControl;
int port;
int loop_count = 0;
si_port_state_t *si_portp;
SIDBG0(SIDBG_INIT|SIDBG_ENTRY, si_ctlp,
"si3124: si_initialize_controller entered");
mutex_enter(&si_ctlp->sictl_mutex);
/* Remove the Global Reset. */
ddi_put32(si_ctlp->sictl_global_acc_handle,
(uint32_t *)GLOBAL_CONTROL_REG(si_ctlp),
GLOBAL_CONTROL_REG_BITS_CLEAR);
for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
if (si_ctlp->sictl_flags & SI_ATTACH) {
/*
* We allocate the port state only during attach
* sequence. We don't want to do it during
* suspend/resume sequence.
*/
if (si_alloc_port_state(si_ctlp, port)) {
mutex_exit(&si_ctlp->sictl_mutex);
return (SI_FAILURE);
}
}
si_portp = si_ctlp->sictl_ports[port];
mutex_enter(&si_portp->siport_mutex);
/* Clear Port Reset. */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
PORT_CONTROL_SET_BITS_PORT_RESET);
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
PORT_CONTROL_CLEAR_BITS_PORT_RESET);
/*
* Arm the interrupts for: Cmd completion, Cmd error,
* Port Ready, PM Change, PhyRdyChange, Commwake,
* UnrecFIS, Devxchanged, SDBNotify.
*/
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_INTERRUPT_ENABLE_SET(si_ctlp, port),
(INTR_COMMAND_COMPLETE |
INTR_COMMAND_ERROR |
INTR_PORT_READY |
INTR_POWER_CHANGE |
INTR_PHYRDY_CHANGE |
INTR_COMWAKE_RECEIVED |
INTR_UNRECOG_FIS |
INTR_DEV_XCHANGED |
INTR_SETDEVBITS_NOTIFY));
/* Now enable the interrupts. */
si_enable_port_interrupts(si_ctlp, port);
/*
* The following PHY initialization is redundant in
* in x86 since the BIOS anyway does this as part of
* device enumeration during the power up. But this
* is a required step in sparc since there is no BIOS.
*
* The way to initialize the PHY is to write a 1 and then
* a 0 to DET field of SControl register.
*/
/*
* Fetch the current SControl before writing the
* DET part with 1
*/
SControl = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_SCONTROL(si_ctlp, port));
SCONTROL_SET_DET(SControl, SCONTROL_DET_COMRESET);
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SCONTROL(si_ctlp, port)),
SControl);
#ifndef __lock_lint
delay(SI_10MS_TICKS); /* give time for COMRESET to percolate */
#endif /* __lock_lint */
/*
* Now fetch the SControl again and rewrite the
* DET part with 0
*/
SControl = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_SCONTROL(si_ctlp, port));
SCONTROL_SET_DET(SControl, SCONTROL_DET_NOACTION);
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SCONTROL(si_ctlp, port)),
SControl);
/*
* PHY may be initialized by now. Check the DET field of
* SStatus to determine if there is a device present.
*
* The DET field is valid only if IPM field indicates that
* the interface is in active state.
*/
loop_count = 0;
do {
SStatus = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_SSTATUS(si_ctlp, port));
if (SSTATUS_GET_IPM(SStatus) !=
SSTATUS_IPM_INTERFACE_ACTIVE) {
/*
* If the interface is not active, the DET field
* is considered not accurate. So we want to
* continue looping.
*/
SSTATUS_SET_DET(SStatus,
SSTATUS_DET_NODEV_NOPHY);
}
if (loop_count++ > SI_POLLRATE_SSTATUS) {
/*
* We are effectively timing out after 0.1 sec.
*/
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (SSTATUS_GET_DET(SStatus) !=
SSTATUS_DET_DEVPRESENT_PHYONLINE);
SIDBG2(SIDBG_POLL_LOOP|SIDBG_INIT, si_ctlp,
"si_initialize_controller: 1st loop count: %d, "
"SStatus: 0x%x",
loop_count,
SStatus);
if ((SSTATUS_GET_IPM(SStatus) !=
SSTATUS_IPM_INTERFACE_ACTIVE) ||
(SSTATUS_GET_DET(SStatus) !=
SSTATUS_DET_DEVPRESENT_PHYONLINE)) {
/*
* Either the port is not active or there
* is no device present.
*/
si_ctlp->sictl_ports[port]->siport_port_type =
PORT_TYPE_NODEV;
mutex_exit(&si_portp->siport_mutex);
continue;
}
/* Wait until Port Ready */
loop_count = 0;
do {
port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_STATUS(si_ctlp, port));
if (loop_count++ > SI_POLLRATE_PORTREADY) {
/*
* We are effectively timing out after 0.5 sec.
*/
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (!(port_status & PORT_STATUS_BITS_PORT_READY));
SIDBG1(SIDBG_POLL_LOOP|SIDBG_INIT, si_ctlp,
"si_initialize_controller: 2nd loop count: %d",
loop_count);
if (si_ctlp->sictl_flags & SI_ATTACH) {
/*
* We want to probe for dev signature only during attach
* case. Don't do it during suspend/resume sequence.
*/
if (port_status & PORT_STATUS_BITS_PORT_READY) {
mutex_exit(&si_portp->siport_mutex);
si_find_dev_signature(si_ctlp, si_portp, port,
PORTMULT_CONTROL_PORT);
mutex_enter(&si_portp->siport_mutex);
} else {
si_ctlp->sictl_ports[port]->siport_port_type =
PORT_TYPE_NODEV;
}
}
mutex_exit(&si_portp->siport_mutex);
}
mutex_exit(&si_ctlp->sictl_mutex);
return (SI_SUCCESS);
}
/*
* Reverse of si_initialize_controller().
*
* WARNING, WARNING: The caller is expected to obtain the sictl_mutex
* before calling us.
*/
static void
si_deinititalize_controller(si_ctl_state_t *si_ctlp)
{
int port;
_NOTE(ASSUMING_PROTECTED(si_ctlp))
SIDBG0(SIDBG_INIT|SIDBG_ENTRY, si_ctlp,
"si3124: si_deinititalize_controller entered");
/* disable all the interrupts. */
si_disable_all_interrupts(si_ctlp);
if (si_ctlp->sictl_flags & SI_DETACH) {
/*
* We want to dealloc all the memory in detach case.
*/
for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
si_dealloc_port_state(si_ctlp, port);
}
}
}
/*
* Prepare the port ready for usage.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static void
si_init_port(si_ctl_state_t *si_ctlp, int port)
{
SIDBG1(SIDBG_ENTRY|SIDBG_INIT, si_ctlp,
"si_init_port entered: port: 0x%x",
port);
/* Initialize the port. */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
PORT_CONTROL_SET_BITS_PORT_INITIALIZE);
/*
* Clear the InterruptNCOR (Interupt No Clear on Read).
* This step ensures that a mere reading of slot_status will clear
* the interrupt; no explicit clearing of interrupt condition
* will be needed for successful completion of commands.
*/
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
PORT_CONTROL_CLEAR_BITS_INTR_NCoR);
/* clear any pending interrupts at this point */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_INTERRUPT_STATUS(si_ctlp, port)),
INTR_MASK);
}
/*
* Enumerate the devices connected to the port multiplier.
* Once a device is detected, we call si_find_dev_signature()
* to find the type of device connected. Even though we are
* called from within si_find_dev_signature(), there is no
* recursion possible.
*/
static int
si_enumerate_port_multiplier(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
uint32_t num_dev_ports = 0;
int pmport;
uint32_t SControl = 0;
uint32_t SStatus = 0;
uint32_t SError = 0;
int loop_count = 0;
SIDBG1(SIDBG_ENTRY|SIDBG_INIT, si_ctlp,
"si_enumerate_port_multiplier entered: port: %d",
port);
mutex_enter(&si_portp->siport_mutex);
/* Enable Port Multiplier context switching. */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
PORT_CONTROL_SET_BITS_PM_ENABLE);
/*
* Read the num dev ports connected.
* GSCR[2] contains the number of device ports.
*/
if (si_read_portmult_reg(si_ctlp, si_portp, port, PORTMULT_CONTROL_PORT,
PSCR_REG2, &num_dev_ports)) {
mutex_exit(&si_portp->siport_mutex);
return (SI_FAILURE);
}
si_portp->siport_portmult_state.sipm_num_ports = num_dev_ports;
SIDBG1(SIDBG_INIT, si_ctlp,
"si_enumerate_port_multiplier: ports found: %d",
num_dev_ports);
for (pmport = 0; pmport < num_dev_ports-1; pmport++) {
/*
* Enable PHY by writing a 1, then a 0 to SControl
* (i.e. PSCR[2]) DET field.
*/
if (si_read_portmult_reg(si_ctlp, si_portp, port, pmport,
PSCR_REG2, &SControl)) {
continue;
}
/* First write a 1 to DET field of SControl. */
SCONTROL_SET_DET(SControl, SCONTROL_DET_COMRESET);
if (si_write_portmult_reg(si_ctlp, si_portp, port, pmport,
PSCR_REG2, SControl)) {
continue;
}
#ifndef __lock_lint
delay(SI_10MS_TICKS); /* give time for COMRESET to percolate */
#endif /* __lock_lint */
/* Then write a 0 to the DET field of SControl. */
SCONTROL_SET_DET(SControl, SCONTROL_DET_NOACTION);
if (si_write_portmult_reg(si_ctlp, si_portp, port, pmport,
PSCR_REG2, SControl)) {
continue;
}
/* Wait for PHYRDY by polling SStatus (i.e. PSCR[0]). */
loop_count = 0;
do {
if (si_read_portmult_reg(si_ctlp, si_portp, port,
pmport, PSCR_REG0, &SStatus)) {
break;
}
SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
"looping for PHYRDY: SStatus: %x",
SStatus);
if (SSTATUS_GET_IPM(SStatus) !=
SSTATUS_IPM_INTERFACE_ACTIVE) {
/*
* If the interface is not active, the DET field
* is considered not accurate. So we want to
* continue looping.
*/
SSTATUS_SET_DET(SStatus,
SSTATUS_DET_NODEV_NOPHY);
}
if (loop_count++ > SI_POLLRATE_SSTATUS) {
/*
* We are effectively timing out after 0.1 sec.
*/
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (SSTATUS_GET_DET(SStatus) !=
SSTATUS_DET_DEVPRESENT_PHYONLINE);
SIDBG2(SIDBG_POLL_LOOP, si_ctlp,
"si_enumerate_port_multiplier: "
"loop count: %d, SStatus: 0x%x",
loop_count,
SStatus);
if ((SSTATUS_GET_IPM(SStatus) ==
SSTATUS_IPM_INTERFACE_ACTIVE) &&
(SSTATUS_GET_DET(SStatus) ==
SSTATUS_DET_DEVPRESENT_PHYONLINE)) {
/* The interface is active and the device is present */
SIDBG1(SIDBG_INIT, si_ctlp,
"Status: %x, device exists",
SStatus);
/*
* Clear error bits in SError register (i.e. PSCR[1]
* by writing back error bits.
*/
if (si_read_portmult_reg(si_ctlp, si_portp, port,
pmport, PSCR_REG1, &SError)) {
continue;
}
SIDBG1(SIDBG_INIT, si_ctlp,
"SError bits are: %x", SError);
if (si_write_portmult_reg(si_ctlp, si_portp, port,
pmport, PSCR_REG1, SError)) {
continue;
}
/* There exists a device. */
mutex_exit(&si_portp->siport_mutex);
si_find_dev_signature(si_ctlp, si_portp, port, pmport);
mutex_enter(&si_portp->siport_mutex);
}
}
mutex_exit(&si_portp->siport_mutex);
return (SI_SUCCESS);
}
/*
* Read a port multiplier register.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static int
si_read_portmult_reg(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port,
int pmport,
int regnum,
uint32_t *regval)
{
int slot;
si_prb_t *prb;
uint32_t *prb_word_ptr;
int i;
uint32_t slot_status;
int loop_count = 0;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG3(SIDBG_ENTRY, si_ctlp, "si_read_portmult_reg: port: %x,"
"pmport: %x, regnum: %x",
port, pmport, regnum);
slot = si_claim_free_slot(si_ctlp, si_portp, port);
if (slot == -1) {
return (SI_FAILURE);
}
prb = &(si_portp->siport_prbpool[slot]);
bzero((void *)prb, sizeof (si_prb_t));
/* Now fill the prb. */
SET_FIS_TYPE(prb->prb_fis, REGISTER_FIS_H2D);
SET_FIS_PMP(prb->prb_fis, PORTMULT_CONTROL_PORT);
SET_FIS_CDMDEVCTL(prb->prb_fis, 1);
SET_FIS_COMMAND(prb->prb_fis, SATAC_READ_PM_REG);
SET_FIS_DEV_HEAD(prb->prb_fis, pmport);
SET_FIS_FEATURES(prb->prb_fis, regnum);
/* no real data transfer is involved. */
SET_SGE_TRM(prb->prb_sge0);
#if SI_DEBUG
if (si_debug_flags & SIDBG_DUMP_PRB) {
int *ptr;
int j;
ptr = (int *)prb;
cmn_err(CE_WARN, "read_port_mult_reg, prb: ");
for (j = 0; j < (sizeof (si_prb_t)/4); j++) {
cmn_err(CE_WARN, "%x ", ptr[j]);
}
}
#endif /* SI_DEBUG */
/* Deliver PRB */
POST_PRB_ADDR(si_ctlp, si_portp, port, slot);
/* Loop till the command is finished. */
do {
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
"looping read_pm slot_status: 0x%x",
slot_status);
if (loop_count++ > SI_POLLRATE_SLOTSTATUS) {
/* We are effectively timing out after 0.5 sec. */
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (slot_status & SI_SLOT_MASK & (0x1 << slot));
SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
"read_portmult_reg: loop count: %d",
loop_count);
CLEAR_BIT(si_portp->siport_pending_tags, slot);
/* Now inspect the port LRAM for the modified FIS. */
prb_word_ptr = (uint32_t *)prb;
for (i = 0; i < (sizeof (si_prb_t)/4); i++) {
prb_word_ptr[i] = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_LRAM(si_ctlp, port, slot)+i*4));
}
if (((GET_FIS_COMMAND(prb->prb_fis) & 0x1) != 0) ||
(GET_FIS_FEATURES(prb->prb_fis) != 0)) {
/* command failed. */
return (SI_FAILURE);
}
/* command succeeded. */
*regval = (GET_FIS_SECTOR_COUNT(prb->prb_fis) & 0xff) |
((GET_FIS_SECTOR(prb->prb_fis) << 8) & 0xff00) |
((GET_FIS_CYL_LOW(prb->prb_fis) << 16) & 0xff0000) |
((GET_FIS_CYL_HI(prb->prb_fis) << 24) & 0xff000000);
return (SI_SUCCESS);
}
/*
* Write a port multiplier register.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static int
si_write_portmult_reg(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port,
int pmport,
int regnum,
uint32_t regval)
{
int slot;
si_prb_t *prb;
uint32_t *prb_word_ptr;
uint32_t slot_status;
int i;
int loop_count = 0;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG4(SIDBG_ENTRY, si_ctlp,
"si_write_portmult_reg: port: %x, pmport: %x,"
"regnum: %x, regval: %x",
port, pmport, regnum, regval);
slot = si_claim_free_slot(si_ctlp, si_portp, port);
if (slot == -1) {
return (SI_FAILURE);
}
prb = &(si_portp->siport_prbpool[slot]);
bzero((void *)prb, sizeof (si_prb_t));
/* Now fill the prb. */
SET_FIS_TYPE(prb->prb_fis, REGISTER_FIS_H2D);
SET_FIS_PMP(prb->prb_fis, PORTMULT_CONTROL_PORT);
SET_FIS_CDMDEVCTL(prb->prb_fis, 1);
SET_FIS_COMMAND(prb->prb_fis, SATAC_WRITE_PM_REG);
SET_FIS_DEV_HEAD(prb->prb_fis, pmport);
SET_FIS_FEATURES(prb->prb_fis, regnum);
SET_FIS_SECTOR_COUNT(prb->prb_fis, regval & 0xff);
SET_FIS_SECTOR(prb->prb_fis, (regval >> 8) & 0xff);
SET_FIS_CYL_LOW(prb->prb_fis, (regval >> 16) & 0xff);
SET_FIS_CYL_HI(prb->prb_fis, (regval >> 24) & 0xff);
/* no real data transfer is involved. */
SET_SGE_TRM(prb->prb_sge0);
#if SI_DEBUG
if (si_debug_flags & SIDBG_DUMP_PRB) {
int *ptr;
int j;
ptr = (int *)prb;
cmn_err(CE_WARN, "read_port_mult_reg, prb: ");
for (j = 0; j < (sizeof (si_prb_t)/4); j++) {
cmn_err(CE_WARN, "%x ", ptr[j]);
}
}
#endif /* SI_DEBUG */
/* Deliver PRB */
POST_PRB_ADDR(si_ctlp, si_portp, port, slot);
/* Loop till the command is finished. */
do {
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
"looping write_pmp slot_status: 0x%x",
slot_status);
if (loop_count++ > SI_POLLRATE_SLOTSTATUS) {
/* We are effectively timing out after 0.5 sec. */
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (slot_status & SI_SLOT_MASK & (0x1 << slot));
SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
"write_portmult_reg: loop count: %d",
loop_count);
CLEAR_BIT(si_portp->siport_pending_tags, slot);
/* Now inspect the port LRAM for the modified FIS. */
prb_word_ptr = (uint32_t *)prb;
for (i = 0; i < (sizeof (si_prb_t)/4); i++) {
prb_word_ptr[i] = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_LRAM(si_ctlp, port, slot)+i*4));
}
if (((GET_FIS_COMMAND(prb->prb_fis) & 0x1) != 0) ||
(GET_FIS_FEATURES(prb->prb_fis) != 0)) {
/* command failed */
return (SI_FAILURE);
}
/* command succeeded */
return (SI_SUCCESS);
}
/*
* Set the auto sense data for ATAPI devices.
*
* Note: Currently the sense data is simulated; this code will be enhanced
* in second phase to fetch the real sense data from the atapi device.
*/
static void
si_set_sense_data(sata_pkt_t *satapkt, int reason)
{
struct scsi_extended_sense *sense;
sense = (struct scsi_extended_sense *)
satapkt->satapkt_cmd.satacmd_rqsense;
bzero(sense, sizeof (struct scsi_extended_sense));
sense->es_valid = 1; /* Valid sense */
sense->es_class = 7; /* Response code 0x70 - current err */
sense->es_key = 0;
sense->es_info_1 = 0;
sense->es_info_2 = 0;
sense->es_info_3 = 0;
sense->es_info_4 = 0;
sense->es_add_len = 6; /* Additional length */
sense->es_cmd_info[0] = 0;
sense->es_cmd_info[1] = 0;
sense->es_cmd_info[2] = 0;
sense->es_cmd_info[3] = 0;
sense->es_add_code = 0;
sense->es_qual_code = 0;
if ((reason == SATA_PKT_DEV_ERROR) || (reason == SATA_PKT_TIMEOUT)) {
sense->es_key = KEY_HARDWARE_ERROR;
}
}
/*
* Interrupt service handler. We loop through each of the ports to find
* if the interrupt belongs to any of them.
*
* Bulk of the interrupt handling is actually done out of subroutines
* like si_intr_command_complete() etc.
*/
/*ARGSUSED*/
static uint_t
si_intr(caddr_t arg1, caddr_t arg2)
{
si_ctl_state_t *si_ctlp = (si_ctl_state_t *)arg1;
si_port_state_t *si_portp;
uint32_t global_intr_status;
uint32_t mask, port_intr_status;
int port;
global_intr_status = ddi_get32(si_ctlp->sictl_global_acc_handle,
(uint32_t *)GLOBAL_INTERRUPT_STATUS(si_ctlp));
SIDBG1(SIDBG_INTR|SIDBG_ENTRY, si_ctlp,
"si_intr: global_int_status: 0x%x",
global_intr_status);
if (!(global_intr_status & SI31xx_INTR_PORT_MASK)) {
/* Sorry, the interrupt is not ours. */
return (DDI_INTR_UNCLAIMED);
}
/* Loop for all the ports. */
for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
mask = 0x1 << port;
if (!(global_intr_status & mask)) {
continue;
}
mutex_enter(&si_ctlp->sictl_mutex);
si_portp = si_ctlp->sictl_ports[port];
mutex_exit(&si_ctlp->sictl_mutex);
port_intr_status = ddi_get32(si_ctlp->sictl_global_acc_handle,
(uint32_t *)PORT_INTERRUPT_STATUS(si_ctlp, port));
SIDBG2(SIDBG_VERBOSE, si_ctlp,
"s_intr: port_intr_status: 0x%x, port: %x",
port_intr_status,
port);
if (port_intr_status & INTR_COMMAND_COMPLETE) {
(void) si_intr_command_complete(si_ctlp, si_portp,
port);
} else {
/* Clear the interrupts */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_INTERRUPT_STATUS(si_ctlp, port)),
port_intr_status & INTR_MASK);
}
/*
* Note that we did not clear the interrupt for command
* completion interrupt. Reading of slot_status takes care
* of clearing the interrupt for command completion case.
*/
if (port_intr_status & INTR_COMMAND_ERROR) {
(void) si_intr_command_error(si_ctlp, si_portp, port);
}
if (port_intr_status & INTR_PORT_READY) {
(void) si_intr_port_ready(si_ctlp, si_portp, port);
}
if (port_intr_status & INTR_POWER_CHANGE) {
(void) si_intr_pwr_change(si_ctlp, si_portp, port);
}
if (port_intr_status & INTR_PHYRDY_CHANGE) {
(void) si_intr_phy_ready_change(si_ctlp, si_portp,
port);
}
if (port_intr_status & INTR_COMWAKE_RECEIVED) {
(void) si_intr_comwake_rcvd(si_ctlp, si_portp,
port);
}
if (port_intr_status & INTR_UNRECOG_FIS) {
(void) si_intr_unrecognised_fis(si_ctlp, si_portp,
port);
}
if (port_intr_status & INTR_DEV_XCHANGED) {
(void) si_intr_dev_xchanged(si_ctlp, si_portp, port);
}
if (port_intr_status & INTR_8B10B_DECODE_ERROR) {
(void) si_intr_decode_err_threshold(si_ctlp, si_portp,
port);
}
if (port_intr_status & INTR_CRC_ERROR) {
(void) si_intr_crc_err_threshold(si_ctlp, si_portp,
port);
}
if (port_intr_status & INTR_HANDSHAKE_ERROR) {
(void) si_intr_handshake_err_threshold(si_ctlp,
si_portp, port);
}
if (port_intr_status & INTR_SETDEVBITS_NOTIFY) {
(void) si_intr_set_devbits_notify(si_ctlp, si_portp,
port);
}
}
return (DDI_INTR_CLAIMED);
}
/*
* Interrupt which indicates that one or more commands have successfully
* completed.
*
* Since we disabled W1C (write-one-to-clear) previously, mere reading
* of slot_status register clears the interrupt. There is no need to
* explicitly clear the interrupt.
*/
static int
si_intr_command_complete(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
uint32_t slot_status;
uint32_t finished_tags;
int finished_slot;
sata_pkt_t *satapkt;
SIDBG0(SIDBG_ENTRY|SIDBG_INTR, si_ctlp,
"si_intr_command_complete enter");
mutex_enter(&si_portp->siport_mutex);
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
if (!si_portp->siport_pending_tags) {
/*
* Spurious interrupt. Nothing to be done.
* The interrupt was cleared when slot_status was read.
*/
mutex_exit(&si_portp->siport_mutex);
return (SI_SUCCESS);
}
SIDBG2(SIDBG_VERBOSE, si_ctlp, "si3124: si_intr_command_complete: "
"pending_tags: %x, slot_status: %x",
si_portp->siport_pending_tags,
slot_status);
finished_tags = si_portp->siport_pending_tags &
~slot_status & SI_SLOT_MASK;
while (finished_tags) {
si_prb_t *prb;
finished_slot = ddi_ffs(finished_tags) - 1;
if (finished_slot == -1) {
break;
}
prb = &si_portp->siport_prbpool[finished_slot];
satapkt = si_portp->siport_slot_pkts[finished_slot];
satapkt->satapkt_cmd.satacmd_status_reg =
GET_FIS_COMMAND(prb->prb_fis);
if (satapkt->satapkt_cmd.satacmd_flags.sata_special_regs)
si_copy_out_regs(&satapkt->satapkt_cmd, &prb->prb_fis);
SENDUP_PACKET(si_portp, satapkt, SATA_PKT_COMPLETED);
CLEAR_BIT(si_portp->siport_pending_tags, finished_slot);
CLEAR_BIT(finished_tags, finished_slot);
}
SIDBG2(SIDBG_PKTCOMP, si_ctlp,
"command_complete done: pend_tags: 0x%x, slot_status: 0x%x",
si_portp->siport_pending_tags,
slot_status);
/*
* tidbit: no need to clear the interrupt since reading of
* slot_status automatically clears the interrupt in the case
* of a successful command completion.
*/
mutex_exit(&si_portp->siport_mutex);
return (SI_SUCCESS);
}
/*
* Interrupt which indicates that a command did not complete successfully.
*
* The port halts whenever a command error interrupt is received.
* The only way to restart it is to reset or reinitialize the port
* but such an operation throws away all the pending commands on
* the port.
*
* We reset the device and mop the commands on the port.
*/
static int
si_intr_command_error(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
uint32_t command_error, slot_status;
uint32_t failed_tags;
command_error = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_COMMAND_ERROR(si_ctlp, port)));
SIDBG1(SIDBG_INTR|SIDBG_ENTRY, si_ctlp,
"si_intr_command_error: command_error: 0x%x",
command_error);
mutex_enter(&si_portp->siport_mutex);
/*
* Remember the slot_status since any of the recovery handler
* can blow it away with reset operation.
*/
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
si_log_error_message(si_ctlp, port, command_error);
switch (command_error) {
case CMD_ERR_DEVICEERRROR:
si_error_recovery_DEVICEERROR(si_ctlp, si_portp, port);
break;
case CMD_ERR_SDBERROR:
si_error_recovery_SDBERROR(si_ctlp, si_portp, port);
break;
case CMD_ERR_DATAFISERROR:
si_error_recovery_DATAFISERROR(si_ctlp, si_portp, port);
break;
case CMD_ERR_SENDFISERROR:
si_error_recovery_SENDFISERROR(si_ctlp, si_portp, port);
break;
default:
si_error_recovery_default(si_ctlp, si_portp, port);
break;
}
/*
* Compute the failed_tags by adding up the error tags.
*
* The siport_err_tags_SDBERROR and siport_err_tags_nonSDBERROR
* were filled in by the si_error_recovery_* routines.
*/
failed_tags = si_portp->siport_pending_tags &
(si_portp->siport_err_tags_SDBERROR |
si_portp->siport_err_tags_nonSDBERROR);
SIDBG3(SIDBG_ERRS|SIDBG_INTR, si_ctlp, "si_intr_command_error: "
"err_tags_SDBERROR: 0x%x, "
"err_tags_nonSDBERRROR: 0x%x, "
"failed_tags: 0x%x",
si_portp->siport_err_tags_SDBERROR,
si_portp->siport_err_tags_nonSDBERROR,
failed_tags);
SIDBG2(SIDBG_ERRS|SIDBG_INTR, si_ctlp, "si3124: si_intr_command_error: "
"slot_status:0x%x, pending_tags: 0x%x",
slot_status,
si_portp->siport_pending_tags);
mutex_exit(&si_portp->siport_mutex);
si_mop_commands(si_ctlp,
si_portp,
port,
slot_status,
failed_tags,
0, /* timedout_tags */
0, /* aborting_tags */
0); /* reset_tags */
mutex_enter(&si_portp->siport_mutex);
ASSERT(si_portp->siport_pending_tags == 0);
si_portp->siport_err_tags_SDBERROR = 0;
si_portp->siport_err_tags_nonSDBERROR = 0;
mutex_exit(&si_portp->siport_mutex);
return (SI_SUCCESS);
}
/*
* There is a subtle difference between errors on a normal port and
* a port-mult port. When an error happens on a normal port, the port
* is halted effectively until the port is reset or initialized.
* However, in port-mult port errors, port does not get halted since
* other non-error devices behind the port multiplier can still
* continue to operate. So we wait till all the commands are drained
* instead of resetting it right away.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static void
si_recover_portmult_errors(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
uint32_t command_error, slot_status, port_status;
int failed_slot;
int loop_count = 0;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
"si_recover_portmult_errors: port: 0x%x",
port);
/* Resume the port */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
PORT_CONTROL_SET_BITS_RESUME);
port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_STATUS(si_ctlp, port));
failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
command_error = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_COMMAND_ERROR(si_ctlp, port)));
if (command_error == CMD_ERR_SDBERROR) {
si_portp->siport_err_tags_SDBERROR |= (0x1 << failed_slot);
} else {
si_portp->siport_err_tags_nonSDBERROR |= (0x1 << failed_slot);
}
/* Now we drain the pending commands. */
do {
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
/*
* Since we have not yet returned DDI_INTR_CLAIMED,
* our interrupt handler is guaranteed not to be called again.
* So we need to check IS_ATTENTION_RAISED() for further
* decisions.
*
* This is a too big a delay for an interrupt context.
* But this is supposed to be a rare condition.
*/
if (IS_ATTENTION_RAISED(slot_status)) {
/* Resume again */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
PORT_CONTROL_SET_BITS_RESUME);
port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_STATUS(si_ctlp, port));
failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
command_error = ddi_get32(
si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_COMMAND_ERROR(si_ctlp,
port)));
if (command_error == CMD_ERR_SDBERROR) {
si_portp->siport_err_tags_SDBERROR |=
(0x1 << failed_slot);
} else {
si_portp->siport_err_tags_nonSDBERROR |=
(0x1 << failed_slot);
}
}
if (loop_count++ > SI_POLLRATE_RECOVERPORTMULT) {
/* We are effectively timing out after 10 sec. */
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (slot_status & SI_SLOT_MASK);
/*
* The above loop can be improved for 3132 since we could obtain the
* Port Multiplier Context of the device in error. Then we could
* do a better job in filtering out commands for the device in error.
* The loop could finish much earlier with such a logic.
*/
/* Clear the RESUME bit. */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
PORT_CONTROL_CLEAR_BITS_RESUME);
}
/*
* If we are connected to port multiplier, drain the non-failed devices.
* Otherwise, we initialize the port (which effectively fails all the
* pending commands in the hope that sd would retry them later).
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static void
si_error_recovery_DEVICEERROR(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
uint32_t port_status;
int failed_slot;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
"si_error_recovery_DEVICEERROR: port: 0x%x",
port);
if (si_portp->siport_port_type == PORT_TYPE_MULTIPLIER) {
si_recover_portmult_errors(si_ctlp, si_portp, port);
} else {
port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_STATUS(si_ctlp, port));
failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
si_portp->siport_err_tags_nonSDBERROR |= (0x1 << failed_slot);
}
/* In either case (port-mult or not), we reinitialize the port. */
(void) si_initialize_port_wait_till_ready(si_ctlp, port);
}
/*
* Handle exactly like DEVICEERROR. Remember the tags with SDBERROR
* to perform read_log_ext on them later. SDBERROR means that the
* error was for an NCQ command.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static void
si_error_recovery_SDBERROR(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
uint32_t port_status;
int failed_slot;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
"si3124: si_error_recovery_SDBERROR: port: 0x%x",
port);
if (si_portp->siport_port_type == PORT_TYPE_MULTIPLIER) {
si_recover_portmult_errors(si_ctlp, si_portp, port);
} else {
port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_STATUS(si_ctlp, port));
failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
si_portp->siport_err_tags_SDBERROR |= (0x1 << failed_slot);
}
/* In either case (port-mult or not), we reinitialize the port. */
(void) si_initialize_port_wait_till_ready(si_ctlp, port);
}
/*
* Handle exactly like DEVICEERROR except resetting the port if there was
* an NCQ command on the port.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static void
si_error_recovery_DATAFISERROR(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
uint32_t port_status;
int failed_slot;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
"si3124: si_error_recovery_DATAFISERROR: port: 0x%x",
port);
/* reset device if we were waiting for any ncq commands. */
if (si_portp->siport_pending_ncq_count) {
port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_STATUS(si_ctlp, port));
failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
si_portp->siport_err_tags_nonSDBERROR |= (0x1 << failed_slot);
(void) si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
SI_DEVICE_RESET);
return;
}
/*
* If we don't have any ncq commands pending, the rest of
* the process is similar to the one for DEVICEERROR.
*/
si_error_recovery_DEVICEERROR(si_ctlp, si_portp, port);
}
/*
* We handle just like DEVICERROR except that we reset the device instead
* of initializing the port.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static void
si_error_recovery_SENDFISERROR(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
uint32_t port_status;
int failed_slot;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
"si3124: si_error_recovery_SENDFISERROR: port: 0x%x",
port);
if (si_portp->siport_port_type == PORT_TYPE_MULTIPLIER) {
si_recover_portmult_errors(si_ctlp, si_portp, port);
} else {
port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_STATUS(si_ctlp, port));
failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
si_portp->siport_err_tags_nonSDBERROR |= (0x1 << failed_slot);
(void) si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
SI_DEVICE_RESET);
}
}
/*
* The default behavior for all other errors is to reset the device.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static void
si_error_recovery_default(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
uint32_t port_status;
int failed_slot;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
"si3124: si_error_recovery_default: port: 0x%x",
port);
port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_STATUS(si_ctlp, port));
failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
si_portp->siport_err_tags_nonSDBERROR |= (0x1 << failed_slot);
(void) si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
SI_DEVICE_RESET);
}
/*
* Read Log Ext with PAGE 10 to retrieve the error for an NCQ command.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static uint8_t
si_read_log_ext(si_ctl_state_t *si_ctlp, si_port_state_t *si_portp, int port)
{
int slot;
si_prb_t *prb;
int i;
uint32_t slot_status;
int loop_count = 0;
uint32_t *prb_word_ptr;
uint8_t error;
_NOTE(ASSUMING_PROTECTED(si_portp))
SIDBG1(SIDBG_ENTRY|SIDBG_ERRS, si_ctlp,
"si_read_log_ext: port: %x", port);
slot = si_claim_free_slot(si_ctlp, si_portp, port);
if (slot == -1) {
return (0);
}
prb = &(si_portp->siport_prbpool[slot]);
bzero((void *)prb, sizeof (si_prb_t));
/* Now fill the prb */
SET_FIS_TYPE(prb->prb_fis, REGISTER_FIS_H2D);
SET_FIS_PMP(prb->prb_fis, PORTMULT_CONTROL_PORT);
SET_FIS_CDMDEVCTL(prb->prb_fis, 1);
SET_FIS_COMMAND(prb->prb_fis, SATAC_READ_LOG_EXT);
SET_FIS_SECTOR(prb->prb_fis, SATA_LOG_PAGE_10);
/* no real data transfer is involved */
SET_SGE_TRM(prb->prb_sge0);
#if SI_DEBUG
if (si_debug_flags & SIDBG_DUMP_PRB) {
int *ptr;
int j;
ptr = (int *)prb;
cmn_err(CE_WARN, "read_port_mult_reg, prb: ");
for (j = 0; j < (sizeof (si_prb_t)/4); j++) {
cmn_err(CE_WARN, "%x ", ptr[j]);
}
}
#endif /* SI_DEBUG */
/* Deliver PRB */
POST_PRB_ADDR(si_ctlp, si_portp, port, slot);
/* Loop till the command is finished. */
do {
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
"looping read_log_ext slot_status: 0x%x",
slot_status);
if (loop_count++ > SI_POLLRATE_SLOTSTATUS) {
/* We are effectively timing out after 0.5 sec. */
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (slot_status & SI_SLOT_MASK & (0x1 << slot));
if (slot_status & SI_SLOT_MASK & (0x1 << slot)) {
/*
* If we fail with the READ LOG EXT command, we need to
* initialize the port to clear the slot_status register.
* We don't need to worry about any other valid commands
* being thrown away because we are already in recovery
* mode and READ LOG EXT is the only pending command.
*/
(void) si_initialize_port_wait_till_ready(si_ctlp, port);
}
SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
"read_portmult_reg: loop count: %d",
loop_count);
/*
* The LRAM contains the the modified FIS.
* Read the modified FIS to obtain the Error.
*/
prb_word_ptr = (uint32_t *)prb;
for (i = 0; i < (sizeof (si_prb_t)/4); i++) {
prb_word_ptr[i] = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_LRAM(si_ctlp, port, slot)+i*4));
}
error = GET_FIS_FEATURES(prb->prb_fis);
CLEAR_BIT(si_portp->siport_pending_tags, slot);
return (error);
}
/*
* Dump the error message to the log.
*/
static void
si_log_error_message(si_ctl_state_t *si_ctlp, int port, uint32_t command_error)
{
char *errstr;
switch (command_error) {
case CMD_ERR_DEVICEERRROR:
errstr = "Standard Error: Error bit set in register - device"
" to host FIS";
break;
case CMD_ERR_SDBERROR:
errstr = "NCQ Error: Error bit set in register - device"
" to host FIS";
break;
case CMD_ERR_DATAFISERROR:
errstr = "Error in data FIS not detected by device";
break;
case CMD_ERR_SENDFISERROR:
errstr = "Initial command FIS transmission failed";
break;
case CMD_ERR_INCONSISTENTSTATE:
errstr = "Inconsistency in protocol";
break;
case CMD_ERR_DIRECTIONERROR:
errstr = "DMA direction flag does not match the command";
break;
case CMD_ERR_UNDERRUNERROR:
errstr = "Run out of scatter gather entries while writing data";
break;
case CMD_ERR_OVERRUNERROR:
errstr = "Run out of scatter gather entries while reading data";
break;
case CMD_ERR_PACKETPROTOCOLERROR:
errstr = "Packet protocol error";
break;
case CMD_ERR_PLDSGTERRORBOUNDARY:
errstr = "Scatter/gather table not on quadword boundary";
break;
case CMD_ERR_PLDSGTERRORTARETABORT:
errstr = "PCI(X) Target abort while fetching scatter/gather"
" table";
break;
case CMD_ERR_PLDSGTERRORMASTERABORT:
errstr = "PCI(X) Master abort while fetching scatter/gather"
" table";
break;
case CMD_ERR_PLDSGTERRORPCIERR:
errstr = "PCI(X) parity error while fetching scatter/gather"
" table";
break;
case CMD_ERR_PLDCMDERRORBOUNDARY:
errstr = "PRB not on quadword boundary";
break;
case CMD_ERR_PLDCMDERRORTARGETABORT:
errstr = "PCI(X) Target abort while fetching PRB";
break;
case CMD_ERR_PLDCMDERRORMASTERABORT:
errstr = "PCI(X) Master abort while fetching PRB";
break;
case CMD_ERR_PLDCMDERORPCIERR:
errstr = "PCI(X) parity error while fetching PRB";
break;
case CMD_ERR_PSDERRORTARGETABORT:
errstr = "PCI(X) Target abort during data transfer";
break;
case CMD_ERR_PSDERRORMASTERABORT:
errstr = "PCI(X) Master abort during data transfer";
break;
case CMD_ERR_PSDERRORPCIERR:
errstr = "PCI(X) parity error during data transfer";
break;
case CMD_ERR_SENDSERVICEERROR:
errstr = "FIS received while sending service FIS in"
" legacy queuing operation";
break;
default:
errstr = "Unknown Error";
break;
}
SIDBG2(SIDBG_ERRS, si_ctlp,
"command error: port: 0x%x, error: %s",
port,
errstr);
}
/*
* Interrupt which indicates that the Port Ready state has changed
* from zero to one.
*
* We are not interested in this interrupt; we just log a debug message.
*/
/*ARGSUSED*/
static int
si_intr_port_ready(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_ready");
return (SI_SUCCESS);
}
/*
* Interrupt which indicates that the port power management state
* has been modified.
*
* We are not interested in this interrupt; we just log a debug message.
*/
/*ARGSUSED*/
static int
si_intr_pwr_change(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_pwr_change");
return (SI_SUCCESS);
}
/*
* Interrupt which indicates that the PHY sate has changed either from
* Not-Ready to Ready or from Ready to Not-Ready.
*/
static int
si_intr_phy_ready_change(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
sata_device_t sdevice;
uint32_t SStatus = 0; /* No dev present & PHY not established. */
int dev_exists_now = 0;
int dev_existed_previously = 0;
SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_phy_rdy_change");
mutex_enter(&si_ctlp->sictl_mutex);
if ((si_ctlp->sictl_sata_hba_tran == NULL) || (si_portp == NULL)) {
/* the whole controller setup is not yet done. */
mutex_exit(&si_ctlp->sictl_mutex);
return (SI_SUCCESS);
}
mutex_exit(&si_ctlp->sictl_mutex);
mutex_enter(&si_portp->siport_mutex);
/* SStatus tells the presence of device. */
SStatus = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_SSTATUS(si_ctlp, port));
dev_exists_now =
(SSTATUS_GET_DET(SStatus) == SSTATUS_DET_DEVPRESENT_PHYONLINE);
if (si_portp->siport_port_type != PORT_TYPE_NODEV) {
dev_existed_previously = 1;
}
bzero((void *)&sdevice, sizeof (sata_device_t));
sdevice.satadev_addr.cport = port;
sdevice.satadev_addr.pmport = PORTMULT_CONTROL_PORT;
/* we don't have a way of determining the exact port-mult port. */
if (si_portp->siport_port_type == PORT_TYPE_MULTIPLIER) {
sdevice.satadev_addr.qual = SATA_ADDR_PMPORT;
} else {
sdevice.satadev_addr.qual = SATA_ADDR_CPORT;
}
sdevice.satadev_state = SATA_STATE_READY; /* port state */
if (dev_exists_now) {
if (dev_existed_previously) {
/* Things are fine now. The loss was temporary. */
SIDBG0(SIDBG_INTR, NULL,
"phyrdy: doing BOTH EVENTS TOGETHER");
if (si_portp->siport_active) {
SIDBG0(SIDBG_EVENT, si_ctlp,
"sending event: LINK_LOST & "
"LINK_ESTABLISHED");
sata_hba_event_notify(
si_ctlp->sictl_sata_hba_tran->\
sata_tran_hba_dip,
&sdevice,
SATA_EVNT_LINK_LOST|
SATA_EVNT_LINK_ESTABLISHED);
}
} else {
/* A new device has been detected. */
mutex_exit(&si_portp->siport_mutex);
si_find_dev_signature(si_ctlp, si_portp, port,
PORTMULT_CONTROL_PORT);
mutex_enter(&si_portp->siport_mutex);
SIDBG0(SIDBG_INTR, NULL, "phyrdy: doing ATTACH event");
if (si_portp->siport_active) {
SIDBG0(SIDBG_EVENT, si_ctlp,
"sending event up: LINK_ESTABLISHED");
sata_hba_event_notify(
si_ctlp->sictl_sata_hba_tran->\
sata_tran_hba_dip,
&sdevice,
SATA_EVNT_LINK_ESTABLISHED);
}
}
} else { /* No device exists now */
if (dev_existed_previously) {
/* An existing device is lost. */
if (si_portp->siport_active) {
SIDBG0(SIDBG_EVENT, si_ctlp,
"sending event up: LINK_LOST");
sata_hba_event_notify(
si_ctlp->sictl_sata_hba_tran->
sata_tran_hba_dip,
&sdevice,
SATA_EVNT_LINK_LOST);
}
si_portp->siport_port_type = PORT_TYPE_NODEV;
} else {
/* spurious interrupt */
SIDBG0(SIDBG_INTR, NULL,
"spurious phy ready interrupt");
}
}
mutex_exit(&si_portp->siport_mutex);
return (SI_SUCCESS);
}
/*
* Interrupt which indicates that a COMWAKE OOB signal has been decoded
* on the receiver.
*
* We are not interested in this interrupt; we just log a debug message.
*/
/*ARGSUSED*/
static int
si_intr_comwake_rcvd(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_commwake_rcvd");
return (SI_SUCCESS);
}
/*
* Interrupt which indicates that the F-bit has been set in SError
* Diag field.
*
* We are not interested in this interrupt; we just log a debug message.
*/
/*ARGSUSED*/
static int
si_intr_unrecognised_fis(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_unrecognised_fis");
return (SI_SUCCESS);
}
/*
* Interrupt which indicates that the X-bit has been set in SError
* Diag field.
*
* We are not interested in this interrupt; we just log a debug message.
*/
/*ARGSUSED*/
static int
si_intr_dev_xchanged(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_dev_xchanged");
return (SI_SUCCESS);
}
/*
* Interrupt which indicates that the 8b/10 Decode Error counter has
* exceeded the programmed non-zero threshold value.
*
* We are not interested in this interrupt; we just log a debug message.
*/
/*ARGSUSED*/
static int
si_intr_decode_err_threshold(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_err_threshold");
return (SI_SUCCESS);
}
/*
* Interrupt which indicates that the CRC Error counter has exceeded the
* programmed non-zero threshold value.
*
* We are not interested in this interrupt; we just log a debug message.
*/
/*ARGSUSED*/
static int
si_intr_crc_err_threshold(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_crc_threshold");
return (SI_SUCCESS);
}
/*
* Interrupt which indicates that the Handshake Error counter has
* exceeded the programmed non-zero threshold value.
*
* We are not interested in this interrupt; we just log a debug message.
*/
/*ARGSUSED*/
static int
si_intr_handshake_err_threshold(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp,
"si_intr_handshake_err_threshold");
return (SI_SUCCESS);
}
/*
* Interrupt which indicates that a "Set Device Bits" FIS has been
* received with N-bit set in the control field.
*
* We are not interested in this interrupt; we just log a debug message.
*/
/*ARGSUSED*/
static int
si_intr_set_devbits_notify(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port)
{
SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_set_devbits_notify");
return (SI_SUCCESS);
}
/*
* Enable the interrupts for a particular port.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static void
si_enable_port_interrupts(si_ctl_state_t *si_ctlp, int port)
{
uint32_t mask;
/* get the current settings first. */
mask = ddi_get32(si_ctlp->sictl_global_acc_handle,
(uint32_t *)GLOBAL_CONTROL_REG(si_ctlp));
SIDBG1(SIDBG_INIT|SIDBG_ENTRY, si_ctlp,
"si_enable_port_interrupts: current mask: 0x%x",
mask);
/* enable the bit for current port. */
SET_BIT(mask, port);
/* now use this mask to enable the interrupt. */
ddi_put32(si_ctlp->sictl_global_acc_handle,
(uint32_t *)GLOBAL_CONTROL_REG(si_ctlp),
mask);
}
/*
* Enable interrupts for all the ports.
*/
static void
si_enable_all_interrupts(si_ctl_state_t *si_ctlp)
{
int port;
for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
si_enable_port_interrupts(si_ctlp, port);
}
}
/*
* Disable interrupts for a particular port.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static void
si_disable_port_interrupts(si_ctl_state_t *si_ctlp, int port)
{
uint32_t mask;
/* get the current settings first. */
mask = ddi_get32(si_ctlp->sictl_global_acc_handle,
(uint32_t *)GLOBAL_CONTROL_REG(si_ctlp));
/* clear the bit for current port. */
CLEAR_BIT(mask, port);
/* now use this mask to disable the interrupt. */
ddi_put32(si_ctlp->sictl_global_acc_handle,
(uint32_t *)GLOBAL_CONTROL_REG(si_ctlp),
mask);
}
/*
* Disable interrupts for all the ports.
*/
static void
si_disable_all_interrupts(si_ctl_state_t *si_ctlp)
{
int port;
for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
si_disable_port_interrupts(si_ctlp, port);
}
}
/*
* Fetches the latest sstatus, scontrol, serror, sactive registers
* and stuffs them into sata_device_t structure.
*/
static void
fill_dev_sregisters(si_ctl_state_t *si_ctlp, int port, sata_device_t *satadev)
{
satadev->satadev_scr.sstatus = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SSTATUS(si_ctlp, port)));
satadev->satadev_scr.serror = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SERROR(si_ctlp, port)));
satadev->satadev_scr.sactive = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SACTIVE(si_ctlp, port)));
satadev->satadev_scr.scontrol =
ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SCONTROL(si_ctlp, port)));
}
/*
* si_add_legacy_intrs() handles INTx and legacy interrupts.
*/
static int
si_add_legacy_intrs(si_ctl_state_t *si_ctlp)
{
dev_info_t *devinfo = si_ctlp->sictl_devinfop;
int actual, count = 0;
int x, y, rc, inum = 0;
SIDBG0(SIDBG_ENTRY, si_ctlp, "si_add_legacy_intrs");
/* get number of interrupts. */
rc = ddi_intr_get_nintrs(devinfo, DDI_INTR_TYPE_FIXED, &count);
if ((rc != DDI_SUCCESS) || (count == 0)) {
SIDBG2(SIDBG_INTR|SIDBG_INIT, si_ctlp,
"ddi_intr_get_nintrs() failed, "
"rc %d count %d\n", rc, count);
return (DDI_FAILURE);
}
/* Allocate an array of interrupt handles. */
si_ctlp->sictl_intr_size = count * sizeof (ddi_intr_handle_t);
si_ctlp->sictl_htable = kmem_zalloc(si_ctlp->sictl_intr_size, KM_SLEEP);
/* call ddi_intr_alloc(). */
rc = ddi_intr_alloc(devinfo, si_ctlp->sictl_htable, DDI_INTR_TYPE_FIXED,
inum, count, &actual, DDI_INTR_ALLOC_STRICT);
if ((rc != DDI_SUCCESS) || (actual == 0)) {
SIDBG1(SIDBG_INTR|SIDBG_INIT, si_ctlp,
"ddi_intr_alloc() failed, rc %d\n", rc);
kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
return (DDI_FAILURE);
}
if (actual < count) {
SIDBG2(SIDBG_INTR|SIDBG_INIT, si_ctlp,
"Requested: %d, Received: %d", count, actual);
for (x = 0; x < actual; x++) {
(void) ddi_intr_free(si_ctlp->sictl_htable[x]);
}
kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
return (DDI_FAILURE);
}
si_ctlp->sictl_intr_cnt = actual;
/* Get intr priority. */
if (ddi_intr_get_pri(si_ctlp->sictl_htable[0],
&si_ctlp->sictl_intr_pri) != DDI_SUCCESS) {
SIDBG0(SIDBG_INTR|SIDBG_INIT, si_ctlp,
"ddi_intr_get_pri() failed");
for (x = 0; x < actual; x++) {
(void) ddi_intr_free(si_ctlp->sictl_htable[x]);
}
kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
return (DDI_FAILURE);
}
/* Test for high level mutex. */
if (si_ctlp->sictl_intr_pri >= ddi_intr_get_hilevel_pri()) {
SIDBG0(SIDBG_INTR|SIDBG_INIT, si_ctlp,
"si_add_legacy_intrs: Hi level intr not supported");
for (x = 0; x < actual; x++) {
(void) ddi_intr_free(si_ctlp->sictl_htable[x]);
}
kmem_free(si_ctlp->sictl_htable, sizeof (ddi_intr_handle_t));
return (DDI_FAILURE);
}
/* Call ddi_intr_add_handler(). */
for (x = 0; x < actual; x++) {
if (ddi_intr_add_handler(si_ctlp->sictl_htable[x], si_intr,
(caddr_t)si_ctlp, NULL) != DDI_SUCCESS) {
SIDBG0(SIDBG_INTR|SIDBG_INIT, si_ctlp,
"ddi_intr_add_handler() failed");
for (y = 0; y < actual; y++) {
(void) ddi_intr_free(si_ctlp->sictl_htable[y]);
}
kmem_free(si_ctlp->sictl_htable,
si_ctlp->sictl_intr_size);
return (DDI_FAILURE);
}
}
/* Call ddi_intr_enable() for legacy interrupts. */
for (x = 0; x < si_ctlp->sictl_intr_cnt; x++) {
(void) ddi_intr_enable(si_ctlp->sictl_htable[x]);
}
return (DDI_SUCCESS);
}
/*
* si_add_msictl_intrs() handles MSI interrupts.
*/
static int
si_add_msi_intrs(si_ctl_state_t *si_ctlp)
{
dev_info_t *devinfo = si_ctlp->sictl_devinfop;
int count, avail, actual;
int x, y, rc, inum = 0;
SIDBG0(SIDBG_ENTRY|SIDBG_INIT, si_ctlp, "si_add_msi_intrs");
/* get number of interrupts. */
rc = ddi_intr_get_nintrs(devinfo, DDI_INTR_TYPE_MSI, &count);
if ((rc != DDI_SUCCESS) || (count == 0)) {
SIDBG2(SIDBG_INIT, si_ctlp,
"ddi_intr_get_nintrs() failed, "
"rc %d count %d\n", rc, count);
return (DDI_FAILURE);
}
/* get number of available interrupts. */
rc = ddi_intr_get_navail(devinfo, DDI_INTR_TYPE_MSI, &avail);
if ((rc != DDI_SUCCESS) || (avail == 0)) {
SIDBG2(SIDBG_INIT, si_ctlp,
"ddi_intr_get_navail() failed, "
"rc %d avail %d\n", rc, avail);
return (DDI_FAILURE);
}
if (avail < count) {
SIDBG2(SIDBG_INIT, si_ctlp,
"ddi_intr_get_nvail returned %d, navail() returned %d",
count, avail);
}
/* Allocate an array of interrupt handles. */
si_ctlp->sictl_intr_size = count * sizeof (ddi_intr_handle_t);
si_ctlp->sictl_htable = kmem_alloc(si_ctlp->sictl_intr_size, KM_SLEEP);
/* call ddi_intr_alloc(). */
rc = ddi_intr_alloc(devinfo, si_ctlp->sictl_htable, DDI_INTR_TYPE_MSI,
inum, count, &actual, DDI_INTR_ALLOC_NORMAL);
if ((rc != DDI_SUCCESS) || (actual == 0)) {
SIDBG1(SIDBG_INIT, si_ctlp,
"ddi_intr_alloc() failed, rc %d\n", rc);
kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
return (DDI_FAILURE);
}
/* use interrupt count returned */
if (actual < count) {
SIDBG2(SIDBG_INIT, si_ctlp,
"Requested: %d, Received: %d", count, actual);
}
si_ctlp->sictl_intr_cnt = actual;
/*
* Get priority for first msi, assume remaining are all the same.
*/
if (ddi_intr_get_pri(si_ctlp->sictl_htable[0],
&si_ctlp->sictl_intr_pri) != DDI_SUCCESS) {
SIDBG0(SIDBG_INIT, si_ctlp, "ddi_intr_get_pri() failed");
/* Free already allocated intr. */
for (y = 0; y < actual; y++) {
(void) ddi_intr_free(si_ctlp->sictl_htable[y]);
}
kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
return (DDI_FAILURE);
}
/* Test for high level mutex. */
if (si_ctlp->sictl_intr_pri >= ddi_intr_get_hilevel_pri()) {
SIDBG0(SIDBG_INIT, si_ctlp,
"si_add_msi_intrs: Hi level intr not supported");
/* Free already allocated intr. */
for (y = 0; y < actual; y++) {
(void) ddi_intr_free(si_ctlp->sictl_htable[y]);
}
kmem_free(si_ctlp->sictl_htable, sizeof (ddi_intr_handle_t));
return (DDI_FAILURE);
}
/* Call ddi_intr_add_handler(). */
for (x = 0; x < actual; x++) {
if (ddi_intr_add_handler(si_ctlp->sictl_htable[x], si_intr,
(caddr_t)si_ctlp, NULL) != DDI_SUCCESS) {
SIDBG0(SIDBG_INIT, si_ctlp,
"ddi_intr_add_handler() failed");
/* Free already allocated intr. */
for (y = 0; y < actual; y++) {
(void) ddi_intr_free(si_ctlp->sictl_htable[y]);
}
kmem_free(si_ctlp->sictl_htable,
si_ctlp->sictl_intr_size);
return (DDI_FAILURE);
}
}
(void) ddi_intr_get_cap(si_ctlp->sictl_htable[0],
&si_ctlp->sictl_intr_cap);
if (si_ctlp->sictl_intr_cap & DDI_INTR_FLAG_BLOCK) {
/* Call ddi_intr_block_enable() for MSI. */
(void) ddi_intr_block_enable(si_ctlp->sictl_htable,
si_ctlp->sictl_intr_cnt);
} else {
/* Call ddi_intr_enable() for MSI non block enable. */
for (x = 0; x < si_ctlp->sictl_intr_cnt; x++) {
(void) ddi_intr_enable(si_ctlp->sictl_htable[x]);
}
}
return (DDI_SUCCESS);
}
/*
* Removes the registered interrupts irrespective of whether they
* were legacy or MSI.
*/
static void
si_rem_intrs(si_ctl_state_t *si_ctlp)
{
int x;
SIDBG0(SIDBG_ENTRY, si_ctlp, "si_rem_intrs entered");
/* Disable all interrupts. */
if ((si_ctlp->sictl_intr_type == DDI_INTR_TYPE_MSI) &&
(si_ctlp->sictl_intr_cap & DDI_INTR_FLAG_BLOCK)) {
/* Call ddi_intr_block_disable(). */
(void) ddi_intr_block_disable(si_ctlp->sictl_htable,
si_ctlp->sictl_intr_cnt);
} else {
for (x = 0; x < si_ctlp->sictl_intr_cnt; x++) {
(void) ddi_intr_disable(si_ctlp->sictl_htable[x]);
}
}
/* Call ddi_intr_remove_handler(). */
for (x = 0; x < si_ctlp->sictl_intr_cnt; x++) {
(void) ddi_intr_remove_handler(si_ctlp->sictl_htable[x]);
(void) ddi_intr_free(si_ctlp->sictl_htable[x]);
}
kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
}
/*
* Resets either the port or the device connected to the port based on
* the flag variable.
*
* The reset effectively throws away all the pending commands. So, the caller
* has to make provision to handle the pending commands.
*
* After the reset, we wait till the port is ready again.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*
* Note: Not port-mult aware.
*/
static int
si_reset_dport_wait_till_ready(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port,
int flag)
{
uint32_t port_status;
int loop_count = 0;
sata_device_t sdevice;
uint32_t SStatus;
uint32_t SControl;
_NOTE(ASSUMING_PROTECTED(si_portp))
if (flag == SI_PORT_RESET) {
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
PORT_CONTROL_SET_BITS_PORT_RESET);
/* Port reset is not self clearing. So clear it now. */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
PORT_CONTROL_CLEAR_BITS_PORT_RESET);
} else {
/* Reset the device. */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
PORT_CONTROL_SET_BITS_DEV_RESET);
/*
* tidbit: this bit is self clearing; so there is no need
* for manual clear as we did for port reset.
*/
}
/* Set the reset in progress flag */
if (!(flag & SI_RESET_NO_EVENTS_UP)) {
si_portp->siport_reset_in_progress = 1;
}
/*
* For some reason, we are losing the interrupt enablement after
* any reset condition. So restore them back now.
*/
SIDBG1(SIDBG_INIT, si_ctlp,
"current interrupt enable set: 0x%x",
ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_INTERRUPT_ENABLE_SET(si_ctlp, port)));
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_INTERRUPT_ENABLE_SET(si_ctlp, port),
(INTR_COMMAND_COMPLETE |
INTR_COMMAND_ERROR |
INTR_PORT_READY |
INTR_POWER_CHANGE |
INTR_PHYRDY_CHANGE |
INTR_COMWAKE_RECEIVED |
INTR_UNRECOG_FIS |
INTR_DEV_XCHANGED |
INTR_SETDEVBITS_NOTIFY));
si_enable_port_interrupts(si_ctlp, port);
/*
* Every reset needs a PHY initialization.
*
* The way to initialize the PHY is to write a 1 and then
* a 0 to DET field of SControl register.
*/
/* Fetch the current SControl before writing the DET part with 1. */
SControl = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_SCONTROL(si_ctlp, port));
SCONTROL_SET_DET(SControl, SCONTROL_DET_COMRESET);
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SCONTROL(si_ctlp, port)),
SControl);
#ifndef __lock_lint
delay(SI_10MS_TICKS); /* give time for COMRESET to percolate */
#endif /* __lock_lint */
/* Now fetch the SControl again and rewrite the DET part with 0 */
SControl = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_SCONTROL(si_ctlp, port));
SCONTROL_SET_DET(SControl, SCONTROL_DET_NOACTION);
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SCONTROL(si_ctlp, port)),
SControl);
/*
* PHY may be initialized by now. Check the DET field of SStatus
* to determine if there is a device present.
*
* The DET field is valid only if IPM field indicates that
* the interface is in active state.
*/
loop_count = 0;
do {
SStatus = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_SSTATUS(si_ctlp, port));
if (SSTATUS_GET_IPM(SStatus) !=
SSTATUS_IPM_INTERFACE_ACTIVE) {
/*
* If the interface is not active, the DET field
* is considered not accurate. So we want to
* continue looping.
*/
SSTATUS_SET_DET(SStatus, SSTATUS_DET_NODEV_NOPHY);
}
if (loop_count++ > SI_POLLRATE_SSTATUS) {
/* We are effectively timing out after 0.1 sec. */
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (SSTATUS_GET_DET(SStatus) != SSTATUS_DET_DEVPRESENT_PHYONLINE);
SIDBG2(SIDBG_POLL_LOOP, si_ctlp,
"si_reset_dport_wait_till_ready: loop count: %d, \
SStatus: 0x%x",
loop_count,
SStatus);
/* Now check for port readiness. */
loop_count = 0;
do {
port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_STATUS(si_ctlp, port));
if (loop_count++ > SI_POLLRATE_PORTREADY) {
/* We are effectively timing out after 0.5 sec. */
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (!(port_status & PORT_STATUS_BITS_PORT_READY));
SIDBG3(SIDBG_POLL_LOOP, si_ctlp,
"si_reset_dport_wait_till_ready: loop count: %d, \
port_status: 0x%x, SStatus: 0x%x",
loop_count,
port_status,
SStatus);
/* Indicate to the framework that a reset has happened. */
if (!(flag & SI_RESET_NO_EVENTS_UP)) {
bzero((void *)&sdevice, sizeof (sata_device_t));
sdevice.satadev_addr.cport = port;
sdevice.satadev_addr.pmport = PORTMULT_CONTROL_PORT;
if (si_portp->siport_port_type == PORT_TYPE_MULTIPLIER) {
sdevice.satadev_addr.qual = SATA_ADDR_DPMPORT;
} else {
sdevice.satadev_addr.qual = SATA_ADDR_DCPORT;
}
sdevice.satadev_state = SATA_DSTATE_RESET |
SATA_DSTATE_PWR_ACTIVE;
if (si_ctlp->sictl_sata_hba_tran) {
sata_hba_event_notify(
si_ctlp->sictl_sata_hba_tran->sata_tran_hba_dip,
&sdevice,
SATA_EVNT_DEVICE_RESET);
}
SIDBG0(SIDBG_EVENT, si_ctlp,
"sending event up: SATA_EVNT_RESET");
}
if ((SSTATUS_GET_IPM(SStatus) == SSTATUS_IPM_INTERFACE_ACTIVE) &&
(SSTATUS_GET_DET(SStatus) ==
SSTATUS_DET_DEVPRESENT_PHYONLINE)) {
/* The interface is active and the device is present */
if (!(port_status & PORT_STATUS_BITS_PORT_READY)) {
/* But the port is is not ready for some reason */
SIDBG0(SIDBG_POLL_LOOP, si_ctlp,
"si_reset_dport_wait_till_ready failed");
return (SI_FAILURE);
}
}
SIDBG0(SIDBG_POLL_LOOP, si_ctlp,
"si_reset_dport_wait_till_ready returning success");
return (SI_SUCCESS);
}
/*
* Initializes the port.
*
* Initialization effectively throws away all the pending commands on
* the port. So, the caller has to make provision to handle the pending
* commands.
*
* After the port initialization, we wait till the port is ready again.
*
* WARNING, WARNING: The caller is expected to obtain the siport_mutex
* before calling us.
*/
static int
si_initialize_port_wait_till_ready(si_ctl_state_t *si_ctlp, int port)
{
uint32_t port_status;
int loop_count = 0;
uint32_t SStatus;
/* Initialize the port. */
ddi_put32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
PORT_CONTROL_SET_BITS_PORT_INITIALIZE);
/* Wait until Port Ready */
loop_count = 0;
do {
port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_STATUS(si_ctlp, port));
if (loop_count++ > SI_POLLRATE_PORTREADY) {
SIDBG1(SIDBG_INTR, si_ctlp,
"si_initialize_port_wait is timing out: "
"port_status: %x",
port_status);
/* We are effectively timing out after 0.5 sec. */
break;
}
/* Wait for 10 millisec */
#ifndef __lock_lint
delay(SI_10MS_TICKS);
#endif /* __lock_lint */
} while (!(port_status & PORT_STATUS_BITS_PORT_READY));
SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
"si_initialize_port_wait_till_ready: loop count: %d",
loop_count);
SStatus = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)PORT_SSTATUS(si_ctlp, port));
if ((SSTATUS_GET_IPM(SStatus) == SSTATUS_IPM_INTERFACE_ACTIVE) &&
(SSTATUS_GET_DET(SStatus) ==
SSTATUS_DET_DEVPRESENT_PHYONLINE)) {
/* The interface is active and the device is present */
if (!(port_status & PORT_STATUS_BITS_PORT_READY)) {
/* But the port is is not ready for some reason */
return (SI_FAILURE);
}
}
return (SI_SUCCESS);
}
/*
* si_watchdog_handler() calls us if it detects that there are some
* commands which timed out. We recalculate the timed out commands once
* again since some of them may have finished recently.
*/
static void
si_timeout_pkts(
si_ctl_state_t *si_ctlp,
si_port_state_t *si_portp,
int port,
uint32_t timedout_tags)
{
uint32_t slot_status;
uint32_t finished_tags;
SIDBG0(SIDBG_TIMEOUT|SIDBG_ENTRY, si_ctlp, "si_timeout_pkts entry");
mutex_enter(&si_portp->siport_mutex);
slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
(uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
/*
* Initialize the controller. The only way to timeout the commands
* is to reset or initialize the controller. We mop commands after
* the initialization.
*/
(void) si_initialize_port_wait_till_ready(si_ctlp, port);
/*
* Recompute the timedout tags since some of them may have finished
* meanwhile.
*/
finished_tags = si_portp->siport_pending_tags &
~slot_status & SI_SLOT_MASK;
timedout_tags &= ~finished_tags;
SIDBG2(SIDBG_TIMEOUT, si_ctlp,
"si_timeout_pkts: finished: %x, timeout: %x",
finished_tags,
timedout_tags);
mutex_exit(&si_portp->siport_mutex);
si_mop_commands(si_ctlp,
si_portp,
port,
slot_status,
0, /* failed_tags */
timedout_tags,
0, /* aborting_tags */
0); /* reset_tags */
}
/*
* Watchdog handler kicks in every 5 seconds to timeout any commands pending
* for long time.
*/
static void
si_watchdog_handler(si_ctl_state_t *si_ctlp)
{
uint32_t pending_tags = 0;
uint32_t timedout_tags = 0;
si_port_state_t *si_portp;
int port;
int tmpslot;
sata_pkt_t *satapkt;
/* max number of cycles this packet should survive */
int max_life_cycles;
/* how many cycles this packet survived so far */
int watched_cycles;
mutex_enter(&si_ctlp->sictl_mutex);
SIDBG0(SIDBG_TIMEOUT|SIDBG_ENTRY, si_ctlp,
"si_watchdog_handler entered");
for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
si_portp = si_ctlp->sictl_ports[port];
if (si_portp == NULL) {
continue;
}
mutex_enter(&si_portp->siport_mutex);
if (si_portp->siport_port_type == PORT_TYPE_NODEV) {
mutex_exit(&si_portp->siport_mutex);
continue;
}
pending_tags = si_portp->siport_pending_tags;
timedout_tags = 0;
while (pending_tags) {
tmpslot = ddi_ffs(pending_tags) - 1;
if (tmpslot == -1) {
break;
}
satapkt = si_portp->siport_slot_pkts[tmpslot];
if ((satapkt != NULL) && satapkt->satapkt_time) {
/*
* We are overloading satapkt_hba_driver_private
* with watched_cycle count.
*
* If a packet has survived for more than it's
* max life cycles, it is a candidate for time
* out.
*/
watched_cycles = (int)(intptr_t)
satapkt->satapkt_hba_driver_private;
watched_cycles++;
max_life_cycles = (satapkt->satapkt_time +
si_watchdog_timeout - 1) /
si_watchdog_timeout;
if (watched_cycles > max_life_cycles) {
timedout_tags |= (0x1 << tmpslot);
SIDBG1(SIDBG_TIMEOUT|SIDBG_VERBOSE,
si_ctlp,
"watchdog: timedout_tags: 0x%x",
timedout_tags);
}
satapkt->satapkt_hba_driver_private =
(void *)(intptr_t)watched_cycles;
}
CLEAR_BIT(pending_tags, tmpslot);
}
if (timedout_tags) {
mutex_exit(&si_portp->siport_mutex);
mutex_exit(&si_ctlp->sictl_mutex);
si_timeout_pkts(si_ctlp, si_portp, port, timedout_tags);
mutex_enter(&si_ctlp->sictl_mutex);
mutex_enter(&si_portp->siport_mutex);
}
mutex_exit(&si_portp->siport_mutex);
}
/* Reinstall the watchdog timeout handler. */
if (!(si_ctlp->sictl_flags & SI_NO_TIMEOUTS)) {
si_ctlp->sictl_timeout_id =
timeout((void (*)(void *))si_watchdog_handler,
(caddr_t)si_ctlp, si_watchdog_tick);
}
mutex_exit(&si_ctlp->sictl_mutex);
}
/*
* Logs the message.
*/
static void
si_log(si_ctl_state_t *si_ctlp, uint_t level, char *fmt, ...)
{
va_list ap;
mutex_enter(&si_log_mutex);
va_start(ap, fmt);
if (si_ctlp) {
(void) sprintf(si_log_buf, "%s-[%d]:",
ddi_get_name(si_ctlp->sictl_devinfop),
ddi_get_instance(si_ctlp->sictl_devinfop));
} else {
(void) sprintf(si_log_buf, "si3124:");
}
(void) vsprintf(si_log_buf, fmt, ap);
va_end(ap);
cmn_err(level, "%s", si_log_buf);
mutex_exit(&si_log_mutex);
}
static void
si_copy_out_regs(sata_cmd_t *scmd, fis_reg_h2d_t *fisp)
{
fis_reg_h2d_t fis = *fisp;
if (scmd->satacmd_flags.sata_copy_out_sec_count_msb)
scmd->satacmd_sec_count_msb = GET_FIS_SECTOR_COUNT_EXP(fis);
if (scmd->satacmd_flags.sata_copy_out_lba_low_msb)
scmd->satacmd_lba_low_msb = GET_FIS_SECTOR_EXP(fis);
if (scmd->satacmd_flags.sata_copy_out_lba_mid_msb)
scmd->satacmd_lba_mid_msb = GET_FIS_CYL_LOW_EXP(fis);
if (scmd->satacmd_flags.sata_copy_out_lba_high_msb)
scmd->satacmd_lba_high_msb = GET_FIS_CYL_HI_EXP(fis);
if (scmd->satacmd_flags.sata_copy_out_sec_count_lsb)
scmd->satacmd_sec_count_lsb = GET_FIS_SECTOR_COUNT(fis);
if (scmd->satacmd_flags.sata_copy_out_lba_low_lsb)
scmd->satacmd_lba_low_lsb = GET_FIS_SECTOR(fis);
if (scmd->satacmd_flags.sata_copy_out_lba_mid_lsb)
scmd->satacmd_lba_mid_lsb = GET_FIS_CYL_LOW(fis);
if (scmd->satacmd_flags.sata_copy_out_lba_high_lsb)
scmd->satacmd_lba_high_lsb = GET_FIS_CYL_HI(fis);
if (scmd->satacmd_flags.sata_copy_out_device_reg)
scmd->satacmd_device_reg = GET_FIS_DEV_HEAD(fis);
if (scmd->satacmd_flags.sata_copy_out_error_reg)
scmd->satacmd_error_reg = GET_FIS_FEATURES(fis);
}