2005-01-06 01:43:34 +00:00
|
|
|
/*-
|
2017-11-27 14:52:40 +00:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
|
|
*
|
2020-11-20 01:15:48 +00:00
|
|
|
* Copyright (c) 2009-2020 Alexander Motin <mav@FreeBSD.org>
|
2009-08-01 01:04:26 +00:00
|
|
|
* Copyright (c) 1997-2009 by Matthew Jacob
|
2007-03-10 02:39:54 +00:00
|
|
|
* All rights reserved.
|
2009-08-01 01:04:26 +00:00
|
|
|
*
|
2007-03-10 02:39:54 +00:00
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
2009-08-01 01:04:26 +00:00
|
|
|
*
|
2007-03-10 02:39:54 +00:00
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
2009-08-01 01:04:26 +00:00
|
|
|
*
|
2007-03-10 02:39:54 +00:00
|
|
|
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
* SUCH DAMAGE.
|
2009-08-01 01:04:26 +00:00
|
|
|
*
|
2007-03-10 02:39:54 +00:00
|
|
|
*/
|
|
|
|
/*
|
2020-11-20 01:15:48 +00:00
|
|
|
* Machine and OS Independent Target Mode Code for the Qlogic FC adapters.
|
2000-01-04 00:00:08 +00:00
|
|
|
*/
|
2002-02-04 21:04:25 +00:00
|
|
|
/*
|
|
|
|
* Bug fixes gratefully acknowledged from:
|
|
|
|
* Oded Kedem <oded@kashya.com>
|
|
|
|
*/
|
2000-01-04 00:00:08 +00:00
|
|
|
/*
|
|
|
|
* Include header file appropriate for platform we're building on.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef __NetBSD__
|
|
|
|
#include <dev/ic/isp_netbsd.h>
|
|
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
2006-07-16 20:11:50 +00:00
|
|
|
#include <sys/cdefs.h>
|
|
|
|
__FBSDID("$FreeBSD$");
|
2000-01-04 00:00:08 +00:00
|
|
|
#include <dev/isp/isp_freebsd.h>
|
|
|
|
#endif
|
|
|
|
#ifdef __OpenBSD__
|
|
|
|
#include <dev/ic/isp_openbsd.h>
|
|
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
|
|
#include "isp_linux.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef ISP_TARGET_MODE
|
2006-11-02 03:21:32 +00:00
|
|
|
static void isp_got_tmf_24xx(ispsoftc_t *, at7_entry_t *);
|
2017-03-18 17:01:11 +00:00
|
|
|
static void isp_handle_abts(ispsoftc_t *, abts_t *);
|
2006-11-02 03:21:32 +00:00
|
|
|
static void isp_handle_ctio7(ispsoftc_t *, ct7_entry_t *);
|
2017-03-18 19:27:16 +00:00
|
|
|
static void isp_handle_notify_24xx(ispsoftc_t *, in_fcentry_24xx_t *);
|
2000-01-04 00:00:08 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The Qlogic driver gets an interrupt to look at response queue entries.
|
|
|
|
* Some of these are status completions for initiatior mode commands, but
|
|
|
|
* if target mode is enabled, we get a whole wad of response queue entries
|
|
|
|
* to be handled here.
|
|
|
|
*
|
|
|
|
* Basically the split into 3 main groups: Lun Enable/Modification responses,
|
|
|
|
* SCSI Command processing, and Immediate Notification events.
|
|
|
|
*
|
|
|
|
* You start by writing a request queue entry to enable target mode (and
|
|
|
|
* establish some resource limitations which you can modify later).
|
|
|
|
* The f/w responds with a LUN ENABLE or LUN MODIFY response with
|
|
|
|
* the status of this action. If the enable was successful, you can expect...
|
|
|
|
*
|
|
|
|
* Response queue entries with SCSI commands encapsulate show up in an ATIO
|
|
|
|
* (Accept Target IO) type- sometimes with enough info to stop the command at
|
|
|
|
* this level. Ultimately the driver has to feed back to the f/w's request
|
|
|
|
* queue a sequence of CTIOs (continue target I/O) that describe data to
|
|
|
|
* be moved and/or status to be sent) and finally finishing with sending
|
|
|
|
* to the f/w's response queue an ATIO which then completes the handshake
|
|
|
|
* with the f/w for that command. There's a lot of variations on this theme,
|
|
|
|
* including flags you can set in the CTIO for the Qlogic 2X00 fibre channel
|
|
|
|
* cards that 'auto-replenish' the f/w's ATIO count, but this is the basic
|
|
|
|
* gist of it.
|
|
|
|
*
|
|
|
|
* The third group that can show up in the response queue are Immediate
|
|
|
|
* Notification events. These include things like notifications of SCSI bus
|
|
|
|
* resets, or Bus Device Reset messages or other messages received. This
|
2001-02-06 09:25:10 +00:00
|
|
|
* a classic oddbins area. It can get a little weird because you then turn
|
2000-01-04 00:00:08 +00:00
|
|
|
* around and acknowledge the Immediate Notify by writing an entry onto the
|
|
|
|
* request queue and then the f/w turns around and gives you an acknowledgement
|
|
|
|
* to *your* acknowledgement on the response queue (the idea being to let
|
|
|
|
* the f/w tell you when the event is *really* over I guess).
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A new response queue entry has arrived. The interrupt service code
|
|
|
|
* has already swizzled it into the platform dependent from canonical form.
|
|
|
|
*
|
|
|
|
* Because of the way this driver is designed, unfortunately most of the
|
|
|
|
* actual synchronization work has to be done in the platform specific
|
|
|
|
* code- we have no synchroniation primitives in the common code.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
2020-11-20 19:36:34 +00:00
|
|
|
isp_target_notify(ispsoftc_t *isp, void *vptr, uint32_t *optrp, uint16_t ql)
|
2000-01-04 00:00:08 +00:00
|
|
|
{
|
|
|
|
union {
|
2006-11-02 03:21:32 +00:00
|
|
|
at7_entry_t *at7iop;
|
|
|
|
ct7_entry_t *ct7iop;
|
|
|
|
in_fcentry_24xx_t *inot_24xx;
|
|
|
|
na_fcentry_24xx_t *nack_24xx;
|
2000-01-04 00:00:08 +00:00
|
|
|
isphdr_t *hp;
|
2006-11-02 03:21:32 +00:00
|
|
|
abts_t *abts;
|
|
|
|
abts_rsp_t *abts_rsp;
|
2000-01-04 00:00:08 +00:00
|
|
|
void * *vp;
|
2006-11-02 03:21:32 +00:00
|
|
|
#define at7iop unp.at7iop
|
|
|
|
#define ct7iop unp.ct7iop
|
|
|
|
#define inot_24xx unp.inot_24xx
|
|
|
|
#define nack_24xx unp.nack_24xx
|
|
|
|
#define abts unp.abts
|
|
|
|
#define abts_rsp unp.abts_rsp
|
2000-01-04 00:00:08 +00:00
|
|
|
#define hdrp unp.hp
|
|
|
|
} unp;
|
2006-02-15 00:31:48 +00:00
|
|
|
uint8_t local[QENTRY_LEN];
|
2017-03-19 09:30:03 +00:00
|
|
|
int type, len, level, rval = 1;
|
2000-01-04 00:00:08 +00:00
|
|
|
|
Major restructuring for swizzling to the request queue and unswizzling from
the response queue. Instead of the ad hoc ISP_SWIZZLE_REQUEST, we now have
a complete set of inline functions in isp_inline.h. Each platform is
responsible for providing just one of a set of ISP_IOX_{GET,PUT}{8,16,32}
macros.
The reason this needs to be done is that we need to have a single set of
functions that will work correctly on multiple architectures for both little
and big endian machines. It also needs to work correctly in the case that
we have the request or response queues in memory that has to be treated
specially (e.g., have ddi_dma_sync called on it for Solaris after we update
it or before we read from it). It also has to handle the SBus cards (for
platforms that have them) which, while on a Big Endian machine, do *not*
require *most* of the request/response queue entry fields to be swizzled
or unswizzled.
One thing that falls out of this is that we no longer build requests in the
request queue itself. Instead, we build the request locally (e.g., on the
stack) and then as part of the swizzling operation, copy it to the request
queue entry we've allocated. I thought long and hard about whether this was
too expensive a change to make as it in a lot of cases requires an extra
copy. On balance, the flexbility is worth it. With any luck, the entry that
we build locally stays in a processor writeback cache (after all, it's only
64 bytes) so that the cost of actually flushing it to the memory area that is
the shared queue with the PCI device is not all that expensive. We may examine
this again and try to get clever in the future to try and avoid copies.
Another change that falls out of this is that MEMORYBARRIER should be taken
a lot more seriously. The macro ISP_ADD_REQUEST does a MEMORYBARRIER on the
entry being added. But there had been many other places this had been missing.
It's now very important that it be done.
Additional changes:
Fix a longstanding buglet of sorts. When we get an entry via isp_getrqentry,
the iptr value that gets returned is the value we intend to eventually plug
into the ISP registers as the entry *one past* the last one we've written-
*not* the current entry we're updating. All along we've been calling sync
functions on the wrong index value. Argh. The 'fix' here is to rename all
'iptr' variables as 'nxti' to remember that this is the 'next' pointer-
not the current pointer.
Devote a single bit to mboxbsy- and set aside bits for output mbox registers
that we need to pick up- we can have at least one command which does not
have any defined output registers (MBOX_EXECUTE_FIRMWARE).
MFC after: 2 weeks
2001-12-11 00:18:45 +00:00
|
|
|
type = isp_get_response_type(isp, (isphdr_t *)vptr);
|
2000-01-04 00:00:08 +00:00
|
|
|
unp.vp = vptr;
|
|
|
|
|
2020-11-27 15:50:20 +00:00
|
|
|
if (isp->isp_dblev & ISP_LOGTDEBUG2)
|
|
|
|
isp_print_qentry(isp, __func__, *optrp, vptr);
|
2000-01-04 00:00:08 +00:00
|
|
|
|
2009-08-01 01:04:26 +00:00
|
|
|
switch (type) {
|
2000-01-04 00:00:08 +00:00
|
|
|
case RQSTYPE_ATIO:
|
2015-11-23 10:06:19 +00:00
|
|
|
isp_get_atio7(isp, at7iop, (at7_entry_t *) local);
|
|
|
|
at7iop = (at7_entry_t *) local;
|
|
|
|
/*
|
|
|
|
* Check for and do something with commands whose
|
|
|
|
* IULEN extends past a single queue entry.
|
|
|
|
*/
|
2016-05-13 18:55:03 +00:00
|
|
|
len = at7iop->at_ta_len & 0x0fff;
|
2015-11-23 10:06:19 +00:00
|
|
|
if (len > (QENTRY_LEN - 8)) {
|
|
|
|
len -= (QENTRY_LEN - 8);
|
|
|
|
isp_prt(isp, ISP_LOGINFO, "long IU length (%d) ignored", len);
|
|
|
|
while (len > 0) {
|
2020-11-20 19:36:34 +00:00
|
|
|
*optrp = ISP_NXT_QENTRY(*optrp, ql);
|
2015-11-23 10:06:19 +00:00
|
|
|
len -= QENTRY_LEN;
|
2006-11-02 03:21:32 +00:00
|
|
|
}
|
|
|
|
}
|
2015-11-23 10:06:19 +00:00
|
|
|
/*
|
|
|
|
* Check for a task management function
|
|
|
|
*/
|
|
|
|
if (at7iop->at_cmnd.fcp_cmnd_task_management) {
|
|
|
|
isp_got_tmf_24xx(isp, at7iop);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Just go straight to outer layer for this one.
|
|
|
|
*/
|
|
|
|
isp_async(isp, ISPASYNC_TARGET_ACTION, local);
|
2000-01-04 00:00:08 +00:00
|
|
|
break;
|
2006-11-02 03:21:32 +00:00
|
|
|
|
|
|
|
case RQSTYPE_CTIO7:
|
|
|
|
isp_get_ctio7(isp, ct7iop, (ct7_entry_t *) local);
|
|
|
|
isp_handle_ctio7(isp, (ct7_entry_t *) local);
|
|
|
|
break;
|
|
|
|
|
2000-01-04 00:00:08 +00:00
|
|
|
case RQSTYPE_NOTIFY:
|
2020-11-20 01:15:48 +00:00
|
|
|
isp_get_notify_24xx(isp, inot_24xx, (in_fcentry_24xx_t *)local);
|
|
|
|
isp_handle_notify_24xx(isp, (in_fcentry_24xx_t *)local);
|
2020-11-27 05:13:52 +00:00
|
|
|
break;
|
2000-01-04 00:00:08 +00:00
|
|
|
|
|
|
|
case RQSTYPE_NOTIFY_ACK:
|
|
|
|
/*
|
|
|
|
* The ISP is acknowledging our acknowledgement of an
|
|
|
|
* Immediate Notify entry for some asynchronous event.
|
|
|
|
*/
|
2020-11-20 01:15:48 +00:00
|
|
|
isp_get_notify_ack_24xx(isp, nack_24xx, (na_fcentry_24xx_t *) local);
|
|
|
|
nack_24xx = (na_fcentry_24xx_t *) local;
|
|
|
|
if (nack_24xx->na_status != NA_OK)
|
|
|
|
level = ISP_LOGINFO;
|
|
|
|
else
|
|
|
|
level = ISP_LOGTDEBUG1;
|
|
|
|
isp_prt(isp, level, "Notify Ack Status=0x%x; Subcode 0x%x seqid=0x%x", nack_24xx->na_status, nack_24xx->na_status_subcode, nack_24xx->na_rxid);
|
2000-01-04 00:00:08 +00:00
|
|
|
break;
|
2006-11-02 03:21:32 +00:00
|
|
|
|
|
|
|
case RQSTYPE_ABTS_RCVD:
|
|
|
|
isp_get_abts(isp, abts, (abts_t *)local);
|
2017-03-18 17:01:11 +00:00
|
|
|
isp_handle_abts(isp, (abts_t *)local);
|
2006-11-02 03:21:32 +00:00
|
|
|
break;
|
|
|
|
case RQSTYPE_ABTS_RSP:
|
|
|
|
isp_get_abts_rsp(isp, abts_rsp, (abts_rsp_t *)local);
|
|
|
|
abts_rsp = (abts_rsp_t *) local;
|
2020-11-20 01:15:48 +00:00
|
|
|
if (abts_rsp->abts_rsp_status)
|
2006-11-02 03:21:32 +00:00
|
|
|
level = ISP_LOGINFO;
|
2020-11-20 01:15:48 +00:00
|
|
|
else
|
2006-11-02 03:21:32 +00:00
|
|
|
level = ISP_LOGTDEBUG0;
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, level, "ABTS RSP response[0x%x]: status=0x%x sub=(0x%x 0x%x)", abts_rsp->abts_rsp_rxid_task, abts_rsp->abts_rsp_status,
|
|
|
|
abts_rsp->abts_rsp_payload.rsp.subcode1, abts_rsp->abts_rsp_payload.rsp.subcode2);
|
2006-11-02 03:21:32 +00:00
|
|
|
break;
|
2000-01-04 00:00:08 +00:00
|
|
|
default:
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGERR, "%s: unknown entry type 0x%x", __func__, type);
|
2002-06-16 04:56:07 +00:00
|
|
|
rval = 0;
|
2000-01-04 00:00:08 +00:00
|
|
|
break;
|
|
|
|
}
|
2006-11-02 03:21:32 +00:00
|
|
|
#undef at7iop
|
|
|
|
#undef ct7iop
|
|
|
|
#undef inot_24xx
|
|
|
|
#undef hack_24xx
|
|
|
|
#undef abts
|
|
|
|
#undef abts_rsp
|
2000-01-04 00:00:08 +00:00
|
|
|
#undef hdrp
|
|
|
|
return (rval);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Command completion- both for handling cases of no resources or
|
|
|
|
* no blackhole driver, or other cases where we have to, inline,
|
|
|
|
* finish the command sanely, or for normal command completion.
|
|
|
|
*
|
|
|
|
* The 'completion' code value has the scsi status byte in the low 8 bits.
|
|
|
|
* If status is a CHECK CONDITION and bit 8 is nonzero, then bits 12..15 have
|
|
|
|
* the sense key and bits 16..23 have the ASCQ and bits 24..31 have the ASC
|
|
|
|
* values.
|
|
|
|
*
|
|
|
|
* NB: the key, asc, ascq, cannot be used for parallel SCSI as it doesn't
|
2001-04-04 21:49:51 +00:00
|
|
|
* NB: inline SCSI sense reporting. As such, we lose this information. XXX.
|
2000-01-04 00:00:08 +00:00
|
|
|
*
|
|
|
|
* For both parallel && fibre channel, we use the feature that does
|
|
|
|
* an automatic resource autoreplenish so we don't have then later do
|
|
|
|
* put of an atio to replenish the f/w's resource count.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_endcmd(ispsoftc_t *isp, ...)
|
2000-01-04 00:00:08 +00:00
|
|
|
{
|
2009-08-01 01:04:26 +00:00
|
|
|
uint32_t code, hdl;
|
|
|
|
uint8_t sts;
|
2020-11-20 01:15:48 +00:00
|
|
|
at7_entry_t *aep;
|
|
|
|
ct7_entry_t _ctio7, *cto = &_ctio7;
|
2009-08-01 01:04:26 +00:00
|
|
|
va_list ap;
|
2017-02-22 06:43:49 +00:00
|
|
|
int vpidx, nphdl;
|
2000-01-04 00:00:08 +00:00
|
|
|
|
2020-11-20 01:15:48 +00:00
|
|
|
va_start(ap, isp);
|
|
|
|
aep = va_arg(ap, at7_entry_t *);
|
|
|
|
nphdl = va_arg(ap, int);
|
|
|
|
/*
|
|
|
|
* Note that vpidx may equal 0xff (unknown) here
|
|
|
|
*/
|
|
|
|
vpidx = va_arg(ap, int);
|
|
|
|
code = va_arg(ap, uint32_t);
|
|
|
|
hdl = va_arg(ap, uint32_t);
|
|
|
|
va_end(ap);
|
|
|
|
isp_prt(isp, ISP_LOGTDEBUG0, "%s: [RX_ID 0x%x] chan %d code %x", __func__, aep->at_rxid, vpidx, code);
|
|
|
|
|
|
|
|
sts = code & 0xff;
|
|
|
|
ISP_MEMZERO(cto, sizeof(*cto));
|
|
|
|
cto->ct_header.rqs_entry_type = RQSTYPE_CTIO7;
|
|
|
|
cto->ct_header.rqs_entry_count = 1;
|
|
|
|
cto->ct_nphdl = nphdl;
|
|
|
|
cto->ct_rxid = aep->at_rxid;
|
|
|
|
cto->ct_iid_lo = (aep->at_hdr.s_id[1] << 8) | aep->at_hdr.s_id[2];
|
|
|
|
cto->ct_iid_hi = aep->at_hdr.s_id[0];
|
|
|
|
cto->ct_oxid = aep->at_hdr.ox_id;
|
|
|
|
cto->ct_scsi_status = sts;
|
|
|
|
cto->ct_vpidx = vpidx;
|
|
|
|
cto->ct_flags = CT7_NOACK;
|
|
|
|
if (code & ECMD_TERMINATE) {
|
|
|
|
cto->ct_flags |= CT7_TERMINATE;
|
|
|
|
} else if (code & ECMD_SVALID) {
|
|
|
|
cto->ct_flags |= CT7_FLAG_MODE1 | CT7_SENDSTATUS;
|
|
|
|
cto->ct_scsi_status |= (FCP_SNSLEN_VALID << 8);
|
|
|
|
cto->ct_senselen = min(16, MAXRESPLEN_24XX);
|
|
|
|
ISP_MEMZERO(cto->rsp.m1.ct_resp, sizeof (cto->rsp.m1.ct_resp));
|
|
|
|
cto->rsp.m1.ct_resp[0] = 0xf0;
|
|
|
|
cto->rsp.m1.ct_resp[2] = (code >> 12) & 0xf;
|
|
|
|
cto->rsp.m1.ct_resp[7] = 8;
|
|
|
|
cto->rsp.m1.ct_resp[12] = (code >> 16) & 0xff;
|
|
|
|
cto->rsp.m1.ct_resp[13] = (code >> 24) & 0xff;
|
|
|
|
} else if (code & ECMD_RVALID) {
|
|
|
|
cto->ct_flags |= CT7_FLAG_MODE1 | CT7_SENDSTATUS;
|
|
|
|
cto->ct_scsi_status |= (FCP_RSPLEN_VALID << 8);
|
|
|
|
cto->rsp.m1.ct_resplen = 4;
|
|
|
|
ISP_MEMZERO(cto->rsp.m1.ct_resp, sizeof (cto->rsp.m1.ct_resp));
|
|
|
|
cto->rsp.m1.ct_resp[0] = (code >> 12) & 0xf;
|
|
|
|
cto->rsp.m1.ct_resp[1] = (code >> 16) & 0xff;
|
|
|
|
cto->rsp.m1.ct_resp[2] = (code >> 24) & 0xff;
|
|
|
|
cto->rsp.m1.ct_resp[3] = 0;
|
2015-11-23 10:06:19 +00:00
|
|
|
} else {
|
2020-11-20 01:15:48 +00:00
|
|
|
cto->ct_flags |= CT7_FLAG_MODE1 | CT7_SENDSTATUS;
|
2000-01-04 00:00:08 +00:00
|
|
|
}
|
2020-11-20 01:15:48 +00:00
|
|
|
if (aep->at_cmnd.cdb_dl.sf.fcp_cmnd_dl != 0) {
|
|
|
|
cto->ct_resid = aep->at_cmnd.cdb_dl.sf.fcp_cmnd_dl;
|
|
|
|
cto->ct_scsi_status |= (FCP_RESID_UNDERFLOW << 8);
|
|
|
|
}
|
|
|
|
cto->ct_syshandle = hdl;
|
2020-11-27 15:50:20 +00:00
|
|
|
return (isp_send_entry(isp, cto));
|
2000-01-04 00:00:08 +00:00
|
|
|
}
|
|
|
|
|
2006-04-21 18:30:01 +00:00
|
|
|
/*
|
|
|
|
* These are either broadcast events or specifically CTIO fast completion
|
|
|
|
*/
|
2009-08-01 01:04:26 +00:00
|
|
|
|
2017-03-14 18:42:33 +00:00
|
|
|
void
|
2006-04-21 18:30:01 +00:00
|
|
|
isp_target_async(ispsoftc_t *isp, int bus, int event)
|
2000-01-04 00:00:08 +00:00
|
|
|
{
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_notify_t notify;
|
2006-01-23 06:23:37 +00:00
|
|
|
|
2009-08-01 01:04:26 +00:00
|
|
|
ISP_MEMZERO(¬ify, sizeof (isp_notify_t));
|
2006-01-23 06:23:37 +00:00
|
|
|
notify.nt_hba = isp;
|
2009-08-01 01:04:26 +00:00
|
|
|
notify.nt_wwn = INI_ANY;
|
|
|
|
notify.nt_nphdl = NIL_HANDLE;
|
|
|
|
notify.nt_sid = PORT_ANY;
|
|
|
|
notify.nt_did = PORT_ANY;
|
|
|
|
notify.nt_tgt = TGT_ANY;
|
|
|
|
notify.nt_channel = bus;
|
2006-01-23 06:23:37 +00:00
|
|
|
notify.nt_lun = LUN_ANY;
|
|
|
|
notify.nt_tagval = TAG_ANY;
|
2009-08-01 01:04:26 +00:00
|
|
|
notify.nt_tagval |= (((uint64_t)(isp->isp_serno++)) << 32);
|
2000-01-04 00:00:08 +00:00
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case ASYNC_LOOP_UP:
|
2006-01-23 06:23:37 +00:00
|
|
|
case ASYNC_PTPMODE:
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGTDEBUG0, "%s: LOOP UP", __func__);
|
2006-01-23 06:23:37 +00:00
|
|
|
notify.nt_ncode = NT_LINK_UP;
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_async(isp, ISPASYNC_TARGET_NOTIFY, ¬ify);
|
2006-01-23 06:23:37 +00:00
|
|
|
break;
|
2000-01-04 00:00:08 +00:00
|
|
|
case ASYNC_LOOP_DOWN:
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGTDEBUG0, "%s: LOOP DOWN", __func__);
|
2006-01-23 06:23:37 +00:00
|
|
|
notify.nt_ncode = NT_LINK_DOWN;
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_async(isp, ISPASYNC_TARGET_NOTIFY, ¬ify);
|
2006-01-23 06:23:37 +00:00
|
|
|
break;
|
2006-11-02 03:21:32 +00:00
|
|
|
case ASYNC_LIP_ERROR:
|
2015-11-24 19:20:49 +00:00
|
|
|
case ASYNC_LIP_NOS_OLS_RECV:
|
2006-01-23 06:23:37 +00:00
|
|
|
case ASYNC_LIP_OCCURRED:
|
2000-01-04 00:00:08 +00:00
|
|
|
case ASYNC_LOOP_RESET:
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGTDEBUG0, "%s: LIP RESET", __func__);
|
2006-01-23 06:23:37 +00:00
|
|
|
notify.nt_ncode = NT_LIP_RESET;
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_async(isp, ISPASYNC_TARGET_NOTIFY, ¬ify);
|
2006-01-23 06:23:37 +00:00
|
|
|
break;
|
2000-01-04 00:00:08 +00:00
|
|
|
default:
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGERR, "%s: unknown event 0x%x", __func__, event);
|
2000-01-04 00:00:08 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-11-02 03:21:32 +00:00
|
|
|
static void
|
|
|
|
isp_got_tmf_24xx(ispsoftc_t *isp, at7_entry_t *aep)
|
|
|
|
{
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_notify_t notify;
|
2017-03-18 16:09:36 +00:00
|
|
|
static const char f1[] = "%s from PortID 0x%06x lun %jx seq 0x%08x";
|
|
|
|
static const char f2[] = "unknown Task Flag 0x%x lun %jx PortID 0x%x tag 0x%08x";
|
2016-05-19 17:02:33 +00:00
|
|
|
fcportdb_t *lp;
|
2009-08-01 01:04:26 +00:00
|
|
|
uint16_t chan;
|
|
|
|
uint32_t sid, did;
|
|
|
|
|
|
|
|
ISP_MEMZERO(¬ify, sizeof (isp_notify_t));
|
|
|
|
notify.nt_hba = isp;
|
|
|
|
notify.nt_wwn = INI_ANY;
|
2017-03-18 16:09:36 +00:00
|
|
|
notify.nt_lun = CAM_EXTLUN_BYTE_SWIZZLE(be64dec(aep->at_cmnd.fcp_cmnd_lun));
|
2009-08-01 01:04:26 +00:00
|
|
|
notify.nt_tagval = aep->at_rxid;
|
|
|
|
notify.nt_tagval |= (((uint64_t)(isp->isp_serno++)) << 32);
|
|
|
|
notify.nt_lreserved = aep;
|
2016-05-19 17:02:33 +00:00
|
|
|
sid = (aep->at_hdr.s_id[0] << 16) | (aep->at_hdr.s_id[1] << 8) | aep->at_hdr.s_id[2];
|
2009-08-01 01:04:26 +00:00
|
|
|
did = (aep->at_hdr.d_id[0] << 16) | (aep->at_hdr.d_id[1] << 8) | aep->at_hdr.d_id[2];
|
2016-05-18 16:15:09 +00:00
|
|
|
if (ISP_CAP_MULTI_ID(isp) && isp->isp_nchan > 1) {
|
|
|
|
/* Channel has to be derived from D_ID */
|
|
|
|
isp_find_chan_by_did(isp, did, &chan);
|
|
|
|
if (chan == ISP_NOCHAN) {
|
2017-03-19 13:46:11 +00:00
|
|
|
isp_prt(isp, ISP_LOGWARN,
|
|
|
|
"%s: D_ID 0x%x not found on any channel",
|
|
|
|
__func__, did);
|
|
|
|
isp_endcmd(isp, aep, NIL_HANDLE, ISP_NOCHAN,
|
|
|
|
ECMD_TERMINATE, 0);
|
2016-05-18 16:15:09 +00:00
|
|
|
return;
|
2009-08-01 01:04:26 +00:00
|
|
|
}
|
2016-05-18 16:15:09 +00:00
|
|
|
} else {
|
|
|
|
chan = 0;
|
2009-08-01 01:04:26 +00:00
|
|
|
}
|
2016-05-19 17:02:33 +00:00
|
|
|
if (isp_find_pdb_by_portid(isp, chan, sid, &lp))
|
|
|
|
notify.nt_nphdl = lp->handle;
|
|
|
|
else
|
|
|
|
notify.nt_nphdl = NIL_HANDLE;
|
2009-08-01 01:04:26 +00:00
|
|
|
notify.nt_sid = sid;
|
|
|
|
notify.nt_did = did;
|
|
|
|
notify.nt_channel = chan;
|
2015-10-23 18:34:18 +00:00
|
|
|
if (aep->at_cmnd.fcp_cmnd_task_management & FCP_CMND_TMF_QUERY_TASK_SET) {
|
|
|
|
isp_prt(isp, ISP_LOGINFO, f1, "QUERY TASK SET", sid, notify.nt_lun, aep->at_rxid);
|
|
|
|
notify.nt_ncode = NT_QUERY_TASK_SET;
|
|
|
|
} else if (aep->at_cmnd.fcp_cmnd_task_management & FCP_CMND_TMF_ABORT_TASK_SET) {
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGINFO, f1, "ABORT TASK SET", sid, notify.nt_lun, aep->at_rxid);
|
|
|
|
notify.nt_ncode = NT_ABORT_TASK_SET;
|
|
|
|
} else if (aep->at_cmnd.fcp_cmnd_task_management & FCP_CMND_TMF_CLEAR_TASK_SET) {
|
|
|
|
isp_prt(isp, ISP_LOGINFO, f1, "CLEAR TASK SET", sid, notify.nt_lun, aep->at_rxid);
|
|
|
|
notify.nt_ncode = NT_CLEAR_TASK_SET;
|
2015-10-23 18:34:18 +00:00
|
|
|
} else if (aep->at_cmnd.fcp_cmnd_task_management & FCP_CMND_TMF_QUERY_ASYNC_EVENT) {
|
|
|
|
isp_prt(isp, ISP_LOGINFO, f1, "QUERY ASYNC EVENT", sid, notify.nt_lun, aep->at_rxid);
|
|
|
|
notify.nt_ncode = NT_QUERY_ASYNC_EVENT;
|
2009-08-01 01:04:26 +00:00
|
|
|
} else if (aep->at_cmnd.fcp_cmnd_task_management & FCP_CMND_TMF_LUN_RESET) {
|
|
|
|
isp_prt(isp, ISP_LOGINFO, f1, "LUN RESET", sid, notify.nt_lun, aep->at_rxid);
|
|
|
|
notify.nt_ncode = NT_LUN_RESET;
|
|
|
|
} else if (aep->at_cmnd.fcp_cmnd_task_management & FCP_CMND_TMF_TGT_RESET) {
|
|
|
|
isp_prt(isp, ISP_LOGINFO, f1, "TARGET RESET", sid, notify.nt_lun, aep->at_rxid);
|
|
|
|
notify.nt_ncode = NT_TARGET_RESET;
|
|
|
|
} else if (aep->at_cmnd.fcp_cmnd_task_management & FCP_CMND_TMF_CLEAR_ACA) {
|
|
|
|
isp_prt(isp, ISP_LOGINFO, f1, "CLEAR ACA", sid, notify.nt_lun, aep->at_rxid);
|
|
|
|
notify.nt_ncode = NT_CLEAR_ACA;
|
2006-11-02 03:21:32 +00:00
|
|
|
} else {
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGWARN, f2, aep->at_cmnd.fcp_cmnd_task_management, notify.nt_lun, sid, aep->at_rxid);
|
|
|
|
notify.nt_ncode = NT_UNKNOWN;
|
2016-05-19 17:02:33 +00:00
|
|
|
isp_endcmd(isp, aep, notify.nt_nphdl, chan, ECMD_RVALID | (0x4 << 12), 0);
|
2006-11-02 03:21:32 +00:00
|
|
|
return;
|
|
|
|
}
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_async(isp, ISPASYNC_TARGET_NOTIFY, ¬ify);
|
2006-11-02 03:21:32 +00:00
|
|
|
}
|
|
|
|
|
2009-08-01 01:04:26 +00:00
|
|
|
int
|
2006-04-21 18:30:01 +00:00
|
|
|
isp_notify_ack(ispsoftc_t *isp, void *arg)
|
2000-01-04 00:00:08 +00:00
|
|
|
{
|
2020-11-20 01:15:48 +00:00
|
|
|
na_fcentry_24xx_t _na, *na = &_na;
|
2000-01-04 00:00:08 +00:00
|
|
|
|
2009-08-01 01:04:26 +00:00
|
|
|
/*
|
|
|
|
* This is in case a Task Management Function ends up here.
|
|
|
|
*/
|
2020-11-20 01:15:48 +00:00
|
|
|
if (((isphdr_t *)arg)->rqs_entry_type == RQSTYPE_ATIO)
|
|
|
|
return (isp_endcmd(isp, arg, NIL_HANDLE, 0, 0, 0));
|
|
|
|
|
|
|
|
in_fcentry_24xx_t *in = arg;
|
|
|
|
|
|
|
|
ISP_MEMZERO(na, sizeof(*na));
|
|
|
|
na->na_header.rqs_entry_type = RQSTYPE_NOTIFY_ACK;
|
|
|
|
na->na_header.rqs_entry_count = 1;
|
|
|
|
na->na_nphdl = in->in_nphdl;
|
|
|
|
na->na_flags = in->in_flags;
|
|
|
|
na->na_status = in->in_status;
|
|
|
|
na->na_status_subcode = in->in_status_subcode;
|
|
|
|
na->na_fwhandle = in->in_fwhandle;
|
|
|
|
na->na_rxid = in->in_rxid;
|
|
|
|
na->na_oxid = in->in_oxid;
|
|
|
|
na->na_vpidx = in->in_vpidx;
|
|
|
|
if (in->in_status == IN24XX_SRR_RCVD) {
|
|
|
|
na->na_srr_rxid = in->in_srr_rxid;
|
|
|
|
na->na_srr_reloff_hi = in->in_srr_reloff_hi;
|
|
|
|
na->na_srr_reloff_lo = in->in_srr_reloff_lo;
|
|
|
|
na->na_srr_iu = in->in_srr_iu;
|
|
|
|
/*
|
|
|
|
* Whether we're accepting the SRR or rejecting
|
|
|
|
* it is determined by looking at the in_reserved
|
|
|
|
* field in the original notify structure.
|
|
|
|
*/
|
|
|
|
if (in->in_reserved) {
|
|
|
|
na->na_srr_flags = 1;
|
|
|
|
na->na_srr_reject_vunique = 0;
|
|
|
|
/* Unable to perform this command at this time. */
|
|
|
|
na->na_srr_reject_code = 9;
|
|
|
|
/* Unable to supply the requested data. */
|
|
|
|
na->na_srr_reject_explanation = 0x2a;
|
2000-01-04 00:00:08 +00:00
|
|
|
}
|
2009-08-01 01:04:26 +00:00
|
|
|
}
|
2020-11-27 15:50:20 +00:00
|
|
|
return (isp_send_entry(isp, na));
|
2009-08-01 01:04:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
isp_acknak_abts(ispsoftc_t *isp, void *arg, int errno)
|
|
|
|
{
|
|
|
|
char storage[QENTRY_LEN];
|
|
|
|
uint16_t tmpw;
|
|
|
|
uint8_t tmpb;
|
|
|
|
abts_t *abts = arg;
|
|
|
|
abts_rsp_t *rsp = (abts_rsp_t *) storage;
|
|
|
|
|
|
|
|
if (abts->abts_header.rqs_entry_type != RQSTYPE_ABTS_RCVD) {
|
|
|
|
isp_prt(isp, ISP_LOGERR, "%s: called for non-ABTS entry (0x%x)", __func__, abts->abts_header.rqs_entry_type);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
ISP_MEMCPY(rsp, abts, QENTRY_LEN);
|
|
|
|
rsp->abts_rsp_header.rqs_entry_type = RQSTYPE_ABTS_RSP;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Swap destination and source for response.
|
|
|
|
*/
|
|
|
|
rsp->abts_rsp_r_ctl = BA_ACC;
|
|
|
|
tmpw = rsp->abts_rsp_did_lo;
|
|
|
|
tmpb = rsp->abts_rsp_did_hi;
|
|
|
|
rsp->abts_rsp_did_lo = rsp->abts_rsp_sid_lo;
|
|
|
|
rsp->abts_rsp_did_hi = rsp->abts_rsp_sid_hi;
|
|
|
|
rsp->abts_rsp_sid_lo = tmpw;
|
|
|
|
rsp->abts_rsp_sid_hi = tmpb;
|
|
|
|
|
|
|
|
rsp->abts_rsp_f_ctl_hi ^= 0x80; /* invert Exchange Context */
|
|
|
|
rsp->abts_rsp_f_ctl_hi &= ~0x7f; /* clear Sequence Initiator and other bits */
|
|
|
|
rsp->abts_rsp_f_ctl_hi |= 0x10; /* abort the whole exchange */
|
|
|
|
rsp->abts_rsp_f_ctl_hi |= 0x8; /* last data frame of sequence */
|
|
|
|
rsp->abts_rsp_f_ctl_hi |= 0x1; /* transfer Sequence Initiative */
|
|
|
|
rsp->abts_rsp_f_ctl_lo = 0;
|
|
|
|
|
|
|
|
if (errno == 0) {
|
|
|
|
uint16_t rx_id, ox_id;
|
|
|
|
|
|
|
|
rx_id = rsp->abts_rsp_rx_id;
|
|
|
|
ox_id = rsp->abts_rsp_ox_id;
|
|
|
|
ISP_MEMZERO(&rsp->abts_rsp_payload.ba_acc, sizeof (rsp->abts_rsp_payload.ba_acc));
|
|
|
|
isp_prt(isp, ISP_LOGTINFO, "[0x%x] ABTS of 0x%x being BA_ACC'd", rsp->abts_rsp_rxid_abts, rsp->abts_rsp_rxid_task);
|
|
|
|
rsp->abts_rsp_payload.ba_acc.aborted_rx_id = rx_id;
|
|
|
|
rsp->abts_rsp_payload.ba_acc.aborted_ox_id = ox_id;
|
|
|
|
rsp->abts_rsp_payload.ba_acc.high_seq_cnt = 0xffff;
|
|
|
|
} else {
|
|
|
|
ISP_MEMZERO(&rsp->abts_rsp_payload.ba_rjt, sizeof (rsp->abts_rsp_payload.ba_acc));
|
|
|
|
switch (errno) {
|
|
|
|
case ENOMEM:
|
-----------
MISC CHANGES
Add a new async event- ISP_TARGET_NOTIFY_ACK, that will guarantee
eventual delivery of a NOTIFY ACK. This is tons better than just
ignoring the return from isp_notify_ack and hoping for the best.
Clean up the lower level lun enable code to be a bit more sensible.
Fix a botch in isp_endcmd which was messing up the sense data.
Fix notify ack for SRR to use a sensible error code in the case
of a reject.
Clean up and make clear what kind of firmware we've loaded and
what capabilities it has.
-----------
FULL (252 byte) SENSE DATA
In CTIOs for the ISP, there's only a limimted amount of space
to load SENSE DATA for associated CHECK CONDITIONS (24 or 26
bytes). This makes it difficult to send full SENSE DATA that can
be up to 252 bytes.
Implement MODE 2 responses which have us build the FCP Response
in system memory which the ISP will put onto the wire directly.
On the initiator side, the same problem occurs in that a command
status response only has a limited amount of space for SENSE DATA.
This data is supplemented by status continuation responses that
the ISP pushes onto the response queue after the status response.
We now pull them all together so that full sense data can be
returned to the periph driver.
This is supported on 23XX, 24XX and 25XX cards.
This is also preparation for doing >16 byte CDBs.
-----------
FC TAPE
Implement full FC-TAPE on both initiator and target mode side. This
capability is driven by firmware loaded, board type, board NVRAM
settings, or hint configuration options to enable or disable. This
is supported for 23XX, 24XX and 25XX cards.
On the initiator side, we pretty much just have to generate a command
reference number for each command we send out. This is FCP-4 compliant
in that we do this per ITL nexus to generate the allowed 1 thru 255
CRN.
In order to support the target side of FC-TAPE, we now pay attention
to more of the PRLI word 3 parameters which will tell us whether
an initiator wants confirmed responses. While we're at it, we'll
pay attention to the initiator view too and report it.
On sending back CTIOs, we will notice whether the initiator wants
confirmed responses and we'll set up flags to do so.
If a response or data frame is lost the initiator sends us an SRR
(Sequence Retransmit Request) ELS which shows up as an SRR notify
and all outstanding CTIOs are nuked with SRR Received status. The
SRR notify contains the offset that the initiator wants us to restart
the data transfer from or to retransmit the response frame.
If the ISP driver still has the CCB around for which the data segment
or response applies, it will retransmit.
However, we typically don't know about a lost data frame until we
send the FCP Response and the initiator totes up counters for data
moved and notices missing segments. In this case we've already
completed the data CCBs already and sent themn back up to the periph
driver. Because there's no really clean mechanism yet in CAM to
handle this, a hack has been put into place to complete the CTIO
CCB with the CAM_MESSAGE_RECV status which will have a MODIFY DATA
POINTER extended message in it. The internal ISP target groks this
and ctl(8) will be modified to deal with this as well.
At any rate, the data is retransmitted and an an FCP response is
sent. The whole point here is to successfully complete a command
so that you don't have to depend on ULP (SCSI) to have to recover,
which in the case of tape is not really possible (hence the name
FC-TAPE).
Sponsored by: Spectralogic
MFC after: 1 month
2012-07-28 20:06:29 +00:00
|
|
|
rsp->abts_rsp_payload.ba_rjt.reason = 5; /* Logical Unit Busy */
|
2009-08-01 01:04:26 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rsp->abts_rsp_payload.ba_rjt.reason = 9; /* Unable to perform command request */
|
|
|
|
break;
|
|
|
|
}
|
2000-01-04 00:00:08 +00:00
|
|
|
}
|
2020-11-27 15:50:20 +00:00
|
|
|
return (isp_send_entry(isp, rsp));
|
2000-01-04 00:00:08 +00:00
|
|
|
}
|
|
|
|
|
2017-03-18 17:01:11 +00:00
|
|
|
static void
|
|
|
|
isp_handle_abts(ispsoftc_t *isp, abts_t *abts)
|
|
|
|
{
|
|
|
|
isp_notify_t notify, *nt = ¬ify;
|
|
|
|
fcportdb_t *lp;
|
|
|
|
uint16_t chan;
|
|
|
|
uint32_t sid, did;
|
|
|
|
|
|
|
|
did = (abts->abts_did_hi << 16) | abts->abts_did_lo;
|
|
|
|
sid = (abts->abts_sid_hi << 16) | abts->abts_sid_lo;
|
|
|
|
ISP_MEMZERO(nt, sizeof (isp_notify_t));
|
|
|
|
|
|
|
|
nt->nt_hba = isp;
|
|
|
|
nt->nt_did = did;
|
|
|
|
nt->nt_nphdl = abts->abts_nphdl;
|
|
|
|
nt->nt_sid = sid;
|
2017-03-19 13:46:11 +00:00
|
|
|
if (ISP_CAP_MULTI_ID(isp) && isp->isp_nchan > 1) {
|
|
|
|
/* Channel has to be derived from D_ID */
|
|
|
|
isp_find_chan_by_did(isp, did, &chan);
|
|
|
|
if (chan == ISP_NOCHAN) {
|
|
|
|
isp_prt(isp, ISP_LOGWARN,
|
|
|
|
"%s: D_ID 0x%x not found on any channel",
|
|
|
|
__func__, did);
|
|
|
|
isp_acknak_abts(isp, abts, ENXIO);
|
|
|
|
return;
|
2017-03-18 17:01:11 +00:00
|
|
|
}
|
2017-03-19 13:46:11 +00:00
|
|
|
} else
|
|
|
|
chan = 0;
|
|
|
|
nt->nt_tgt = FCPARAM(isp, chan)->isp_wwpn;
|
|
|
|
if (isp_find_pdb_by_handle(isp, chan, abts->abts_nphdl, &lp))
|
|
|
|
nt->nt_wwn = lp->port_wwn;
|
|
|
|
else
|
|
|
|
nt->nt_wwn = INI_ANY;
|
2017-03-18 17:01:11 +00:00
|
|
|
nt->nt_lun = LUN_ANY;
|
|
|
|
nt->nt_need_ack = 1;
|
|
|
|
nt->nt_tagval = abts->abts_rxid_task;
|
|
|
|
nt->nt_tagval |= (((uint64_t) abts->abts_rxid_abts) << 32);
|
|
|
|
isp_prt(isp, ISP_LOGTINFO, "[0x%x] ABTS from N-Port handle 0x%x"
|
|
|
|
" Port 0x%06x for task 0x%x (rx_id 0x%04x ox_id 0x%04x)",
|
|
|
|
abts->abts_rxid_abts, abts->abts_nphdl, sid, abts->abts_rxid_task,
|
|
|
|
abts->abts_rx_id, abts->abts_ox_id);
|
|
|
|
nt->nt_channel = chan;
|
|
|
|
nt->nt_ncode = NT_ABORT_TASK;
|
|
|
|
nt->nt_lreserved = abts;
|
|
|
|
isp_async(isp, ISPASYNC_TARGET_NOTIFY, ¬ify);
|
|
|
|
}
|
|
|
|
|
2006-11-02 03:21:32 +00:00
|
|
|
static void
|
|
|
|
isp_handle_ctio7(ispsoftc_t *isp, ct7_entry_t *ct)
|
|
|
|
{
|
|
|
|
void *xs;
|
|
|
|
int pl = ISP_LOGTDEBUG2;
|
|
|
|
char *fmsg = NULL;
|
|
|
|
|
|
|
|
if (ct->ct_syshandle) {
|
2015-12-25 13:03:18 +00:00
|
|
|
xs = isp_find_xs(isp, ct->ct_syshandle);
|
2006-11-02 03:21:32 +00:00
|
|
|
if (xs == NULL) {
|
|
|
|
pl = ISP_LOGALL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
xs = NULL;
|
|
|
|
}
|
|
|
|
|
2009-08-01 01:04:26 +00:00
|
|
|
switch (ct->ct_nphdl) {
|
2006-11-02 03:21:32 +00:00
|
|
|
case CT7_BUS_ERROR:
|
|
|
|
isp_prt(isp, ISP_LOGERR, "PCI DMA Bus Error");
|
|
|
|
/* FALL Through */
|
|
|
|
case CT7_DATA_OVER:
|
|
|
|
case CT7_DATA_UNDER:
|
|
|
|
case CT7_OK:
|
|
|
|
/*
|
|
|
|
* There are generally 2 possibilities as to why we'd get
|
|
|
|
* this condition:
|
|
|
|
* We sent or received data.
|
|
|
|
* We sent status & command complete.
|
|
|
|
*/
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CT7_RESET:
|
|
|
|
if (fmsg == NULL) {
|
|
|
|
fmsg = "LIP Reset";
|
|
|
|
}
|
|
|
|
/*FALLTHROUGH*/
|
|
|
|
case CT7_ABORTED:
|
|
|
|
/*
|
|
|
|
* When an Abort message is received the firmware goes to
|
|
|
|
* Bus Free and returns all outstanding CTIOs with the status
|
|
|
|
* set, then sends us an Immediate Notify entry.
|
|
|
|
*/
|
|
|
|
if (fmsg == NULL) {
|
|
|
|
fmsg = "ABORT";
|
|
|
|
}
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGTDEBUG0, "CTIO7 destroyed by %s: RX_ID=0x%x", fmsg, ct->ct_rxid);
|
2006-11-02 03:21:32 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CT7_TIMEOUT:
|
|
|
|
if (fmsg == NULL) {
|
|
|
|
fmsg = "command";
|
|
|
|
}
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGWARN, "Firmware timed out on %s", fmsg);
|
2006-11-02 03:21:32 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CT7_ERR:
|
|
|
|
fmsg = "Completed with Error";
|
|
|
|
/*FALLTHROUGH*/
|
|
|
|
case CT7_LOGOUT:
|
|
|
|
if (fmsg == NULL) {
|
|
|
|
fmsg = "Port Logout";
|
|
|
|
}
|
|
|
|
/*FALLTHROUGH*/
|
|
|
|
case CT7_PORTUNAVAIL:
|
|
|
|
if (fmsg == NULL) {
|
|
|
|
fmsg = "Port not available";
|
|
|
|
}
|
|
|
|
/*FALLTHROUGH*/
|
|
|
|
case CT7_PORTCHANGED:
|
|
|
|
if (fmsg == NULL) {
|
|
|
|
fmsg = "Port Changed";
|
|
|
|
}
|
|
|
|
isp_prt(isp, ISP_LOGWARN, "CTIO returned by f/w- %s", fmsg);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CT7_INVRXID:
|
|
|
|
/*
|
|
|
|
* CTIO rejected by the firmware because an invalid RX_ID.
|
|
|
|
* Just print a message.
|
|
|
|
*/
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGWARN, "CTIO7 completed with Invalid RX_ID 0x%x", ct->ct_rxid);
|
2006-11-02 03:21:32 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CT7_REASSY_ERR:
|
|
|
|
isp_prt(isp, ISP_LOGWARN, "reassembly error");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CT7_SRR:
|
-----------
MISC CHANGES
Add a new async event- ISP_TARGET_NOTIFY_ACK, that will guarantee
eventual delivery of a NOTIFY ACK. This is tons better than just
ignoring the return from isp_notify_ack and hoping for the best.
Clean up the lower level lun enable code to be a bit more sensible.
Fix a botch in isp_endcmd which was messing up the sense data.
Fix notify ack for SRR to use a sensible error code in the case
of a reject.
Clean up and make clear what kind of firmware we've loaded and
what capabilities it has.
-----------
FULL (252 byte) SENSE DATA
In CTIOs for the ISP, there's only a limimted amount of space
to load SENSE DATA for associated CHECK CONDITIONS (24 or 26
bytes). This makes it difficult to send full SENSE DATA that can
be up to 252 bytes.
Implement MODE 2 responses which have us build the FCP Response
in system memory which the ISP will put onto the wire directly.
On the initiator side, the same problem occurs in that a command
status response only has a limited amount of space for SENSE DATA.
This data is supplemented by status continuation responses that
the ISP pushes onto the response queue after the status response.
We now pull them all together so that full sense data can be
returned to the periph driver.
This is supported on 23XX, 24XX and 25XX cards.
This is also preparation for doing >16 byte CDBs.
-----------
FC TAPE
Implement full FC-TAPE on both initiator and target mode side. This
capability is driven by firmware loaded, board type, board NVRAM
settings, or hint configuration options to enable or disable. This
is supported for 23XX, 24XX and 25XX cards.
On the initiator side, we pretty much just have to generate a command
reference number for each command we send out. This is FCP-4 compliant
in that we do this per ITL nexus to generate the allowed 1 thru 255
CRN.
In order to support the target side of FC-TAPE, we now pay attention
to more of the PRLI word 3 parameters which will tell us whether
an initiator wants confirmed responses. While we're at it, we'll
pay attention to the initiator view too and report it.
On sending back CTIOs, we will notice whether the initiator wants
confirmed responses and we'll set up flags to do so.
If a response or data frame is lost the initiator sends us an SRR
(Sequence Retransmit Request) ELS which shows up as an SRR notify
and all outstanding CTIOs are nuked with SRR Received status. The
SRR notify contains the offset that the initiator wants us to restart
the data transfer from or to retransmit the response frame.
If the ISP driver still has the CCB around for which the data segment
or response applies, it will retransmit.
However, we typically don't know about a lost data frame until we
send the FCP Response and the initiator totes up counters for data
moved and notices missing segments. In this case we've already
completed the data CCBs already and sent themn back up to the periph
driver. Because there's no really clean mechanism yet in CAM to
handle this, a hack has been put into place to complete the CTIO
CCB with the CAM_MESSAGE_RECV status which will have a MODIFY DATA
POINTER extended message in it. The internal ISP target groks this
and ctl(8) will be modified to deal with this as well.
At any rate, the data is retransmitted and an an FCP response is
sent. The whole point here is to successfully complete a command
so that you don't have to depend on ULP (SCSI) to have to recover,
which in the case of tape is not really possible (hence the name
FC-TAPE).
Sponsored by: Spectralogic
MFC after: 1 month
2012-07-28 20:06:29 +00:00
|
|
|
isp_prt(isp, ISP_LOGTDEBUG0, "SRR received");
|
2006-11-02 03:21:32 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, ISP_LOGERR, "Unknown CTIO7 status 0x%x", ct->ct_nphdl);
|
2006-11-02 03:21:32 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xs == NULL) {
|
|
|
|
/*
|
|
|
|
* There may be more than one CTIO for a data transfer,
|
|
|
|
* or this may be a status CTIO we're not monitoring.
|
|
|
|
*
|
|
|
|
* The assumption is that they'll all be returned in the
|
|
|
|
* order we got them.
|
|
|
|
*/
|
|
|
|
if (ct->ct_syshandle == 0) {
|
|
|
|
if (ct->ct_flags & CT7_TERMINATE) {
|
-----------
MISC CHANGES
Add a new async event- ISP_TARGET_NOTIFY_ACK, that will guarantee
eventual delivery of a NOTIFY ACK. This is tons better than just
ignoring the return from isp_notify_ack and hoping for the best.
Clean up the lower level lun enable code to be a bit more sensible.
Fix a botch in isp_endcmd which was messing up the sense data.
Fix notify ack for SRR to use a sensible error code in the case
of a reject.
Clean up and make clear what kind of firmware we've loaded and
what capabilities it has.
-----------
FULL (252 byte) SENSE DATA
In CTIOs for the ISP, there's only a limimted amount of space
to load SENSE DATA for associated CHECK CONDITIONS (24 or 26
bytes). This makes it difficult to send full SENSE DATA that can
be up to 252 bytes.
Implement MODE 2 responses which have us build the FCP Response
in system memory which the ISP will put onto the wire directly.
On the initiator side, the same problem occurs in that a command
status response only has a limited amount of space for SENSE DATA.
This data is supplemented by status continuation responses that
the ISP pushes onto the response queue after the status response.
We now pull them all together so that full sense data can be
returned to the periph driver.
This is supported on 23XX, 24XX and 25XX cards.
This is also preparation for doing >16 byte CDBs.
-----------
FC TAPE
Implement full FC-TAPE on both initiator and target mode side. This
capability is driven by firmware loaded, board type, board NVRAM
settings, or hint configuration options to enable or disable. This
is supported for 23XX, 24XX and 25XX cards.
On the initiator side, we pretty much just have to generate a command
reference number for each command we send out. This is FCP-4 compliant
in that we do this per ITL nexus to generate the allowed 1 thru 255
CRN.
In order to support the target side of FC-TAPE, we now pay attention
to more of the PRLI word 3 parameters which will tell us whether
an initiator wants confirmed responses. While we're at it, we'll
pay attention to the initiator view too and report it.
On sending back CTIOs, we will notice whether the initiator wants
confirmed responses and we'll set up flags to do so.
If a response or data frame is lost the initiator sends us an SRR
(Sequence Retransmit Request) ELS which shows up as an SRR notify
and all outstanding CTIOs are nuked with SRR Received status. The
SRR notify contains the offset that the initiator wants us to restart
the data transfer from or to retransmit the response frame.
If the ISP driver still has the CCB around for which the data segment
or response applies, it will retransmit.
However, we typically don't know about a lost data frame until we
send the FCP Response and the initiator totes up counters for data
moved and notices missing segments. In this case we've already
completed the data CCBs already and sent themn back up to the periph
driver. Because there's no really clean mechanism yet in CAM to
handle this, a hack has been put into place to complete the CTIO
CCB with the CAM_MESSAGE_RECV status which will have a MODIFY DATA
POINTER extended message in it. The internal ISP target groks this
and ctl(8) will be modified to deal with this as well.
At any rate, the data is retransmitted and an an FCP response is
sent. The whole point here is to successfully complete a command
so that you don't have to depend on ULP (SCSI) to have to recover,
which in the case of tape is not really possible (hence the name
FC-TAPE).
Sponsored by: Spectralogic
MFC after: 1 month
2012-07-28 20:06:29 +00:00
|
|
|
isp_prt(isp, ISP_LOGINFO, "termination of [RX_ID 0x%x] complete", ct->ct_rxid);
|
2006-11-02 03:21:32 +00:00
|
|
|
} else if ((ct->ct_flags & CT7_SENDSTATUS) == 0) {
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, pl, "intermediate CTIO completed ok");
|
2006-11-02 03:21:32 +00:00
|
|
|
} else {
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, pl, "unmonitored CTIO completed ok");
|
2006-11-02 03:21:32 +00:00
|
|
|
}
|
|
|
|
} else {
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_prt(isp, pl, "NO xs for CTIO (handle 0x%x) status 0x%x", ct->ct_syshandle, ct->ct_nphdl);
|
2006-11-02 03:21:32 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-11-20 18:02:04 +00:00
|
|
|
ISP_DMAFREE(isp, xs);
|
2009-08-01 01:04:26 +00:00
|
|
|
if (ct->ct_flags & CT7_SENDSTATUS) {
|
2006-11-02 03:21:32 +00:00
|
|
|
/*
|
|
|
|
* Sent status and command complete.
|
|
|
|
*
|
|
|
|
* We're now really done with this command, so we
|
|
|
|
* punt to the platform dependent layers because
|
|
|
|
* only there can we do the appropriate command
|
|
|
|
* complete thread synchronization.
|
|
|
|
*/
|
|
|
|
isp_prt(isp, pl, "status CTIO complete");
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Final CTIO completed. Release DMA resources and
|
|
|
|
* notify platform dependent layers.
|
|
|
|
*/
|
|
|
|
isp_prt(isp, pl, "data CTIO complete");
|
|
|
|
}
|
2009-08-01 01:04:26 +00:00
|
|
|
isp_async(isp, ISPASYNC_TARGET_ACTION, ct);
|
2006-11-02 03:21:32 +00:00
|
|
|
/*
|
|
|
|
* The platform layer will destroy the handle if appropriate.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
2009-08-01 01:04:26 +00:00
|
|
|
|
2017-03-18 19:27:16 +00:00
|
|
|
static void
|
2017-03-19 09:30:03 +00:00
|
|
|
isp_handle_notify_24xx(ispsoftc_t *isp, in_fcentry_24xx_t *inot)
|
2009-08-01 01:04:26 +00:00
|
|
|
{
|
2017-03-19 09:30:03 +00:00
|
|
|
uint8_t chan;
|
|
|
|
uint16_t nphdl, prli_options = 0;
|
|
|
|
uint32_t portid;
|
|
|
|
fcportdb_t *lp;
|
|
|
|
char *msg = NULL;
|
|
|
|
uint8_t *ptr = (uint8_t *)inot;
|
|
|
|
uint64_t wwpn = INI_NONE, wwnn = INI_NONE;
|
|
|
|
isp_notify_t notify;
|
|
|
|
char buf[16];
|
2009-08-01 01:04:26 +00:00
|
|
|
|
2017-03-19 09:30:03 +00:00
|
|
|
nphdl = inot->in_nphdl;
|
|
|
|
if (nphdl != NIL_HANDLE) {
|
|
|
|
portid = inot->in_portid_hi << 16 | inot->in_portid_lo;
|
2009-08-01 01:04:26 +00:00
|
|
|
} else {
|
2017-03-19 09:30:03 +00:00
|
|
|
portid = PORT_ANY;
|
|
|
|
}
|
|
|
|
|
|
|
|
chan = ISP_GET_VPIDX(isp, inot->in_vpidx);
|
|
|
|
if (chan >= isp->isp_nchan &&
|
|
|
|
inot->in_status != IN24XX_LIP_RESET &&
|
|
|
|
inot->in_status != IN24XX_LINK_RESET &&
|
|
|
|
inot->in_status != IN24XX_LINK_FAILED) {
|
|
|
|
isp_prt(isp, ISP_LOGWARN, "%s: Received INOT with status %x on VP %x",
|
|
|
|
__func__, inot->in_status, chan);
|
|
|
|
isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (inot->in_status) {
|
|
|
|
case IN24XX_ELS_RCVD:
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Note that we're just getting notification that an ELS was
|
|
|
|
* received (possibly with some associated information sent
|
|
|
|
* upstream). This is *not* the same as being given the ELS
|
|
|
|
* frame to accept or reject.
|
|
|
|
*/
|
|
|
|
switch (inot->in_status_subcode) {
|
|
|
|
case LOGO:
|
|
|
|
msg = "LOGO";
|
|
|
|
wwpn = be64dec(&ptr[IN24XX_PLOGI_WWPN_OFF]);
|
|
|
|
isp_del_wwn_entry(isp, chan, wwpn, nphdl, portid);
|
|
|
|
break;
|
|
|
|
case PRLO:
|
|
|
|
msg = "PRLO";
|
|
|
|
break;
|
|
|
|
case PLOGI:
|
|
|
|
msg = "PLOGI";
|
|
|
|
wwnn = be64dec(&ptr[IN24XX_PLOGI_WWNN_OFF]);
|
|
|
|
wwpn = be64dec(&ptr[IN24XX_PLOGI_WWPN_OFF]);
|
|
|
|
isp_add_wwn_entry(isp, chan, wwpn, wwnn,
|
|
|
|
nphdl, portid, prli_options);
|
|
|
|
break;
|
|
|
|
case PRLI:
|
|
|
|
msg = "PRLI";
|
|
|
|
prli_options = inot->in_prli_options;
|
|
|
|
if (inot->in_flags & IN24XX_FLAG_PN_NN_VALID)
|
|
|
|
wwnn = be64dec(&ptr[IN24XX_PRLI_WWNN_OFF]);
|
|
|
|
wwpn = be64dec(&ptr[IN24XX_PRLI_WWPN_OFF]);
|
|
|
|
isp_add_wwn_entry(isp, chan, wwpn, wwnn,
|
|
|
|
nphdl, portid, prli_options);
|
|
|
|
break;
|
2020-11-26 01:59:44 +00:00
|
|
|
case TPRLO:
|
|
|
|
msg = "TPRLO";
|
|
|
|
break;
|
2017-03-19 09:30:03 +00:00
|
|
|
case PDISC:
|
|
|
|
msg = "PDISC";
|
|
|
|
break;
|
|
|
|
case ADISC:
|
|
|
|
msg = "ADISC";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ISP_SNPRINTF(buf, sizeof (buf), "ELS 0x%x",
|
|
|
|
inot->in_status_subcode);
|
|
|
|
msg = buf;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (inot->in_flags & IN24XX_FLAG_PUREX_IOCB) {
|
|
|
|
isp_prt(isp, ISP_LOGERR, "%s Chan %d ELS N-port handle %x"
|
|
|
|
" PortID 0x%06x marked as needing a PUREX response",
|
|
|
|
msg, chan, nphdl, portid);
|
|
|
|
break;
|
2009-08-01 01:04:26 +00:00
|
|
|
}
|
2017-03-19 09:30:03 +00:00
|
|
|
isp_prt(isp, ISP_LOGTDEBUG0, "%s Chan %d ELS N-port handle %x"
|
|
|
|
" PortID 0x%06x RX_ID 0x%x OX_ID 0x%x", msg, chan, nphdl,
|
|
|
|
portid, inot->in_rxid, inot->in_oxid);
|
|
|
|
isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot);
|
|
|
|
break;
|
2009-08-01 01:04:26 +00:00
|
|
|
}
|
2017-03-19 09:30:03 +00:00
|
|
|
|
2017-03-18 10:34:29 +00:00
|
|
|
case IN24XX_PORT_LOGOUT:
|
2017-03-19 09:30:03 +00:00
|
|
|
msg = "PORT LOGOUT";
|
|
|
|
if (isp_find_pdb_by_handle(isp, chan, nphdl, &lp))
|
|
|
|
isp_del_wwn_entry(isp, chan, lp->port_wwn, nphdl, lp->portid);
|
|
|
|
/* FALLTHROUGH */
|
2017-03-18 10:34:29 +00:00
|
|
|
case IN24XX_PORT_CHANGED:
|
2017-03-19 09:30:03 +00:00
|
|
|
if (msg == NULL)
|
|
|
|
msg = "PORT CHANGED";
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case IN24XX_LIP_RESET:
|
|
|
|
if (msg == NULL)
|
|
|
|
msg = "LIP RESET";
|
|
|
|
isp_prt(isp, ISP_LOGINFO, "Chan %d %s (sub-status 0x%x) for "
|
|
|
|
"N-port handle 0x%x",
|
|
|
|
chan, msg, inot->in_status_subcode, nphdl);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* All subcodes here are irrelevant. What is relevant
|
|
|
|
* is that we need to terminate all active commands from
|
|
|
|
* this initiator (known by N-port handle).
|
|
|
|
*/
|
|
|
|
/* XXX IMPLEMENT XXX */
|
|
|
|
isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot);
|
|
|
|
break;
|
|
|
|
|
2017-03-18 10:34:29 +00:00
|
|
|
case IN24XX_SRR_RCVD:
|
2017-03-19 09:30:03 +00:00
|
|
|
#ifdef ISP_TARGET_MODE
|
|
|
|
ISP_MEMZERO(¬ify, sizeof (isp_notify_t));
|
|
|
|
notify.nt_hba = isp;
|
|
|
|
notify.nt_wwn = INI_ANY;
|
|
|
|
notify.nt_tgt = FCPARAM(isp, chan)->isp_wwpn;
|
|
|
|
notify.nt_nphdl = nphdl;
|
|
|
|
notify.nt_sid = portid;
|
|
|
|
notify.nt_did = PORT_ANY;
|
|
|
|
notify.nt_lun = LUN_ANY;
|
|
|
|
notify.nt_tagval = inot->in_rxid;
|
|
|
|
notify.nt_tagval |= ((uint64_t)inot->in_srr_rxid << 32);
|
|
|
|
notify.nt_need_ack = 1;
|
|
|
|
notify.nt_channel = chan;
|
|
|
|
notify.nt_lreserved = inot;
|
|
|
|
notify.nt_ncode = NT_SRR;
|
|
|
|
isp_async(isp, ISPASYNC_TARGET_NOTIFY, ¬ify);
|
2017-03-18 10:34:29 +00:00
|
|
|
break;
|
2017-03-19 09:30:03 +00:00
|
|
|
#else
|
|
|
|
if (msg == NULL)
|
|
|
|
msg = "SRR RCVD";
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
#endif
|
|
|
|
case IN24XX_LINK_RESET:
|
|
|
|
if (msg == NULL)
|
|
|
|
msg = "LINK RESET";
|
|
|
|
case IN24XX_LINK_FAILED:
|
|
|
|
if (msg == NULL)
|
|
|
|
msg = "LINK FAILED";
|
2017-03-18 10:34:29 +00:00
|
|
|
default:
|
2017-03-19 09:30:03 +00:00
|
|
|
isp_prt(isp, ISP_LOGWARN, "Chan %d %s", chan, msg);
|
|
|
|
isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot);
|
2017-03-18 10:34:29 +00:00
|
|
|
break;
|
2009-08-01 01:04:26 +00:00
|
|
|
}
|
|
|
|
}
|
2000-01-04 00:00:08 +00:00
|
|
|
#endif
|