45633fdc96
zfcp implements a device file to allow Linux guests changing the Access Control Tables stored in the adapter. The code for the device file has nothing to do with the other parts of the driver, so move it to a new file and cleanup the code while doing so. Signed-off-by: Christof Schmitt <christof.schmitt@de.ibm.com> Signed-off-by: Swen Schillig <swen@vnet.ibm.com> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
4524 lines
139 KiB
C
4524 lines
139 KiB
C
/*
|
|
* This file is part of the zfcp device driver for
|
|
* FCP adapters for IBM System z9 and zSeries.
|
|
*
|
|
* (C) Copyright IBM Corp. 2002, 2006
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include "zfcp_ext.h"
|
|
|
|
static int zfcp_fsf_exchange_config_data_handler(struct zfcp_fsf_req *);
|
|
static void zfcp_fsf_exchange_port_data_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_open_port_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_close_port_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_close_physical_port_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_open_unit_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_close_unit_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_send_fcp_command_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_send_fcp_command_task_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_send_fcp_command_task_management_handler(
|
|
struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_abort_fcp_command_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_status_read_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_send_ct_handler(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_send_els_handler(struct zfcp_fsf_req *);
|
|
static void zfcp_fsf_control_file_handler(struct zfcp_fsf_req *);
|
|
static inline int zfcp_fsf_req_sbal_check(
|
|
unsigned long *, struct zfcp_qdio_queue *, int);
|
|
static inline int zfcp_use_one_sbal(
|
|
struct scatterlist *, int, struct scatterlist *, int);
|
|
static struct zfcp_fsf_req *zfcp_fsf_req_alloc(mempool_t *, int);
|
|
static int zfcp_fsf_req_send(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_protstatus_eval(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_fsfstatus_eval(struct zfcp_fsf_req *);
|
|
static int zfcp_fsf_fsfstatus_qual_eval(struct zfcp_fsf_req *);
|
|
static void zfcp_fsf_link_down_info_eval(struct zfcp_fsf_req *, u8,
|
|
struct fsf_link_down_info *);
|
|
static int zfcp_fsf_req_dispatch(struct zfcp_fsf_req *);
|
|
|
|
/* association between FSF command and FSF QTCB type */
|
|
static u32 fsf_qtcb_type[] = {
|
|
[FSF_QTCB_FCP_CMND] = FSF_IO_COMMAND,
|
|
[FSF_QTCB_ABORT_FCP_CMND] = FSF_SUPPORT_COMMAND,
|
|
[FSF_QTCB_OPEN_PORT_WITH_DID] = FSF_SUPPORT_COMMAND,
|
|
[FSF_QTCB_OPEN_LUN] = FSF_SUPPORT_COMMAND,
|
|
[FSF_QTCB_CLOSE_LUN] = FSF_SUPPORT_COMMAND,
|
|
[FSF_QTCB_CLOSE_PORT] = FSF_SUPPORT_COMMAND,
|
|
[FSF_QTCB_CLOSE_PHYSICAL_PORT] = FSF_SUPPORT_COMMAND,
|
|
[FSF_QTCB_SEND_ELS] = FSF_SUPPORT_COMMAND,
|
|
[FSF_QTCB_SEND_GENERIC] = FSF_SUPPORT_COMMAND,
|
|
[FSF_QTCB_EXCHANGE_CONFIG_DATA] = FSF_CONFIG_COMMAND,
|
|
[FSF_QTCB_EXCHANGE_PORT_DATA] = FSF_PORT_COMMAND,
|
|
[FSF_QTCB_DOWNLOAD_CONTROL_FILE] = FSF_SUPPORT_COMMAND,
|
|
[FSF_QTCB_UPLOAD_CONTROL_FILE] = FSF_SUPPORT_COMMAND
|
|
};
|
|
|
|
static const char zfcp_act_subtable_type[5][8] = {
|
|
"unknown", "OS", "WWPN", "DID", "LUN"
|
|
};
|
|
|
|
/****************************************************************/
|
|
/*************** FSF related Functions *************************/
|
|
/****************************************************************/
|
|
|
|
#define ZFCP_LOG_AREA ZFCP_LOG_AREA_FSF
|
|
|
|
/*
|
|
* function: zfcp_fsf_req_alloc
|
|
*
|
|
* purpose: Obtains an fsf_req and potentially a qtcb (for all but
|
|
* unsolicited requests) via helper functions
|
|
* Does some initial fsf request set-up.
|
|
*
|
|
* returns: pointer to allocated fsf_req if successfull
|
|
* NULL otherwise
|
|
*
|
|
* locks: none
|
|
*
|
|
*/
|
|
static struct zfcp_fsf_req *
|
|
zfcp_fsf_req_alloc(mempool_t *pool, int req_flags)
|
|
{
|
|
size_t size;
|
|
void *ptr;
|
|
struct zfcp_fsf_req *fsf_req = NULL;
|
|
|
|
if (req_flags & ZFCP_REQ_NO_QTCB)
|
|
size = sizeof(struct zfcp_fsf_req);
|
|
else
|
|
size = sizeof(struct zfcp_fsf_req_qtcb);
|
|
|
|
if (likely(pool))
|
|
ptr = mempool_alloc(pool, GFP_ATOMIC);
|
|
else {
|
|
if (req_flags & ZFCP_REQ_NO_QTCB)
|
|
ptr = kmalloc(size, GFP_ATOMIC);
|
|
else
|
|
ptr = kmem_cache_alloc(zfcp_data.fsf_req_qtcb_cache,
|
|
GFP_ATOMIC);
|
|
}
|
|
|
|
if (unlikely(!ptr))
|
|
goto out;
|
|
|
|
memset(ptr, 0, size);
|
|
|
|
if (req_flags & ZFCP_REQ_NO_QTCB) {
|
|
fsf_req = (struct zfcp_fsf_req *) ptr;
|
|
} else {
|
|
fsf_req = &((struct zfcp_fsf_req_qtcb *) ptr)->fsf_req;
|
|
fsf_req->qtcb = &((struct zfcp_fsf_req_qtcb *) ptr)->qtcb;
|
|
}
|
|
|
|
fsf_req->pool = pool;
|
|
|
|
out:
|
|
return fsf_req;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_req_free
|
|
*
|
|
* purpose: Frees the memory of an fsf_req (and potentially a qtcb) or
|
|
* returns it into the pool via helper functions.
|
|
*
|
|
* returns: sod all
|
|
*
|
|
* locks: none
|
|
*/
|
|
void
|
|
zfcp_fsf_req_free(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
if (likely(fsf_req->pool)) {
|
|
mempool_free(fsf_req, fsf_req->pool);
|
|
return;
|
|
}
|
|
|
|
if (fsf_req->qtcb) {
|
|
kmem_cache_free(zfcp_data.fsf_req_qtcb_cache, fsf_req);
|
|
return;
|
|
}
|
|
|
|
kfree(fsf_req);
|
|
}
|
|
|
|
/*
|
|
* Never ever call this without shutting down the adapter first.
|
|
* Otherwise the adapter would continue using and corrupting s390 storage.
|
|
* Included BUG_ON() call to ensure this is done.
|
|
* ERP is supposed to be the only user of this function.
|
|
*/
|
|
void zfcp_fsf_req_dismiss_all(struct zfcp_adapter *adapter)
|
|
{
|
|
struct zfcp_fsf_req *fsf_req, *tmp;
|
|
unsigned long flags;
|
|
LIST_HEAD(remove_queue);
|
|
unsigned int i;
|
|
|
|
BUG_ON(atomic_test_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status));
|
|
spin_lock_irqsave(&adapter->req_list_lock, flags);
|
|
atomic_set(&adapter->reqs_active, 0);
|
|
for (i = 0; i < REQUEST_LIST_SIZE; i++)
|
|
list_splice_init(&adapter->req_list[i], &remove_queue);
|
|
spin_unlock_irqrestore(&adapter->req_list_lock, flags);
|
|
|
|
list_for_each_entry_safe(fsf_req, tmp, &remove_queue, list) {
|
|
list_del(&fsf_req->list);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_DISMISSED;
|
|
zfcp_fsf_req_complete(fsf_req);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_req_complete
|
|
*
|
|
* purpose: Updates active counts and timers for openfcp-reqs
|
|
* May cleanup request after req_eval returns
|
|
*
|
|
* returns: 0 - success
|
|
* !0 - failure
|
|
*
|
|
* context:
|
|
*/
|
|
int
|
|
zfcp_fsf_req_complete(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = 0;
|
|
int cleanup;
|
|
|
|
if (unlikely(fsf_req->fsf_command == FSF_QTCB_UNSOLICITED_STATUS)) {
|
|
ZFCP_LOG_DEBUG("Status read response received\n");
|
|
/*
|
|
* Note: all cleanup handling is done in the callchain of
|
|
* the function call-chain below.
|
|
*/
|
|
zfcp_fsf_status_read_handler(fsf_req);
|
|
goto out;
|
|
} else {
|
|
del_timer(&fsf_req->timer);
|
|
zfcp_fsf_protstatus_eval(fsf_req);
|
|
}
|
|
|
|
/*
|
|
* fsf_req may be deleted due to waking up functions, so
|
|
* cleanup is saved here and used later
|
|
*/
|
|
if (likely(fsf_req->status & ZFCP_STATUS_FSFREQ_CLEANUP))
|
|
cleanup = 1;
|
|
else
|
|
cleanup = 0;
|
|
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_COMPLETED;
|
|
|
|
/* cleanup request if requested by initiator */
|
|
if (likely(cleanup)) {
|
|
ZFCP_LOG_TRACE("removing FSF request %p\n", fsf_req);
|
|
/*
|
|
* lock must not be held here since it will be
|
|
* grabed by the called routine, too
|
|
*/
|
|
zfcp_fsf_req_free(fsf_req);
|
|
} else {
|
|
/* notify initiator waiting for the requests completion */
|
|
ZFCP_LOG_TRACE("waking initiator of FSF request %p\n",fsf_req);
|
|
/*
|
|
* FIXME: Race! We must not access fsf_req here as it might have been
|
|
* cleaned up already due to the set ZFCP_STATUS_FSFREQ_COMPLETED
|
|
* flag. It's an improbable case. But, we have the same paranoia for
|
|
* the cleanup flag already.
|
|
* Might better be handled using complete()?
|
|
* (setting the flag and doing wakeup ought to be atomic
|
|
* with regard to checking the flag as long as waitqueue is
|
|
* part of the to be released structure)
|
|
*/
|
|
wake_up(&fsf_req->completion_wq);
|
|
}
|
|
|
|
out:
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_protstatus_eval
|
|
*
|
|
* purpose: evaluates the QTCB of the finished FSF request
|
|
* and initiates appropriate actions
|
|
* (usually calling FSF command specific handlers)
|
|
*
|
|
* returns:
|
|
*
|
|
* context:
|
|
*
|
|
* locks:
|
|
*/
|
|
static int
|
|
zfcp_fsf_protstatus_eval(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = 0;
|
|
struct zfcp_adapter *adapter = fsf_req->adapter;
|
|
struct fsf_qtcb *qtcb = fsf_req->qtcb;
|
|
union fsf_prot_status_qual *prot_status_qual =
|
|
&qtcb->prefix.prot_status_qual;
|
|
|
|
zfcp_hba_dbf_event_fsf_response(fsf_req);
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_DISMISSED) {
|
|
ZFCP_LOG_DEBUG("fsf_req 0x%lx has been dismissed\n",
|
|
(unsigned long) fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR |
|
|
ZFCP_STATUS_FSFREQ_RETRY; /* only for SCSI cmnds. */
|
|
goto skip_protstatus;
|
|
}
|
|
|
|
/* evaluate FSF Protocol Status */
|
|
switch (qtcb->prefix.prot_status) {
|
|
|
|
case FSF_PROT_GOOD:
|
|
case FSF_PROT_FSF_STATUS_PRESENTED:
|
|
break;
|
|
|
|
case FSF_PROT_QTCB_VERSION_ERROR:
|
|
ZFCP_LOG_NORMAL("error: The adapter %s contains "
|
|
"microcode of version 0x%x, the device driver "
|
|
"only supports 0x%x. Aborting.\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
prot_status_qual->version_error.fsf_version,
|
|
ZFCP_QTCB_VERSION);
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 117, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PROT_SEQ_NUMB_ERROR:
|
|
ZFCP_LOG_NORMAL("bug: Sequence number mismatch between "
|
|
"driver (0x%x) and adapter %s (0x%x). "
|
|
"Restarting all operations on this adapter.\n",
|
|
qtcb->prefix.req_seq_no,
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
prot_status_qual->sequence_error.exp_req_seq_no);
|
|
zfcp_erp_adapter_reopen(adapter, 0, 98, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_RETRY;
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PROT_UNSUPP_QTCB_TYPE:
|
|
ZFCP_LOG_NORMAL("error: Packet header type used by the "
|
|
"device driver is incompatible with "
|
|
"that used on adapter %s. "
|
|
"Stopping all operations on this adapter.\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 118, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PROT_HOST_CONNECTION_INITIALIZING:
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
atomic_set_mask(ZFCP_STATUS_ADAPTER_HOST_CON_INIT,
|
|
&(adapter->status));
|
|
break;
|
|
|
|
case FSF_PROT_DUPLICATE_REQUEST_ID:
|
|
ZFCP_LOG_NORMAL("bug: The request identifier 0x%Lx "
|
|
"to the adapter %s is ambiguous. "
|
|
"Stopping all operations on this adapter.\n",
|
|
*(unsigned long long*)
|
|
(&qtcb->bottom.support.req_handle),
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 78, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PROT_LINK_DOWN:
|
|
zfcp_fsf_link_down_info_eval(fsf_req, 37,
|
|
&prot_status_qual->link_down_info);
|
|
/* FIXME: reopening adapter now? better wait for link up */
|
|
zfcp_erp_adapter_reopen(adapter, 0, 79, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PROT_REEST_QUEUE:
|
|
ZFCP_LOG_NORMAL("The local link to adapter with "
|
|
"%s was re-plugged. "
|
|
"Re-starting operations on this adapter.\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
/* All ports should be marked as ready to run again */
|
|
zfcp_erp_modify_adapter_status(adapter, 28, NULL,
|
|
ZFCP_STATUS_COMMON_RUNNING,
|
|
ZFCP_SET);
|
|
zfcp_erp_adapter_reopen(adapter,
|
|
ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED
|
|
| ZFCP_STATUS_COMMON_ERP_FAILED,
|
|
99, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PROT_ERROR_STATE:
|
|
ZFCP_LOG_NORMAL("error: The adapter %s "
|
|
"has entered the error state. "
|
|
"Restarting all operations on this "
|
|
"adapter.\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_erp_adapter_reopen(adapter, 0, 100, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_RETRY;
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: Transfer protocol status information "
|
|
"provided by the adapter %s "
|
|
"is not compatible with the device driver. "
|
|
"Stopping all operations on this adapter. "
|
|
"(debug info 0x%x).\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
qtcb->prefix.prot_status);
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 119, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
}
|
|
|
|
skip_protstatus:
|
|
/*
|
|
* always call specific handlers to give them a chance to do
|
|
* something meaningful even in error cases
|
|
*/
|
|
zfcp_fsf_fsfstatus_eval(fsf_req);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_fsfstatus_eval
|
|
*
|
|
* purpose: evaluates FSF status of completed FSF request
|
|
* and acts accordingly
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_fsfstatus_eval(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = 0;
|
|
|
|
if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) {
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
/* evaluate FSF Status */
|
|
switch (fsf_req->qtcb->header.fsf_status) {
|
|
case FSF_UNKNOWN_COMMAND:
|
|
ZFCP_LOG_NORMAL("bug: Command issued by the device driver is "
|
|
"not known by the adapter %s "
|
|
"Stopping all operations on this adapter. "
|
|
"(debug info 0x%x).\n",
|
|
zfcp_get_busid_by_adapter(fsf_req->adapter),
|
|
fsf_req->qtcb->header.fsf_command);
|
|
zfcp_erp_adapter_shutdown(fsf_req->adapter, 0, 120, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_FCP_RSP_AVAILABLE:
|
|
ZFCP_LOG_DEBUG("FCP Sense data will be presented to the "
|
|
"SCSI stack.\n");
|
|
break;
|
|
|
|
case FSF_ADAPTER_STATUS_AVAILABLE:
|
|
zfcp_fsf_fsfstatus_qual_eval(fsf_req);
|
|
break;
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
/*
|
|
* always call specific handlers to give them a chance to do
|
|
* something meaningful even in error cases
|
|
*/
|
|
zfcp_fsf_req_dispatch(fsf_req);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_fsfstatus_qual_eval
|
|
*
|
|
* purpose: evaluates FSF status-qualifier of completed FSF request
|
|
* and acts accordingly
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_fsfstatus_qual_eval(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = 0;
|
|
|
|
switch (fsf_req->qtcb->header.fsf_status_qual.word[0]) {
|
|
case FSF_SQ_FCP_RSP_AVAILABLE:
|
|
break;
|
|
case FSF_SQ_RETRY_IF_POSSIBLE:
|
|
/* The SCSI-stack may now issue retries or escalate */
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_COMMAND_ABORTED:
|
|
/* Carry the aborted state on to upper layer */
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTED;
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_NO_RECOM:
|
|
ZFCP_LOG_NORMAL("bug: No recommendation could be given for a "
|
|
"problem on the adapter %s "
|
|
"Stopping all operations on this adapter. ",
|
|
zfcp_get_busid_by_adapter(fsf_req->adapter));
|
|
zfcp_erp_adapter_shutdown(fsf_req->adapter, 0, 121, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_ULP_PROGRAMMING_ERROR:
|
|
ZFCP_LOG_NORMAL("error: not enough SBALs for data transfer "
|
|
"(adapter %s)\n",
|
|
zfcp_get_busid_by_adapter(fsf_req->adapter));
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
|
|
case FSF_SQ_NO_RETRY_POSSIBLE:
|
|
case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
|
|
/* dealt with in the respective functions */
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: Additional status info could "
|
|
"not be interpreted properly.\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL,
|
|
(char *) &fsf_req->qtcb->header.fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_link_down_info_eval - evaluate link down information block
|
|
*/
|
|
static void
|
|
zfcp_fsf_link_down_info_eval(struct zfcp_fsf_req *fsf_req, u8 id,
|
|
struct fsf_link_down_info *link_down)
|
|
{
|
|
struct zfcp_adapter *adapter = fsf_req->adapter;
|
|
|
|
if (atomic_test_mask(ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED,
|
|
&adapter->status))
|
|
return;
|
|
|
|
atomic_set_mask(ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED, &adapter->status);
|
|
|
|
if (link_down == NULL)
|
|
goto out;
|
|
|
|
switch (link_down->error_code) {
|
|
case FSF_PSQ_LINK_NO_LIGHT:
|
|
ZFCP_LOG_NORMAL("The local link to adapter %s is down "
|
|
"(no light detected)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_WRAP_PLUG:
|
|
ZFCP_LOG_NORMAL("The local link to adapter %s is down "
|
|
"(wrap plug detected)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_NO_FCP:
|
|
ZFCP_LOG_NORMAL("The local link to adapter %s is down "
|
|
"(adjacent node on link does not support FCP)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_FIRMWARE_UPDATE:
|
|
ZFCP_LOG_NORMAL("The local link to adapter %s is down "
|
|
"(firmware update in progress)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_INVALID_WWPN:
|
|
ZFCP_LOG_NORMAL("The local link to adapter %s is down "
|
|
"(duplicate or invalid WWPN detected)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_NO_NPIV_SUPPORT:
|
|
ZFCP_LOG_NORMAL("The local link to adapter %s is down "
|
|
"(no support for NPIV by Fabric)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_NO_FCP_RESOURCES:
|
|
ZFCP_LOG_NORMAL("The local link to adapter %s is down "
|
|
"(out of resource in FCP daughtercard)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_NO_FABRIC_RESOURCES:
|
|
ZFCP_LOG_NORMAL("The local link to adapter %s is down "
|
|
"(out of resource in Fabric)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_FABRIC_LOGIN_UNABLE:
|
|
ZFCP_LOG_NORMAL("The local link to adapter %s is down "
|
|
"(unable to Fabric login)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_WWPN_ASSIGNMENT_CORRUPTED:
|
|
ZFCP_LOG_NORMAL("WWPN assignment file corrupted on adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_MODE_TABLE_CURRUPTED:
|
|
ZFCP_LOG_NORMAL("Mode table corrupted on adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_PSQ_LINK_NO_WWPN_ASSIGNMENT:
|
|
ZFCP_LOG_NORMAL("No WWPN for assignment table on adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL("The local link to adapter %s is down "
|
|
"(warning: unknown reason code %d)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
link_down->error_code);
|
|
}
|
|
|
|
if (adapter->connection_features & FSF_FEATURE_NPIV_MODE)
|
|
ZFCP_LOG_DEBUG("Debug information to link down: "
|
|
"primary_status=0x%02x "
|
|
"ioerr_code=0x%02x "
|
|
"action_code=0x%02x "
|
|
"reason_code=0x%02x "
|
|
"explanation_code=0x%02x "
|
|
"vendor_specific_code=0x%02x\n",
|
|
link_down->primary_status,
|
|
link_down->ioerr_code,
|
|
link_down->action_code,
|
|
link_down->reason_code,
|
|
link_down->explanation_code,
|
|
link_down->vendor_specific_code);
|
|
|
|
out:
|
|
zfcp_erp_adapter_failed(adapter, id, fsf_req);
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_req_dispatch
|
|
*
|
|
* purpose: calls the appropriate command specific handler
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_req_dispatch(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
struct zfcp_erp_action *erp_action = fsf_req->erp_action;
|
|
struct zfcp_adapter *adapter = fsf_req->adapter;
|
|
int retval = 0;
|
|
|
|
|
|
switch (fsf_req->fsf_command) {
|
|
|
|
case FSF_QTCB_FCP_CMND:
|
|
zfcp_fsf_send_fcp_command_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_ABORT_FCP_CMND:
|
|
zfcp_fsf_abort_fcp_command_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_SEND_GENERIC:
|
|
zfcp_fsf_send_ct_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_OPEN_PORT_WITH_DID:
|
|
zfcp_fsf_open_port_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_OPEN_LUN:
|
|
zfcp_fsf_open_unit_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_CLOSE_LUN:
|
|
zfcp_fsf_close_unit_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_CLOSE_PORT:
|
|
zfcp_fsf_close_port_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_CLOSE_PHYSICAL_PORT:
|
|
zfcp_fsf_close_physical_port_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_EXCHANGE_CONFIG_DATA:
|
|
zfcp_fsf_exchange_config_data_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_EXCHANGE_PORT_DATA:
|
|
zfcp_fsf_exchange_port_data_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_SEND_ELS:
|
|
zfcp_fsf_send_els_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_DOWNLOAD_CONTROL_FILE:
|
|
zfcp_fsf_control_file_handler(fsf_req);
|
|
break;
|
|
|
|
case FSF_QTCB_UPLOAD_CONTROL_FILE:
|
|
zfcp_fsf_control_file_handler(fsf_req);
|
|
break;
|
|
|
|
default:
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
ZFCP_LOG_NORMAL("bug: Command issued by the device driver is "
|
|
"not supported by the adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
if (fsf_req->fsf_command != fsf_req->qtcb->header.fsf_command)
|
|
ZFCP_LOG_NORMAL
|
|
("bug: Command issued by the device driver differs "
|
|
"from the command returned by the adapter %s "
|
|
"(debug info 0x%x, 0x%x).\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
fsf_req->fsf_command,
|
|
fsf_req->qtcb->header.fsf_command);
|
|
}
|
|
|
|
if (!erp_action)
|
|
return retval;
|
|
|
|
zfcp_erp_async_handler(erp_action, 0);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_status_read
|
|
*
|
|
* purpose: initiates a Status Read command at the specified adapter
|
|
*
|
|
* returns:
|
|
*/
|
|
int
|
|
zfcp_fsf_status_read(struct zfcp_adapter *adapter, int req_flags)
|
|
{
|
|
struct zfcp_fsf_req *fsf_req;
|
|
struct fsf_status_read_buffer *status_buffer;
|
|
unsigned long lock_flags;
|
|
volatile struct qdio_buffer_element *sbale;
|
|
int retval;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(adapter, FSF_QTCB_UNSOLICITED_STATUS,
|
|
req_flags | ZFCP_REQ_NO_QTCB,
|
|
adapter->pool.fsf_req_status_read,
|
|
&lock_flags, &fsf_req);
|
|
if (retval < 0) {
|
|
ZFCP_LOG_INFO("error: Could not create unsolicited status "
|
|
"buffer for adapter %s.\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
goto failed_req_create;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_STATUS;
|
|
sbale[2].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
fsf_req->sbale_curr = 2;
|
|
|
|
retval = -ENOMEM;
|
|
status_buffer =
|
|
mempool_alloc(adapter->pool.data_status_read, GFP_ATOMIC);
|
|
if (!status_buffer)
|
|
goto failed_buf;
|
|
memset(status_buffer, 0, sizeof (struct fsf_status_read_buffer));
|
|
fsf_req->data = (unsigned long) status_buffer;
|
|
|
|
/* insert pointer to respective buffer */
|
|
sbale = zfcp_qdio_sbale_curr(fsf_req);
|
|
sbale->addr = (void *) status_buffer;
|
|
sbale->length = sizeof(struct fsf_status_read_buffer);
|
|
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
if (retval) {
|
|
ZFCP_LOG_DEBUG("error: Could not set-up unsolicited status "
|
|
"environment.\n");
|
|
goto failed_req_send;
|
|
}
|
|
|
|
ZFCP_LOG_TRACE("Status Read request initiated (adapter%s)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
goto out;
|
|
|
|
failed_req_send:
|
|
mempool_free(status_buffer, adapter->pool.data_status_read);
|
|
|
|
failed_buf:
|
|
zfcp_fsf_req_free(fsf_req);
|
|
failed_req_create:
|
|
zfcp_hba_dbf_event_fsf_unsol("fail", adapter, NULL);
|
|
out:
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
zfcp_fsf_status_read_port_closed(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
struct fsf_status_read_buffer *status_buffer;
|
|
struct zfcp_adapter *adapter;
|
|
struct zfcp_port *port;
|
|
unsigned long flags;
|
|
|
|
status_buffer = (struct fsf_status_read_buffer *) fsf_req->data;
|
|
adapter = fsf_req->adapter;
|
|
|
|
read_lock_irqsave(&zfcp_data.config_lock, flags);
|
|
list_for_each_entry(port, &adapter->port_list_head, list)
|
|
if (port->d_id == (status_buffer->d_id & ZFCP_DID_MASK))
|
|
break;
|
|
read_unlock_irqrestore(&zfcp_data.config_lock, flags);
|
|
|
|
if (!port || (port->d_id != (status_buffer->d_id & ZFCP_DID_MASK))) {
|
|
ZFCP_LOG_NORMAL("bug: Reopen port indication received for "
|
|
"nonexisting port with d_id 0x%06x on "
|
|
"adapter %s. Ignored.\n",
|
|
status_buffer->d_id & ZFCP_DID_MASK,
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
goto out;
|
|
}
|
|
|
|
switch (status_buffer->status_subtype) {
|
|
|
|
case FSF_STATUS_READ_SUB_CLOSE_PHYS_PORT:
|
|
zfcp_erp_port_reopen(port, 0, 101, fsf_req);
|
|
break;
|
|
|
|
case FSF_STATUS_READ_SUB_ERROR_PORT:
|
|
zfcp_erp_port_shutdown(port, 0, 122, fsf_req);
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: Undefined status subtype received "
|
|
"for a reopen indication on port with "
|
|
"d_id 0x%06x on the adapter %s. "
|
|
"Ignored. (debug info 0x%x)\n",
|
|
status_buffer->d_id,
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
status_buffer->status_subtype);
|
|
}
|
|
out:
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_status_read_handler
|
|
*
|
|
* purpose: is called for finished Open Port command
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_status_read_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = 0;
|
|
struct zfcp_adapter *adapter = fsf_req->adapter;
|
|
struct fsf_status_read_buffer *status_buffer =
|
|
(struct fsf_status_read_buffer *) fsf_req->data;
|
|
struct fsf_bit_error_payload *fsf_bit_error;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_DISMISSED) {
|
|
zfcp_hba_dbf_event_fsf_unsol("dism", adapter, status_buffer);
|
|
mempool_free(status_buffer, adapter->pool.data_status_read);
|
|
zfcp_fsf_req_free(fsf_req);
|
|
goto out;
|
|
}
|
|
|
|
zfcp_hba_dbf_event_fsf_unsol("read", adapter, status_buffer);
|
|
|
|
switch (status_buffer->status_type) {
|
|
|
|
case FSF_STATUS_READ_PORT_CLOSED:
|
|
zfcp_fsf_status_read_port_closed(fsf_req);
|
|
break;
|
|
|
|
case FSF_STATUS_READ_INCOMING_ELS:
|
|
zfcp_fc_incoming_els(fsf_req);
|
|
break;
|
|
|
|
case FSF_STATUS_READ_SENSE_DATA_AVAIL:
|
|
ZFCP_LOG_INFO("unsolicited sense data received (adapter %s)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
|
|
case FSF_STATUS_READ_BIT_ERROR_THRESHOLD:
|
|
fsf_bit_error = (struct fsf_bit_error_payload *)
|
|
status_buffer->payload;
|
|
ZFCP_LOG_NORMAL("Warning: bit error threshold data "
|
|
"received (adapter %s, "
|
|
"link failures = %i, loss of sync errors = %i, "
|
|
"loss of signal errors = %i, "
|
|
"primitive sequence errors = %i, "
|
|
"invalid transmission word errors = %i, "
|
|
"CRC errors = %i)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
fsf_bit_error->link_failure_error_count,
|
|
fsf_bit_error->loss_of_sync_error_count,
|
|
fsf_bit_error->loss_of_signal_error_count,
|
|
fsf_bit_error->primitive_sequence_error_count,
|
|
fsf_bit_error->invalid_transmission_word_error_count,
|
|
fsf_bit_error->crc_error_count);
|
|
ZFCP_LOG_INFO("Additional bit error threshold data "
|
|
"(adapter %s, "
|
|
"primitive sequence event time-outs = %i, "
|
|
"elastic buffer overrun errors = %i, "
|
|
"advertised receive buffer-to-buffer credit = %i, "
|
|
"current receice buffer-to-buffer credit = %i, "
|
|
"advertised transmit buffer-to-buffer credit = %i, "
|
|
"current transmit buffer-to-buffer credit = %i)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
fsf_bit_error->primitive_sequence_event_timeout_count,
|
|
fsf_bit_error->elastic_buffer_overrun_error_count,
|
|
fsf_bit_error->advertised_receive_b2b_credit,
|
|
fsf_bit_error->current_receive_b2b_credit,
|
|
fsf_bit_error->advertised_transmit_b2b_credit,
|
|
fsf_bit_error->current_transmit_b2b_credit);
|
|
break;
|
|
|
|
case FSF_STATUS_READ_LINK_DOWN:
|
|
switch (status_buffer->status_subtype) {
|
|
case FSF_STATUS_READ_SUB_NO_PHYSICAL_LINK:
|
|
ZFCP_LOG_INFO("Physical link to adapter %s is down\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_fsf_link_down_info_eval(fsf_req, 38,
|
|
(struct fsf_link_down_info *)
|
|
&status_buffer->payload);
|
|
break;
|
|
case FSF_STATUS_READ_SUB_FDISC_FAILED:
|
|
ZFCP_LOG_INFO("Local link to adapter %s is down "
|
|
"due to failed FDISC login\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_fsf_link_down_info_eval(fsf_req, 39,
|
|
(struct fsf_link_down_info *)
|
|
&status_buffer->payload);
|
|
break;
|
|
case FSF_STATUS_READ_SUB_FIRMWARE_UPDATE:
|
|
ZFCP_LOG_INFO("Local link to adapter %s is down "
|
|
"due to firmware update on adapter\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_fsf_link_down_info_eval(fsf_req, 40, NULL);
|
|
break;
|
|
default:
|
|
ZFCP_LOG_INFO("Local link to adapter %s is down "
|
|
"due to unknown reason\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_fsf_link_down_info_eval(fsf_req, 41, NULL);
|
|
};
|
|
break;
|
|
|
|
case FSF_STATUS_READ_LINK_UP:
|
|
ZFCP_LOG_NORMAL("Local link to adapter %s was replugged. "
|
|
"Restarting operations on this adapter\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
/* All ports should be marked as ready to run again */
|
|
zfcp_erp_modify_adapter_status(adapter, 30, NULL,
|
|
ZFCP_STATUS_COMMON_RUNNING,
|
|
ZFCP_SET);
|
|
zfcp_erp_adapter_reopen(adapter,
|
|
ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED
|
|
| ZFCP_STATUS_COMMON_ERP_FAILED,
|
|
102, fsf_req);
|
|
break;
|
|
|
|
case FSF_STATUS_READ_NOTIFICATION_LOST:
|
|
ZFCP_LOG_NORMAL("Unsolicited status notification(s) lost: "
|
|
"adapter %s%s%s%s%s%s%s%s%s\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
(status_buffer->status_subtype &
|
|
FSF_STATUS_READ_SUB_INCOMING_ELS) ?
|
|
", incoming ELS" : "",
|
|
(status_buffer->status_subtype &
|
|
FSF_STATUS_READ_SUB_SENSE_DATA) ?
|
|
", sense data" : "",
|
|
(status_buffer->status_subtype &
|
|
FSF_STATUS_READ_SUB_LINK_STATUS) ?
|
|
", link status change" : "",
|
|
(status_buffer->status_subtype &
|
|
FSF_STATUS_READ_SUB_PORT_CLOSED) ?
|
|
", port close" : "",
|
|
(status_buffer->status_subtype &
|
|
FSF_STATUS_READ_SUB_BIT_ERROR_THRESHOLD) ?
|
|
", bit error exception" : "",
|
|
(status_buffer->status_subtype &
|
|
FSF_STATUS_READ_SUB_ACT_UPDATED) ?
|
|
", ACT update" : "",
|
|
(status_buffer->status_subtype &
|
|
FSF_STATUS_READ_SUB_ACT_HARDENED) ?
|
|
", ACT hardening" : "",
|
|
(status_buffer->status_subtype &
|
|
FSF_STATUS_READ_SUB_FEATURE_UPDATE_ALERT) ?
|
|
", adapter feature change" : "");
|
|
|
|
if (status_buffer->status_subtype &
|
|
FSF_STATUS_READ_SUB_ACT_UPDATED)
|
|
zfcp_erp_adapter_access_changed(adapter, 135, fsf_req);
|
|
break;
|
|
|
|
case FSF_STATUS_READ_CFDC_UPDATED:
|
|
ZFCP_LOG_NORMAL("CFDC has been updated on the adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_erp_adapter_access_changed(adapter, 136, fsf_req);
|
|
break;
|
|
|
|
case FSF_STATUS_READ_CFDC_HARDENED:
|
|
switch (status_buffer->status_subtype) {
|
|
case FSF_STATUS_READ_SUB_CFDC_HARDENED_ON_SE:
|
|
ZFCP_LOG_NORMAL("CFDC of adapter %s saved on SE\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
case FSF_STATUS_READ_SUB_CFDC_HARDENED_ON_SE2:
|
|
ZFCP_LOG_NORMAL("CFDC of adapter %s has been copied "
|
|
"to the secondary SE\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL("CFDC of adapter %s has been hardened\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
}
|
|
break;
|
|
|
|
case FSF_STATUS_READ_FEATURE_UPDATE_ALERT:
|
|
ZFCP_LOG_INFO("List of supported features on adapter %s has "
|
|
"been changed from 0x%08X to 0x%08X\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
*(u32*) (status_buffer->payload + 4),
|
|
*(u32*) (status_buffer->payload));
|
|
adapter->adapter_features = *(u32*) status_buffer->payload;
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL("warning: An unsolicited status packet of unknown "
|
|
"type was received (debug info 0x%x)\n",
|
|
status_buffer->status_type);
|
|
ZFCP_LOG_DEBUG("Dump of status_read_buffer %p:\n",
|
|
status_buffer);
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) status_buffer,
|
|
sizeof (struct fsf_status_read_buffer));
|
|
break;
|
|
}
|
|
mempool_free(status_buffer, adapter->pool.data_status_read);
|
|
zfcp_fsf_req_free(fsf_req);
|
|
/*
|
|
* recycle buffer and start new request repeat until outbound
|
|
* queue is empty or adapter shutdown is requested
|
|
*/
|
|
/*
|
|
* FIXME(qdio):
|
|
* we may wait in the req_create for 5s during shutdown, so
|
|
* qdio_cleanup will have to wait at least that long before returning
|
|
* with failure to allow us a proper cleanup under all circumstances
|
|
*/
|
|
/*
|
|
* FIXME:
|
|
* allocation failure possible? (Is this code needed?)
|
|
*/
|
|
|
|
atomic_inc(&adapter->stat_miss);
|
|
schedule_work(&adapter->stat_work);
|
|
out:
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_abort_fcp_command
|
|
*
|
|
* purpose: tells FSF to abort a running SCSI command
|
|
*
|
|
* returns: address of initiated FSF request
|
|
* NULL - request could not be initiated
|
|
*
|
|
* FIXME(design): should be watched by a timeout !!!
|
|
* FIXME(design) shouldn't this be modified to return an int
|
|
* also...don't know how though
|
|
*/
|
|
struct zfcp_fsf_req *
|
|
zfcp_fsf_abort_fcp_command(unsigned long old_req_id,
|
|
struct zfcp_adapter *adapter,
|
|
struct zfcp_unit *unit, int req_flags)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req = NULL;
|
|
unsigned long lock_flags;
|
|
int retval = 0;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(adapter, FSF_QTCB_ABORT_FCP_CMND,
|
|
req_flags, adapter->pool.fsf_req_abort,
|
|
&lock_flags, &fsf_req);
|
|
if (retval < 0) {
|
|
ZFCP_LOG_INFO("error: Failed to create an abort command "
|
|
"request for lun 0x%016Lx on port 0x%016Lx "
|
|
"on adapter %s.\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
goto out;
|
|
}
|
|
|
|
if (unlikely(!atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED,
|
|
&unit->status)))
|
|
goto unit_blocked;
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
fsf_req->data = (unsigned long) unit;
|
|
|
|
/* set handles of unit and its parent port in QTCB */
|
|
fsf_req->qtcb->header.lun_handle = unit->handle;
|
|
fsf_req->qtcb->header.port_handle = unit->port->handle;
|
|
|
|
/* set handle of request which should be aborted */
|
|
fsf_req->qtcb->bottom.support.req_handle = (u64) old_req_id;
|
|
|
|
zfcp_fsf_start_timer(fsf_req, ZFCP_SCSI_ER_TIMEOUT);
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
if (!retval)
|
|
goto out;
|
|
|
|
unit_blocked:
|
|
zfcp_fsf_req_free(fsf_req);
|
|
fsf_req = NULL;
|
|
|
|
out:
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
|
|
return fsf_req;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_abort_fcp_command_handler
|
|
*
|
|
* purpose: is called for finished Abort FCP Command request
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_abort_fcp_command_handler(struct zfcp_fsf_req *new_fsf_req)
|
|
{
|
|
int retval = -EINVAL;
|
|
struct zfcp_unit *unit;
|
|
union fsf_status_qual *fsf_stat_qual =
|
|
&new_fsf_req->qtcb->header.fsf_status_qual;
|
|
|
|
if (new_fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
|
|
/* do not set ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED */
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
unit = (struct zfcp_unit *) new_fsf_req->data;
|
|
|
|
/* evaluate FSF status in QTCB */
|
|
switch (new_fsf_req->qtcb->header.fsf_status) {
|
|
|
|
case FSF_PORT_HANDLE_NOT_VALID:
|
|
if (fsf_stat_qual->word[0] != fsf_stat_qual->word[1]) {
|
|
/*
|
|
* In this case a command that was sent prior to a port
|
|
* reopen was aborted (handles are different). This is
|
|
* fine.
|
|
*/
|
|
} else {
|
|
ZFCP_LOG_INFO("Temporary port identifier 0x%x for "
|
|
"port 0x%016Lx on adapter %s invalid. "
|
|
"This may happen occasionally.\n",
|
|
unit->port->handle,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
ZFCP_LOG_INFO("status qualifier:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO,
|
|
(char *) &new_fsf_req->qtcb->header.
|
|
fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
/* Let's hope this sorts out the mess */
|
|
zfcp_erp_adapter_reopen(unit->port->adapter, 0, 104,
|
|
new_fsf_req);
|
|
new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
}
|
|
break;
|
|
|
|
case FSF_LUN_HANDLE_NOT_VALID:
|
|
if (fsf_stat_qual->word[0] != fsf_stat_qual->word[1]) {
|
|
/*
|
|
* In this case a command that was sent prior to a unit
|
|
* reopen was aborted (handles are different).
|
|
* This is fine.
|
|
*/
|
|
} else {
|
|
ZFCP_LOG_INFO
|
|
("Warning: Temporary LUN identifier 0x%x of LUN "
|
|
"0x%016Lx on port 0x%016Lx on adapter %s is "
|
|
"invalid. This may happen in rare cases. "
|
|
"Trying to re-establish link.\n",
|
|
unit->handle,
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
ZFCP_LOG_DEBUG("Status qualifier data:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &new_fsf_req->qtcb->header.
|
|
fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
/* Let's hope this sorts out the mess */
|
|
zfcp_erp_port_reopen(unit->port, 0, 105, new_fsf_req);
|
|
new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
}
|
|
break;
|
|
|
|
case FSF_FCP_COMMAND_DOES_NOT_EXIST:
|
|
retval = 0;
|
|
new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTNOTNEEDED;
|
|
break;
|
|
|
|
case FSF_PORT_BOXED:
|
|
ZFCP_LOG_INFO("Remote port 0x%016Lx on adapter %s needs to "
|
|
"be reopened\n", unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
zfcp_erp_port_boxed(unit->port, 47, new_fsf_req);
|
|
new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR
|
|
| ZFCP_STATUS_FSFREQ_RETRY;
|
|
break;
|
|
|
|
case FSF_LUN_BOXED:
|
|
ZFCP_LOG_INFO(
|
|
"unit 0x%016Lx on port 0x%016Lx on adapter %s needs "
|
|
"to be reopened\n",
|
|
unit->fcp_lun, unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
zfcp_erp_unit_boxed(unit, 48, new_fsf_req);
|
|
new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR
|
|
| ZFCP_STATUS_FSFREQ_RETRY;
|
|
break;
|
|
|
|
case FSF_ADAPTER_STATUS_AVAILABLE:
|
|
switch (new_fsf_req->qtcb->header.fsf_status_qual.word[0]) {
|
|
case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
|
|
zfcp_test_link(unit->port);
|
|
new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
|
|
/* SCSI stack will escalate */
|
|
new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL
|
|
("bug: Wrong status qualifier 0x%x arrived.\n",
|
|
new_fsf_req->qtcb->header.fsf_status_qual.word[0]);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case FSF_GOOD:
|
|
retval = 0;
|
|
new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED;
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
|
|
"(debug info 0x%x)\n",
|
|
new_fsf_req->qtcb->header.fsf_status);
|
|
break;
|
|
}
|
|
skip_fsfstatus:
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* zfcp_use_one_sbal - checks whether req buffer and resp bother each fit into
|
|
* one SBALE
|
|
* Two scatter-gather lists are passed, one for the reqeust and one for the
|
|
* response.
|
|
*/
|
|
static inline int
|
|
zfcp_use_one_sbal(struct scatterlist *req, int req_count,
|
|
struct scatterlist *resp, int resp_count)
|
|
{
|
|
return ((req_count == 1) &&
|
|
(resp_count == 1) &&
|
|
(((unsigned long) zfcp_sg_to_address(&req[0]) &
|
|
PAGE_MASK) ==
|
|
((unsigned long) (zfcp_sg_to_address(&req[0]) +
|
|
req[0].length - 1) & PAGE_MASK)) &&
|
|
(((unsigned long) zfcp_sg_to_address(&resp[0]) &
|
|
PAGE_MASK) ==
|
|
((unsigned long) (zfcp_sg_to_address(&resp[0]) +
|
|
resp[0].length - 1) & PAGE_MASK)));
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_send_ct - initiate a Generic Service request (FC-GS)
|
|
* @ct: pointer to struct zfcp_send_ct which conatins all needed data for
|
|
* the request
|
|
* @pool: pointer to memory pool, if non-null this pool is used to allocate
|
|
* a struct zfcp_fsf_req
|
|
* @erp_action: pointer to erp_action, if non-null the Generic Service request
|
|
* is sent within error recovery
|
|
*/
|
|
int
|
|
zfcp_fsf_send_ct(struct zfcp_send_ct *ct, mempool_t *pool,
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_port *port;
|
|
struct zfcp_adapter *adapter;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
unsigned long lock_flags;
|
|
int bytes;
|
|
int ret = 0;
|
|
|
|
port = ct->port;
|
|
adapter = port->adapter;
|
|
|
|
ret = zfcp_fsf_req_create(adapter, FSF_QTCB_SEND_GENERIC,
|
|
ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
|
|
pool, &lock_flags, &fsf_req);
|
|
if (ret < 0) {
|
|
ZFCP_LOG_INFO("error: Could not create CT request (FC-GS) for "
|
|
"adapter: %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
goto failed_req;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
if (zfcp_use_one_sbal(ct->req, ct->req_count,
|
|
ct->resp, ct->resp_count)){
|
|
/* both request buffer and response buffer
|
|
fit into one sbale each */
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_WRITE_READ;
|
|
sbale[2].addr = zfcp_sg_to_address(&ct->req[0]);
|
|
sbale[2].length = ct->req[0].length;
|
|
sbale[3].addr = zfcp_sg_to_address(&ct->resp[0]);
|
|
sbale[3].length = ct->resp[0].length;
|
|
sbale[3].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
} else if (adapter->adapter_features &
|
|
FSF_FEATURE_ELS_CT_CHAINED_SBALS) {
|
|
/* try to use chained SBALs */
|
|
bytes = zfcp_qdio_sbals_from_sg(fsf_req,
|
|
SBAL_FLAGS0_TYPE_WRITE_READ,
|
|
ct->req, ct->req_count,
|
|
ZFCP_MAX_SBALS_PER_CT_REQ);
|
|
if (bytes <= 0) {
|
|
ZFCP_LOG_INFO("error: creation of CT request failed "
|
|
"on adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
if (bytes == 0)
|
|
ret = -ENOMEM;
|
|
else
|
|
ret = bytes;
|
|
|
|
goto failed_send;
|
|
}
|
|
fsf_req->qtcb->bottom.support.req_buf_length = bytes;
|
|
fsf_req->sbale_curr = ZFCP_LAST_SBALE_PER_SBAL;
|
|
bytes = zfcp_qdio_sbals_from_sg(fsf_req,
|
|
SBAL_FLAGS0_TYPE_WRITE_READ,
|
|
ct->resp, ct->resp_count,
|
|
ZFCP_MAX_SBALS_PER_CT_REQ);
|
|
if (bytes <= 0) {
|
|
ZFCP_LOG_INFO("error: creation of CT request failed "
|
|
"on adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
if (bytes == 0)
|
|
ret = -ENOMEM;
|
|
else
|
|
ret = bytes;
|
|
|
|
goto failed_send;
|
|
}
|
|
fsf_req->qtcb->bottom.support.resp_buf_length = bytes;
|
|
} else {
|
|
/* reject send generic request */
|
|
ZFCP_LOG_INFO(
|
|
"error: microcode does not support chained SBALs,"
|
|
"CT request too big (adapter %s)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
ret = -EOPNOTSUPP;
|
|
goto failed_send;
|
|
}
|
|
|
|
/* settings in QTCB */
|
|
fsf_req->qtcb->header.port_handle = port->handle;
|
|
fsf_req->qtcb->bottom.support.service_class =
|
|
ZFCP_FC_SERVICE_CLASS_DEFAULT;
|
|
fsf_req->qtcb->bottom.support.timeout = ct->timeout;
|
|
fsf_req->data = (unsigned long) ct;
|
|
|
|
zfcp_san_dbf_event_ct_request(fsf_req);
|
|
|
|
if (erp_action) {
|
|
erp_action->fsf_req = fsf_req;
|
|
fsf_req->erp_action = erp_action;
|
|
zfcp_erp_start_timer(fsf_req);
|
|
} else
|
|
zfcp_fsf_start_timer(fsf_req, ZFCP_FSF_REQUEST_TIMEOUT);
|
|
|
|
ret = zfcp_fsf_req_send(fsf_req);
|
|
if (ret) {
|
|
ZFCP_LOG_DEBUG("error: initiation of CT request failed "
|
|
"(adapter %s, port 0x%016Lx)\n",
|
|
zfcp_get_busid_by_adapter(adapter), port->wwpn);
|
|
goto failed_send;
|
|
}
|
|
|
|
ZFCP_LOG_DEBUG("CT request initiated (adapter %s, port 0x%016Lx)\n",
|
|
zfcp_get_busid_by_adapter(adapter), port->wwpn);
|
|
goto out;
|
|
|
|
failed_send:
|
|
zfcp_fsf_req_free(fsf_req);
|
|
if (erp_action != NULL) {
|
|
erp_action->fsf_req = NULL;
|
|
}
|
|
failed_req:
|
|
out:
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_send_ct_handler - handler for Generic Service requests
|
|
* @fsf_req: pointer to struct zfcp_fsf_req
|
|
*
|
|
* Data specific for the Generic Service request is passed using
|
|
* fsf_req->data. There we find the pointer to struct zfcp_send_ct.
|
|
* Usually a specific handler for the CT request is called which is
|
|
* found in this structure.
|
|
*/
|
|
static int
|
|
zfcp_fsf_send_ct_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
struct zfcp_port *port;
|
|
struct zfcp_adapter *adapter;
|
|
struct zfcp_send_ct *send_ct;
|
|
struct fsf_qtcb_header *header;
|
|
struct fsf_qtcb_bottom_support *bottom;
|
|
int retval = -EINVAL;
|
|
u16 subtable, rule, counter;
|
|
|
|
adapter = fsf_req->adapter;
|
|
send_ct = (struct zfcp_send_ct *) fsf_req->data;
|
|
port = send_ct->port;
|
|
header = &fsf_req->qtcb->header;
|
|
bottom = &fsf_req->qtcb->bottom.support;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)
|
|
goto skip_fsfstatus;
|
|
|
|
/* evaluate FSF status in QTCB */
|
|
switch (header->fsf_status) {
|
|
|
|
case FSF_GOOD:
|
|
zfcp_san_dbf_event_ct_response(fsf_req);
|
|
retval = 0;
|
|
break;
|
|
|
|
case FSF_SERVICE_CLASS_NOT_SUPPORTED:
|
|
ZFCP_LOG_INFO("error: adapter %s does not support fc "
|
|
"class %d.\n",
|
|
zfcp_get_busid_by_port(port),
|
|
ZFCP_FC_SERVICE_CLASS_DEFAULT);
|
|
/* stop operation for this adapter */
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 123, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_ADAPTER_STATUS_AVAILABLE:
|
|
switch (header->fsf_status_qual.word[0]){
|
|
case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
|
|
/* reopening link to port */
|
|
zfcp_test_link(port);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
|
|
/* ERP strategy will escalate */
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
default:
|
|
ZFCP_LOG_INFO("bug: Wrong status qualifier 0x%x "
|
|
"arrived.\n",
|
|
header->fsf_status_qual.word[0]);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case FSF_ACCESS_DENIED:
|
|
ZFCP_LOG_NORMAL("access denied, cannot send generic service "
|
|
"command (adapter %s, port d_id=0x%06x)\n",
|
|
zfcp_get_busid_by_port(port), port->d_id);
|
|
for (counter = 0; counter < 2; counter++) {
|
|
subtable = header->fsf_status_qual.halfword[counter * 2];
|
|
rule = header->fsf_status_qual.halfword[counter * 2 + 1];
|
|
switch (subtable) {
|
|
case FSF_SQ_CFDC_SUBTABLE_OS:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
|
|
case FSF_SQ_CFDC_SUBTABLE_LUN:
|
|
ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
|
|
zfcp_act_subtable_type[subtable], rule);
|
|
break;
|
|
}
|
|
}
|
|
zfcp_erp_port_access_denied(port, 55, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_GENERIC_COMMAND_REJECTED:
|
|
ZFCP_LOG_INFO("generic service command rejected "
|
|
"(adapter %s, port d_id=0x%06x)\n",
|
|
zfcp_get_busid_by_port(port), port->d_id);
|
|
ZFCP_LOG_INFO("status qualifier:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO,
|
|
(char *) &header->fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PORT_HANDLE_NOT_VALID:
|
|
ZFCP_LOG_DEBUG("Temporary port identifier 0x%x for port "
|
|
"0x%016Lx on adapter %s invalid. This may "
|
|
"happen occasionally.\n", port->handle,
|
|
port->wwpn, zfcp_get_busid_by_port(port));
|
|
ZFCP_LOG_INFO("status qualifier:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO,
|
|
(char *) &header->fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_adapter_reopen(adapter, 0, 106, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PORT_BOXED:
|
|
ZFCP_LOG_INFO("port needs to be reopened "
|
|
"(adapter %s, port d_id=0x%06x)\n",
|
|
zfcp_get_busid_by_port(port), port->d_id);
|
|
zfcp_erp_port_boxed(port, 49, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR
|
|
| ZFCP_STATUS_FSFREQ_RETRY;
|
|
break;
|
|
|
|
/* following states should never occure, all cases avoided
|
|
in zfcp_fsf_send_ct - but who knows ... */
|
|
case FSF_PAYLOAD_SIZE_MISMATCH:
|
|
ZFCP_LOG_INFO("payload size mismatch (adapter: %s, "
|
|
"req_buf_length=%d, resp_buf_length=%d)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
bottom->req_buf_length, bottom->resp_buf_length);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_REQUEST_SIZE_TOO_LARGE:
|
|
ZFCP_LOG_INFO("request size too large (adapter: %s, "
|
|
"req_buf_length=%d)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
bottom->req_buf_length);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_RESPONSE_SIZE_TOO_LARGE:
|
|
ZFCP_LOG_INFO("response size too large (adapter: %s, "
|
|
"resp_buf_length=%d)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
bottom->resp_buf_length);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SBAL_MISMATCH:
|
|
ZFCP_LOG_INFO("SBAL mismatch (adapter: %s, req_buf_length=%d, "
|
|
"resp_buf_length=%d)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
bottom->req_buf_length, bottom->resp_buf_length);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
|
|
"(debug info 0x%x)\n", header->fsf_status);
|
|
break;
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
send_ct->status = retval;
|
|
|
|
if (send_ct->handler != NULL)
|
|
send_ct->handler(send_ct->handler_data);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_send_els - initiate an ELS command (FC-FS)
|
|
* @els: pointer to struct zfcp_send_els which contains all needed data for
|
|
* the command.
|
|
*/
|
|
int
|
|
zfcp_fsf_send_els(struct zfcp_send_els *els)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
u32 d_id;
|
|
struct zfcp_adapter *adapter;
|
|
unsigned long lock_flags;
|
|
int bytes;
|
|
int ret = 0;
|
|
|
|
d_id = els->d_id;
|
|
adapter = els->adapter;
|
|
|
|
ret = zfcp_fsf_req_create(adapter, FSF_QTCB_SEND_ELS,
|
|
ZFCP_REQ_AUTO_CLEANUP,
|
|
NULL, &lock_flags, &fsf_req);
|
|
if (ret < 0) {
|
|
ZFCP_LOG_INFO("error: creation of ELS request failed "
|
|
"(adapter %s, port d_id: 0x%06x)\n",
|
|
zfcp_get_busid_by_adapter(adapter), d_id);
|
|
goto failed_req;
|
|
}
|
|
|
|
if (unlikely(!atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED,
|
|
&els->port->status))) {
|
|
ret = -EBUSY;
|
|
goto port_blocked;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
if (zfcp_use_one_sbal(els->req, els->req_count,
|
|
els->resp, els->resp_count)){
|
|
/* both request buffer and response buffer
|
|
fit into one sbale each */
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_WRITE_READ;
|
|
sbale[2].addr = zfcp_sg_to_address(&els->req[0]);
|
|
sbale[2].length = els->req[0].length;
|
|
sbale[3].addr = zfcp_sg_to_address(&els->resp[0]);
|
|
sbale[3].length = els->resp[0].length;
|
|
sbale[3].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
} else if (adapter->adapter_features &
|
|
FSF_FEATURE_ELS_CT_CHAINED_SBALS) {
|
|
/* try to use chained SBALs */
|
|
bytes = zfcp_qdio_sbals_from_sg(fsf_req,
|
|
SBAL_FLAGS0_TYPE_WRITE_READ,
|
|
els->req, els->req_count,
|
|
ZFCP_MAX_SBALS_PER_ELS_REQ);
|
|
if (bytes <= 0) {
|
|
ZFCP_LOG_INFO("error: creation of ELS request failed "
|
|
"(adapter %s, port d_id: 0x%06x)\n",
|
|
zfcp_get_busid_by_adapter(adapter), d_id);
|
|
if (bytes == 0) {
|
|
ret = -ENOMEM;
|
|
} else {
|
|
ret = bytes;
|
|
}
|
|
goto failed_send;
|
|
}
|
|
fsf_req->qtcb->bottom.support.req_buf_length = bytes;
|
|
fsf_req->sbale_curr = ZFCP_LAST_SBALE_PER_SBAL;
|
|
bytes = zfcp_qdio_sbals_from_sg(fsf_req,
|
|
SBAL_FLAGS0_TYPE_WRITE_READ,
|
|
els->resp, els->resp_count,
|
|
ZFCP_MAX_SBALS_PER_ELS_REQ);
|
|
if (bytes <= 0) {
|
|
ZFCP_LOG_INFO("error: creation of ELS request failed "
|
|
"(adapter %s, port d_id: 0x%06x)\n",
|
|
zfcp_get_busid_by_adapter(adapter), d_id);
|
|
if (bytes == 0) {
|
|
ret = -ENOMEM;
|
|
} else {
|
|
ret = bytes;
|
|
}
|
|
goto failed_send;
|
|
}
|
|
fsf_req->qtcb->bottom.support.resp_buf_length = bytes;
|
|
} else {
|
|
/* reject request */
|
|
ZFCP_LOG_INFO("error: microcode does not support chained SBALs"
|
|
", ELS request too big (adapter %s, "
|
|
"port d_id: 0x%06x)\n",
|
|
zfcp_get_busid_by_adapter(adapter), d_id);
|
|
ret = -EOPNOTSUPP;
|
|
goto failed_send;
|
|
}
|
|
|
|
/* settings in QTCB */
|
|
fsf_req->qtcb->bottom.support.d_id = d_id;
|
|
fsf_req->qtcb->bottom.support.service_class =
|
|
ZFCP_FC_SERVICE_CLASS_DEFAULT;
|
|
fsf_req->qtcb->bottom.support.timeout = ZFCP_ELS_TIMEOUT;
|
|
fsf_req->data = (unsigned long) els;
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
|
|
zfcp_san_dbf_event_els_request(fsf_req);
|
|
|
|
zfcp_fsf_start_timer(fsf_req, ZFCP_FSF_REQUEST_TIMEOUT);
|
|
ret = zfcp_fsf_req_send(fsf_req);
|
|
if (ret) {
|
|
ZFCP_LOG_DEBUG("error: initiation of ELS request failed "
|
|
"(adapter %s, port d_id: 0x%06x)\n",
|
|
zfcp_get_busid_by_adapter(adapter), d_id);
|
|
goto failed_send;
|
|
}
|
|
|
|
ZFCP_LOG_DEBUG("ELS request initiated (adapter %s, port d_id: "
|
|
"0x%06x)\n", zfcp_get_busid_by_adapter(adapter), d_id);
|
|
goto out;
|
|
|
|
port_blocked:
|
|
failed_send:
|
|
zfcp_fsf_req_free(fsf_req);
|
|
|
|
failed_req:
|
|
out:
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_send_els_handler - handler for ELS commands
|
|
* @fsf_req: pointer to struct zfcp_fsf_req
|
|
*
|
|
* Data specific for the ELS command is passed using
|
|
* fsf_req->data. There we find the pointer to struct zfcp_send_els.
|
|
* Usually a specific handler for the ELS command is called which is
|
|
* found in this structure.
|
|
*/
|
|
static int zfcp_fsf_send_els_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
struct zfcp_adapter *adapter;
|
|
struct zfcp_port *port;
|
|
u32 d_id;
|
|
struct fsf_qtcb_header *header;
|
|
struct fsf_qtcb_bottom_support *bottom;
|
|
struct zfcp_send_els *send_els;
|
|
int retval = -EINVAL;
|
|
u16 subtable, rule, counter;
|
|
|
|
send_els = (struct zfcp_send_els *) fsf_req->data;
|
|
adapter = send_els->adapter;
|
|
port = send_els->port;
|
|
d_id = send_els->d_id;
|
|
header = &fsf_req->qtcb->header;
|
|
bottom = &fsf_req->qtcb->bottom.support;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)
|
|
goto skip_fsfstatus;
|
|
|
|
switch (header->fsf_status) {
|
|
|
|
case FSF_GOOD:
|
|
zfcp_san_dbf_event_els_response(fsf_req);
|
|
retval = 0;
|
|
break;
|
|
|
|
case FSF_SERVICE_CLASS_NOT_SUPPORTED:
|
|
ZFCP_LOG_INFO("error: adapter %s does not support fc "
|
|
"class %d.\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
ZFCP_FC_SERVICE_CLASS_DEFAULT);
|
|
/* stop operation for this adapter */
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 124, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_ADAPTER_STATUS_AVAILABLE:
|
|
switch (header->fsf_status_qual.word[0]){
|
|
case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
|
|
if (port && (send_els->ls_code != ZFCP_LS_ADISC))
|
|
zfcp_test_link(port);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_RETRY_IF_POSSIBLE:
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
default:
|
|
ZFCP_LOG_INFO("bug: Wrong status qualifier 0x%x\n",
|
|
header->fsf_status_qual.word[0]);
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO,
|
|
(char*)header->fsf_status_qual.word, 16);
|
|
}
|
|
break;
|
|
|
|
case FSF_ELS_COMMAND_REJECTED:
|
|
ZFCP_LOG_INFO("ELS has been rejected because command filter "
|
|
"prohibited sending "
|
|
"(adapter: %s, port d_id: 0x%06x)\n",
|
|
zfcp_get_busid_by_adapter(adapter), d_id);
|
|
|
|
break;
|
|
|
|
case FSF_PAYLOAD_SIZE_MISMATCH:
|
|
ZFCP_LOG_INFO(
|
|
"ELS request size and ELS response size must be either "
|
|
"both 0, or both greater than 0 "
|
|
"(adapter: %s, req_buf_length=%d resp_buf_length=%d)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
bottom->req_buf_length,
|
|
bottom->resp_buf_length);
|
|
break;
|
|
|
|
case FSF_REQUEST_SIZE_TOO_LARGE:
|
|
ZFCP_LOG_INFO(
|
|
"Length of the ELS request buffer, "
|
|
"specified in QTCB bottom, "
|
|
"exceeds the size of the buffers "
|
|
"that have been allocated for ELS request data "
|
|
"(adapter: %s, req_buf_length=%d)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
bottom->req_buf_length);
|
|
break;
|
|
|
|
case FSF_RESPONSE_SIZE_TOO_LARGE:
|
|
ZFCP_LOG_INFO(
|
|
"Length of the ELS response buffer, "
|
|
"specified in QTCB bottom, "
|
|
"exceeds the size of the buffers "
|
|
"that have been allocated for ELS response data "
|
|
"(adapter: %s, resp_buf_length=%d)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
bottom->resp_buf_length);
|
|
break;
|
|
|
|
case FSF_SBAL_MISMATCH:
|
|
/* should never occure, avoided in zfcp_fsf_send_els */
|
|
ZFCP_LOG_INFO("SBAL mismatch (adapter: %s, req_buf_length=%d, "
|
|
"resp_buf_length=%d)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
bottom->req_buf_length, bottom->resp_buf_length);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_ACCESS_DENIED:
|
|
ZFCP_LOG_NORMAL("access denied, cannot send ELS command "
|
|
"(adapter %s, port d_id=0x%06x)\n",
|
|
zfcp_get_busid_by_adapter(adapter), d_id);
|
|
for (counter = 0; counter < 2; counter++) {
|
|
subtable = header->fsf_status_qual.halfword[counter * 2];
|
|
rule = header->fsf_status_qual.halfword[counter * 2 + 1];
|
|
switch (subtable) {
|
|
case FSF_SQ_CFDC_SUBTABLE_OS:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
|
|
case FSF_SQ_CFDC_SUBTABLE_LUN:
|
|
ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
|
|
zfcp_act_subtable_type[subtable], rule);
|
|
break;
|
|
}
|
|
}
|
|
if (port != NULL)
|
|
zfcp_erp_port_access_denied(port, 56, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL(
|
|
"bug: An unknown FSF Status was presented "
|
|
"(adapter: %s, fsf_status=0x%08x)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
header->fsf_status);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
send_els->status = retval;
|
|
|
|
if (send_els->handler)
|
|
send_els->handler(send_els->handler_data);
|
|
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
zfcp_fsf_exchange_config_data(struct zfcp_erp_action *erp_action)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
struct zfcp_adapter *adapter = erp_action->adapter;
|
|
unsigned long lock_flags;
|
|
int retval;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(adapter,
|
|
FSF_QTCB_EXCHANGE_CONFIG_DATA,
|
|
ZFCP_REQ_AUTO_CLEANUP,
|
|
adapter->pool.fsf_req_erp,
|
|
&lock_flags, &fsf_req);
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Could not create exchange configuration "
|
|
"data request for adapter %s.\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
fsf_req->qtcb->bottom.config.feature_selection =
|
|
FSF_FEATURE_CFDC |
|
|
FSF_FEATURE_LUN_SHARING |
|
|
FSF_FEATURE_NOTIFICATION_LOST |
|
|
FSF_FEATURE_UPDATE_ALERT;
|
|
fsf_req->erp_action = erp_action;
|
|
erp_action->fsf_req = fsf_req;
|
|
|
|
zfcp_erp_start_timer(fsf_req);
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Could not send exchange configuration "
|
|
"data command on the adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_fsf_req_free(fsf_req);
|
|
erp_action->fsf_req = NULL;
|
|
}
|
|
else
|
|
ZFCP_LOG_DEBUG("exchange configuration data request initiated "
|
|
"(adapter %s)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
zfcp_fsf_exchange_config_data_sync(struct zfcp_adapter *adapter,
|
|
struct fsf_qtcb_bottom_config *data)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
unsigned long lock_flags;
|
|
int retval;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(adapter, FSF_QTCB_EXCHANGE_CONFIG_DATA,
|
|
ZFCP_WAIT_FOR_SBAL, NULL, &lock_flags,
|
|
&fsf_req);
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Could not create exchange configuration "
|
|
"data request for adapter %s.\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
fsf_req->qtcb->bottom.config.feature_selection =
|
|
FSF_FEATURE_CFDC |
|
|
FSF_FEATURE_LUN_SHARING |
|
|
FSF_FEATURE_NOTIFICATION_LOST |
|
|
FSF_FEATURE_UPDATE_ALERT;
|
|
|
|
if (data)
|
|
fsf_req->data = (unsigned long) data;
|
|
|
|
zfcp_fsf_start_timer(fsf_req, ZFCP_FSF_REQUEST_TIMEOUT);
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
if (retval)
|
|
ZFCP_LOG_INFO("error: Could not send exchange configuration "
|
|
"data command on the adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
else
|
|
wait_event(fsf_req->completion_wq,
|
|
fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED);
|
|
|
|
zfcp_fsf_req_free(fsf_req);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_exchange_config_evaluate
|
|
* @fsf_req: fsf_req which belongs to xchg config data request
|
|
* @xchg_ok: specifies if xchg config data was incomplete or complete (0/1)
|
|
*
|
|
* returns: -EIO on error, 0 otherwise
|
|
*/
|
|
static int
|
|
zfcp_fsf_exchange_config_evaluate(struct zfcp_fsf_req *fsf_req, int xchg_ok)
|
|
{
|
|
struct fsf_qtcb_bottom_config *bottom;
|
|
struct zfcp_adapter *adapter = fsf_req->adapter;
|
|
struct Scsi_Host *shost = adapter->scsi_host;
|
|
|
|
bottom = &fsf_req->qtcb->bottom.config;
|
|
ZFCP_LOG_DEBUG("low/high QTCB version 0x%x/0x%x of FSF\n",
|
|
bottom->low_qtcb_version, bottom->high_qtcb_version);
|
|
adapter->fsf_lic_version = bottom->lic_version;
|
|
adapter->adapter_features = bottom->adapter_features;
|
|
adapter->connection_features = bottom->connection_features;
|
|
adapter->peer_wwpn = 0;
|
|
adapter->peer_wwnn = 0;
|
|
adapter->peer_d_id = 0;
|
|
|
|
if (xchg_ok) {
|
|
|
|
if (fsf_req->data)
|
|
memcpy((struct fsf_qtcb_bottom_config *) fsf_req->data,
|
|
bottom, sizeof (struct fsf_qtcb_bottom_config));
|
|
|
|
fc_host_node_name(shost) = bottom->nport_serv_param.wwnn;
|
|
fc_host_port_name(shost) = bottom->nport_serv_param.wwpn;
|
|
fc_host_port_id(shost) = bottom->s_id & ZFCP_DID_MASK;
|
|
fc_host_speed(shost) = bottom->fc_link_speed;
|
|
fc_host_supported_classes(shost) =
|
|
FC_COS_CLASS2 | FC_COS_CLASS3;
|
|
adapter->hydra_version = bottom->adapter_type;
|
|
adapter->timer_ticks = bottom->timer_interval;
|
|
if (fc_host_permanent_port_name(shost) == -1)
|
|
fc_host_permanent_port_name(shost) =
|
|
fc_host_port_name(shost);
|
|
if (bottom->fc_topology == FSF_TOPO_P2P) {
|
|
adapter->peer_d_id = bottom->peer_d_id & ZFCP_DID_MASK;
|
|
adapter->peer_wwpn = bottom->plogi_payload.wwpn;
|
|
adapter->peer_wwnn = bottom->plogi_payload.wwnn;
|
|
fc_host_port_type(shost) = FC_PORTTYPE_PTP;
|
|
} else if (bottom->fc_topology == FSF_TOPO_FABRIC)
|
|
fc_host_port_type(shost) = FC_PORTTYPE_NPORT;
|
|
else if (bottom->fc_topology == FSF_TOPO_AL)
|
|
fc_host_port_type(shost) = FC_PORTTYPE_NLPORT;
|
|
else
|
|
fc_host_port_type(shost) = FC_PORTTYPE_UNKNOWN;
|
|
} else {
|
|
fc_host_node_name(shost) = 0;
|
|
fc_host_port_name(shost) = 0;
|
|
fc_host_port_id(shost) = 0;
|
|
fc_host_speed(shost) = FC_PORTSPEED_UNKNOWN;
|
|
fc_host_port_type(shost) = FC_PORTTYPE_UNKNOWN;
|
|
adapter->hydra_version = 0;
|
|
}
|
|
|
|
if (adapter->adapter_features & FSF_FEATURE_HBAAPI_MANAGEMENT) {
|
|
adapter->hardware_version = bottom->hardware_version;
|
|
memcpy(fc_host_serial_number(shost), bottom->serial_number,
|
|
min(FC_SERIAL_NUMBER_SIZE, 17));
|
|
EBCASC(fc_host_serial_number(shost),
|
|
min(FC_SERIAL_NUMBER_SIZE, 17));
|
|
}
|
|
|
|
if (fsf_req->erp_action)
|
|
ZFCP_LOG_NORMAL("The adapter %s reported the following "
|
|
"characteristics:\n"
|
|
"WWNN 0x%016Lx, WWPN 0x%016Lx, "
|
|
"S_ID 0x%06x,\n"
|
|
"adapter version 0x%x, "
|
|
"LIC version 0x%x, "
|
|
"FC link speed %d Gb/s\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
(wwn_t) fc_host_node_name(shost),
|
|
(wwn_t) fc_host_port_name(shost),
|
|
fc_host_port_id(shost),
|
|
adapter->hydra_version,
|
|
adapter->fsf_lic_version,
|
|
fc_host_speed(shost));
|
|
if (ZFCP_QTCB_VERSION < bottom->low_qtcb_version) {
|
|
ZFCP_LOG_NORMAL("error: the adapter %s "
|
|
"only supports newer control block "
|
|
"versions in comparison to this device "
|
|
"driver (try updated device driver)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 125, fsf_req);
|
|
return -EIO;
|
|
}
|
|
if (ZFCP_QTCB_VERSION > bottom->high_qtcb_version) {
|
|
ZFCP_LOG_NORMAL("error: the adapter %s "
|
|
"only supports older control block "
|
|
"versions than this device driver uses"
|
|
"(consider a microcode upgrade)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 126, fsf_req);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* function: zfcp_fsf_exchange_config_data_handler
|
|
*
|
|
* purpose: is called for finished Exchange Configuration Data command
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_exchange_config_data_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
struct fsf_qtcb_bottom_config *bottom;
|
|
struct zfcp_adapter *adapter = fsf_req->adapter;
|
|
struct fsf_qtcb *qtcb = fsf_req->qtcb;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)
|
|
return -EIO;
|
|
|
|
switch (qtcb->header.fsf_status) {
|
|
|
|
case FSF_GOOD:
|
|
if (zfcp_fsf_exchange_config_evaluate(fsf_req, 1))
|
|
return -EIO;
|
|
|
|
switch (fc_host_port_type(adapter->scsi_host)) {
|
|
case FC_PORTTYPE_PTP:
|
|
ZFCP_LOG_NORMAL("Point-to-Point fibrechannel "
|
|
"configuration detected at adapter %s\n"
|
|
"Peer WWNN 0x%016llx, "
|
|
"peer WWPN 0x%016llx, "
|
|
"peer d_id 0x%06x\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
adapter->peer_wwnn,
|
|
adapter->peer_wwpn,
|
|
adapter->peer_d_id);
|
|
break;
|
|
case FC_PORTTYPE_NLPORT:
|
|
ZFCP_LOG_NORMAL("error: Arbitrated loop fibrechannel "
|
|
"topology detected at adapter %s "
|
|
"unsupported, shutting down adapter\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 127, fsf_req);
|
|
return -EIO;
|
|
case FC_PORTTYPE_NPORT:
|
|
if (fsf_req->erp_action)
|
|
ZFCP_LOG_NORMAL("Switched fabric fibrechannel "
|
|
"network detected at adapter "
|
|
"%s.\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: The fibrechannel topology "
|
|
"reported by the exchange "
|
|
"configuration command for "
|
|
"the adapter %s is not "
|
|
"of a type known to the zfcp "
|
|
"driver, shutting down adapter\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 128, fsf_req);
|
|
return -EIO;
|
|
}
|
|
bottom = &qtcb->bottom.config;
|
|
if (bottom->max_qtcb_size < sizeof(struct fsf_qtcb)) {
|
|
ZFCP_LOG_NORMAL("bug: Maximum QTCB size (%d bytes) "
|
|
"allowed by the adapter %s "
|
|
"is lower than the minimum "
|
|
"required by the driver (%ld bytes).\n",
|
|
bottom->max_qtcb_size,
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
sizeof(struct fsf_qtcb));
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 129, fsf_req);
|
|
return -EIO;
|
|
}
|
|
atomic_set_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK,
|
|
&adapter->status);
|
|
break;
|
|
case FSF_EXCHANGE_CONFIG_DATA_INCOMPLETE:
|
|
if (zfcp_fsf_exchange_config_evaluate(fsf_req, 0))
|
|
return -EIO;
|
|
|
|
atomic_set_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK,
|
|
&adapter->status);
|
|
|
|
zfcp_fsf_link_down_info_eval(fsf_req, 42,
|
|
&qtcb->header.fsf_status_qual.link_down_info);
|
|
break;
|
|
default:
|
|
zfcp_erp_adapter_shutdown(adapter, 0, 130, fsf_req);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_exchange_port_data - request information about local port
|
|
* @erp_action: ERP action for the adapter for which port data is requested
|
|
*/
|
|
int
|
|
zfcp_fsf_exchange_port_data(struct zfcp_erp_action *erp_action)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
struct zfcp_adapter *adapter = erp_action->adapter;
|
|
unsigned long lock_flags;
|
|
int retval;
|
|
|
|
if (!(adapter->adapter_features & FSF_FEATURE_HBAAPI_MANAGEMENT)) {
|
|
ZFCP_LOG_INFO("error: exchange port data "
|
|
"command not supported by adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(adapter, FSF_QTCB_EXCHANGE_PORT_DATA,
|
|
ZFCP_REQ_AUTO_CLEANUP,
|
|
adapter->pool.fsf_req_erp,
|
|
&lock_flags, &fsf_req);
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Out of resources. Could not create an "
|
|
"exchange port data request for "
|
|
"the adapter %s.\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
erp_action->fsf_req = fsf_req;
|
|
fsf_req->erp_action = erp_action;
|
|
zfcp_erp_start_timer(fsf_req);
|
|
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
|
|
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Could not send an exchange port data "
|
|
"command on the adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
zfcp_fsf_req_free(fsf_req);
|
|
erp_action->fsf_req = NULL;
|
|
}
|
|
else
|
|
ZFCP_LOG_DEBUG("exchange port data request initiated "
|
|
"(adapter %s)\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
return retval;
|
|
}
|
|
|
|
|
|
/**
|
|
* zfcp_fsf_exchange_port_data_sync - request information about local port
|
|
* and wait until information is ready
|
|
*/
|
|
int
|
|
zfcp_fsf_exchange_port_data_sync(struct zfcp_adapter *adapter,
|
|
struct fsf_qtcb_bottom_port *data)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
unsigned long lock_flags;
|
|
int retval;
|
|
|
|
if (!(adapter->adapter_features & FSF_FEATURE_HBAAPI_MANAGEMENT)) {
|
|
ZFCP_LOG_INFO("error: exchange port data "
|
|
"command not supported by adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(adapter, FSF_QTCB_EXCHANGE_PORT_DATA,
|
|
0, NULL, &lock_flags, &fsf_req);
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Out of resources. Could not create an "
|
|
"exchange port data request for "
|
|
"the adapter %s.\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
if (data)
|
|
fsf_req->data = (unsigned long) data;
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
zfcp_fsf_start_timer(fsf_req, ZFCP_FSF_REQUEST_TIMEOUT);
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
|
|
|
|
if (retval)
|
|
ZFCP_LOG_INFO("error: Could not send an exchange port data "
|
|
"command on the adapter %s\n",
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
else
|
|
wait_event(fsf_req->completion_wq,
|
|
fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED);
|
|
|
|
zfcp_fsf_req_free(fsf_req);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_exchange_port_evaluate
|
|
* @fsf_req: fsf_req which belongs to xchg port data request
|
|
* @xchg_ok: specifies if xchg port data was incomplete or complete (0/1)
|
|
*/
|
|
static void
|
|
zfcp_fsf_exchange_port_evaluate(struct zfcp_fsf_req *fsf_req, int xchg_ok)
|
|
{
|
|
struct zfcp_adapter *adapter;
|
|
struct fsf_qtcb_bottom_port *bottom;
|
|
struct Scsi_Host *shost;
|
|
|
|
adapter = fsf_req->adapter;
|
|
bottom = &fsf_req->qtcb->bottom.port;
|
|
shost = adapter->scsi_host;
|
|
|
|
if (fsf_req->data)
|
|
memcpy((struct fsf_qtcb_bottom_port*) fsf_req->data, bottom,
|
|
sizeof(struct fsf_qtcb_bottom_port));
|
|
|
|
if (adapter->connection_features & FSF_FEATURE_NPIV_MODE)
|
|
fc_host_permanent_port_name(shost) = bottom->wwpn;
|
|
else
|
|
fc_host_permanent_port_name(shost) = fc_host_port_name(shost);
|
|
fc_host_maxframe_size(shost) = bottom->maximum_frame_size;
|
|
fc_host_supported_speeds(shost) = bottom->supported_speed;
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_exchange_port_data_handler - handler for exchange_port_data request
|
|
* @fsf_req: pointer to struct zfcp_fsf_req
|
|
*/
|
|
static void
|
|
zfcp_fsf_exchange_port_data_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
struct zfcp_adapter *adapter;
|
|
struct fsf_qtcb *qtcb;
|
|
|
|
adapter = fsf_req->adapter;
|
|
qtcb = fsf_req->qtcb;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)
|
|
return;
|
|
|
|
switch (qtcb->header.fsf_status) {
|
|
case FSF_GOOD:
|
|
zfcp_fsf_exchange_port_evaluate(fsf_req, 1);
|
|
atomic_set_mask(ZFCP_STATUS_ADAPTER_XPORT_OK, &adapter->status);
|
|
break;
|
|
case FSF_EXCHANGE_CONFIG_DATA_INCOMPLETE:
|
|
zfcp_fsf_exchange_port_evaluate(fsf_req, 0);
|
|
atomic_set_mask(ZFCP_STATUS_ADAPTER_XPORT_OK, &adapter->status);
|
|
zfcp_fsf_link_down_info_eval(fsf_req, 43,
|
|
&qtcb->header.fsf_status_qual.link_down_info);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* function: zfcp_fsf_open_port
|
|
*
|
|
* purpose:
|
|
*
|
|
* returns: address of initiated FSF request
|
|
* NULL - request could not be initiated
|
|
*/
|
|
int
|
|
zfcp_fsf_open_port(struct zfcp_erp_action *erp_action)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
unsigned long lock_flags;
|
|
int retval = 0;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(erp_action->adapter,
|
|
FSF_QTCB_OPEN_PORT_WITH_DID,
|
|
ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
|
|
erp_action->adapter->pool.fsf_req_erp,
|
|
&lock_flags, &fsf_req);
|
|
if (retval < 0) {
|
|
ZFCP_LOG_INFO("error: Could not create open port request "
|
|
"for port 0x%016Lx on adapter %s.\n",
|
|
erp_action->port->wwpn,
|
|
zfcp_get_busid_by_adapter(erp_action->adapter));
|
|
goto out;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
fsf_req->qtcb->bottom.support.d_id = erp_action->port->d_id;
|
|
atomic_set_mask(ZFCP_STATUS_COMMON_OPENING, &erp_action->port->status);
|
|
fsf_req->data = (unsigned long) erp_action->port;
|
|
fsf_req->erp_action = erp_action;
|
|
erp_action->fsf_req = fsf_req;
|
|
|
|
zfcp_erp_start_timer(fsf_req);
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Could not send open port request for "
|
|
"port 0x%016Lx on adapter %s.\n",
|
|
erp_action->port->wwpn,
|
|
zfcp_get_busid_by_adapter(erp_action->adapter));
|
|
zfcp_fsf_req_free(fsf_req);
|
|
erp_action->fsf_req = NULL;
|
|
goto out;
|
|
}
|
|
|
|
ZFCP_LOG_DEBUG("open port request initiated "
|
|
"(adapter %s, port 0x%016Lx)\n",
|
|
zfcp_get_busid_by_adapter(erp_action->adapter),
|
|
erp_action->port->wwpn);
|
|
out:
|
|
write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_open_port_handler
|
|
*
|
|
* purpose: is called for finished Open Port command
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_open_port_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = -EINVAL;
|
|
struct zfcp_port *port;
|
|
struct fsf_plogi *plogi;
|
|
struct fsf_qtcb_header *header;
|
|
u16 subtable, rule, counter;
|
|
|
|
port = (struct zfcp_port *) fsf_req->data;
|
|
header = &fsf_req->qtcb->header;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
|
|
/* don't change port status in our bookkeeping */
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
/* evaluate FSF status in QTCB */
|
|
switch (header->fsf_status) {
|
|
|
|
case FSF_PORT_ALREADY_OPEN:
|
|
ZFCP_LOG_NORMAL("bug: remote port 0x%016Lx on adapter %s "
|
|
"is already open.\n",
|
|
port->wwpn, zfcp_get_busid_by_port(port));
|
|
/*
|
|
* This is a bug, however operation should continue normally
|
|
* if it is simply ignored
|
|
*/
|
|
break;
|
|
|
|
case FSF_ACCESS_DENIED:
|
|
ZFCP_LOG_NORMAL("Access denied, cannot open port 0x%016Lx "
|
|
"on adapter %s\n",
|
|
port->wwpn, zfcp_get_busid_by_port(port));
|
|
for (counter = 0; counter < 2; counter++) {
|
|
subtable = header->fsf_status_qual.halfword[counter * 2];
|
|
rule = header->fsf_status_qual.halfword[counter * 2 + 1];
|
|
switch (subtable) {
|
|
case FSF_SQ_CFDC_SUBTABLE_OS:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
|
|
case FSF_SQ_CFDC_SUBTABLE_LUN:
|
|
ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
|
|
zfcp_act_subtable_type[subtable], rule);
|
|
break;
|
|
}
|
|
}
|
|
zfcp_erp_port_access_denied(port, 57, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_MAXIMUM_NUMBER_OF_PORTS_EXCEEDED:
|
|
ZFCP_LOG_INFO("error: The FSF adapter is out of resources. "
|
|
"The remote port 0x%016Lx on adapter %s "
|
|
"could not be opened. Disabling it.\n",
|
|
port->wwpn, zfcp_get_busid_by_port(port));
|
|
zfcp_erp_port_failed(port, 31, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_ADAPTER_STATUS_AVAILABLE:
|
|
switch (header->fsf_status_qual.word[0]) {
|
|
case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
|
|
/* ERP strategy will escalate */
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
|
|
/* ERP strategy will escalate */
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_NO_RETRY_POSSIBLE:
|
|
ZFCP_LOG_NORMAL("The remote port 0x%016Lx on "
|
|
"adapter %s could not be opened. "
|
|
"Disabling it.\n",
|
|
port->wwpn,
|
|
zfcp_get_busid_by_port(port));
|
|
zfcp_erp_port_failed(port, 32, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL
|
|
("bug: Wrong status qualifier 0x%x arrived.\n",
|
|
header->fsf_status_qual.word[0]);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case FSF_GOOD:
|
|
/* save port handle assigned by FSF */
|
|
port->handle = header->port_handle;
|
|
ZFCP_LOG_INFO("The remote port 0x%016Lx via adapter %s "
|
|
"was opened, it's port handle is 0x%x\n",
|
|
port->wwpn, zfcp_get_busid_by_port(port),
|
|
port->handle);
|
|
/* mark port as open */
|
|
atomic_set_mask(ZFCP_STATUS_COMMON_OPEN |
|
|
ZFCP_STATUS_PORT_PHYS_OPEN, &port->status);
|
|
atomic_clear_mask(ZFCP_STATUS_COMMON_ACCESS_DENIED |
|
|
ZFCP_STATUS_COMMON_ACCESS_BOXED,
|
|
&port->status);
|
|
retval = 0;
|
|
/* check whether D_ID has changed during open */
|
|
/*
|
|
* FIXME: This check is not airtight, as the FCP channel does
|
|
* not monitor closures of target port connections caused on
|
|
* the remote side. Thus, they might miss out on invalidating
|
|
* locally cached WWPNs (and other N_Port parameters) of gone
|
|
* target ports. So, our heroic attempt to make things safe
|
|
* could be undermined by 'open port' response data tagged with
|
|
* obsolete WWPNs. Another reason to monitor potential
|
|
* connection closures ourself at least (by interpreting
|
|
* incoming ELS' and unsolicited status). It just crosses my
|
|
* mind that one should be able to cross-check by means of
|
|
* another GID_PN straight after a port has been opened.
|
|
* Alternately, an ADISC/PDISC ELS should suffice, as well.
|
|
*/
|
|
plogi = (struct fsf_plogi *) fsf_req->qtcb->bottom.support.els;
|
|
if (!atomic_test_mask(ZFCP_STATUS_PORT_NO_WWPN, &port->status))
|
|
{
|
|
if (fsf_req->qtcb->bottom.support.els1_length <
|
|
sizeof (struct fsf_plogi)) {
|
|
ZFCP_LOG_INFO(
|
|
"warning: insufficient length of "
|
|
"PLOGI payload (%i)\n",
|
|
fsf_req->qtcb->bottom.support.els1_length);
|
|
/* skip sanity check and assume wwpn is ok */
|
|
} else {
|
|
if (plogi->serv_param.wwpn != port->wwpn) {
|
|
ZFCP_LOG_INFO("warning: d_id of port "
|
|
"0x%016Lx changed during "
|
|
"open\n", port->wwpn);
|
|
atomic_clear_mask(
|
|
ZFCP_STATUS_PORT_DID_DID,
|
|
&port->status);
|
|
} else {
|
|
port->wwnn = plogi->serv_param.wwnn;
|
|
zfcp_fc_plogi_evaluate(port, plogi);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FSF_UNKNOWN_OP_SUBTYPE:
|
|
/* should never occure, subtype not set in zfcp_fsf_open_port */
|
|
ZFCP_LOG_INFO("unknown operation subtype (adapter: %s, "
|
|
"op_subtype=0x%x)\n",
|
|
zfcp_get_busid_by_port(port),
|
|
fsf_req->qtcb->bottom.support.operation_subtype);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
|
|
"(debug info 0x%x)\n",
|
|
header->fsf_status);
|
|
break;
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING, &port->status);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_close_port
|
|
*
|
|
* purpose: submit FSF command "close port"
|
|
*
|
|
* returns: address of initiated FSF request
|
|
* NULL - request could not be initiated
|
|
*/
|
|
int
|
|
zfcp_fsf_close_port(struct zfcp_erp_action *erp_action)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
unsigned long lock_flags;
|
|
int retval = 0;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(erp_action->adapter,
|
|
FSF_QTCB_CLOSE_PORT,
|
|
ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
|
|
erp_action->adapter->pool.fsf_req_erp,
|
|
&lock_flags, &fsf_req);
|
|
if (retval < 0) {
|
|
ZFCP_LOG_INFO("error: Could not create a close port request "
|
|
"for port 0x%016Lx on adapter %s.\n",
|
|
erp_action->port->wwpn,
|
|
zfcp_get_busid_by_adapter(erp_action->adapter));
|
|
goto out;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
atomic_set_mask(ZFCP_STATUS_COMMON_CLOSING, &erp_action->port->status);
|
|
fsf_req->data = (unsigned long) erp_action->port;
|
|
fsf_req->erp_action = erp_action;
|
|
fsf_req->qtcb->header.port_handle = erp_action->port->handle;
|
|
fsf_req->erp_action = erp_action;
|
|
erp_action->fsf_req = fsf_req;
|
|
|
|
zfcp_erp_start_timer(fsf_req);
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Could not send a close port request for "
|
|
"port 0x%016Lx on adapter %s.\n",
|
|
erp_action->port->wwpn,
|
|
zfcp_get_busid_by_adapter(erp_action->adapter));
|
|
zfcp_fsf_req_free(fsf_req);
|
|
erp_action->fsf_req = NULL;
|
|
goto out;
|
|
}
|
|
|
|
ZFCP_LOG_TRACE("close port request initiated "
|
|
"(adapter %s, port 0x%016Lx)\n",
|
|
zfcp_get_busid_by_adapter(erp_action->adapter),
|
|
erp_action->port->wwpn);
|
|
out:
|
|
write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_close_port_handler
|
|
*
|
|
* purpose: is called for finished Close Port FSF command
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_close_port_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = -EINVAL;
|
|
struct zfcp_port *port;
|
|
|
|
port = (struct zfcp_port *) fsf_req->data;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
|
|
/* don't change port status in our bookkeeping */
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
/* evaluate FSF status in QTCB */
|
|
switch (fsf_req->qtcb->header.fsf_status) {
|
|
|
|
case FSF_PORT_HANDLE_NOT_VALID:
|
|
ZFCP_LOG_INFO("Temporary port identifier 0x%x for port "
|
|
"0x%016Lx on adapter %s invalid. This may happen "
|
|
"occasionally.\n", port->handle,
|
|
port->wwpn, zfcp_get_busid_by_port(port));
|
|
ZFCP_LOG_DEBUG("status qualifier:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &fsf_req->qtcb->header.fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_adapter_reopen(port->adapter, 0, 107, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_ADAPTER_STATUS_AVAILABLE:
|
|
/* Note: FSF has actually closed the port in this case.
|
|
* The status code is just daft. Fingers crossed for a change
|
|
*/
|
|
retval = 0;
|
|
break;
|
|
|
|
case FSF_GOOD:
|
|
ZFCP_LOG_TRACE("remote port 0x016%Lx on adapter %s closed, "
|
|
"port handle 0x%x\n", port->wwpn,
|
|
zfcp_get_busid_by_port(port), port->handle);
|
|
zfcp_erp_modify_port_status(port, 33, fsf_req,
|
|
ZFCP_STATUS_COMMON_OPEN,
|
|
ZFCP_CLEAR);
|
|
retval = 0;
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
|
|
"(debug info 0x%x)\n",
|
|
fsf_req->qtcb->header.fsf_status);
|
|
break;
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
atomic_clear_mask(ZFCP_STATUS_COMMON_CLOSING, &port->status);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_close_physical_port
|
|
*
|
|
* purpose: submit FSF command "close physical port"
|
|
*
|
|
* returns: address of initiated FSF request
|
|
* NULL - request could not be initiated
|
|
*/
|
|
int
|
|
zfcp_fsf_close_physical_port(struct zfcp_erp_action *erp_action)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
unsigned long lock_flags;
|
|
int retval = 0;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(erp_action->adapter,
|
|
FSF_QTCB_CLOSE_PHYSICAL_PORT,
|
|
ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
|
|
erp_action->adapter->pool.fsf_req_erp,
|
|
&lock_flags, &fsf_req);
|
|
if (retval < 0) {
|
|
ZFCP_LOG_INFO("error: Could not create close physical port "
|
|
"request (adapter %s, port 0x%016Lx)\n",
|
|
zfcp_get_busid_by_adapter(erp_action->adapter),
|
|
erp_action->port->wwpn);
|
|
|
|
goto out;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
/* mark port as being closed */
|
|
atomic_set_mask(ZFCP_STATUS_PORT_PHYS_CLOSING,
|
|
&erp_action->port->status);
|
|
/* save a pointer to this port */
|
|
fsf_req->data = (unsigned long) erp_action->port;
|
|
fsf_req->qtcb->header.port_handle = erp_action->port->handle;
|
|
fsf_req->erp_action = erp_action;
|
|
erp_action->fsf_req = fsf_req;
|
|
|
|
zfcp_erp_start_timer(fsf_req);
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Could not send close physical port "
|
|
"request (adapter %s, port 0x%016Lx)\n",
|
|
zfcp_get_busid_by_adapter(erp_action->adapter),
|
|
erp_action->port->wwpn);
|
|
zfcp_fsf_req_free(fsf_req);
|
|
erp_action->fsf_req = NULL;
|
|
goto out;
|
|
}
|
|
|
|
ZFCP_LOG_TRACE("close physical port request initiated "
|
|
"(adapter %s, port 0x%016Lx)\n",
|
|
zfcp_get_busid_by_adapter(erp_action->adapter),
|
|
erp_action->port->wwpn);
|
|
out:
|
|
write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_close_physical_port_handler
|
|
*
|
|
* purpose: is called for finished Close Physical Port FSF command
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_close_physical_port_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = -EINVAL;
|
|
struct zfcp_port *port;
|
|
struct zfcp_unit *unit;
|
|
struct fsf_qtcb_header *header;
|
|
u16 subtable, rule, counter;
|
|
|
|
port = (struct zfcp_port *) fsf_req->data;
|
|
header = &fsf_req->qtcb->header;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
|
|
/* don't change port status in our bookkeeping */
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
/* evaluate FSF status in QTCB */
|
|
switch (header->fsf_status) {
|
|
|
|
case FSF_PORT_HANDLE_NOT_VALID:
|
|
ZFCP_LOG_INFO("Temporary port identifier 0x%x invalid"
|
|
"(adapter %s, port 0x%016Lx). "
|
|
"This may happen occasionally.\n",
|
|
port->handle,
|
|
zfcp_get_busid_by_port(port),
|
|
port->wwpn);
|
|
ZFCP_LOG_DEBUG("status qualifier:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &header->fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_adapter_reopen(port->adapter, 0, 108, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_ACCESS_DENIED:
|
|
ZFCP_LOG_NORMAL("Access denied, cannot close "
|
|
"physical port 0x%016Lx on adapter %s\n",
|
|
port->wwpn, zfcp_get_busid_by_port(port));
|
|
for (counter = 0; counter < 2; counter++) {
|
|
subtable = header->fsf_status_qual.halfword[counter * 2];
|
|
rule = header->fsf_status_qual.halfword[counter * 2 + 1];
|
|
switch (subtable) {
|
|
case FSF_SQ_CFDC_SUBTABLE_OS:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
|
|
case FSF_SQ_CFDC_SUBTABLE_LUN:
|
|
ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
|
|
zfcp_act_subtable_type[subtable], rule);
|
|
break;
|
|
}
|
|
}
|
|
zfcp_erp_port_access_denied(port, 58, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PORT_BOXED:
|
|
ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter "
|
|
"%s needs to be reopened but it was attempted "
|
|
"to close it physically.\n",
|
|
port->wwpn,
|
|
zfcp_get_busid_by_port(port));
|
|
zfcp_erp_port_boxed(port, 50, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR |
|
|
ZFCP_STATUS_FSFREQ_RETRY;
|
|
|
|
/* can't use generic zfcp_erp_modify_port_status because
|
|
* ZFCP_STATUS_COMMON_OPEN must not be reset for the port */
|
|
atomic_clear_mask(ZFCP_STATUS_PORT_PHYS_OPEN, &port->status);
|
|
list_for_each_entry(unit, &port->unit_list_head, list)
|
|
atomic_clear_mask(ZFCP_STATUS_COMMON_OPEN,
|
|
&unit->status);
|
|
break;
|
|
|
|
case FSF_ADAPTER_STATUS_AVAILABLE:
|
|
switch (header->fsf_status_qual.word[0]) {
|
|
case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
|
|
/* This will now be escalated by ERP */
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
|
|
/* ERP strategy will escalate */
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL
|
|
("bug: Wrong status qualifier 0x%x arrived.\n",
|
|
header->fsf_status_qual.word[0]);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case FSF_GOOD:
|
|
ZFCP_LOG_DEBUG("Remote port 0x%016Lx via adapter %s "
|
|
"physically closed, port handle 0x%x\n",
|
|
port->wwpn,
|
|
zfcp_get_busid_by_port(port), port->handle);
|
|
/* can't use generic zfcp_erp_modify_port_status because
|
|
* ZFCP_STATUS_COMMON_OPEN must not be reset for the port
|
|
*/
|
|
atomic_clear_mask(ZFCP_STATUS_PORT_PHYS_OPEN, &port->status);
|
|
list_for_each_entry(unit, &port->unit_list_head, list)
|
|
atomic_clear_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status);
|
|
retval = 0;
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
|
|
"(debug info 0x%x)\n",
|
|
header->fsf_status);
|
|
break;
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
atomic_clear_mask(ZFCP_STATUS_PORT_PHYS_CLOSING, &port->status);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_open_unit
|
|
*
|
|
* purpose:
|
|
*
|
|
* returns:
|
|
*
|
|
* assumptions: This routine does not check whether the associated
|
|
* remote port has already been opened. This should be
|
|
* done by calling routines. Otherwise some status
|
|
* may be presented by FSF
|
|
*/
|
|
int
|
|
zfcp_fsf_open_unit(struct zfcp_erp_action *erp_action)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
unsigned long lock_flags;
|
|
int retval = 0;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(erp_action->adapter,
|
|
FSF_QTCB_OPEN_LUN,
|
|
ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
|
|
erp_action->adapter->pool.fsf_req_erp,
|
|
&lock_flags, &fsf_req);
|
|
if (retval < 0) {
|
|
ZFCP_LOG_INFO("error: Could not create open unit request for "
|
|
"unit 0x%016Lx on port 0x%016Lx on adapter %s.\n",
|
|
erp_action->unit->fcp_lun,
|
|
erp_action->unit->port->wwpn,
|
|
zfcp_get_busid_by_adapter(erp_action->adapter));
|
|
goto out;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
fsf_req->qtcb->header.port_handle = erp_action->port->handle;
|
|
fsf_req->qtcb->bottom.support.fcp_lun = erp_action->unit->fcp_lun;
|
|
if (!(erp_action->adapter->connection_features & FSF_FEATURE_NPIV_MODE))
|
|
fsf_req->qtcb->bottom.support.option =
|
|
FSF_OPEN_LUN_SUPPRESS_BOXING;
|
|
atomic_set_mask(ZFCP_STATUS_COMMON_OPENING, &erp_action->unit->status);
|
|
fsf_req->data = (unsigned long) erp_action->unit;
|
|
fsf_req->erp_action = erp_action;
|
|
erp_action->fsf_req = fsf_req;
|
|
|
|
zfcp_erp_start_timer(fsf_req);
|
|
retval = zfcp_fsf_req_send(erp_action->fsf_req);
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Could not send an open unit request "
|
|
"on the adapter %s, port 0x%016Lx for "
|
|
"unit 0x%016Lx\n",
|
|
zfcp_get_busid_by_adapter(erp_action->adapter),
|
|
erp_action->port->wwpn,
|
|
erp_action->unit->fcp_lun);
|
|
zfcp_fsf_req_free(fsf_req);
|
|
erp_action->fsf_req = NULL;
|
|
goto out;
|
|
}
|
|
|
|
ZFCP_LOG_TRACE("Open LUN request initiated (adapter %s, "
|
|
"port 0x%016Lx, unit 0x%016Lx)\n",
|
|
zfcp_get_busid_by_adapter(erp_action->adapter),
|
|
erp_action->port->wwpn, erp_action->unit->fcp_lun);
|
|
out:
|
|
write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_open_unit_handler
|
|
*
|
|
* purpose: is called for finished Open LUN command
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_open_unit_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = -EINVAL;
|
|
struct zfcp_adapter *adapter;
|
|
struct zfcp_unit *unit;
|
|
struct fsf_qtcb_header *header;
|
|
struct fsf_qtcb_bottom_support *bottom;
|
|
struct fsf_queue_designator *queue_designator;
|
|
u16 subtable, rule, counter;
|
|
int exclusive, readwrite;
|
|
|
|
unit = (struct zfcp_unit *) fsf_req->data;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
|
|
/* don't change unit status in our bookkeeping */
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
adapter = fsf_req->adapter;
|
|
header = &fsf_req->qtcb->header;
|
|
bottom = &fsf_req->qtcb->bottom.support;
|
|
queue_designator = &header->fsf_status_qual.fsf_queue_designator;
|
|
|
|
atomic_clear_mask(ZFCP_STATUS_COMMON_ACCESS_DENIED |
|
|
ZFCP_STATUS_COMMON_ACCESS_BOXED |
|
|
ZFCP_STATUS_UNIT_SHARED |
|
|
ZFCP_STATUS_UNIT_READONLY,
|
|
&unit->status);
|
|
|
|
/* evaluate FSF status in QTCB */
|
|
switch (header->fsf_status) {
|
|
|
|
case FSF_PORT_HANDLE_NOT_VALID:
|
|
ZFCP_LOG_INFO("Temporary port identifier 0x%x "
|
|
"for port 0x%016Lx on adapter %s invalid "
|
|
"This may happen occasionally\n",
|
|
unit->port->handle,
|
|
unit->port->wwpn, zfcp_get_busid_by_unit(unit));
|
|
ZFCP_LOG_DEBUG("status qualifier:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &header->fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_adapter_reopen(unit->port->adapter, 0, 109, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_LUN_ALREADY_OPEN:
|
|
ZFCP_LOG_NORMAL("bug: Attempted to open unit 0x%016Lx on "
|
|
"remote port 0x%016Lx on adapter %s twice.\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn, zfcp_get_busid_by_unit(unit));
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_ACCESS_DENIED:
|
|
ZFCP_LOG_NORMAL("Access denied, cannot open unit 0x%016Lx on "
|
|
"remote port 0x%016Lx on adapter %s\n",
|
|
unit->fcp_lun, unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
for (counter = 0; counter < 2; counter++) {
|
|
subtable = header->fsf_status_qual.halfword[counter * 2];
|
|
rule = header->fsf_status_qual.halfword[counter * 2 + 1];
|
|
switch (subtable) {
|
|
case FSF_SQ_CFDC_SUBTABLE_OS:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
|
|
case FSF_SQ_CFDC_SUBTABLE_LUN:
|
|
ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
|
|
zfcp_act_subtable_type[subtable], rule);
|
|
break;
|
|
}
|
|
}
|
|
zfcp_erp_unit_access_denied(unit, 59, fsf_req);
|
|
atomic_clear_mask(ZFCP_STATUS_UNIT_SHARED, &unit->status);
|
|
atomic_clear_mask(ZFCP_STATUS_UNIT_READONLY, &unit->status);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PORT_BOXED:
|
|
ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter %s "
|
|
"needs to be reopened\n",
|
|
unit->port->wwpn, zfcp_get_busid_by_unit(unit));
|
|
zfcp_erp_port_boxed(unit->port, 51, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR |
|
|
ZFCP_STATUS_FSFREQ_RETRY;
|
|
break;
|
|
|
|
case FSF_LUN_SHARING_VIOLATION:
|
|
if (header->fsf_status_qual.word[0] != 0) {
|
|
ZFCP_LOG_NORMAL("FCP-LUN 0x%Lx at the remote port "
|
|
"with WWPN 0x%Lx "
|
|
"connected to the adapter %s "
|
|
"is already in use in LPAR%d, CSS%d\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
queue_designator->hla,
|
|
queue_designator->cssid);
|
|
} else {
|
|
subtable = header->fsf_status_qual.halfword[4];
|
|
rule = header->fsf_status_qual.halfword[5];
|
|
switch (subtable) {
|
|
case FSF_SQ_CFDC_SUBTABLE_OS:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
|
|
case FSF_SQ_CFDC_SUBTABLE_LUN:
|
|
ZFCP_LOG_NORMAL("Access to FCP-LUN 0x%Lx at the "
|
|
"remote port with WWPN 0x%Lx "
|
|
"connected to the adapter %s "
|
|
"is denied (%s rule %d)\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
zfcp_act_subtable_type[subtable],
|
|
rule);
|
|
break;
|
|
}
|
|
}
|
|
ZFCP_LOG_DEBUG("status qualifier:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &header->fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_unit_access_denied(unit, 60, fsf_req);
|
|
atomic_clear_mask(ZFCP_STATUS_UNIT_SHARED, &unit->status);
|
|
atomic_clear_mask(ZFCP_STATUS_UNIT_READONLY, &unit->status);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_MAXIMUM_NUMBER_OF_LUNS_EXCEEDED:
|
|
ZFCP_LOG_INFO("error: The adapter ran out of resources. "
|
|
"There is no handle (temporary port identifier) "
|
|
"available for unit 0x%016Lx on port 0x%016Lx "
|
|
"on adapter %s\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
zfcp_erp_unit_failed(unit, 34, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_ADAPTER_STATUS_AVAILABLE:
|
|
switch (header->fsf_status_qual.word[0]) {
|
|
case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
|
|
/* Re-establish link to port */
|
|
zfcp_test_link(unit->port);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
|
|
/* ERP strategy will escalate */
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL
|
|
("bug: Wrong status qualifier 0x%x arrived.\n",
|
|
header->fsf_status_qual.word[0]);
|
|
}
|
|
break;
|
|
|
|
case FSF_INVALID_COMMAND_OPTION:
|
|
ZFCP_LOG_NORMAL(
|
|
"Invalid option 0x%x has been specified "
|
|
"in QTCB bottom sent to the adapter %s\n",
|
|
bottom->option,
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
retval = -EINVAL;
|
|
break;
|
|
|
|
case FSF_GOOD:
|
|
/* save LUN handle assigned by FSF */
|
|
unit->handle = header->lun_handle;
|
|
ZFCP_LOG_TRACE("unit 0x%016Lx on remote port 0x%016Lx on "
|
|
"adapter %s opened, port handle 0x%x\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
unit->handle);
|
|
/* mark unit as open */
|
|
atomic_set_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status);
|
|
|
|
if (!(adapter->connection_features & FSF_FEATURE_NPIV_MODE) &&
|
|
(adapter->adapter_features & FSF_FEATURE_LUN_SHARING) &&
|
|
(adapter->ccw_device->id.dev_model != ZFCP_DEVICE_MODEL_PRIV)) {
|
|
exclusive = (bottom->lun_access_info &
|
|
FSF_UNIT_ACCESS_EXCLUSIVE);
|
|
readwrite = (bottom->lun_access_info &
|
|
FSF_UNIT_ACCESS_OUTBOUND_TRANSFER);
|
|
|
|
if (!exclusive)
|
|
atomic_set_mask(ZFCP_STATUS_UNIT_SHARED,
|
|
&unit->status);
|
|
|
|
if (!readwrite) {
|
|
atomic_set_mask(ZFCP_STATUS_UNIT_READONLY,
|
|
&unit->status);
|
|
ZFCP_LOG_NORMAL("read-only access for unit "
|
|
"(adapter %s, wwpn=0x%016Lx, "
|
|
"fcp_lun=0x%016Lx)\n",
|
|
zfcp_get_busid_by_unit(unit),
|
|
unit->port->wwpn,
|
|
unit->fcp_lun);
|
|
}
|
|
|
|
if (exclusive && !readwrite) {
|
|
ZFCP_LOG_NORMAL("exclusive access of read-only "
|
|
"unit not supported\n");
|
|
zfcp_erp_unit_failed(unit, 35, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
zfcp_erp_unit_shutdown(unit, 0, 80, fsf_req);
|
|
} else if (!exclusive && readwrite) {
|
|
ZFCP_LOG_NORMAL("shared access of read-write "
|
|
"unit not supported\n");
|
|
zfcp_erp_unit_failed(unit, 36, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
zfcp_erp_unit_shutdown(unit, 0, 81, fsf_req);
|
|
}
|
|
}
|
|
|
|
retval = 0;
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
|
|
"(debug info 0x%x)\n",
|
|
header->fsf_status);
|
|
break;
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING, &unit->status);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_close_unit
|
|
*
|
|
* purpose:
|
|
*
|
|
* returns: address of fsf_req - request successfully initiated
|
|
* NULL -
|
|
*
|
|
* assumptions: This routine does not check whether the associated
|
|
* remote port/lun has already been opened. This should be
|
|
* done by calling routines. Otherwise some status
|
|
* may be presented by FSF
|
|
*/
|
|
int
|
|
zfcp_fsf_close_unit(struct zfcp_erp_action *erp_action)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req;
|
|
unsigned long lock_flags;
|
|
int retval = 0;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(erp_action->adapter,
|
|
FSF_QTCB_CLOSE_LUN,
|
|
ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP,
|
|
erp_action->adapter->pool.fsf_req_erp,
|
|
&lock_flags, &fsf_req);
|
|
if (retval < 0) {
|
|
ZFCP_LOG_INFO("error: Could not create close unit request for "
|
|
"unit 0x%016Lx on port 0x%016Lx on adapter %s.\n",
|
|
erp_action->unit->fcp_lun,
|
|
erp_action->port->wwpn,
|
|
zfcp_get_busid_by_adapter(erp_action->adapter));
|
|
goto out;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_READ;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
fsf_req->qtcb->header.port_handle = erp_action->port->handle;
|
|
fsf_req->qtcb->header.lun_handle = erp_action->unit->handle;
|
|
atomic_set_mask(ZFCP_STATUS_COMMON_CLOSING, &erp_action->unit->status);
|
|
fsf_req->data = (unsigned long) erp_action->unit;
|
|
fsf_req->erp_action = erp_action;
|
|
erp_action->fsf_req = fsf_req;
|
|
|
|
zfcp_erp_start_timer(fsf_req);
|
|
retval = zfcp_fsf_req_send(erp_action->fsf_req);
|
|
if (retval) {
|
|
ZFCP_LOG_INFO("error: Could not send a close unit request for "
|
|
"unit 0x%016Lx on port 0x%016Lx onadapter %s.\n",
|
|
erp_action->unit->fcp_lun,
|
|
erp_action->port->wwpn,
|
|
zfcp_get_busid_by_adapter(erp_action->adapter));
|
|
zfcp_fsf_req_free(fsf_req);
|
|
erp_action->fsf_req = NULL;
|
|
goto out;
|
|
}
|
|
|
|
ZFCP_LOG_TRACE("Close LUN request initiated (adapter %s, "
|
|
"port 0x%016Lx, unit 0x%016Lx)\n",
|
|
zfcp_get_busid_by_adapter(erp_action->adapter),
|
|
erp_action->port->wwpn, erp_action->unit->fcp_lun);
|
|
out:
|
|
write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock,
|
|
lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_close_unit_handler
|
|
*
|
|
* purpose: is called for finished Close LUN FSF command
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_close_unit_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = -EINVAL;
|
|
struct zfcp_unit *unit;
|
|
|
|
unit = (struct zfcp_unit *) fsf_req->data;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
|
|
/* don't change unit status in our bookkeeping */
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
/* evaluate FSF status in QTCB */
|
|
switch (fsf_req->qtcb->header.fsf_status) {
|
|
|
|
case FSF_PORT_HANDLE_NOT_VALID:
|
|
ZFCP_LOG_INFO("Temporary port identifier 0x%x for port "
|
|
"0x%016Lx on adapter %s invalid. This may "
|
|
"happen in rare circumstances\n",
|
|
unit->port->handle,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
ZFCP_LOG_DEBUG("status qualifier:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &fsf_req->qtcb->header.fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_adapter_reopen(unit->port->adapter, 0, 110, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_LUN_HANDLE_NOT_VALID:
|
|
ZFCP_LOG_INFO("Temporary LUN identifier 0x%x of unit "
|
|
"0x%016Lx on port 0x%016Lx on adapter %s is "
|
|
"invalid. This may happen occasionally.\n",
|
|
unit->handle,
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
ZFCP_LOG_DEBUG("Status qualifier data:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &fsf_req->qtcb->header.fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_port_reopen(unit->port, 0, 111, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PORT_BOXED:
|
|
ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter %s "
|
|
"needs to be reopened\n",
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
zfcp_erp_port_boxed(unit->port, 52, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR |
|
|
ZFCP_STATUS_FSFREQ_RETRY;
|
|
break;
|
|
|
|
case FSF_ADAPTER_STATUS_AVAILABLE:
|
|
switch (fsf_req->qtcb->header.fsf_status_qual.word[0]) {
|
|
case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
|
|
/* re-establish link to port */
|
|
zfcp_test_link(unit->port);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
|
|
/* ERP strategy will escalate */
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL
|
|
("bug: Wrong status qualifier 0x%x arrived.\n",
|
|
fsf_req->qtcb->header.fsf_status_qual.word[0]);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case FSF_GOOD:
|
|
ZFCP_LOG_TRACE("unit 0x%016Lx on port 0x%016Lx on adapter %s "
|
|
"closed, port handle 0x%x\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
unit->handle);
|
|
/* mark unit as closed */
|
|
atomic_clear_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status);
|
|
retval = 0;
|
|
break;
|
|
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented "
|
|
"(debug info 0x%x)\n",
|
|
fsf_req->qtcb->header.fsf_status);
|
|
break;
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
atomic_clear_mask(ZFCP_STATUS_COMMON_CLOSING, &unit->status);
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_send_fcp_command_task - initiate an FCP command (for a SCSI command)
|
|
* @adapter: adapter where scsi command is issued
|
|
* @unit: unit where command is sent to
|
|
* @scsi_cmnd: scsi command to be sent
|
|
* @timer: timer to be started when request is initiated
|
|
* @req_flags: flags for fsf_request
|
|
*/
|
|
int
|
|
zfcp_fsf_send_fcp_command_task(struct zfcp_adapter *adapter,
|
|
struct zfcp_unit *unit,
|
|
struct scsi_cmnd * scsi_cmnd,
|
|
int use_timer, int req_flags)
|
|
{
|
|
struct zfcp_fsf_req *fsf_req = NULL;
|
|
struct fcp_cmnd_iu *fcp_cmnd_iu;
|
|
unsigned int sbtype;
|
|
unsigned long lock_flags;
|
|
int real_bytes = 0;
|
|
int retval = 0;
|
|
int mask;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(adapter, FSF_QTCB_FCP_CMND, req_flags,
|
|
adapter->pool.fsf_req_scsi,
|
|
&lock_flags, &fsf_req);
|
|
if (unlikely(retval < 0)) {
|
|
ZFCP_LOG_DEBUG("error: Could not create FCP command request "
|
|
"for unit 0x%016Lx on port 0x%016Lx on "
|
|
"adapter %s\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_adapter(adapter));
|
|
goto failed_req_create;
|
|
}
|
|
|
|
if (unlikely(!atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED,
|
|
&unit->status))) {
|
|
retval = -EBUSY;
|
|
goto unit_blocked;
|
|
}
|
|
|
|
zfcp_unit_get(unit);
|
|
fsf_req->unit = unit;
|
|
|
|
/* associate FSF request with SCSI request (for look up on abort) */
|
|
scsi_cmnd->host_scribble = (unsigned char *) fsf_req->req_id;
|
|
|
|
/* associate SCSI command with FSF request */
|
|
fsf_req->data = (unsigned long) scsi_cmnd;
|
|
|
|
/* set handles of unit and its parent port in QTCB */
|
|
fsf_req->qtcb->header.lun_handle = unit->handle;
|
|
fsf_req->qtcb->header.port_handle = unit->port->handle;
|
|
|
|
/* FSF does not define the structure of the FCP_CMND IU */
|
|
fcp_cmnd_iu = (struct fcp_cmnd_iu *)
|
|
&(fsf_req->qtcb->bottom.io.fcp_cmnd);
|
|
|
|
/*
|
|
* set depending on data direction:
|
|
* data direction bits in SBALE (SB Type)
|
|
* data direction bits in QTCB
|
|
* data direction bits in FCP_CMND IU
|
|
*/
|
|
switch (scsi_cmnd->sc_data_direction) {
|
|
case DMA_NONE:
|
|
fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_CMND;
|
|
/*
|
|
* FIXME(qdio):
|
|
* what is the correct type for commands
|
|
* without 'real' data buffers?
|
|
*/
|
|
sbtype = SBAL_FLAGS0_TYPE_READ;
|
|
break;
|
|
case DMA_FROM_DEVICE:
|
|
fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_READ;
|
|
sbtype = SBAL_FLAGS0_TYPE_READ;
|
|
fcp_cmnd_iu->rddata = 1;
|
|
break;
|
|
case DMA_TO_DEVICE:
|
|
fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_WRITE;
|
|
sbtype = SBAL_FLAGS0_TYPE_WRITE;
|
|
fcp_cmnd_iu->wddata = 1;
|
|
break;
|
|
case DMA_BIDIRECTIONAL:
|
|
default:
|
|
/*
|
|
* dummy, catch this condition earlier
|
|
* in zfcp_scsi_queuecommand
|
|
*/
|
|
goto failed_scsi_cmnd;
|
|
}
|
|
|
|
/* set FC service class in QTCB (3 per default) */
|
|
fsf_req->qtcb->bottom.io.service_class = ZFCP_FC_SERVICE_CLASS_DEFAULT;
|
|
|
|
/* set FCP_LUN in FCP_CMND IU in QTCB */
|
|
fcp_cmnd_iu->fcp_lun = unit->fcp_lun;
|
|
|
|
mask = ZFCP_STATUS_UNIT_READONLY | ZFCP_STATUS_UNIT_SHARED;
|
|
|
|
/* set task attributes in FCP_CMND IU in QTCB */
|
|
if (likely((scsi_cmnd->device->simple_tags) ||
|
|
(atomic_test_mask(mask, &unit->status))))
|
|
fcp_cmnd_iu->task_attribute = SIMPLE_Q;
|
|
else
|
|
fcp_cmnd_iu->task_attribute = UNTAGGED;
|
|
|
|
/* set additional length of FCP_CDB in FCP_CMND IU in QTCB, if needed */
|
|
if (unlikely(scsi_cmnd->cmd_len > FCP_CDB_LENGTH)) {
|
|
fcp_cmnd_iu->add_fcp_cdb_length
|
|
= (scsi_cmnd->cmd_len - FCP_CDB_LENGTH) >> 2;
|
|
ZFCP_LOG_TRACE("SCSI CDB length is 0x%x, "
|
|
"additional FCP_CDB length is 0x%x "
|
|
"(shifted right 2 bits)\n",
|
|
scsi_cmnd->cmd_len,
|
|
fcp_cmnd_iu->add_fcp_cdb_length);
|
|
}
|
|
/*
|
|
* copy SCSI CDB (including additional length, if any) to
|
|
* FCP_CDB in FCP_CMND IU in QTCB
|
|
*/
|
|
memcpy(fcp_cmnd_iu->fcp_cdb, scsi_cmnd->cmnd, scsi_cmnd->cmd_len);
|
|
|
|
/* FCP CMND IU length in QTCB */
|
|
fsf_req->qtcb->bottom.io.fcp_cmnd_length =
|
|
sizeof (struct fcp_cmnd_iu) +
|
|
fcp_cmnd_iu->add_fcp_cdb_length + sizeof (fcp_dl_t);
|
|
|
|
/* generate SBALEs from data buffer */
|
|
real_bytes = zfcp_qdio_sbals_from_scsicmnd(fsf_req, sbtype, scsi_cmnd);
|
|
if (unlikely(real_bytes < 0)) {
|
|
if (fsf_req->sbal_number < ZFCP_MAX_SBALS_PER_REQ) {
|
|
ZFCP_LOG_DEBUG(
|
|
"Data did not fit into available buffer(s), "
|
|
"waiting for more...\n");
|
|
retval = -EIO;
|
|
} else {
|
|
ZFCP_LOG_NORMAL("error: No truncation implemented but "
|
|
"required. Shutting down unit "
|
|
"(adapter %s, port 0x%016Lx, "
|
|
"unit 0x%016Lx)\n",
|
|
zfcp_get_busid_by_unit(unit),
|
|
unit->port->wwpn,
|
|
unit->fcp_lun);
|
|
zfcp_erp_unit_shutdown(unit, 0, 131, fsf_req);
|
|
retval = -EINVAL;
|
|
}
|
|
goto no_fit;
|
|
}
|
|
|
|
/* set length of FCP data length in FCP_CMND IU in QTCB */
|
|
zfcp_set_fcp_dl(fcp_cmnd_iu, real_bytes);
|
|
|
|
ZFCP_LOG_DEBUG("Sending SCSI command:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) scsi_cmnd->cmnd, scsi_cmnd->cmd_len);
|
|
|
|
if (use_timer)
|
|
zfcp_fsf_start_timer(fsf_req, ZFCP_FSF_REQUEST_TIMEOUT);
|
|
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
if (unlikely(retval < 0)) {
|
|
ZFCP_LOG_INFO("error: Could not send FCP command request "
|
|
"on adapter %s, port 0x%016Lx, unit 0x%016Lx\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
unit->port->wwpn,
|
|
unit->fcp_lun);
|
|
goto send_failed;
|
|
}
|
|
|
|
ZFCP_LOG_TRACE("Send FCP Command initiated (adapter %s, "
|
|
"port 0x%016Lx, unit 0x%016Lx)\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
unit->port->wwpn,
|
|
unit->fcp_lun);
|
|
goto success;
|
|
|
|
send_failed:
|
|
no_fit:
|
|
failed_scsi_cmnd:
|
|
zfcp_unit_put(unit);
|
|
unit_blocked:
|
|
zfcp_fsf_req_free(fsf_req);
|
|
fsf_req = NULL;
|
|
scsi_cmnd->host_scribble = NULL;
|
|
success:
|
|
failed_req_create:
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
|
|
return retval;
|
|
}
|
|
|
|
struct zfcp_fsf_req *
|
|
zfcp_fsf_send_fcp_command_task_management(struct zfcp_adapter *adapter,
|
|
struct zfcp_unit *unit,
|
|
u8 tm_flags, int req_flags)
|
|
{
|
|
struct zfcp_fsf_req *fsf_req = NULL;
|
|
int retval = 0;
|
|
struct fcp_cmnd_iu *fcp_cmnd_iu;
|
|
unsigned long lock_flags;
|
|
volatile struct qdio_buffer_element *sbale;
|
|
|
|
/* setup new FSF request */
|
|
retval = zfcp_fsf_req_create(adapter, FSF_QTCB_FCP_CMND, req_flags,
|
|
adapter->pool.fsf_req_scsi,
|
|
&lock_flags, &fsf_req);
|
|
if (retval < 0) {
|
|
ZFCP_LOG_INFO("error: Could not create FCP command (task "
|
|
"management) request for adapter %s, port "
|
|
" 0x%016Lx, unit 0x%016Lx.\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
unit->port->wwpn, unit->fcp_lun);
|
|
goto out;
|
|
}
|
|
|
|
if (unlikely(!atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED,
|
|
&unit->status)))
|
|
goto unit_blocked;
|
|
|
|
/*
|
|
* Used to decide on proper handler in the return path,
|
|
* could be either zfcp_fsf_send_fcp_command_task_handler or
|
|
* zfcp_fsf_send_fcp_command_task_management_handler */
|
|
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT;
|
|
|
|
/*
|
|
* hold a pointer to the unit being target of this
|
|
* task management request
|
|
*/
|
|
fsf_req->data = (unsigned long) unit;
|
|
|
|
/* set FSF related fields in QTCB */
|
|
fsf_req->qtcb->header.lun_handle = unit->handle;
|
|
fsf_req->qtcb->header.port_handle = unit->port->handle;
|
|
fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_CMND;
|
|
fsf_req->qtcb->bottom.io.service_class = ZFCP_FC_SERVICE_CLASS_DEFAULT;
|
|
fsf_req->qtcb->bottom.io.fcp_cmnd_length =
|
|
sizeof (struct fcp_cmnd_iu) + sizeof (fcp_dl_t);
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= SBAL_FLAGS0_TYPE_WRITE;
|
|
sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY;
|
|
|
|
/* set FCP related fields in FCP_CMND IU in QTCB */
|
|
fcp_cmnd_iu = (struct fcp_cmnd_iu *)
|
|
&(fsf_req->qtcb->bottom.io.fcp_cmnd);
|
|
fcp_cmnd_iu->fcp_lun = unit->fcp_lun;
|
|
fcp_cmnd_iu->task_management_flags = tm_flags;
|
|
|
|
zfcp_fsf_start_timer(fsf_req, ZFCP_SCSI_ER_TIMEOUT);
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
if (!retval)
|
|
goto out;
|
|
|
|
unit_blocked:
|
|
zfcp_fsf_req_free(fsf_req);
|
|
fsf_req = NULL;
|
|
|
|
out:
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
|
|
return fsf_req;
|
|
}
|
|
|
|
static void zfcp_fsf_update_lat(struct fsf_latency_record *lat_rec, u32 lat)
|
|
{
|
|
lat_rec->sum += lat;
|
|
if (lat_rec->min > lat)
|
|
lat_rec->min = lat;
|
|
if (lat_rec->max < lat)
|
|
lat_rec->max = lat;
|
|
}
|
|
|
|
static void zfcp_fsf_req_latency(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
struct fsf_qual_latency_info *lat_inf;
|
|
struct latency_cont *lat;
|
|
struct zfcp_unit *unit;
|
|
unsigned long flags;
|
|
|
|
lat_inf = &fsf_req->qtcb->prefix.prot_status_qual.latency_info;
|
|
unit = fsf_req->unit;
|
|
|
|
switch (fsf_req->qtcb->bottom.io.data_direction) {
|
|
case FSF_DATADIR_READ:
|
|
lat = &unit->latencies.read;
|
|
break;
|
|
case FSF_DATADIR_WRITE:
|
|
lat = &unit->latencies.write;
|
|
break;
|
|
case FSF_DATADIR_CMND:
|
|
lat = &unit->latencies.cmd;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&unit->latencies.lock, flags);
|
|
zfcp_fsf_update_lat(&lat->channel, lat_inf->channel_lat);
|
|
zfcp_fsf_update_lat(&lat->fabric, lat_inf->fabric_lat);
|
|
lat->counter++;
|
|
spin_unlock_irqrestore(&unit->latencies.lock, flags);
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_send_fcp_command_handler
|
|
*
|
|
* purpose: is called for finished Send FCP Command
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_send_fcp_command_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = -EINVAL;
|
|
struct zfcp_unit *unit;
|
|
struct fsf_qtcb_header *header;
|
|
u16 subtable, rule, counter;
|
|
|
|
header = &fsf_req->qtcb->header;
|
|
|
|
if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT))
|
|
unit = (struct zfcp_unit *) fsf_req->data;
|
|
else
|
|
unit = fsf_req->unit;
|
|
|
|
if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) {
|
|
/* go directly to calls of special handlers */
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
/* evaluate FSF status in QTCB */
|
|
switch (header->fsf_status) {
|
|
|
|
case FSF_PORT_HANDLE_NOT_VALID:
|
|
ZFCP_LOG_INFO("Temporary port identifier 0x%x for port "
|
|
"0x%016Lx on adapter %s invalid\n",
|
|
unit->port->handle,
|
|
unit->port->wwpn, zfcp_get_busid_by_unit(unit));
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &header->fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_adapter_reopen(unit->port->adapter, 0, 112, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_LUN_HANDLE_NOT_VALID:
|
|
ZFCP_LOG_INFO("Temporary LUN identifier 0x%x for unit "
|
|
"0x%016Lx on port 0x%016Lx on adapter %s is "
|
|
"invalid. This may happen occasionally.\n",
|
|
unit->handle,
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
ZFCP_LOG_NORMAL("Status qualifier data:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL,
|
|
(char *) &header->fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_port_reopen(unit->port, 0, 113, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_HANDLE_MISMATCH:
|
|
ZFCP_LOG_NORMAL("bug: The port handle 0x%x has changed "
|
|
"unexpectedly. (adapter %s, port 0x%016Lx, "
|
|
"unit 0x%016Lx)\n",
|
|
unit->port->handle,
|
|
zfcp_get_busid_by_unit(unit),
|
|
unit->port->wwpn,
|
|
unit->fcp_lun);
|
|
ZFCP_LOG_NORMAL("status qualifier:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL,
|
|
(char *) &header->fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_adapter_reopen(unit->port->adapter, 0, 114, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_SERVICE_CLASS_NOT_SUPPORTED:
|
|
ZFCP_LOG_INFO("error: adapter %s does not support fc "
|
|
"class %d.\n",
|
|
zfcp_get_busid_by_unit(unit),
|
|
ZFCP_FC_SERVICE_CLASS_DEFAULT);
|
|
/* stop operation for this adapter */
|
|
zfcp_erp_adapter_shutdown(unit->port->adapter, 0, 132, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_FCPLUN_NOT_VALID:
|
|
ZFCP_LOG_NORMAL("bug: unit 0x%016Lx on port 0x%016Lx on "
|
|
"adapter %s does not have correct unit "
|
|
"handle 0x%x\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
unit->handle);
|
|
ZFCP_LOG_DEBUG("status qualifier:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &header->fsf_status_qual,
|
|
sizeof (union fsf_status_qual));
|
|
zfcp_erp_port_reopen(unit->port, 0, 115, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_ACCESS_DENIED:
|
|
ZFCP_LOG_NORMAL("Access denied, cannot send FCP command to "
|
|
"unit 0x%016Lx on port 0x%016Lx on "
|
|
"adapter %s\n", unit->fcp_lun, unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
for (counter = 0; counter < 2; counter++) {
|
|
subtable = header->fsf_status_qual.halfword[counter * 2];
|
|
rule = header->fsf_status_qual.halfword[counter * 2 + 1];
|
|
switch (subtable) {
|
|
case FSF_SQ_CFDC_SUBTABLE_OS:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN:
|
|
case FSF_SQ_CFDC_SUBTABLE_PORT_DID:
|
|
case FSF_SQ_CFDC_SUBTABLE_LUN:
|
|
ZFCP_LOG_INFO("Access denied (%s rule %d)\n",
|
|
zfcp_act_subtable_type[subtable], rule);
|
|
break;
|
|
}
|
|
}
|
|
zfcp_erp_unit_access_denied(unit, 61, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_DIRECTION_INDICATOR_NOT_VALID:
|
|
ZFCP_LOG_INFO("bug: Invalid data direction given for unit "
|
|
"0x%016Lx on port 0x%016Lx on adapter %s "
|
|
"(debug info %d)\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
fsf_req->qtcb->bottom.io.data_direction);
|
|
/* stop operation for this adapter */
|
|
zfcp_erp_adapter_shutdown(unit->port->adapter, 0, 133, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_CMND_LENGTH_NOT_VALID:
|
|
ZFCP_LOG_NORMAL
|
|
("bug: An invalid control-data-block length field "
|
|
"was found in a command for unit 0x%016Lx on port "
|
|
"0x%016Lx on adapter %s " "(debug info %d)\n",
|
|
unit->fcp_lun, unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
fsf_req->qtcb->bottom.io.fcp_cmnd_length);
|
|
/* stop operation for this adapter */
|
|
zfcp_erp_adapter_shutdown(unit->port->adapter, 0, 134, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_PORT_BOXED:
|
|
ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter %s "
|
|
"needs to be reopened\n",
|
|
unit->port->wwpn, zfcp_get_busid_by_unit(unit));
|
|
zfcp_erp_port_boxed(unit->port, 53, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR |
|
|
ZFCP_STATUS_FSFREQ_RETRY;
|
|
break;
|
|
|
|
case FSF_LUN_BOXED:
|
|
ZFCP_LOG_NORMAL("unit needs to be reopened (adapter %s, "
|
|
"wwpn=0x%016Lx, fcp_lun=0x%016Lx)\n",
|
|
zfcp_get_busid_by_unit(unit),
|
|
unit->port->wwpn, unit->fcp_lun);
|
|
zfcp_erp_unit_boxed(unit, 54, fsf_req);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR
|
|
| ZFCP_STATUS_FSFREQ_RETRY;
|
|
break;
|
|
|
|
case FSF_ADAPTER_STATUS_AVAILABLE:
|
|
switch (header->fsf_status_qual.word[0]) {
|
|
case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE:
|
|
/* re-establish link to port */
|
|
zfcp_test_link(unit->port);
|
|
break;
|
|
case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED:
|
|
/* FIXME(hw) need proper specs for proper action */
|
|
/* let scsi stack deal with retries and escalation */
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL
|
|
("Unknown status qualifier 0x%x arrived.\n",
|
|
header->fsf_status_qual.word[0]);
|
|
break;
|
|
}
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
break;
|
|
|
|
case FSF_GOOD:
|
|
break;
|
|
|
|
case FSF_FCP_RSP_AVAILABLE:
|
|
break;
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT) {
|
|
retval =
|
|
zfcp_fsf_send_fcp_command_task_management_handler(fsf_req);
|
|
} else {
|
|
retval = zfcp_fsf_send_fcp_command_task_handler(fsf_req);
|
|
fsf_req->unit = NULL;
|
|
zfcp_unit_put(unit);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_send_fcp_command_task_handler
|
|
*
|
|
* purpose: evaluates FCP_RSP IU
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_send_fcp_command_task_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = 0;
|
|
struct scsi_cmnd *scpnt;
|
|
struct fcp_rsp_iu *fcp_rsp_iu = (struct fcp_rsp_iu *)
|
|
&(fsf_req->qtcb->bottom.io.fcp_rsp);
|
|
struct fcp_cmnd_iu *fcp_cmnd_iu = (struct fcp_cmnd_iu *)
|
|
&(fsf_req->qtcb->bottom.io.fcp_cmnd);
|
|
u32 sns_len;
|
|
char *fcp_rsp_info = zfcp_get_fcp_rsp_info_ptr(fcp_rsp_iu);
|
|
unsigned long flags;
|
|
struct zfcp_unit *unit = fsf_req->unit;
|
|
|
|
read_lock_irqsave(&fsf_req->adapter->abort_lock, flags);
|
|
scpnt = (struct scsi_cmnd *) fsf_req->data;
|
|
if (unlikely(!scpnt)) {
|
|
ZFCP_LOG_DEBUG
|
|
("Command with fsf_req %p is not associated to "
|
|
"a scsi command anymore. Aborted?\n", fsf_req);
|
|
goto out;
|
|
}
|
|
if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ABORTED)) {
|
|
/* FIXME: (design) mid-layer should handle DID_ABORT like
|
|
* DID_SOFT_ERROR by retrying the request for devices
|
|
* that allow retries.
|
|
*/
|
|
ZFCP_LOG_DEBUG("Setting DID_SOFT_ERROR and SUGGEST_RETRY\n");
|
|
set_host_byte(&scpnt->result, DID_SOFT_ERROR);
|
|
set_driver_byte(&scpnt->result, SUGGEST_RETRY);
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) {
|
|
ZFCP_LOG_DEBUG("Setting DID_ERROR\n");
|
|
set_host_byte(&scpnt->result, DID_ERROR);
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
/* set message byte of result in SCSI command */
|
|
scpnt->result |= COMMAND_COMPLETE << 8;
|
|
|
|
/*
|
|
* copy SCSI status code of FCP_STATUS of FCP_RSP IU to status byte
|
|
* of result in SCSI command
|
|
*/
|
|
scpnt->result |= fcp_rsp_iu->scsi_status;
|
|
if (unlikely(fcp_rsp_iu->scsi_status)) {
|
|
/* DEBUG */
|
|
ZFCP_LOG_DEBUG("status for SCSI Command:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
scpnt->cmnd, scpnt->cmd_len);
|
|
ZFCP_LOG_DEBUG("SCSI status code 0x%x\n",
|
|
fcp_rsp_iu->scsi_status);
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(void *) fcp_rsp_iu, sizeof (struct fcp_rsp_iu));
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
zfcp_get_fcp_sns_info_ptr(fcp_rsp_iu),
|
|
fcp_rsp_iu->fcp_sns_len);
|
|
}
|
|
|
|
if (fsf_req->adapter->adapter_features & FSF_FEATURE_MEASUREMENT_DATA)
|
|
zfcp_fsf_req_latency(fsf_req);
|
|
|
|
/* check FCP_RSP_INFO */
|
|
if (unlikely(fcp_rsp_iu->validity.bits.fcp_rsp_len_valid)) {
|
|
ZFCP_LOG_DEBUG("rsp_len is valid\n");
|
|
switch (fcp_rsp_info[3]) {
|
|
case RSP_CODE_GOOD:
|
|
/* ok, continue */
|
|
ZFCP_LOG_TRACE("no failure or Task Management "
|
|
"Function complete\n");
|
|
set_host_byte(&scpnt->result, DID_OK);
|
|
break;
|
|
case RSP_CODE_LENGTH_MISMATCH:
|
|
/* hardware bug */
|
|
ZFCP_LOG_NORMAL("bug: FCP response code indictates "
|
|
"that the fibrechannel protocol data "
|
|
"length differs from the burst length. "
|
|
"The problem occured on unit 0x%016Lx "
|
|
"on port 0x%016Lx on adapter %s",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
/* dump SCSI CDB as prepared by zfcp */
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &fsf_req->qtcb->
|
|
bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE);
|
|
set_host_byte(&scpnt->result, DID_ERROR);
|
|
goto skip_fsfstatus;
|
|
case RSP_CODE_FIELD_INVALID:
|
|
/* driver or hardware bug */
|
|
ZFCP_LOG_NORMAL("bug: FCP response code indictates "
|
|
"that the fibrechannel protocol data "
|
|
"fields were incorrectly set up. "
|
|
"The problem occured on the unit "
|
|
"0x%016Lx on port 0x%016Lx on "
|
|
"adapter %s",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
/* dump SCSI CDB as prepared by zfcp */
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &fsf_req->qtcb->
|
|
bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE);
|
|
set_host_byte(&scpnt->result, DID_ERROR);
|
|
goto skip_fsfstatus;
|
|
case RSP_CODE_RO_MISMATCH:
|
|
/* hardware bug */
|
|
ZFCP_LOG_NORMAL("bug: The FCP response code indicates "
|
|
"that conflicting values for the "
|
|
"fibrechannel payload offset from the "
|
|
"header were found. "
|
|
"The problem occured on unit 0x%016Lx "
|
|
"on port 0x%016Lx on adapter %s.\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
/* dump SCSI CDB as prepared by zfcp */
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &fsf_req->qtcb->
|
|
bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE);
|
|
set_host_byte(&scpnt->result, DID_ERROR);
|
|
goto skip_fsfstatus;
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: An invalid FCP response "
|
|
"code was detected for a command. "
|
|
"The problem occured on the unit "
|
|
"0x%016Lx on port 0x%016Lx on "
|
|
"adapter %s (debug info 0x%x)\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
fcp_rsp_info[3]);
|
|
/* dump SCSI CDB as prepared by zfcp */
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG,
|
|
(char *) &fsf_req->qtcb->
|
|
bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE);
|
|
set_host_byte(&scpnt->result, DID_ERROR);
|
|
goto skip_fsfstatus;
|
|
}
|
|
}
|
|
|
|
/* check for sense data */
|
|
if (unlikely(fcp_rsp_iu->validity.bits.fcp_sns_len_valid)) {
|
|
sns_len = FSF_FCP_RSP_SIZE -
|
|
sizeof (struct fcp_rsp_iu) + fcp_rsp_iu->fcp_rsp_len;
|
|
ZFCP_LOG_TRACE("room for %i bytes sense data in QTCB\n",
|
|
sns_len);
|
|
sns_len = min(sns_len, (u32) SCSI_SENSE_BUFFERSIZE);
|
|
ZFCP_LOG_TRACE("room for %i bytes sense data in SCSI command\n",
|
|
SCSI_SENSE_BUFFERSIZE);
|
|
sns_len = min(sns_len, fcp_rsp_iu->fcp_sns_len);
|
|
ZFCP_LOG_TRACE("scpnt->result =0x%x, command was:\n",
|
|
scpnt->result);
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE,
|
|
scpnt->cmnd, scpnt->cmd_len);
|
|
|
|
ZFCP_LOG_TRACE("%i bytes sense data provided by FCP\n",
|
|
fcp_rsp_iu->fcp_sns_len);
|
|
memcpy(scpnt->sense_buffer,
|
|
zfcp_get_fcp_sns_info_ptr(fcp_rsp_iu), sns_len);
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE,
|
|
(void *)scpnt->sense_buffer, sns_len);
|
|
}
|
|
|
|
/* check for overrun */
|
|
if (unlikely(fcp_rsp_iu->validity.bits.fcp_resid_over)) {
|
|
ZFCP_LOG_INFO("A data overrun was detected for a command. "
|
|
"unit 0x%016Lx, port 0x%016Lx, adapter %s. "
|
|
"The response data length is "
|
|
"%d, the original length was %d.\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
fcp_rsp_iu->fcp_resid,
|
|
(int) zfcp_get_fcp_dl(fcp_cmnd_iu));
|
|
}
|
|
|
|
/* check for underrun */
|
|
if (unlikely(fcp_rsp_iu->validity.bits.fcp_resid_under)) {
|
|
ZFCP_LOG_INFO("A data underrun was detected for a command. "
|
|
"unit 0x%016Lx, port 0x%016Lx, adapter %s. "
|
|
"The response data length is "
|
|
"%d, the original length was %d.\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
fcp_rsp_iu->fcp_resid,
|
|
(int) zfcp_get_fcp_dl(fcp_cmnd_iu));
|
|
|
|
scsi_set_resid(scpnt, fcp_rsp_iu->fcp_resid);
|
|
if (scsi_bufflen(scpnt) - scsi_get_resid(scpnt) <
|
|
scpnt->underflow)
|
|
set_host_byte(&scpnt->result, DID_ERROR);
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
ZFCP_LOG_DEBUG("scpnt->result =0x%x\n", scpnt->result);
|
|
|
|
if (scpnt->result != 0)
|
|
zfcp_scsi_dbf_event_result("erro", 3, fsf_req->adapter, scpnt, fsf_req);
|
|
else if (scpnt->retries > 0)
|
|
zfcp_scsi_dbf_event_result("retr", 4, fsf_req->adapter, scpnt, fsf_req);
|
|
else
|
|
zfcp_scsi_dbf_event_result("norm", 6, fsf_req->adapter, scpnt, fsf_req);
|
|
|
|
/* cleanup pointer (need this especially for abort) */
|
|
scpnt->host_scribble = NULL;
|
|
|
|
/* always call back */
|
|
(scpnt->scsi_done) (scpnt);
|
|
|
|
/*
|
|
* We must hold this lock until scsi_done has been called.
|
|
* Otherwise we may call scsi_done after abort regarding this
|
|
* command has completed.
|
|
* Note: scsi_done must not block!
|
|
*/
|
|
out:
|
|
read_unlock_irqrestore(&fsf_req->adapter->abort_lock, flags);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_send_fcp_command_task_management_handler
|
|
*
|
|
* purpose: evaluates FCP_RSP IU
|
|
*
|
|
* returns:
|
|
*/
|
|
static int
|
|
zfcp_fsf_send_fcp_command_task_management_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
int retval = 0;
|
|
struct fcp_rsp_iu *fcp_rsp_iu = (struct fcp_rsp_iu *)
|
|
&(fsf_req->qtcb->bottom.io.fcp_rsp);
|
|
char *fcp_rsp_info = zfcp_get_fcp_rsp_info_ptr(fcp_rsp_iu);
|
|
struct zfcp_unit *unit = (struct zfcp_unit *) fsf_req->data;
|
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) {
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCFAILED;
|
|
goto skip_fsfstatus;
|
|
}
|
|
|
|
/* check FCP_RSP_INFO */
|
|
switch (fcp_rsp_info[3]) {
|
|
case RSP_CODE_GOOD:
|
|
/* ok, continue */
|
|
ZFCP_LOG_DEBUG("no failure or Task Management "
|
|
"Function complete\n");
|
|
break;
|
|
case RSP_CODE_TASKMAN_UNSUPP:
|
|
ZFCP_LOG_NORMAL("bug: A reuested task management function "
|
|
"is not supported on the target device "
|
|
"unit 0x%016Lx, port 0x%016Lx, adapter %s\n ",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCNOTSUPP;
|
|
break;
|
|
case RSP_CODE_TASKMAN_FAILED:
|
|
ZFCP_LOG_NORMAL("bug: A reuested task management function "
|
|
"failed to complete successfully. "
|
|
"unit 0x%016Lx, port 0x%016Lx, adapter %s.\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit));
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCFAILED;
|
|
break;
|
|
default:
|
|
ZFCP_LOG_NORMAL("bug: An invalid FCP response "
|
|
"code was detected for a command. "
|
|
"unit 0x%016Lx, port 0x%016Lx, adapter %s "
|
|
"(debug info 0x%x)\n",
|
|
unit->fcp_lun,
|
|
unit->port->wwpn,
|
|
zfcp_get_busid_by_unit(unit),
|
|
fcp_rsp_info[3]);
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCFAILED;
|
|
}
|
|
|
|
skip_fsfstatus:
|
|
return retval;
|
|
}
|
|
|
|
|
|
/*
|
|
* function: zfcp_fsf_control_file
|
|
*
|
|
* purpose: Initiator of the control file upload/download FSF requests
|
|
*
|
|
* returns: 0 - FSF request is successfuly created and queued
|
|
* -EOPNOTSUPP - The FCP adapter does not have Control File support
|
|
* -EINVAL - Invalid direction specified
|
|
* -ENOMEM - Insufficient memory
|
|
* -EPERM - Cannot create FSF request or place it in QDIO queue
|
|
*/
|
|
struct zfcp_fsf_req *zfcp_fsf_control_file(struct zfcp_adapter *adapter,
|
|
struct zfcp_fsf_cfdc *fsf_cfdc)
|
|
{
|
|
struct zfcp_fsf_req *fsf_req;
|
|
struct fsf_qtcb_bottom_support *bottom;
|
|
volatile struct qdio_buffer_element *sbale;
|
|
unsigned long lock_flags;
|
|
int direction;
|
|
int retval;
|
|
int bytes;
|
|
|
|
if (!(adapter->adapter_features & FSF_FEATURE_CFDC))
|
|
return ERR_PTR(-EOPNOTSUPP);
|
|
|
|
switch (fsf_cfdc->command) {
|
|
case FSF_QTCB_DOWNLOAD_CONTROL_FILE:
|
|
direction = SBAL_FLAGS0_TYPE_WRITE;
|
|
break;
|
|
case FSF_QTCB_UPLOAD_CONTROL_FILE:
|
|
direction = SBAL_FLAGS0_TYPE_READ;
|
|
break;
|
|
default:
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
retval = zfcp_fsf_req_create(adapter, fsf_cfdc->command,
|
|
ZFCP_WAIT_FOR_SBAL,
|
|
NULL, &lock_flags, &fsf_req);
|
|
if (retval < 0) {
|
|
retval = -EPERM;
|
|
goto unlock_queue_lock;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
sbale[0].flags |= direction;
|
|
|
|
bottom = &fsf_req->qtcb->bottom.support;
|
|
bottom->operation_subtype = FSF_CFDC_OPERATION_SUBTYPE;
|
|
bottom->option = fsf_cfdc->option;
|
|
|
|
bytes = zfcp_qdio_sbals_from_sg(fsf_req, direction,
|
|
fsf_cfdc->sg, ZFCP_CFDC_PAGES,
|
|
ZFCP_MAX_SBALS_PER_REQ);
|
|
if (bytes != ZFCP_CFDC_MAX_SIZE) {
|
|
retval = -ENOMEM;
|
|
goto free_fsf_req;
|
|
}
|
|
|
|
zfcp_fsf_start_timer(fsf_req, ZFCP_FSF_REQUEST_TIMEOUT);
|
|
retval = zfcp_fsf_req_send(fsf_req);
|
|
if (retval < 0) {
|
|
retval = -EPERM;
|
|
goto free_fsf_req;
|
|
}
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
|
|
|
|
wait_event(fsf_req->completion_wq,
|
|
fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED);
|
|
|
|
return fsf_req;
|
|
|
|
free_fsf_req:
|
|
zfcp_fsf_req_free(fsf_req);
|
|
unlock_queue_lock:
|
|
write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags);
|
|
return ERR_PTR(retval);
|
|
}
|
|
|
|
static void zfcp_fsf_control_file_handler(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
if (fsf_req->qtcb->header.fsf_status != FSF_GOOD)
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR;
|
|
}
|
|
|
|
static inline int
|
|
zfcp_fsf_req_sbal_check(unsigned long *flags,
|
|
struct zfcp_qdio_queue *queue, int needed)
|
|
{
|
|
write_lock_irqsave(&queue->queue_lock, *flags);
|
|
if (likely(atomic_read(&queue->free_count) >= needed))
|
|
return 1;
|
|
write_unlock_irqrestore(&queue->queue_lock, *flags);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* set qtcb pointer in fsf_req and initialize QTCB
|
|
*/
|
|
static void
|
|
zfcp_fsf_req_qtcb_init(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
if (likely(fsf_req->qtcb != NULL)) {
|
|
fsf_req->qtcb->prefix.req_seq_no =
|
|
fsf_req->adapter->fsf_req_seq_no;
|
|
fsf_req->qtcb->prefix.req_id = fsf_req->req_id;
|
|
fsf_req->qtcb->prefix.ulp_info = ZFCP_ULP_INFO_VERSION;
|
|
fsf_req->qtcb->prefix.qtcb_type =
|
|
fsf_qtcb_type[fsf_req->fsf_command];
|
|
fsf_req->qtcb->prefix.qtcb_version = ZFCP_QTCB_VERSION;
|
|
fsf_req->qtcb->header.req_handle = fsf_req->req_id;
|
|
fsf_req->qtcb->header.fsf_command = fsf_req->fsf_command;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* zfcp_fsf_req_sbal_get - try to get one SBAL in the request queue
|
|
* @adapter: adapter for which request queue is examined
|
|
* @req_flags: flags indicating whether to wait for needed SBAL or not
|
|
* @lock_flags: lock_flags if queue_lock is taken
|
|
* Return: 0 on success, otherwise -EIO, or -ERESTARTSYS
|
|
* Locks: lock adapter->request_queue->queue_lock on success
|
|
*/
|
|
static int
|
|
zfcp_fsf_req_sbal_get(struct zfcp_adapter *adapter, int req_flags,
|
|
unsigned long *lock_flags)
|
|
{
|
|
long ret;
|
|
struct zfcp_qdio_queue *req_queue = &adapter->request_queue;
|
|
|
|
if (unlikely(req_flags & ZFCP_WAIT_FOR_SBAL)) {
|
|
ret = wait_event_interruptible_timeout(adapter->request_wq,
|
|
zfcp_fsf_req_sbal_check(lock_flags, req_queue, 1),
|
|
ZFCP_SBAL_TIMEOUT);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (!ret)
|
|
return -EIO;
|
|
} else if (!zfcp_fsf_req_sbal_check(lock_flags, req_queue, 1))
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_req_create
|
|
*
|
|
* purpose: create an FSF request at the specified adapter and
|
|
* setup common fields
|
|
*
|
|
* returns: -ENOMEM if there was insufficient memory for a request
|
|
* -EIO if no qdio buffers could be allocate to the request
|
|
* -EINVAL/-EPERM on bug conditions in req_dequeue
|
|
* 0 in success
|
|
*
|
|
* note: The created request is returned by reference.
|
|
*
|
|
* locks: lock of concerned request queue must not be held,
|
|
* but is held on completion (write, irqsave)
|
|
*/
|
|
int
|
|
zfcp_fsf_req_create(struct zfcp_adapter *adapter, u32 fsf_cmd, int req_flags,
|
|
mempool_t *pool, unsigned long *lock_flags,
|
|
struct zfcp_fsf_req **fsf_req_p)
|
|
{
|
|
volatile struct qdio_buffer_element *sbale;
|
|
struct zfcp_fsf_req *fsf_req = NULL;
|
|
int ret = 0;
|
|
struct zfcp_qdio_queue *req_queue = &adapter->request_queue;
|
|
|
|
/* allocate new FSF request */
|
|
fsf_req = zfcp_fsf_req_alloc(pool, req_flags);
|
|
if (unlikely(NULL == fsf_req)) {
|
|
ZFCP_LOG_DEBUG("error: Could not put an FSF request into "
|
|
"the outbound (send) queue.\n");
|
|
ret = -ENOMEM;
|
|
goto failed_fsf_req;
|
|
}
|
|
|
|
fsf_req->adapter = adapter;
|
|
fsf_req->fsf_command = fsf_cmd;
|
|
INIT_LIST_HEAD(&fsf_req->list);
|
|
init_timer(&fsf_req->timer);
|
|
|
|
/* initialize waitqueue which may be used to wait on
|
|
this request completion */
|
|
init_waitqueue_head(&fsf_req->completion_wq);
|
|
|
|
ret = zfcp_fsf_req_sbal_get(adapter, req_flags, lock_flags);
|
|
if (ret < 0)
|
|
goto failed_sbals;
|
|
|
|
/* this is serialized (we are holding req_queue-lock of adapter) */
|
|
if (adapter->req_no == 0)
|
|
adapter->req_no++;
|
|
fsf_req->req_id = adapter->req_no++;
|
|
|
|
zfcp_fsf_req_qtcb_init(fsf_req);
|
|
|
|
/*
|
|
* We hold queue_lock here. Check if QDIOUP is set and let request fail
|
|
* if it is not set (see also *_open_qdio and *_close_qdio).
|
|
*/
|
|
|
|
if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status)) {
|
|
write_unlock_irqrestore(&req_queue->queue_lock, *lock_flags);
|
|
ret = -EIO;
|
|
goto failed_sbals;
|
|
}
|
|
|
|
if (fsf_req->qtcb) {
|
|
fsf_req->seq_no = adapter->fsf_req_seq_no;
|
|
fsf_req->qtcb->prefix.req_seq_no = adapter->fsf_req_seq_no;
|
|
}
|
|
fsf_req->sbal_number = 1;
|
|
fsf_req->sbal_first = req_queue->free_index;
|
|
fsf_req->sbal_last = req_queue->free_index;
|
|
fsf_req->sbale_curr = 1;
|
|
|
|
if (likely(req_flags & ZFCP_REQ_AUTO_CLEANUP)) {
|
|
fsf_req->status |= ZFCP_STATUS_FSFREQ_CLEANUP;
|
|
}
|
|
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_last, 0);
|
|
|
|
/* setup common SBALE fields */
|
|
sbale[0].addr = (void *) fsf_req->req_id;
|
|
sbale[0].flags |= SBAL_FLAGS0_COMMAND;
|
|
if (likely(fsf_req->qtcb != NULL)) {
|
|
sbale[1].addr = (void *) fsf_req->qtcb;
|
|
sbale[1].length = sizeof(struct fsf_qtcb);
|
|
}
|
|
|
|
ZFCP_LOG_TRACE("got %i free BUFFERs starting at index %i\n",
|
|
fsf_req->sbal_number, fsf_req->sbal_first);
|
|
|
|
goto success;
|
|
|
|
failed_sbals:
|
|
/* dequeue new FSF request previously enqueued */
|
|
zfcp_fsf_req_free(fsf_req);
|
|
fsf_req = NULL;
|
|
|
|
failed_fsf_req:
|
|
write_lock_irqsave(&req_queue->queue_lock, *lock_flags);
|
|
success:
|
|
*fsf_req_p = fsf_req;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* function: zfcp_fsf_req_send
|
|
*
|
|
* purpose: start transfer of FSF request via QDIO
|
|
*
|
|
* returns: 0 - request transfer succesfully started
|
|
* !0 - start of request transfer failed
|
|
*/
|
|
static int zfcp_fsf_req_send(struct zfcp_fsf_req *fsf_req)
|
|
{
|
|
struct zfcp_adapter *adapter;
|
|
struct zfcp_qdio_queue *req_queue;
|
|
volatile struct qdio_buffer_element *sbale;
|
|
int inc_seq_no;
|
|
int new_distance_from_int;
|
|
int retval = 0;
|
|
|
|
adapter = fsf_req->adapter;
|
|
req_queue = &adapter->request_queue,
|
|
|
|
|
|
/* FIXME(debug): remove it later */
|
|
sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_first, 0);
|
|
ZFCP_LOG_DEBUG("SBALE0 flags=0x%x\n", sbale[0].flags);
|
|
ZFCP_LOG_TRACE("HEX DUMP OF SBALE1 PAYLOAD:\n");
|
|
ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE, (char *) sbale[1].addr,
|
|
sbale[1].length);
|
|
|
|
/* put allocated FSF request into hash table */
|
|
spin_lock(&adapter->req_list_lock);
|
|
zfcp_reqlist_add(adapter, fsf_req);
|
|
spin_unlock(&adapter->req_list_lock);
|
|
|
|
inc_seq_no = (fsf_req->qtcb != NULL);
|
|
|
|
ZFCP_LOG_TRACE("request queue of adapter %s: "
|
|
"next free SBAL is %i, %i free SBALs\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
req_queue->free_index,
|
|
atomic_read(&req_queue->free_count));
|
|
|
|
ZFCP_LOG_DEBUG("calling do_QDIO adapter %s, flags=0x%x, queue_no=%i, "
|
|
"index_in_queue=%i, count=%i, buffers=%p\n",
|
|
zfcp_get_busid_by_adapter(adapter),
|
|
QDIO_FLAG_SYNC_OUTPUT,
|
|
0, fsf_req->sbal_first, fsf_req->sbal_number,
|
|
&req_queue->buffer[fsf_req->sbal_first]);
|
|
|
|
/*
|
|
* adjust the number of free SBALs in request queue as well as
|
|
* position of first one
|
|
*/
|
|
atomic_sub(fsf_req->sbal_number, &req_queue->free_count);
|
|
ZFCP_LOG_TRACE("free_count=%d\n", atomic_read(&req_queue->free_count));
|
|
req_queue->free_index += fsf_req->sbal_number; /* increase */
|
|
req_queue->free_index %= QDIO_MAX_BUFFERS_PER_Q; /* wrap if needed */
|
|
new_distance_from_int = zfcp_qdio_determine_pci(req_queue, fsf_req);
|
|
|
|
fsf_req->issued = get_clock();
|
|
|
|
retval = do_QDIO(adapter->ccw_device,
|
|
QDIO_FLAG_SYNC_OUTPUT,
|
|
0, fsf_req->sbal_first, fsf_req->sbal_number, NULL);
|
|
|
|
if (unlikely(retval)) {
|
|
/* Queues are down..... */
|
|
retval = -EIO;
|
|
del_timer(&fsf_req->timer);
|
|
spin_lock(&adapter->req_list_lock);
|
|
zfcp_reqlist_remove(adapter, fsf_req);
|
|
spin_unlock(&adapter->req_list_lock);
|
|
/* undo changes in request queue made for this request */
|
|
zfcp_qdio_zero_sbals(req_queue->buffer,
|
|
fsf_req->sbal_first, fsf_req->sbal_number);
|
|
atomic_add(fsf_req->sbal_number, &req_queue->free_count);
|
|
req_queue->free_index -= fsf_req->sbal_number;
|
|
req_queue->free_index += QDIO_MAX_BUFFERS_PER_Q;
|
|
req_queue->free_index %= QDIO_MAX_BUFFERS_PER_Q; /* wrap */
|
|
zfcp_erp_adapter_reopen(adapter, 0, 116, fsf_req);
|
|
} else {
|
|
req_queue->distance_from_int = new_distance_from_int;
|
|
/*
|
|
* increase FSF sequence counter -
|
|
* this must only be done for request successfully enqueued to
|
|
* QDIO this rejected requests may be cleaned up by calling
|
|
* routines resulting in missing sequence counter values
|
|
* otherwise,
|
|
*/
|
|
|
|
/* Don't increase for unsolicited status */
|
|
if (inc_seq_no)
|
|
adapter->fsf_req_seq_no++;
|
|
|
|
/* count FSF requests pending */
|
|
atomic_inc(&adapter->reqs_active);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
#undef ZFCP_LOG_AREA
|