/*
* 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
* 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
*/
/*
*/
/*
* This file contains functions that are called via interrupts.
*/
#ifdef DEBUG
#define VALID_IOMB_CHECK(p, w, m, b, c) \
if (!(w & PMCS_IOMB_VALID)) { \
char l[64]; \
(void) snprintf(l, sizeof (l), \
"%s: INVALID IOMB (oq_ci=%u oq_pi=%u)", __func__, b, c); \
continue; \
}
if (((w & PMCS_IOMB_OBID_MASK) >> PMCS_IOMB_OBID_SHIFT) != q) { \
"%s: COMPLETION WITH WRONG OBID (0x%x)", __func__, \
(w & PMCS_IOMB_OBID_MASK) >> PMCS_IOMB_OBID_SHIFT); \
}
#else
#define VALID_IOMB_CHECK(a, b, c, d, e)
#define WRONG_OBID_CHECK(a, b, c)
#endif
#define OQLIM_CHECK(p, l) \
if (++l == (p)->ioq_depth) { \
"%s: possible ob queue overflow", \
__func__); \
break; \
}
#define COPY_OUTBOUND(p, w, l, n, a, x, q, c) \
n = ((w & PMCS_IOMB_BC_MASK) >> PMCS_IOMB_BC_SHIFT); \
a = PMCS_QENTRY_SIZE; \
(void) memcpy(l, x, PMCS_QENTRY_SIZE); \
if (n > 1) { \
a <<= 1; \
(void) memcpy(&l[PMCS_QENTRY_SIZE], \
} \
"%s: ptr %p ci %d w0 %x nbuf %d", \
/*
* Map the link rate reported in the event to the SAS link rate value
*/
static uint8_t
{
switch (event_link_rate) {
case 1:
break;
case 2:
break;
case 4:
break;
}
return (sas_link_rate);
}
/*
* Called with pwrk lock
*/
static void
{
#ifdef DEBUG
#endif
/*
* If the command has timed out, leave it in that state.
*/
}
}
static void
{
#ifdef DEBUG
int i;
"unable to find work structure for tag 0x%x", htag);
if (htag == 0) {
return;
}
for (i = 0; i < 256; i++) {
"same tag already completed (%llu us ago)",
}
"same tag started (line %d) (%llu ns ago)",
pwp->ftag_lines[i], (unsigned long long)
}
}
#else
char buf[64];
"unable to find work structure for tag 0x%x", htag);
#endif
}
static void
{
return;
}
/*
* If the command has timed out, leave it in that state.
*/
}
/*
* Some SATA and SAS commands are run in "WAIT" mode.
* We can tell this from the tag type. In this case,
* we just do a wakeup (not a callback).
*/
if (tag_type == PMCS_TAG_TYPE_WAIT) {
}
return;
}
#ifdef DEBUG
#endif
/*
* Only update state to IOCOMPQ if we were in the INTR state.
* Any other state (e.g. TIMED_OUT, ABORTED) needs to remain.
*/
}
if (pwp->iocomp_cb_tail) {
} else {
}
/* Completion queue will be run at end of pmcs_iodone_intr */
}
static void
{
return;
}
/*
* The pwrk lock is now released
*/
}
static void
{
return;
}
/*
* Clear any subsidiary phys
*/
pptr->subsidiary) {
}
}
}
void
{
int need_ack = 0;
int primary;
switch (IOP_EVENT_EVENT(w1)) {
if (IOP_EVENT_STATUS(w1)) {
"PORT %d failed to stop (0x%x)",
} else {
"PHY 0x%x Stopped", phynum);
if (pptr->configured) {
} else {
}
}
/* Reposition htag to the 'expected' position. */
break;
case IOP_EVENT_SAS_PHY_UP:
{
0x5c, 0x5a, 0x56, 0x00
};
/*
* If we're not at running state, don't do anything
*/
break;
}
/*
* No need to lock the primary root PHY. It can never go
* away, and we're only concerned with the port width and
* the portid, both of which only ever change in this function.
*/
/* Copy the remote address into our phy handle */
/*
* Check to see if there is a PortID already active.
*/
if (rp) {
"PortID 0x%x: PHY 0x%x SAS LINK UP IS FOR "
break;
}
/*
* If the dtype isn't NOTHING, then this is actually
* the primary PHY for this port. It probably went
* down and came back up, so be sure not to mark it
* as a subsidiary.
*/
}
/* Add this PHY to the phymap */
wwn);
}
/*
* Get our iport, if attached, and set it up. Update
* the PHY's phymask props while we're locked.
*/
if (iport) {
if (primary) {
}
}
}
"PortID 0x%x: PHY 0x%x SAS LINK UP WIDENS PORT "
break;
}
/*
* Check to see if anything is here already
*/
"PortID 0x%x: SAS PHY 0x%x UP HITS EXISTING "
break;
}
"SAS link up on phy 0x%x, "
"but unexpected frame type 0x%x found", phynum,
break;
}
/*
* Check to see whether this is an expander or an endpoint
*/
switch (af.device_type) {
case SAS_IF_DTYPE_ENDPOINT:
break;
case SAS_IF_DTYPE_EDGE:
case SAS_IF_DTYPE_FANOUT:
break;
default:
break;
}
/*
* If this is a direct-attached SAS drive, do the spinup
* release now.
*/
"Release spinup hold on PHY 0x%x", phynum);
}
"PortID 0x%x: PHY 0x%x SAS"
} else {
"PortID 0x%x: PHY 0x%x SAS"
}
/* Add this PHY to the phymap */
}
/* Get a pointer to our iport and set it up if attached */
if (iport) {
if (primary) {
}
}
}
break;
}
case IOP_EVENT_SATA_PHY_UP: {
/*
* If we're not at running state, don't do anything
*/
break;
}
/*
* Check to see if anything is here already
*/
"PortID 0x%x: SATA PHY 0x%x"
" UP HITS EXISTING CONFIGURED TREE",
break;
}
/*
* Install the PHY number in the least significant byte
* with a NAA=3 (locally assigned address) in the most
* significant nubble.
*
* Later, we'll either use that or dig a
* WWN out of words 108..111.
*/
"PortID 0x%x: PHY 0x%x SATA LINK UP @ %s Gb/s",
/* Add this PHY to the phymap */
wwn);
}
/* Get our iport, if attached, and set it up */
if (iport) {
}
}
break;
}
/*
* No need to lock the entire tree for this
*/
break;
case IOP_EVENT_PHY_DOWN: {
/*
* If we're not at running state, don't do anything
*/
break;
}
/*
* subphy is a pointer to the PHY corresponding to the incoming
* event. pptr points to the primary PHY for the corresponding
* port. So, subphy and pptr may or may not be the same PHY,
* but that doesn't change what we need to do with each.
*/
"PortID 0x%x: PHY 0x%x LINK DOWN- no portid ptr",
break;
}
"PortID 0x%x: PHY 0x%x NOT VALID YET",
need_ack = 1;
break;
}
"PortID 0x%x: PHY 0x%x IN RESET",
/* Entire port is down due to a host-initiated reset */
/* Clear the phymask props in pptr */
if (iport) {
}
/* Clear down all PHYs in the port */
if (pptr->valid_device_id) {
}
}
pwp, DDI_NOSLEEP);
}
break;
}
"PortID 0x%x: PHY 0x%x TEMPORARILY DOWN",
need_ack = 1;
break;
}
/*
* This is not the last phy in the port, so if this
* is the primary PHY, promote another PHY to primary.
*/
if (pptr) {
/* Update primary pptr in ports */
NULL, "PortID 0x%x: PHY 0x%x "
"promoted to primary", portid,
} else {
NULL, "PortID 0x%x: PHY 0x%x: "
"unable to promote phy", portid,
phynum);
}
}
/*
* Drop port width on the primary phy handle
* No need to lock the entire tree for this
*/
/* Clear the iport reference and portid on the subphy */
/*
* If the iport was set on this phy, decrement its
* nphy count and remove this phy from the phys list.
*/
if (iport) {
subphy);
}
}
if (subphy->subsidiary)
/* Remove this PHY from the phymap */
DDI_SUCCESS) {
"Unable to remove phy %u for 0x%" PRIx64
}
"PortID 0x%x: PHY 0x%x LINK DOWN NARROWS PORT "
break;
}
"PortID 0x%x: PHY 0x%x LINK DOWN NOT HANDLED "
need_ack = 1;
break;
}
/* Remove this PHY from the phymap */
DDI_SUCCESS) {
"Unable to remove phy %u for 0x%" PRIx64
}
"PortID 0x%x: PHY 0x%x LINK DOWN (port invalid)",
/*
* Last PHY on the port.
* Assumption: pptr and subphy are both "valid". In fact,
* they should be one and the same.
*
* Drop port width on the primary phy handle
* Report the event and clear its PHY pm props while we've
* got the lock
*/
/* Clear the iport reference and portid on the subphy */
pwp, DDI_NOSLEEP);
/*
* If the iport was set on this phy, decrement its
* nphy count and remove this phy from the phys list.
* Also, clear the iport's pptr as this port is now
* down.
*/
if (iport) {
}
}
if (subphy->subsidiary)
/*
* Since we're now really dead, it's time to clean up.
*/
need_ack = 1;
break;
}
need_ack = 1;
if (pptr) {
}
}
break;
case IOP_EVENT_BROADCAST_SES:
if (pptr) {
}
break;
{
need_ack = 1;
break;
}
break;
break;
case IOP_EVENT_BROADCAST_EXP:
"PortID 0x%x: PHY 0x%x Broadcast Exp Change",
/*
* Comparing Section 6.8.1.4 of SMHBA (rev 7) spec and Section
* 7.2.3 of SAS2 (Rev 15) spec,
* _BROADCAST_EXPANDER event corresponds to _D01_4 primitive
*/
if (pptr) {
}
break;
switch (IOP_EVENT_STATUS(w1)) {
case IOP_PHY_START_OK:
"PHY 0x%x Started", phynum);
break;
case IOP_PHY_START_ALREADY:
"PHY 0x%x Started (Already)", phynum);
break;
case IOP_PHY_START_INVALID:
"PHY 0x%x failed to start (invalid phy)", phynum);
break;
case IOP_PHY_START_ERROR:
"PHY 0x%x Start Error", phynum);
break;
default:
"PHY 0x%x failed to start (0x%x)", phynum,
break;
}
/* Reposition htag to the 'expected' position. */
break;
need_ack = 1;
break;
need_ack = 1;
break;
need_ack = 1;
break;
need_ack = 1;
break;
need_ack = 1;
break;
break;
case IOP_EVENT_PORT_RECOVER:
break;
case IOP_EVENT_PORT_INVALID:
break;
}
"PortID 0x%x: PORT Now Invalid", portid);
break;
break;
break;
/*
* Comparing Section 6.8.1.4 of SMHBA (rev 7) spec and Section
* 7.2.3 of SAS2 (Rev 15) spec,
* _BROADCAST_ASYNC event corresponds to _D04_7 primitive
*/
if (pptr) {
}
break;
default:
"unknown SAS H/W Event PHY 0x%x", phynum);
break;
}
if (need_ack) {
/*
* Don't lock the entire tree for this. Just grab the mutex
* on the root PHY.
*/
}
}
static void
{
if (pwrk) {
} else {
"ECHO completion with no work structure", iomb);
}
}
static void
{
char *path;
w = iomb;
path = "????";
} else {
}
if (status != PMCOUT_STATUS_XFER_CMD_FRAME_ISSUED) {
status);
}
/*
* There may be pending command on a target device.
* Or, it may be a double fault.
*/
}
} else {
"%s: tag %x put onto the wire for %s",
if (pwrk) {
}
}
}
static void
{
w = iomb;
/*
* If the status is PMCOUT_STATUS_XFER_ERROR_ABORTED_NCQ_MODE,
* we have to issue a READ LOG EXT ATA (page 0x10) command
* to the device. In this case, htag is not valid.
*
* If the status is PMCOUT_STATUS_XFER_CMD_FRAME_ISSUED, we're
* just noting that an I/O got put onto the wire.
*
* Othewise, other errors are indicative that things need to
* be aborted.
*/
if (htag) {
}
if (pwrk) {
} else {
"cannot find work structure for SATA completion", __func__);
return;
}
if (status != PMCOUT_STATUS_XFER_CMD_FRAME_ISSUED) {
status);
}
htag = 0;
} else {
}
"%s: Bad SATA Status (tag 0x%x) %s on %s",
/*
* Unlike SSP devices, we let the abort we
* schedule above force the completion of
* problem commands.
*/
} else {
"%s: tag %x put onto the wire for %s",
}
}
static void
{
char *path;
"%s: cannot find work structure for ABORT", __func__);
return;
}
if (pptr) {
pptr->abort_pending = 0;
pptr->abort_sent = 0;
/*
* Don't do this if the status was ABORT_IN_PROGRESS and
* the scope bit was set
*/
pptr->abort_all_start = 0;
}
} else {
path = "(no phy)";
}
switch (status) {
case PMCOUT_STATUS_OK:
if (scope) {
"%s: abort all succeeded for %s. (htag=0x%x)",
} else {
"%s: abort tag 0x%x succeeded for %s. (htag=0x%x)",
}
break;
if (scope) {
"%s: ABORT %s failed (DEV NOT VALID) for %s. "
} else {
"%s: ABORT %s failed (I/O NOT VALID) for %s. "
}
break;
"for %s, htag 0x%x (ABORT IN PROGRESS)", __func__,
break;
default:
break;
}
}
static void
{
int i;
"VALID bit not set on INBOUND IOMB");
"opcode not set on inbound IOMB");
} else {
"unknown GENERAL EVENT status (0x%x)",
}
/* Pull up bad IOMB into usual position */
for (i = 0; i < PMCS_MSG_SIZE - 2; i++) {
}
/* overwrite status with an error */
if (pwrk) {
}
}
void
{
switch (w0 & PMCS_IOMB_OPCODE_MASK) {
case PMCOUT_SSP_COMPLETION:
/*
* We only get SSP completion here for Task Management
* completions.
*/
case PMCOUT_SMP_COMPLETION:
case PMCOUT_LOCAL_PHY_CONTROL:
case PMCOUT_GET_NVMD_DATA:
case PMCOUT_SET_NVMD_DATA:
case PMCOUT_GET_DEVICE_STATE:
case PMCOUT_SET_DEVICE_STATE:
break;
case PMCOUT_SSP_ABORT:
case PMCOUT_SATA_ABORT:
case PMCOUT_SMP_ABORT:
break;
case PMCOUT_SSP_EVENT:
break;
case PMCOUT_ECHO:
break;
"InvPort\2InvPhy\1InvSEA");
}
break;
case PMCOUT_SKIP_ENTRIES:
break;
default:
"%s: unhandled message", __func__);
break;
}
}
if (lim) {
}
}
/*
* pmcs_check_intr_coal
*
* This function makes a determination on the dynamic value of the
* interrupt coalescing timer register. We only use this for I/O
* completions.
*
* The basic algorithm is as follows:
*
* PMCS_MAX_IO_COMPS_PER_INTR: The maximum number of I/O completions per
* I/O completion interrupt. We won't increase the interrupt coalescing
* timer if we're already processing this many completions per interrupt
* beyond the threshold.
*
* Values in io_intr_coal structure:
*
* intr_latency: The average number of nsecs between interrupts during
* the echo test. Used to help determine whether to increase the coalescing
* timer.
*
* intr_threshold: Calculated number of interrupts beyond which we may
* increase the timer. This value is calculated based on the calculated
* interrupt latency during the ECHO test and the current value of the
* coalescing timer.
*
* nsecs_between_intrs: Total number of nsecs between all the interrupts
* in the current timeslice.
*
* last_io_comp: Time of the last I/O interrupt.
*
* num_io_completions: Number of I/O completions during the slice
*
* num_intrs: Number of I/O completion interrupts during the slice
*
* max_io_completions: Number of times we hit >= PMCS_MAX_IO_COMPS_PER_INTR
* during interrupt processing.
*
* Low and high marks used to determine whether we processed enough interrupts
* that contained the maximum number of I/O completions to warrant increasing
* the timer
*
* intr_coal_timer: The current value of the register (in usecs)
*
* timer_on: B_TRUE means we are using the timer
*
* The timer is increased if we processed more than intr_threshold interrupts
* during the quantum and the number of interrupts containing the maximum
* number of I/O completions is between PMCS_MAX_IO_COMPS_LOWAT_SHIFT and
* _HIWAT_SHIFT
*
* If the average time between completions is greater than twice
* the current timer value, the timer value is decreased.
*
* If we did not take any interrupts during a quantum, we turn the timer off.
*/
void
{
/*
* Wait for next time quantum... collect stats
*/
lbolt = ddi_get_lbolt();
if (ret == -1) {
break;
}
}
continue;
}
&pwp->io_intr_coal);
/*
* Determine whether to adjust timer
*/
/*
* If timer is off, nothing more to do.
*/
continue;
}
/*
* No interrupts. Turn off the timer.
*/
}
ici->max_io_completions = 0;
ici->num_io_completions = 0;
pmcs_io_intr_coal_t *, ici);
continue;
}
} else if (avg_nsecs >
}
/*
* Reset values for new sampling period.
*/
ici->max_io_completions = 0;
ici->nsecs_between_intrs = 0;
ici->num_io_completions = 0;
/*
* If a firmware event log file is configured, check to see
* if it needs to be written to the file. We do this here
* because writing to a file from a callout thread (i.e.
* from the watchdog timer) can cause livelocks.
*/
if (pwp->fwlog_file) {
}
}
thread_exit();
}
void
{
if ((iomb_opcode == PMCOUT_SSP_COMPLETION) ||
(iomb_opcode == PMCOUT_SATA_COMPLETION)) {
ioccb =
"%s: kmem_cache_alloc failed", __func__);
break;
}
PMCS_OQ_IODONE, ci);
niodone++;
} else {
PMCS_OQ_IODONE, ci);
switch (iomb_opcode) {
case PMCOUT_ECHO:
break;
case PMCOUT_SATA_EVENT:
break;
case PMCOUT_SSP_EVENT:
break;
case PMCOUT_SKIP_ENTRIES:
break;
default:
"%s: unhandled message", __func__);
ptr);
break;
}
}
}
if (lim != 0) {
}
/*
* Update the interrupt coalescing timer check stats and run
* completions for queued up commands.
*/
if (niodone > 0) {
/*
* If we can't get the lock, then completions are either
* already running or will be scheduled to do so shortly.
*/
}
if (niodone >= PMCS_MAX_IO_COMPS_PER_INTR) {
}
}
}
void
{
PMCS_OQ_EVENTS, ci);
switch (w0 & PMCS_IOMB_OPCODE_MASK) {
case PMCOUT_ECHO:
break;
case PMCOUT_SATA_EVENT:
break;
case PMCOUT_SSP_EVENT:
break;
case PMCOUT_GENERAL_EVENT:
break;
{
break;
}
case PMCOUT_SAS_HW_EVENT:
if (nbuf > 1) {
"multiple SAS HW_EVENT (%d) responses "
"in EVENT OQ", nbuf);
}
break;
case PMCOUT_FW_FLASH_UPDATE:
case PMCOUT_GET_TIME_STAMP:
case PMCOUT_GET_DEVICE_STATE:
case PMCOUT_SET_DEVICE_STATE:
case PMCOUT_SAS_DIAG_EXECUTE:
break;
case PMCOUT_SKIP_ENTRIES:
break;
default:
"%s: unhandled message", __func__);
break;
}
}
if (lim) {
}
}
void
{
#ifdef DEBUG
int i;
for (i = 0; i < 256; i++) {
"Inbound msg (tag 0x%8x) timed out - "
"was started %llu ns ago in %s:%d",
return;
}
}
#endif
"Inbound Message (tag 0x%08x) timed out- was started in %s",
}