mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2025-01-10 07:44:23 +08:00
d0dff2ac98
At the moment we allocate and register the Scsi_Host object corresponding to a zfcp adapter (FCP device) very early in the life cycle of the adapter - even before we fully discover and initialize the underlying firmware/hardware. This had the advantage that we could already use the Scsi_Host object, and fill in all its information during said discover and initialize. Due to commit737eb78e82
("block: Delay default elevator initialization") (first released in v5.4), we noticed a regression that would prevent us from using any storage volume if zfcp is configured with support for DIF or DIX (zfcp.dif=1 || zfcp.dix=1). Doing so would result in an illegal memory access as soon as the first request is sent with such an configuration. As example for a crash resulting from this: scsi host0: scsi_eh_0: sleeping scsi host0: zfcp qdio: 0.0.1900 ZFCP on SC 4bd using AI:1 QEBSM:0 PRI:1 TDD:1 SIGA: W AP scsi 0:0:0:0: scsi scan: INQUIRY pass 1 length 36 Unable to handle kernel pointer dereference in virtual kernel address space Failing address: 0000000000000000 TEID: 0000000000000483 Fault in home space mode while using kernel ASCE. AS:0000000035c7c007 R3:00000001effcc007 S:00000001effd1000 P:000000000000003d Oops: 0004 ilc:3 [#1] PREEMPT SMP DEBUG_PAGEALLOC Modules linked in: ... CPU: 1 PID: 783 Comm: kworker/u760:5 Kdump: loaded Not tainted 5.6.0-rc2-bb-next+ #1 Hardware name: ... Workqueue: scsi_wq_0 fc_scsi_scan_rport [scsi_transport_fc] Krnl PSW : 0704e00180000000 000003ff801fcdae (scsi_queue_rq+0x436/0x740 [scsi_mod]) R:0 T:1 IO:1 EX:1 Key:0 M:1 W:0 P:0 AS:3 CC:2 PM:0 RI:0 EA:3 Krnl GPRS: 0fffffffffffffff 0000000000000000 0000000187150120 0000000000000000 000003ff80223d20 000000000000018e 000000018adc6400 0000000187711000 000003e0062337e8 00000001ae719000 0000000187711000 0000000187150000 00000001ab808100 0000000187150120 000003ff801fcd74 000003e0062336a0 Krnl Code: 000003ff801fcd9e: e310a35c0012 lt %r1,860(%r10) 000003ff801fcda4: a7840010 brc 8,000003ff801fcdc4 #000003ff801fcda8: e310b2900004 lg %r1,656(%r11) >000003ff801fcdae: d71710001000 xc 0(24,%r1),0(%r1) 000003ff801fcdb4: e310b2900004 lg %r1,656(%r11) 000003ff801fcdba: 41201018 la %r2,24(%r1) 000003ff801fcdbe: e32010000024 stg %r2,0(%r1) 000003ff801fcdc4: b904002b lgr %r2,%r11 Call Trace: [<000003ff801fcdae>] scsi_queue_rq+0x436/0x740 [scsi_mod] ([<000003ff801fcd74>] scsi_queue_rq+0x3fc/0x740 [scsi_mod]) [<00000000349c9970>] blk_mq_dispatch_rq_list+0x390/0x680 [<00000000349d1596>] blk_mq_sched_dispatch_requests+0x196/0x1a8 [<00000000349c7a04>] __blk_mq_run_hw_queue+0x144/0x160 [<00000000349c7ab6>] __blk_mq_delay_run_hw_queue+0x96/0x228 [<00000000349c7d5a>] blk_mq_run_hw_queue+0xd2/0xe0 [<00000000349d194a>] blk_mq_sched_insert_request+0x192/0x1d8 [<00000000349c17b8>] blk_execute_rq_nowait+0x80/0x90 [<00000000349c1856>] blk_execute_rq+0x6e/0xb0 [<000003ff801f8ac2>] __scsi_execute+0xe2/0x1f0 [scsi_mod] [<000003ff801fef98>] scsi_probe_and_add_lun+0x358/0x840 [scsi_mod] [<000003ff8020001c>] __scsi_scan_target+0xc4/0x228 [scsi_mod] [<000003ff80200254>] scsi_scan_target+0xd4/0x100 [scsi_mod] [<000003ff802d8b96>] fc_scsi_scan_rport+0x96/0xc0 [scsi_transport_fc] [<0000000034245ce8>] process_one_work+0x458/0x7d0 [<00000000342462a2>] worker_thread+0x242/0x448 [<0000000034250994>] kthread+0x15c/0x170 [<0000000034e1979c>] ret_from_fork+0x30/0x38 INFO: lockdep is turned off. Last Breaking-Event-Address: [<000003ff801fbc36>] scsi_add_cmd_to_list+0x9e/0xa8 [scsi_mod] Kernel panic - not syncing: Fatal exception: panic_on_oops While this issue is exposed by the commit named above, this is only by accident. The real issue exists for longer already - basically since it's possible to use blk-mq via scsi-mq, and blk-mq pre-allocates all requests for a tag-set during initialization of the same. For a given Scsi_Host object this is done when adding the object to the midlayer (`scsi_add_host()` and such). In `scsi_mq_setup_tags()` the midlayer calculates how much memory is required for a single scsi_cmnd, and its additional data, which also might include space for additional protection data - depending on whether the Scsi_Host has any form of protection capabilities (`scsi_host_get_prot()`). The problem is now thus, because zfcp does this step before we actually know whether the firmware/hardware has these capabilities, we don't set any protection capabilities in the Scsi_Host object. And so, no space is allocated for additional protection data for requests in the Scsi_Host tag-set. Once we go through discover and initialize the FCP device firmware/hardware fully (this is done via the firmware commands "Exchange Config Data" and "Exchange Port Data") we find out whether it actually supports DIF and DIX, and we set the corresponding capabilities in the Scsi_Host object (in `zfcp_scsi_set_prot()`). Now the Scsi_Host potentially has protection capabilities, but the already allocated requests in the tag-set don't have any space allocated for that. When we then trigger target scanning or add scsi_devices manually, the midlayer will use requests from that tag-set, and before sending most requests, it will also call `scsi_mq_prep_fn()`. To prepare the scsi_cmnd this function will check again whether the used Scsi_Host has any protection capabilities - and now it potentially has - and if so, it will try to initialize the assumed to be preallocated structures and thus it causes the crash, like shown above. Before delaying the default elevator initialization with the commit named above, we always would also allocate an elevator for any scsi_device before ever sending any requests - in contrast to now, where we do it after device-probing. That elevator in turn would have its own tag-set, and that is initialized after we went through discovery and initialization of the underlying firmware/hardware. So requests from that tag-set can be allocated properly, and if used - unless the user changes/disabled the default elevator - this would hide the underlying issue. To fix this for any configuration - with or without an elevator - we move the allocation and registration of the Scsi_Host object for a given FCP device to after the first complete discovery and initialization of the underlying firmware/hardware. By doing that we can make all basic properties of the Scsi_Host known to the midlayer by the time we call `scsi_add_host()`, including whether we have any protection capabilities. To do that we have to delay all the accesses that we would have done in the past during discovery and initialization, and do them instead once we are finished with it. The previous patches ramp up to this by fencing and factoring out all these accesses, and make it possible to re-do them later on. In addition we make also use of the diagnostic buffers we recently added with commit92953c6e0a
("scsi: zfcp: signal incomplete or error for sync exchange config/port data") commit7e418833e6
("scsi: zfcp: diagnostics buffer caching and use for exchange port data") commit088210233e
("scsi: zfcp: add diagnostics buffer for exchange config data") (first released in v5.5), because these already cache all the information we need for that "re-do operation" - the information cached are always updated during xconf or xport data, so it won't be stale. In addition to the move and re-do, this patch also updates the function-documentation of `zfcp_scsi_adapter_register()` and changes how it reports if a Scsi_Host object already exists. In that case future recovery-operations can skip this step completely and behave much like they would do in the past - zfcp does not release a once allocated Scsi_Host object unless the corresponding FCP device is deconstructed completely. Link: https://lore.kernel.org/r/030dd6da318bbb529f0b5268ec65cebcd20fc0a3.1588956679.git.bblock@linux.ibm.com Reviewed-by: Steffen Maier <maier@linux.ibm.com> Signed-off-by: Benjamin Block <bblock@linux.ibm.com> Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
1860 lines
52 KiB
C
1860 lines
52 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* zfcp device driver
|
|
*
|
|
* Error Recovery Procedures (ERP).
|
|
*
|
|
* Copyright IBM Corp. 2002, 2020
|
|
*/
|
|
|
|
#define KMSG_COMPONENT "zfcp"
|
|
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
|
|
|
|
#include <linux/kthread.h>
|
|
#include <linux/bug.h>
|
|
#include "zfcp_ext.h"
|
|
#include "zfcp_reqlist.h"
|
|
#include "zfcp_diag.h"
|
|
|
|
#define ZFCP_MAX_ERPS 3
|
|
|
|
enum zfcp_erp_act_flags {
|
|
ZFCP_STATUS_ERP_TIMEDOUT = 0x10000000,
|
|
ZFCP_STATUS_ERP_CLOSE_ONLY = 0x01000000,
|
|
ZFCP_STATUS_ERP_DISMISSED = 0x00200000,
|
|
ZFCP_STATUS_ERP_LOWMEM = 0x00400000,
|
|
ZFCP_STATUS_ERP_NO_REF = 0x00800000,
|
|
};
|
|
|
|
/*
|
|
* Eyecatcher pseudo flag to bitwise or-combine with enum zfcp_erp_act_type.
|
|
* Used to indicate that an ERP action could not be set up despite a detected
|
|
* need for some recovery.
|
|
*/
|
|
#define ZFCP_ERP_ACTION_NONE 0xc0
|
|
/*
|
|
* Eyecatcher pseudo flag to bitwise or-combine with enum zfcp_erp_act_type.
|
|
* Used to indicate that ERP not needed because the object has
|
|
* ZFCP_STATUS_COMMON_ERP_FAILED.
|
|
*/
|
|
#define ZFCP_ERP_ACTION_FAILED 0xe0
|
|
|
|
enum zfcp_erp_act_result {
|
|
ZFCP_ERP_SUCCEEDED = 0,
|
|
ZFCP_ERP_FAILED = 1,
|
|
ZFCP_ERP_CONTINUES = 2,
|
|
ZFCP_ERP_EXIT = 3,
|
|
ZFCP_ERP_DISMISSED = 4,
|
|
ZFCP_ERP_NOMEM = 5,
|
|
};
|
|
|
|
static void zfcp_erp_adapter_block(struct zfcp_adapter *adapter, int mask)
|
|
{
|
|
zfcp_erp_clear_adapter_status(adapter,
|
|
ZFCP_STATUS_COMMON_UNBLOCKED | mask);
|
|
}
|
|
|
|
static bool zfcp_erp_action_is_running(struct zfcp_erp_action *act)
|
|
{
|
|
struct zfcp_erp_action *curr_act;
|
|
|
|
list_for_each_entry(curr_act, &act->adapter->erp_running_head, list)
|
|
if (act == curr_act)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static void zfcp_erp_action_ready(struct zfcp_erp_action *act)
|
|
{
|
|
struct zfcp_adapter *adapter = act->adapter;
|
|
|
|
list_move(&act->list, &act->adapter->erp_ready_head);
|
|
zfcp_dbf_rec_run("erardy1", act);
|
|
wake_up(&adapter->erp_ready_wq);
|
|
zfcp_dbf_rec_run("erardy2", act);
|
|
}
|
|
|
|
static void zfcp_erp_action_dismiss(struct zfcp_erp_action *act)
|
|
{
|
|
act->status |= ZFCP_STATUS_ERP_DISMISSED;
|
|
if (zfcp_erp_action_is_running(act))
|
|
zfcp_erp_action_ready(act);
|
|
}
|
|
|
|
static void zfcp_erp_action_dismiss_lun(struct scsi_device *sdev)
|
|
{
|
|
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
|
|
|
|
if (atomic_read(&zfcp_sdev->status) & ZFCP_STATUS_COMMON_ERP_INUSE)
|
|
zfcp_erp_action_dismiss(&zfcp_sdev->erp_action);
|
|
}
|
|
|
|
static void zfcp_erp_action_dismiss_port(struct zfcp_port *port)
|
|
{
|
|
struct scsi_device *sdev;
|
|
|
|
if (atomic_read(&port->status) & ZFCP_STATUS_COMMON_ERP_INUSE)
|
|
zfcp_erp_action_dismiss(&port->erp_action);
|
|
else {
|
|
spin_lock(port->adapter->scsi_host->host_lock);
|
|
__shost_for_each_device(sdev, port->adapter->scsi_host)
|
|
if (sdev_to_zfcp(sdev)->port == port)
|
|
zfcp_erp_action_dismiss_lun(sdev);
|
|
spin_unlock(port->adapter->scsi_host->host_lock);
|
|
}
|
|
}
|
|
|
|
static void zfcp_erp_action_dismiss_adapter(struct zfcp_adapter *adapter)
|
|
{
|
|
struct zfcp_port *port;
|
|
|
|
if (atomic_read(&adapter->status) & ZFCP_STATUS_COMMON_ERP_INUSE)
|
|
zfcp_erp_action_dismiss(&adapter->erp_action);
|
|
else {
|
|
read_lock(&adapter->port_list_lock);
|
|
list_for_each_entry(port, &adapter->port_list, list)
|
|
zfcp_erp_action_dismiss_port(port);
|
|
read_unlock(&adapter->port_list_lock);
|
|
}
|
|
}
|
|
|
|
static enum zfcp_erp_act_type zfcp_erp_handle_failed(
|
|
enum zfcp_erp_act_type want, struct zfcp_adapter *adapter,
|
|
struct zfcp_port *port, struct scsi_device *sdev)
|
|
{
|
|
enum zfcp_erp_act_type need = want;
|
|
struct zfcp_scsi_dev *zsdev;
|
|
|
|
switch (want) {
|
|
case ZFCP_ERP_ACTION_REOPEN_LUN:
|
|
zsdev = sdev_to_zfcp(sdev);
|
|
if (atomic_read(&zsdev->status) & ZFCP_STATUS_COMMON_ERP_FAILED)
|
|
need = 0;
|
|
break;
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
|
|
if (atomic_read(&port->status) & ZFCP_STATUS_COMMON_ERP_FAILED)
|
|
need = 0;
|
|
break;
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT:
|
|
if (atomic_read(&port->status) &
|
|
ZFCP_STATUS_COMMON_ERP_FAILED) {
|
|
need = 0;
|
|
/* ensure propagation of failed status to new devices */
|
|
zfcp_erp_set_port_status(
|
|
port, ZFCP_STATUS_COMMON_ERP_FAILED);
|
|
}
|
|
break;
|
|
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
|
|
if (atomic_read(&adapter->status) &
|
|
ZFCP_STATUS_COMMON_ERP_FAILED) {
|
|
need = 0;
|
|
/* ensure propagation of failed status to new devices */
|
|
zfcp_erp_set_adapter_status(
|
|
adapter, ZFCP_STATUS_COMMON_ERP_FAILED);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return need;
|
|
}
|
|
|
|
static enum zfcp_erp_act_type zfcp_erp_required_act(enum zfcp_erp_act_type want,
|
|
struct zfcp_adapter *adapter,
|
|
struct zfcp_port *port,
|
|
struct scsi_device *sdev)
|
|
{
|
|
enum zfcp_erp_act_type need = want;
|
|
int l_status, p_status, a_status;
|
|
struct zfcp_scsi_dev *zfcp_sdev;
|
|
|
|
switch (want) {
|
|
case ZFCP_ERP_ACTION_REOPEN_LUN:
|
|
zfcp_sdev = sdev_to_zfcp(sdev);
|
|
l_status = atomic_read(&zfcp_sdev->status);
|
|
if (l_status & ZFCP_STATUS_COMMON_ERP_INUSE)
|
|
return 0;
|
|
p_status = atomic_read(&port->status);
|
|
if (!(p_status & ZFCP_STATUS_COMMON_RUNNING) ||
|
|
p_status & ZFCP_STATUS_COMMON_ERP_FAILED)
|
|
return 0;
|
|
if (!(p_status & ZFCP_STATUS_COMMON_UNBLOCKED))
|
|
need = ZFCP_ERP_ACTION_REOPEN_PORT;
|
|
fallthrough;
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
|
|
p_status = atomic_read(&port->status);
|
|
if (!(p_status & ZFCP_STATUS_COMMON_OPEN))
|
|
need = ZFCP_ERP_ACTION_REOPEN_PORT;
|
|
fallthrough;
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT:
|
|
p_status = atomic_read(&port->status);
|
|
if (p_status & ZFCP_STATUS_COMMON_ERP_INUSE)
|
|
return 0;
|
|
a_status = atomic_read(&adapter->status);
|
|
if (!(a_status & ZFCP_STATUS_COMMON_RUNNING) ||
|
|
a_status & ZFCP_STATUS_COMMON_ERP_FAILED)
|
|
return 0;
|
|
if (p_status & ZFCP_STATUS_COMMON_NOESC)
|
|
return need;
|
|
if (!(a_status & ZFCP_STATUS_COMMON_UNBLOCKED))
|
|
need = ZFCP_ERP_ACTION_REOPEN_ADAPTER;
|
|
fallthrough;
|
|
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
|
|
a_status = atomic_read(&adapter->status);
|
|
if (a_status & ZFCP_STATUS_COMMON_ERP_INUSE)
|
|
return 0;
|
|
if (!(a_status & ZFCP_STATUS_COMMON_RUNNING) &&
|
|
!(a_status & ZFCP_STATUS_COMMON_OPEN))
|
|
return 0; /* shutdown requested for closed adapter */
|
|
}
|
|
|
|
return need;
|
|
}
|
|
|
|
static struct zfcp_erp_action *zfcp_erp_setup_act(enum zfcp_erp_act_type need,
|
|
u32 act_status,
|
|
struct zfcp_adapter *adapter,
|
|
struct zfcp_port *port,
|
|
struct scsi_device *sdev)
|
|
{
|
|
struct zfcp_erp_action *erp_action;
|
|
struct zfcp_scsi_dev *zfcp_sdev;
|
|
|
|
if (WARN_ON_ONCE(need != ZFCP_ERP_ACTION_REOPEN_LUN &&
|
|
need != ZFCP_ERP_ACTION_REOPEN_PORT &&
|
|
need != ZFCP_ERP_ACTION_REOPEN_PORT_FORCED &&
|
|
need != ZFCP_ERP_ACTION_REOPEN_ADAPTER))
|
|
return NULL;
|
|
|
|
switch (need) {
|
|
case ZFCP_ERP_ACTION_REOPEN_LUN:
|
|
zfcp_sdev = sdev_to_zfcp(sdev);
|
|
if (!(act_status & ZFCP_STATUS_ERP_NO_REF))
|
|
if (scsi_device_get(sdev))
|
|
return NULL;
|
|
atomic_or(ZFCP_STATUS_COMMON_ERP_INUSE,
|
|
&zfcp_sdev->status);
|
|
erp_action = &zfcp_sdev->erp_action;
|
|
WARN_ON_ONCE(erp_action->port != port);
|
|
WARN_ON_ONCE(erp_action->sdev != sdev);
|
|
if (!(atomic_read(&zfcp_sdev->status) &
|
|
ZFCP_STATUS_COMMON_RUNNING))
|
|
act_status |= ZFCP_STATUS_ERP_CLOSE_ONLY;
|
|
break;
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT:
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
|
|
if (!get_device(&port->dev))
|
|
return NULL;
|
|
zfcp_erp_action_dismiss_port(port);
|
|
atomic_or(ZFCP_STATUS_COMMON_ERP_INUSE, &port->status);
|
|
erp_action = &port->erp_action;
|
|
WARN_ON_ONCE(erp_action->port != port);
|
|
WARN_ON_ONCE(erp_action->sdev != NULL);
|
|
if (!(atomic_read(&port->status) & ZFCP_STATUS_COMMON_RUNNING))
|
|
act_status |= ZFCP_STATUS_ERP_CLOSE_ONLY;
|
|
break;
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
|
|
kref_get(&adapter->ref);
|
|
zfcp_erp_action_dismiss_adapter(adapter);
|
|
atomic_or(ZFCP_STATUS_COMMON_ERP_INUSE, &adapter->status);
|
|
erp_action = &adapter->erp_action;
|
|
WARN_ON_ONCE(erp_action->port != NULL);
|
|
WARN_ON_ONCE(erp_action->sdev != NULL);
|
|
if (!(atomic_read(&adapter->status) &
|
|
ZFCP_STATUS_COMMON_RUNNING))
|
|
act_status |= ZFCP_STATUS_ERP_CLOSE_ONLY;
|
|
break;
|
|
}
|
|
|
|
WARN_ON_ONCE(erp_action->adapter != adapter);
|
|
memset(&erp_action->list, 0, sizeof(erp_action->list));
|
|
memset(&erp_action->timer, 0, sizeof(erp_action->timer));
|
|
erp_action->step = ZFCP_ERP_STEP_UNINITIALIZED;
|
|
erp_action->fsf_req_id = 0;
|
|
erp_action->type = need;
|
|
erp_action->status = act_status;
|
|
|
|
return erp_action;
|
|
}
|
|
|
|
static void zfcp_erp_action_enqueue(enum zfcp_erp_act_type want,
|
|
struct zfcp_adapter *adapter,
|
|
struct zfcp_port *port,
|
|
struct scsi_device *sdev,
|
|
char *dbftag, u32 act_status)
|
|
{
|
|
enum zfcp_erp_act_type need;
|
|
struct zfcp_erp_action *act;
|
|
|
|
need = zfcp_erp_handle_failed(want, adapter, port, sdev);
|
|
if (!need) {
|
|
need = ZFCP_ERP_ACTION_FAILED; /* marker for trace */
|
|
goto out;
|
|
}
|
|
|
|
if (!adapter->erp_thread) {
|
|
need = ZFCP_ERP_ACTION_NONE; /* marker for trace */
|
|
goto out;
|
|
}
|
|
|
|
need = zfcp_erp_required_act(want, adapter, port, sdev);
|
|
if (!need)
|
|
goto out;
|
|
|
|
act = zfcp_erp_setup_act(need, act_status, adapter, port, sdev);
|
|
if (!act) {
|
|
need |= ZFCP_ERP_ACTION_NONE; /* marker for trace */
|
|
goto out;
|
|
}
|
|
atomic_or(ZFCP_STATUS_ADAPTER_ERP_PENDING, &adapter->status);
|
|
++adapter->erp_total_count;
|
|
list_add_tail(&act->list, &adapter->erp_ready_head);
|
|
wake_up(&adapter->erp_ready_wq);
|
|
out:
|
|
zfcp_dbf_rec_trig(dbftag, adapter, port, sdev, want, need);
|
|
}
|
|
|
|
void zfcp_erp_port_forced_no_port_dbf(char *dbftag,
|
|
struct zfcp_adapter *adapter,
|
|
u64 port_name, u32 port_id)
|
|
{
|
|
unsigned long flags;
|
|
static /* don't waste stack */ struct zfcp_port tmpport;
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
/* Stand-in zfcp port with fields just good enough for
|
|
* zfcp_dbf_rec_trig() and zfcp_dbf_set_common().
|
|
* Under lock because tmpport is static.
|
|
*/
|
|
atomic_set(&tmpport.status, -1); /* unknown */
|
|
tmpport.wwpn = port_name;
|
|
tmpport.d_id = port_id;
|
|
zfcp_dbf_rec_trig(dbftag, adapter, &tmpport, NULL,
|
|
ZFCP_ERP_ACTION_REOPEN_PORT_FORCED,
|
|
ZFCP_ERP_ACTION_NONE);
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
}
|
|
|
|
static void _zfcp_erp_adapter_reopen(struct zfcp_adapter *adapter,
|
|
int clear_mask, char *dbftag)
|
|
{
|
|
zfcp_erp_adapter_block(adapter, clear_mask);
|
|
zfcp_scsi_schedule_rports_block(adapter);
|
|
|
|
zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_ADAPTER,
|
|
adapter, NULL, NULL, dbftag, 0);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_adapter_reopen - Reopen adapter.
|
|
* @adapter: Adapter to reopen.
|
|
* @clear: Status flags to clear.
|
|
* @dbftag: Tag for debug trace event.
|
|
*/
|
|
void zfcp_erp_adapter_reopen(struct zfcp_adapter *adapter, int clear,
|
|
char *dbftag)
|
|
{
|
|
unsigned long flags;
|
|
|
|
zfcp_erp_adapter_block(adapter, clear);
|
|
zfcp_scsi_schedule_rports_block(adapter);
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_ADAPTER, adapter,
|
|
NULL, NULL, dbftag, 0);
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_adapter_shutdown - Shutdown adapter.
|
|
* @adapter: Adapter to shut down.
|
|
* @clear: Status flags to clear.
|
|
* @dbftag: Tag for debug trace event.
|
|
*/
|
|
void zfcp_erp_adapter_shutdown(struct zfcp_adapter *adapter, int clear,
|
|
char *dbftag)
|
|
{
|
|
int flags = ZFCP_STATUS_COMMON_RUNNING | ZFCP_STATUS_COMMON_ERP_FAILED;
|
|
zfcp_erp_adapter_reopen(adapter, clear | flags, dbftag);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_port_shutdown - Shutdown port
|
|
* @port: Port to shut down.
|
|
* @clear: Status flags to clear.
|
|
* @dbftag: Tag for debug trace event.
|
|
*/
|
|
void zfcp_erp_port_shutdown(struct zfcp_port *port, int clear, char *dbftag)
|
|
{
|
|
int flags = ZFCP_STATUS_COMMON_RUNNING | ZFCP_STATUS_COMMON_ERP_FAILED;
|
|
zfcp_erp_port_reopen(port, clear | flags, dbftag);
|
|
}
|
|
|
|
static void zfcp_erp_port_block(struct zfcp_port *port, int clear)
|
|
{
|
|
zfcp_erp_clear_port_status(port,
|
|
ZFCP_STATUS_COMMON_UNBLOCKED | clear);
|
|
}
|
|
|
|
static void _zfcp_erp_port_forced_reopen(struct zfcp_port *port, int clear,
|
|
char *dbftag)
|
|
{
|
|
zfcp_erp_port_block(port, clear);
|
|
zfcp_scsi_schedule_rport_block(port);
|
|
|
|
zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_PORT_FORCED,
|
|
port->adapter, port, NULL, dbftag, 0);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_port_forced_reopen - Forced close of port and open again
|
|
* @port: Port to force close and to reopen.
|
|
* @clear: Status flags to clear.
|
|
* @dbftag: Tag for debug trace event.
|
|
*/
|
|
void zfcp_erp_port_forced_reopen(struct zfcp_port *port, int clear,
|
|
char *dbftag)
|
|
{
|
|
unsigned long flags;
|
|
struct zfcp_adapter *adapter = port->adapter;
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
_zfcp_erp_port_forced_reopen(port, clear, dbftag);
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
}
|
|
|
|
static void _zfcp_erp_port_reopen(struct zfcp_port *port, int clear,
|
|
char *dbftag)
|
|
{
|
|
zfcp_erp_port_block(port, clear);
|
|
zfcp_scsi_schedule_rport_block(port);
|
|
|
|
zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_PORT,
|
|
port->adapter, port, NULL, dbftag, 0);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_port_reopen - trigger remote port recovery
|
|
* @port: port to recover
|
|
* @clear: flags in port status to be cleared
|
|
* @dbftag: Tag for debug trace event.
|
|
*/
|
|
void zfcp_erp_port_reopen(struct zfcp_port *port, int clear, char *dbftag)
|
|
{
|
|
unsigned long flags;
|
|
struct zfcp_adapter *adapter = port->adapter;
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
_zfcp_erp_port_reopen(port, clear, dbftag);
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
}
|
|
|
|
static void zfcp_erp_lun_block(struct scsi_device *sdev, int clear_mask)
|
|
{
|
|
zfcp_erp_clear_lun_status(sdev,
|
|
ZFCP_STATUS_COMMON_UNBLOCKED | clear_mask);
|
|
}
|
|
|
|
static void _zfcp_erp_lun_reopen(struct scsi_device *sdev, int clear,
|
|
char *dbftag, u32 act_status)
|
|
{
|
|
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
|
|
struct zfcp_adapter *adapter = zfcp_sdev->port->adapter;
|
|
|
|
zfcp_erp_lun_block(sdev, clear);
|
|
|
|
zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_LUN, adapter,
|
|
zfcp_sdev->port, sdev, dbftag, act_status);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_lun_reopen - initiate reopen of a LUN
|
|
* @sdev: SCSI device / LUN to be reopened
|
|
* @clear: specifies flags in LUN status to be cleared
|
|
* @dbftag: Tag for debug trace event.
|
|
*
|
|
* Return: 0 on success, < 0 on error
|
|
*/
|
|
void zfcp_erp_lun_reopen(struct scsi_device *sdev, int clear, char *dbftag)
|
|
{
|
|
unsigned long flags;
|
|
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
|
|
struct zfcp_port *port = zfcp_sdev->port;
|
|
struct zfcp_adapter *adapter = port->adapter;
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
_zfcp_erp_lun_reopen(sdev, clear, dbftag, 0);
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_lun_shutdown - Shutdown LUN
|
|
* @sdev: SCSI device / LUN to shut down.
|
|
* @clear: Status flags to clear.
|
|
* @dbftag: Tag for debug trace event.
|
|
*/
|
|
void zfcp_erp_lun_shutdown(struct scsi_device *sdev, int clear, char *dbftag)
|
|
{
|
|
int flags = ZFCP_STATUS_COMMON_RUNNING | ZFCP_STATUS_COMMON_ERP_FAILED;
|
|
zfcp_erp_lun_reopen(sdev, clear | flags, dbftag);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_lun_shutdown_wait - Shutdown LUN and wait for erp completion
|
|
* @sdev: SCSI device / LUN to shut down.
|
|
* @dbftag: Tag for debug trace event.
|
|
*
|
|
* Do not acquire a reference for the LUN when creating the ERP
|
|
* action. It is safe, because this function waits for the ERP to
|
|
* complete first. This allows to shutdown the LUN, even when the SCSI
|
|
* device is in the state SDEV_DEL when scsi_device_get will fail.
|
|
*/
|
|
void zfcp_erp_lun_shutdown_wait(struct scsi_device *sdev, char *dbftag)
|
|
{
|
|
unsigned long flags;
|
|
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
|
|
struct zfcp_port *port = zfcp_sdev->port;
|
|
struct zfcp_adapter *adapter = port->adapter;
|
|
int clear = ZFCP_STATUS_COMMON_RUNNING | ZFCP_STATUS_COMMON_ERP_FAILED;
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
_zfcp_erp_lun_reopen(sdev, clear, dbftag, ZFCP_STATUS_ERP_NO_REF);
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
|
|
zfcp_erp_wait(adapter);
|
|
}
|
|
|
|
static int zfcp_erp_status_change_set(unsigned long mask, atomic_t *status)
|
|
{
|
|
return (atomic_read(status) ^ mask) & mask;
|
|
}
|
|
|
|
static void zfcp_erp_adapter_unblock(struct zfcp_adapter *adapter)
|
|
{
|
|
if (zfcp_erp_status_change_set(ZFCP_STATUS_COMMON_UNBLOCKED,
|
|
&adapter->status))
|
|
zfcp_dbf_rec_run("eraubl1", &adapter->erp_action);
|
|
atomic_or(ZFCP_STATUS_COMMON_UNBLOCKED, &adapter->status);
|
|
}
|
|
|
|
static void zfcp_erp_port_unblock(struct zfcp_port *port)
|
|
{
|
|
if (zfcp_erp_status_change_set(ZFCP_STATUS_COMMON_UNBLOCKED,
|
|
&port->status))
|
|
zfcp_dbf_rec_run("erpubl1", &port->erp_action);
|
|
atomic_or(ZFCP_STATUS_COMMON_UNBLOCKED, &port->status);
|
|
}
|
|
|
|
static void zfcp_erp_lun_unblock(struct scsi_device *sdev)
|
|
{
|
|
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
|
|
|
|
if (zfcp_erp_status_change_set(ZFCP_STATUS_COMMON_UNBLOCKED,
|
|
&zfcp_sdev->status))
|
|
zfcp_dbf_rec_run("erlubl1", &sdev_to_zfcp(sdev)->erp_action);
|
|
atomic_or(ZFCP_STATUS_COMMON_UNBLOCKED, &zfcp_sdev->status);
|
|
}
|
|
|
|
static void zfcp_erp_action_to_running(struct zfcp_erp_action *erp_action)
|
|
{
|
|
list_move(&erp_action->list, &erp_action->adapter->erp_running_head);
|
|
zfcp_dbf_rec_run("erator1", erp_action);
|
|
}
|
|
|
|
static void zfcp_erp_strategy_check_fsfreq(struct zfcp_erp_action *act)
|
|
{
|
|
struct zfcp_adapter *adapter = act->adapter;
|
|
struct zfcp_fsf_req *req;
|
|
|
|
if (!act->fsf_req_id)
|
|
return;
|
|
|
|
spin_lock(&adapter->req_list->lock);
|
|
req = _zfcp_reqlist_find(adapter->req_list, act->fsf_req_id);
|
|
if (req && req->erp_action == act) {
|
|
if (act->status & (ZFCP_STATUS_ERP_DISMISSED |
|
|
ZFCP_STATUS_ERP_TIMEDOUT)) {
|
|
req->status |= ZFCP_STATUS_FSFREQ_DISMISSED;
|
|
zfcp_dbf_rec_run("erscf_1", act);
|
|
req->erp_action = NULL;
|
|
}
|
|
if (act->status & ZFCP_STATUS_ERP_TIMEDOUT)
|
|
zfcp_dbf_rec_run("erscf_2", act);
|
|
if (req->status & ZFCP_STATUS_FSFREQ_DISMISSED)
|
|
act->fsf_req_id = 0;
|
|
} else
|
|
act->fsf_req_id = 0;
|
|
spin_unlock(&adapter->req_list->lock);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_notify - Trigger ERP action.
|
|
* @erp_action: ERP action to continue.
|
|
* @set_mask: ERP action status flags to set.
|
|
*/
|
|
void zfcp_erp_notify(struct zfcp_erp_action *erp_action, unsigned long set_mask)
|
|
{
|
|
struct zfcp_adapter *adapter = erp_action->adapter;
|
|
unsigned long flags;
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
if (zfcp_erp_action_is_running(erp_action)) {
|
|
erp_action->status |= set_mask;
|
|
zfcp_erp_action_ready(erp_action);
|
|
}
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_timeout_handler - Trigger ERP action from timed out ERP request
|
|
* @t: timer list entry embedded in zfcp FSF request
|
|
*/
|
|
void zfcp_erp_timeout_handler(struct timer_list *t)
|
|
{
|
|
struct zfcp_fsf_req *fsf_req = from_timer(fsf_req, t, timer);
|
|
struct zfcp_erp_action *act = fsf_req->erp_action;
|
|
|
|
zfcp_erp_notify(act, ZFCP_STATUS_ERP_TIMEDOUT);
|
|
}
|
|
|
|
static void zfcp_erp_memwait_handler(struct timer_list *t)
|
|
{
|
|
struct zfcp_erp_action *act = from_timer(act, t, timer);
|
|
|
|
zfcp_erp_notify(act, 0);
|
|
}
|
|
|
|
static void zfcp_erp_strategy_memwait(struct zfcp_erp_action *erp_action)
|
|
{
|
|
timer_setup(&erp_action->timer, zfcp_erp_memwait_handler, 0);
|
|
erp_action->timer.expires = jiffies + HZ;
|
|
add_timer(&erp_action->timer);
|
|
}
|
|
|
|
void zfcp_erp_port_forced_reopen_all(struct zfcp_adapter *adapter,
|
|
int clear, char *dbftag)
|
|
{
|
|
unsigned long flags;
|
|
struct zfcp_port *port;
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
read_lock(&adapter->port_list_lock);
|
|
list_for_each_entry(port, &adapter->port_list, list)
|
|
_zfcp_erp_port_forced_reopen(port, clear, dbftag);
|
|
read_unlock(&adapter->port_list_lock);
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
}
|
|
|
|
static void _zfcp_erp_port_reopen_all(struct zfcp_adapter *adapter,
|
|
int clear, char *dbftag)
|
|
{
|
|
struct zfcp_port *port;
|
|
|
|
read_lock(&adapter->port_list_lock);
|
|
list_for_each_entry(port, &adapter->port_list, list)
|
|
_zfcp_erp_port_reopen(port, clear, dbftag);
|
|
read_unlock(&adapter->port_list_lock);
|
|
}
|
|
|
|
static void _zfcp_erp_lun_reopen_all(struct zfcp_port *port, int clear,
|
|
char *dbftag)
|
|
{
|
|
struct scsi_device *sdev;
|
|
|
|
spin_lock(port->adapter->scsi_host->host_lock);
|
|
__shost_for_each_device(sdev, port->adapter->scsi_host)
|
|
if (sdev_to_zfcp(sdev)->port == port)
|
|
_zfcp_erp_lun_reopen(sdev, clear, dbftag, 0);
|
|
spin_unlock(port->adapter->scsi_host->host_lock);
|
|
}
|
|
|
|
static void zfcp_erp_strategy_followup_failed(struct zfcp_erp_action *act)
|
|
{
|
|
switch (act->type) {
|
|
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
|
|
_zfcp_erp_adapter_reopen(act->adapter, 0, "ersff_1");
|
|
break;
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
|
|
_zfcp_erp_port_forced_reopen(act->port, 0, "ersff_2");
|
|
break;
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT:
|
|
_zfcp_erp_port_reopen(act->port, 0, "ersff_3");
|
|
break;
|
|
case ZFCP_ERP_ACTION_REOPEN_LUN:
|
|
_zfcp_erp_lun_reopen(act->sdev, 0, "ersff_4", 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void zfcp_erp_strategy_followup_success(struct zfcp_erp_action *act)
|
|
{
|
|
switch (act->type) {
|
|
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
|
|
_zfcp_erp_port_reopen_all(act->adapter, 0, "ersfs_1");
|
|
break;
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
|
|
_zfcp_erp_port_reopen(act->port, 0, "ersfs_2");
|
|
break;
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT:
|
|
_zfcp_erp_lun_reopen_all(act->port, 0, "ersfs_3");
|
|
break;
|
|
case ZFCP_ERP_ACTION_REOPEN_LUN:
|
|
/* NOP */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void zfcp_erp_wakeup(struct zfcp_adapter *adapter)
|
|
{
|
|
unsigned long flags;
|
|
|
|
read_lock_irqsave(&adapter->erp_lock, flags);
|
|
if (list_empty(&adapter->erp_ready_head) &&
|
|
list_empty(&adapter->erp_running_head)) {
|
|
atomic_andnot(ZFCP_STATUS_ADAPTER_ERP_PENDING,
|
|
&adapter->status);
|
|
wake_up(&adapter->erp_done_wqh);
|
|
}
|
|
read_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
}
|
|
|
|
static void zfcp_erp_enqueue_ptp_port(struct zfcp_adapter *adapter)
|
|
{
|
|
struct zfcp_port *port;
|
|
port = zfcp_port_enqueue(adapter, adapter->peer_wwpn, 0,
|
|
adapter->peer_d_id);
|
|
if (IS_ERR(port)) /* error or port already attached */
|
|
return;
|
|
zfcp_erp_port_reopen(port, 0, "ereptp1");
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_adapter_strat_fsf_xconf(
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
int retries;
|
|
int sleep = 1;
|
|
struct zfcp_adapter *adapter = erp_action->adapter;
|
|
|
|
atomic_andnot(ZFCP_STATUS_ADAPTER_XCONFIG_OK, &adapter->status);
|
|
|
|
for (retries = 7; retries; retries--) {
|
|
atomic_andnot(ZFCP_STATUS_ADAPTER_HOST_CON_INIT,
|
|
&adapter->status);
|
|
write_lock_irq(&adapter->erp_lock);
|
|
zfcp_erp_action_to_running(erp_action);
|
|
write_unlock_irq(&adapter->erp_lock);
|
|
if (zfcp_fsf_exchange_config_data(erp_action)) {
|
|
atomic_andnot(ZFCP_STATUS_ADAPTER_HOST_CON_INIT,
|
|
&adapter->status);
|
|
return ZFCP_ERP_FAILED;
|
|
}
|
|
|
|
wait_event(adapter->erp_ready_wq,
|
|
!list_empty(&adapter->erp_ready_head));
|
|
if (erp_action->status & ZFCP_STATUS_ERP_TIMEDOUT)
|
|
break;
|
|
|
|
if (!(atomic_read(&adapter->status) &
|
|
ZFCP_STATUS_ADAPTER_HOST_CON_INIT))
|
|
break;
|
|
|
|
ssleep(sleep);
|
|
sleep *= 2;
|
|
}
|
|
|
|
atomic_andnot(ZFCP_STATUS_ADAPTER_HOST_CON_INIT,
|
|
&adapter->status);
|
|
|
|
if (!(atomic_read(&adapter->status) & ZFCP_STATUS_ADAPTER_XCONFIG_OK))
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
}
|
|
|
|
static void
|
|
zfcp_erp_adapter_strategy_open_ptp_port(struct zfcp_adapter *const adapter)
|
|
{
|
|
if (fc_host_port_type(adapter->scsi_host) == FC_PORTTYPE_PTP)
|
|
zfcp_erp_enqueue_ptp_port(adapter);
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_adapter_strategy_open_fsf_xport(
|
|
struct zfcp_erp_action *act)
|
|
{
|
|
int ret;
|
|
struct zfcp_adapter *adapter = act->adapter;
|
|
|
|
write_lock_irq(&adapter->erp_lock);
|
|
zfcp_erp_action_to_running(act);
|
|
write_unlock_irq(&adapter->erp_lock);
|
|
|
|
ret = zfcp_fsf_exchange_port_data(act);
|
|
if (ret == -EOPNOTSUPP)
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
if (ret)
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
zfcp_dbf_rec_run("erasox1", act);
|
|
wait_event(adapter->erp_ready_wq,
|
|
!list_empty(&adapter->erp_ready_head));
|
|
zfcp_dbf_rec_run("erasox2", act);
|
|
if (act->status & ZFCP_STATUS_ERP_TIMEDOUT)
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result
|
|
zfcp_erp_adapter_strategy_alloc_shost(struct zfcp_adapter *const adapter)
|
|
{
|
|
struct zfcp_diag_adapter_config_data *const config_data =
|
|
&adapter->diagnostics->config_data;
|
|
struct zfcp_diag_adapter_port_data *const port_data =
|
|
&adapter->diagnostics->port_data;
|
|
unsigned long flags;
|
|
int rc;
|
|
|
|
rc = zfcp_scsi_adapter_register(adapter);
|
|
if (rc == -EEXIST)
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
else if (rc)
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
/*
|
|
* We allocated the shost for the first time. Before it was NULL,
|
|
* and so we deferred all updates in the xconf- and xport-data
|
|
* handlers. We need to make up for that now, and make all the updates
|
|
* that would have been done before.
|
|
*
|
|
* We can be sure that xconf- and xport-data succeeded, because
|
|
* otherwise this function is not called. But they might have been
|
|
* incomplete.
|
|
*/
|
|
|
|
spin_lock_irqsave(&config_data->header.access_lock, flags);
|
|
zfcp_scsi_shost_update_config_data(adapter, &config_data->data,
|
|
!!config_data->header.incomplete);
|
|
spin_unlock_irqrestore(&config_data->header.access_lock, flags);
|
|
|
|
if (adapter->adapter_features & FSF_FEATURE_HBAAPI_MANAGEMENT) {
|
|
spin_lock_irqsave(&port_data->header.access_lock, flags);
|
|
zfcp_scsi_shost_update_port_data(adapter, &port_data->data);
|
|
spin_unlock_irqrestore(&port_data->header.access_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* There is a remote possibility that the 'Exchange Port Data' request
|
|
* reports a different connectivity status than 'Exchange Config Data'.
|
|
* But any change to the connectivity status of the local optic that
|
|
* happens after the initial xconf request is expected to be reported
|
|
* to us, as soon as we post Status Read Buffers to the FCP channel
|
|
* firmware after this function. So any resulting inconsistency will
|
|
* only be momentary.
|
|
*/
|
|
if (config_data->header.incomplete)
|
|
zfcp_fsf_fc_host_link_down(adapter);
|
|
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_adapter_strategy_open_fsf(
|
|
struct zfcp_erp_action *act)
|
|
{
|
|
if (zfcp_erp_adapter_strat_fsf_xconf(act) == ZFCP_ERP_FAILED)
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
if (zfcp_erp_adapter_strategy_open_fsf_xport(act) == ZFCP_ERP_FAILED)
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
if (zfcp_erp_adapter_strategy_alloc_shost(act->adapter) ==
|
|
ZFCP_ERP_FAILED)
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
zfcp_erp_adapter_strategy_open_ptp_port(act->adapter);
|
|
|
|
if (mempool_resize(act->adapter->pool.sr_data,
|
|
act->adapter->stat_read_buf_num))
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
if (mempool_resize(act->adapter->pool.status_read_req,
|
|
act->adapter->stat_read_buf_num))
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
atomic_set(&act->adapter->stat_miss, act->adapter->stat_read_buf_num);
|
|
if (zfcp_status_read_refill(act->adapter))
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
}
|
|
|
|
static void zfcp_erp_adapter_strategy_close(struct zfcp_erp_action *act)
|
|
{
|
|
struct zfcp_adapter *adapter = act->adapter;
|
|
|
|
/* close queues to ensure that buffers are not accessed by adapter */
|
|
zfcp_qdio_close(adapter->qdio);
|
|
zfcp_fsf_req_dismiss_all(adapter);
|
|
adapter->fsf_req_seq_no = 0;
|
|
zfcp_fc_wka_ports_force_offline(adapter->gs);
|
|
/* all ports and LUNs are closed */
|
|
zfcp_erp_clear_adapter_status(adapter, ZFCP_STATUS_COMMON_OPEN);
|
|
|
|
atomic_andnot(ZFCP_STATUS_ADAPTER_XCONFIG_OK |
|
|
ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED, &adapter->status);
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_adapter_strategy_open(
|
|
struct zfcp_erp_action *act)
|
|
{
|
|
struct zfcp_adapter *adapter = act->adapter;
|
|
|
|
if (zfcp_qdio_open(adapter->qdio)) {
|
|
atomic_andnot(ZFCP_STATUS_ADAPTER_XCONFIG_OK |
|
|
ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED,
|
|
&adapter->status);
|
|
return ZFCP_ERP_FAILED;
|
|
}
|
|
|
|
if (zfcp_erp_adapter_strategy_open_fsf(act)) {
|
|
zfcp_erp_adapter_strategy_close(act);
|
|
return ZFCP_ERP_FAILED;
|
|
}
|
|
|
|
atomic_or(ZFCP_STATUS_COMMON_OPEN, &adapter->status);
|
|
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_adapter_strategy(
|
|
struct zfcp_erp_action *act)
|
|
{
|
|
struct zfcp_adapter *adapter = act->adapter;
|
|
|
|
if (atomic_read(&adapter->status) & ZFCP_STATUS_COMMON_OPEN) {
|
|
zfcp_erp_adapter_strategy_close(act);
|
|
if (act->status & ZFCP_STATUS_ERP_CLOSE_ONLY)
|
|
return ZFCP_ERP_EXIT;
|
|
}
|
|
|
|
if (zfcp_erp_adapter_strategy_open(act)) {
|
|
ssleep(8);
|
|
return ZFCP_ERP_FAILED;
|
|
}
|
|
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_port_forced_strategy_close(
|
|
struct zfcp_erp_action *act)
|
|
{
|
|
int retval;
|
|
|
|
retval = zfcp_fsf_close_physical_port(act);
|
|
if (retval == -ENOMEM)
|
|
return ZFCP_ERP_NOMEM;
|
|
act->step = ZFCP_ERP_STEP_PHYS_PORT_CLOSING;
|
|
if (retval)
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
return ZFCP_ERP_CONTINUES;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_port_forced_strategy(
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
struct zfcp_port *port = erp_action->port;
|
|
int status = atomic_read(&port->status);
|
|
|
|
switch (erp_action->step) {
|
|
case ZFCP_ERP_STEP_UNINITIALIZED:
|
|
if ((status & ZFCP_STATUS_PORT_PHYS_OPEN) &&
|
|
(status & ZFCP_STATUS_COMMON_OPEN))
|
|
return zfcp_erp_port_forced_strategy_close(erp_action);
|
|
else
|
|
return ZFCP_ERP_FAILED;
|
|
|
|
case ZFCP_ERP_STEP_PHYS_PORT_CLOSING:
|
|
if (!(status & ZFCP_STATUS_PORT_PHYS_OPEN))
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
break;
|
|
case ZFCP_ERP_STEP_PORT_CLOSING:
|
|
case ZFCP_ERP_STEP_PORT_OPENING:
|
|
case ZFCP_ERP_STEP_LUN_CLOSING:
|
|
case ZFCP_ERP_STEP_LUN_OPENING:
|
|
/* NOP */
|
|
break;
|
|
}
|
|
return ZFCP_ERP_FAILED;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_port_strategy_close(
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
int retval;
|
|
|
|
retval = zfcp_fsf_close_port(erp_action);
|
|
if (retval == -ENOMEM)
|
|
return ZFCP_ERP_NOMEM;
|
|
erp_action->step = ZFCP_ERP_STEP_PORT_CLOSING;
|
|
if (retval)
|
|
return ZFCP_ERP_FAILED;
|
|
return ZFCP_ERP_CONTINUES;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_port_strategy_open_port(
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
int retval;
|
|
|
|
retval = zfcp_fsf_open_port(erp_action);
|
|
if (retval == -ENOMEM)
|
|
return ZFCP_ERP_NOMEM;
|
|
erp_action->step = ZFCP_ERP_STEP_PORT_OPENING;
|
|
if (retval)
|
|
return ZFCP_ERP_FAILED;
|
|
return ZFCP_ERP_CONTINUES;
|
|
}
|
|
|
|
static int zfcp_erp_open_ptp_port(struct zfcp_erp_action *act)
|
|
{
|
|
struct zfcp_adapter *adapter = act->adapter;
|
|
struct zfcp_port *port = act->port;
|
|
|
|
if (port->wwpn != adapter->peer_wwpn) {
|
|
zfcp_erp_set_port_status(port, ZFCP_STATUS_COMMON_ERP_FAILED);
|
|
return ZFCP_ERP_FAILED;
|
|
}
|
|
port->d_id = adapter->peer_d_id;
|
|
return zfcp_erp_port_strategy_open_port(act);
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_port_strategy_open_common(
|
|
struct zfcp_erp_action *act)
|
|
{
|
|
struct zfcp_adapter *adapter = act->adapter;
|
|
struct zfcp_port *port = act->port;
|
|
int p_status = atomic_read(&port->status);
|
|
|
|
switch (act->step) {
|
|
case ZFCP_ERP_STEP_UNINITIALIZED:
|
|
case ZFCP_ERP_STEP_PHYS_PORT_CLOSING:
|
|
case ZFCP_ERP_STEP_PORT_CLOSING:
|
|
if (fc_host_port_type(adapter->scsi_host) == FC_PORTTYPE_PTP)
|
|
return zfcp_erp_open_ptp_port(act);
|
|
if (!port->d_id) {
|
|
zfcp_fc_trigger_did_lookup(port);
|
|
return ZFCP_ERP_EXIT;
|
|
}
|
|
return zfcp_erp_port_strategy_open_port(act);
|
|
|
|
case ZFCP_ERP_STEP_PORT_OPENING:
|
|
/* D_ID might have changed during open */
|
|
if (p_status & ZFCP_STATUS_COMMON_OPEN) {
|
|
if (!port->d_id) {
|
|
zfcp_fc_trigger_did_lookup(port);
|
|
return ZFCP_ERP_EXIT;
|
|
}
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
}
|
|
if (port->d_id && !(p_status & ZFCP_STATUS_COMMON_NOESC)) {
|
|
port->d_id = 0;
|
|
return ZFCP_ERP_FAILED;
|
|
}
|
|
/* no early return otherwise, continue after switch case */
|
|
break;
|
|
case ZFCP_ERP_STEP_LUN_CLOSING:
|
|
case ZFCP_ERP_STEP_LUN_OPENING:
|
|
/* NOP */
|
|
break;
|
|
}
|
|
return ZFCP_ERP_FAILED;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_port_strategy(
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
struct zfcp_port *port = erp_action->port;
|
|
int p_status = atomic_read(&port->status);
|
|
|
|
if ((p_status & ZFCP_STATUS_COMMON_NOESC) &&
|
|
!(p_status & ZFCP_STATUS_COMMON_OPEN))
|
|
goto close_init_done;
|
|
|
|
switch (erp_action->step) {
|
|
case ZFCP_ERP_STEP_UNINITIALIZED:
|
|
if (p_status & ZFCP_STATUS_COMMON_OPEN)
|
|
return zfcp_erp_port_strategy_close(erp_action);
|
|
break;
|
|
|
|
case ZFCP_ERP_STEP_PORT_CLOSING:
|
|
if (p_status & ZFCP_STATUS_COMMON_OPEN)
|
|
return ZFCP_ERP_FAILED;
|
|
break;
|
|
case ZFCP_ERP_STEP_PHYS_PORT_CLOSING:
|
|
case ZFCP_ERP_STEP_PORT_OPENING:
|
|
case ZFCP_ERP_STEP_LUN_CLOSING:
|
|
case ZFCP_ERP_STEP_LUN_OPENING:
|
|
/* NOP */
|
|
break;
|
|
}
|
|
|
|
close_init_done:
|
|
if (erp_action->status & ZFCP_STATUS_ERP_CLOSE_ONLY)
|
|
return ZFCP_ERP_EXIT;
|
|
|
|
return zfcp_erp_port_strategy_open_common(erp_action);
|
|
}
|
|
|
|
static void zfcp_erp_lun_strategy_clearstati(struct scsi_device *sdev)
|
|
{
|
|
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
|
|
|
|
atomic_andnot(ZFCP_STATUS_COMMON_ACCESS_DENIED,
|
|
&zfcp_sdev->status);
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_lun_strategy_close(
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
int retval = zfcp_fsf_close_lun(erp_action);
|
|
if (retval == -ENOMEM)
|
|
return ZFCP_ERP_NOMEM;
|
|
erp_action->step = ZFCP_ERP_STEP_LUN_CLOSING;
|
|
if (retval)
|
|
return ZFCP_ERP_FAILED;
|
|
return ZFCP_ERP_CONTINUES;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_lun_strategy_open(
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
int retval = zfcp_fsf_open_lun(erp_action);
|
|
if (retval == -ENOMEM)
|
|
return ZFCP_ERP_NOMEM;
|
|
erp_action->step = ZFCP_ERP_STEP_LUN_OPENING;
|
|
if (retval)
|
|
return ZFCP_ERP_FAILED;
|
|
return ZFCP_ERP_CONTINUES;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_lun_strategy(
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
struct scsi_device *sdev = erp_action->sdev;
|
|
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
|
|
|
|
switch (erp_action->step) {
|
|
case ZFCP_ERP_STEP_UNINITIALIZED:
|
|
zfcp_erp_lun_strategy_clearstati(sdev);
|
|
if (atomic_read(&zfcp_sdev->status) & ZFCP_STATUS_COMMON_OPEN)
|
|
return zfcp_erp_lun_strategy_close(erp_action);
|
|
/* already closed */
|
|
fallthrough;
|
|
case ZFCP_ERP_STEP_LUN_CLOSING:
|
|
if (atomic_read(&zfcp_sdev->status) & ZFCP_STATUS_COMMON_OPEN)
|
|
return ZFCP_ERP_FAILED;
|
|
if (erp_action->status & ZFCP_STATUS_ERP_CLOSE_ONLY)
|
|
return ZFCP_ERP_EXIT;
|
|
return zfcp_erp_lun_strategy_open(erp_action);
|
|
|
|
case ZFCP_ERP_STEP_LUN_OPENING:
|
|
if (atomic_read(&zfcp_sdev->status) & ZFCP_STATUS_COMMON_OPEN)
|
|
return ZFCP_ERP_SUCCEEDED;
|
|
break;
|
|
case ZFCP_ERP_STEP_PHYS_PORT_CLOSING:
|
|
case ZFCP_ERP_STEP_PORT_CLOSING:
|
|
case ZFCP_ERP_STEP_PORT_OPENING:
|
|
/* NOP */
|
|
break;
|
|
}
|
|
return ZFCP_ERP_FAILED;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_strategy_check_lun(
|
|
struct scsi_device *sdev, enum zfcp_erp_act_result result)
|
|
{
|
|
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
|
|
|
|
switch (result) {
|
|
case ZFCP_ERP_SUCCEEDED :
|
|
atomic_set(&zfcp_sdev->erp_counter, 0);
|
|
zfcp_erp_lun_unblock(sdev);
|
|
break;
|
|
case ZFCP_ERP_FAILED :
|
|
atomic_inc(&zfcp_sdev->erp_counter);
|
|
if (atomic_read(&zfcp_sdev->erp_counter) > ZFCP_MAX_ERPS) {
|
|
dev_err(&zfcp_sdev->port->adapter->ccw_device->dev,
|
|
"ERP failed for LUN 0x%016Lx on "
|
|
"port 0x%016Lx\n",
|
|
(unsigned long long)zfcp_scsi_dev_lun(sdev),
|
|
(unsigned long long)zfcp_sdev->port->wwpn);
|
|
zfcp_erp_set_lun_status(sdev,
|
|
ZFCP_STATUS_COMMON_ERP_FAILED);
|
|
}
|
|
break;
|
|
case ZFCP_ERP_CONTINUES:
|
|
case ZFCP_ERP_EXIT:
|
|
case ZFCP_ERP_DISMISSED:
|
|
case ZFCP_ERP_NOMEM:
|
|
/* NOP */
|
|
break;
|
|
}
|
|
|
|
if (atomic_read(&zfcp_sdev->status) & ZFCP_STATUS_COMMON_ERP_FAILED) {
|
|
zfcp_erp_lun_block(sdev, 0);
|
|
result = ZFCP_ERP_EXIT;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_strategy_check_port(
|
|
struct zfcp_port *port, enum zfcp_erp_act_result result)
|
|
{
|
|
switch (result) {
|
|
case ZFCP_ERP_SUCCEEDED :
|
|
atomic_set(&port->erp_counter, 0);
|
|
zfcp_erp_port_unblock(port);
|
|
break;
|
|
|
|
case ZFCP_ERP_FAILED :
|
|
if (atomic_read(&port->status) & ZFCP_STATUS_COMMON_NOESC) {
|
|
zfcp_erp_port_block(port, 0);
|
|
result = ZFCP_ERP_EXIT;
|
|
}
|
|
atomic_inc(&port->erp_counter);
|
|
if (atomic_read(&port->erp_counter) > ZFCP_MAX_ERPS) {
|
|
dev_err(&port->adapter->ccw_device->dev,
|
|
"ERP failed for remote port 0x%016Lx\n",
|
|
(unsigned long long)port->wwpn);
|
|
zfcp_erp_set_port_status(port,
|
|
ZFCP_STATUS_COMMON_ERP_FAILED);
|
|
}
|
|
break;
|
|
case ZFCP_ERP_CONTINUES:
|
|
case ZFCP_ERP_EXIT:
|
|
case ZFCP_ERP_DISMISSED:
|
|
case ZFCP_ERP_NOMEM:
|
|
/* NOP */
|
|
break;
|
|
}
|
|
|
|
if (atomic_read(&port->status) & ZFCP_STATUS_COMMON_ERP_FAILED) {
|
|
zfcp_erp_port_block(port, 0);
|
|
result = ZFCP_ERP_EXIT;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_strategy_check_adapter(
|
|
struct zfcp_adapter *adapter, enum zfcp_erp_act_result result)
|
|
{
|
|
switch (result) {
|
|
case ZFCP_ERP_SUCCEEDED :
|
|
atomic_set(&adapter->erp_counter, 0);
|
|
zfcp_erp_adapter_unblock(adapter);
|
|
break;
|
|
|
|
case ZFCP_ERP_FAILED :
|
|
atomic_inc(&adapter->erp_counter);
|
|
if (atomic_read(&adapter->erp_counter) > ZFCP_MAX_ERPS) {
|
|
dev_err(&adapter->ccw_device->dev,
|
|
"ERP cannot recover an error "
|
|
"on the FCP device\n");
|
|
zfcp_erp_set_adapter_status(adapter,
|
|
ZFCP_STATUS_COMMON_ERP_FAILED);
|
|
}
|
|
break;
|
|
case ZFCP_ERP_CONTINUES:
|
|
case ZFCP_ERP_EXIT:
|
|
case ZFCP_ERP_DISMISSED:
|
|
case ZFCP_ERP_NOMEM:
|
|
/* NOP */
|
|
break;
|
|
}
|
|
|
|
if (atomic_read(&adapter->status) & ZFCP_STATUS_COMMON_ERP_FAILED) {
|
|
zfcp_erp_adapter_block(adapter, 0);
|
|
result = ZFCP_ERP_EXIT;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_strategy_check_target(
|
|
struct zfcp_erp_action *erp_action, enum zfcp_erp_act_result result)
|
|
{
|
|
struct zfcp_adapter *adapter = erp_action->adapter;
|
|
struct zfcp_port *port = erp_action->port;
|
|
struct scsi_device *sdev = erp_action->sdev;
|
|
|
|
switch (erp_action->type) {
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_LUN:
|
|
result = zfcp_erp_strategy_check_lun(sdev, result);
|
|
break;
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT:
|
|
result = zfcp_erp_strategy_check_port(port, result);
|
|
break;
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
|
|
result = zfcp_erp_strategy_check_adapter(adapter, result);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int zfcp_erp_strat_change_det(atomic_t *target_status, u32 erp_status)
|
|
{
|
|
int status = atomic_read(target_status);
|
|
|
|
if ((status & ZFCP_STATUS_COMMON_RUNNING) &&
|
|
(erp_status & ZFCP_STATUS_ERP_CLOSE_ONLY))
|
|
return 1; /* take it online */
|
|
|
|
if (!(status & ZFCP_STATUS_COMMON_RUNNING) &&
|
|
!(erp_status & ZFCP_STATUS_ERP_CLOSE_ONLY))
|
|
return 1; /* take it offline */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_strategy_statechange(
|
|
struct zfcp_erp_action *act, enum zfcp_erp_act_result result)
|
|
{
|
|
enum zfcp_erp_act_type type = act->type;
|
|
struct zfcp_adapter *adapter = act->adapter;
|
|
struct zfcp_port *port = act->port;
|
|
struct scsi_device *sdev = act->sdev;
|
|
struct zfcp_scsi_dev *zfcp_sdev;
|
|
u32 erp_status = act->status;
|
|
|
|
switch (type) {
|
|
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
|
|
if (zfcp_erp_strat_change_det(&adapter->status, erp_status)) {
|
|
_zfcp_erp_adapter_reopen(adapter,
|
|
ZFCP_STATUS_COMMON_ERP_FAILED,
|
|
"ersscg1");
|
|
return ZFCP_ERP_EXIT;
|
|
}
|
|
break;
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT:
|
|
if (zfcp_erp_strat_change_det(&port->status, erp_status)) {
|
|
_zfcp_erp_port_reopen(port,
|
|
ZFCP_STATUS_COMMON_ERP_FAILED,
|
|
"ersscg2");
|
|
return ZFCP_ERP_EXIT;
|
|
}
|
|
break;
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_LUN:
|
|
zfcp_sdev = sdev_to_zfcp(sdev);
|
|
if (zfcp_erp_strat_change_det(&zfcp_sdev->status, erp_status)) {
|
|
_zfcp_erp_lun_reopen(sdev,
|
|
ZFCP_STATUS_COMMON_ERP_FAILED,
|
|
"ersscg3", 0);
|
|
return ZFCP_ERP_EXIT;
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void zfcp_erp_action_dequeue(struct zfcp_erp_action *erp_action)
|
|
{
|
|
struct zfcp_adapter *adapter = erp_action->adapter;
|
|
struct zfcp_scsi_dev *zfcp_sdev;
|
|
|
|
adapter->erp_total_count--;
|
|
if (erp_action->status & ZFCP_STATUS_ERP_LOWMEM) {
|
|
adapter->erp_low_mem_count--;
|
|
erp_action->status &= ~ZFCP_STATUS_ERP_LOWMEM;
|
|
}
|
|
|
|
list_del(&erp_action->list);
|
|
zfcp_dbf_rec_run("eractd1", erp_action);
|
|
|
|
switch (erp_action->type) {
|
|
case ZFCP_ERP_ACTION_REOPEN_LUN:
|
|
zfcp_sdev = sdev_to_zfcp(erp_action->sdev);
|
|
atomic_andnot(ZFCP_STATUS_COMMON_ERP_INUSE,
|
|
&zfcp_sdev->status);
|
|
break;
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT:
|
|
atomic_andnot(ZFCP_STATUS_COMMON_ERP_INUSE,
|
|
&erp_action->port->status);
|
|
break;
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
|
|
atomic_andnot(ZFCP_STATUS_COMMON_ERP_INUSE,
|
|
&erp_action->adapter->status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_try_rport_unblock - unblock rport if no more/new recovery
|
|
* @port: zfcp_port whose fc_rport we should try to unblock
|
|
*/
|
|
static void zfcp_erp_try_rport_unblock(struct zfcp_port *port)
|
|
{
|
|
unsigned long flags;
|
|
struct zfcp_adapter *adapter = port->adapter;
|
|
int port_status;
|
|
struct Scsi_Host *shost = adapter->scsi_host;
|
|
struct scsi_device *sdev;
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
port_status = atomic_read(&port->status);
|
|
if ((port_status & ZFCP_STATUS_COMMON_UNBLOCKED) == 0 ||
|
|
(port_status & (ZFCP_STATUS_COMMON_ERP_INUSE |
|
|
ZFCP_STATUS_COMMON_ERP_FAILED)) != 0) {
|
|
/* new ERP of severity >= port triggered elsewhere meanwhile or
|
|
* local link down (adapter erp_failed but not clear unblock)
|
|
*/
|
|
zfcp_dbf_rec_run_lvl(4, "ertru_p", &port->erp_action);
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
return;
|
|
}
|
|
spin_lock(shost->host_lock);
|
|
__shost_for_each_device(sdev, shost) {
|
|
struct zfcp_scsi_dev *zsdev = sdev_to_zfcp(sdev);
|
|
int lun_status;
|
|
|
|
if (sdev->sdev_state == SDEV_DEL ||
|
|
sdev->sdev_state == SDEV_CANCEL)
|
|
continue;
|
|
if (zsdev->port != port)
|
|
continue;
|
|
/* LUN under port of interest */
|
|
lun_status = atomic_read(&zsdev->status);
|
|
if ((lun_status & ZFCP_STATUS_COMMON_ERP_FAILED) != 0)
|
|
continue; /* unblock rport despite failed LUNs */
|
|
/* LUN recovery not given up yet [maybe follow-up pending] */
|
|
if ((lun_status & ZFCP_STATUS_COMMON_UNBLOCKED) == 0 ||
|
|
(lun_status & ZFCP_STATUS_COMMON_ERP_INUSE) != 0) {
|
|
/* LUN blocked:
|
|
* not yet unblocked [LUN recovery pending]
|
|
* or meanwhile blocked [new LUN recovery triggered]
|
|
*/
|
|
zfcp_dbf_rec_run_lvl(4, "ertru_l", &zsdev->erp_action);
|
|
spin_unlock(shost->host_lock);
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
return;
|
|
}
|
|
}
|
|
/* now port has no child or all children have completed recovery,
|
|
* and no ERP of severity >= port was meanwhile triggered elsewhere
|
|
*/
|
|
zfcp_scsi_schedule_rport_register(port);
|
|
spin_unlock(shost->host_lock);
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
}
|
|
|
|
static void zfcp_erp_action_cleanup(struct zfcp_erp_action *act,
|
|
enum zfcp_erp_act_result result)
|
|
{
|
|
struct zfcp_adapter *adapter = act->adapter;
|
|
struct zfcp_port *port = act->port;
|
|
struct scsi_device *sdev = act->sdev;
|
|
|
|
switch (act->type) {
|
|
case ZFCP_ERP_ACTION_REOPEN_LUN:
|
|
if (!(act->status & ZFCP_STATUS_ERP_NO_REF))
|
|
scsi_device_put(sdev);
|
|
zfcp_erp_try_rport_unblock(port);
|
|
break;
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT:
|
|
/* This switch case might also happen after a forced reopen
|
|
* was successfully done and thus overwritten with a new
|
|
* non-forced reopen at `ersfs_2'. In this case, we must not
|
|
* do the clean-up of the non-forced version.
|
|
*/
|
|
if (act->step != ZFCP_ERP_STEP_UNINITIALIZED)
|
|
if (result == ZFCP_ERP_SUCCEEDED)
|
|
zfcp_erp_try_rport_unblock(port);
|
|
fallthrough;
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
|
|
put_device(&port->dev);
|
|
break;
|
|
|
|
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
|
|
if (result == ZFCP_ERP_SUCCEEDED) {
|
|
register_service_level(&adapter->service_level);
|
|
zfcp_fc_conditional_port_scan(adapter);
|
|
queue_work(adapter->work_queue, &adapter->ns_up_work);
|
|
} else
|
|
unregister_service_level(&adapter->service_level);
|
|
|
|
kref_put(&adapter->ref, zfcp_adapter_release);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_strategy_do_action(
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
switch (erp_action->type) {
|
|
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
|
|
return zfcp_erp_adapter_strategy(erp_action);
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
|
|
return zfcp_erp_port_forced_strategy(erp_action);
|
|
case ZFCP_ERP_ACTION_REOPEN_PORT:
|
|
return zfcp_erp_port_strategy(erp_action);
|
|
case ZFCP_ERP_ACTION_REOPEN_LUN:
|
|
return zfcp_erp_lun_strategy(erp_action);
|
|
}
|
|
return ZFCP_ERP_FAILED;
|
|
}
|
|
|
|
static enum zfcp_erp_act_result zfcp_erp_strategy(
|
|
struct zfcp_erp_action *erp_action)
|
|
{
|
|
enum zfcp_erp_act_result result;
|
|
unsigned long flags;
|
|
struct zfcp_adapter *adapter = erp_action->adapter;
|
|
|
|
kref_get(&adapter->ref);
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
zfcp_erp_strategy_check_fsfreq(erp_action);
|
|
|
|
if (erp_action->status & ZFCP_STATUS_ERP_DISMISSED) {
|
|
zfcp_erp_action_dequeue(erp_action);
|
|
result = ZFCP_ERP_DISMISSED;
|
|
goto unlock;
|
|
}
|
|
|
|
if (erp_action->status & ZFCP_STATUS_ERP_TIMEDOUT) {
|
|
result = ZFCP_ERP_FAILED;
|
|
goto check_target;
|
|
}
|
|
|
|
zfcp_erp_action_to_running(erp_action);
|
|
|
|
/* no lock to allow for blocking operations */
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
result = zfcp_erp_strategy_do_action(erp_action);
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
|
|
if (erp_action->status & ZFCP_STATUS_ERP_DISMISSED)
|
|
result = ZFCP_ERP_CONTINUES;
|
|
|
|
switch (result) {
|
|
case ZFCP_ERP_NOMEM:
|
|
if (!(erp_action->status & ZFCP_STATUS_ERP_LOWMEM)) {
|
|
++adapter->erp_low_mem_count;
|
|
erp_action->status |= ZFCP_STATUS_ERP_LOWMEM;
|
|
}
|
|
if (adapter->erp_total_count == adapter->erp_low_mem_count)
|
|
_zfcp_erp_adapter_reopen(adapter, 0, "erstgy1");
|
|
else {
|
|
zfcp_erp_strategy_memwait(erp_action);
|
|
result = ZFCP_ERP_CONTINUES;
|
|
}
|
|
goto unlock;
|
|
|
|
case ZFCP_ERP_CONTINUES:
|
|
if (erp_action->status & ZFCP_STATUS_ERP_LOWMEM) {
|
|
--adapter->erp_low_mem_count;
|
|
erp_action->status &= ~ZFCP_STATUS_ERP_LOWMEM;
|
|
}
|
|
goto unlock;
|
|
case ZFCP_ERP_SUCCEEDED:
|
|
case ZFCP_ERP_FAILED:
|
|
case ZFCP_ERP_EXIT:
|
|
case ZFCP_ERP_DISMISSED:
|
|
/* NOP */
|
|
break;
|
|
}
|
|
|
|
check_target:
|
|
result = zfcp_erp_strategy_check_target(erp_action, result);
|
|
zfcp_erp_action_dequeue(erp_action);
|
|
result = zfcp_erp_strategy_statechange(erp_action, result);
|
|
if (result == ZFCP_ERP_EXIT)
|
|
goto unlock;
|
|
if (result == ZFCP_ERP_SUCCEEDED)
|
|
zfcp_erp_strategy_followup_success(erp_action);
|
|
if (result == ZFCP_ERP_FAILED)
|
|
zfcp_erp_strategy_followup_failed(erp_action);
|
|
|
|
unlock:
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
|
|
if (result != ZFCP_ERP_CONTINUES)
|
|
zfcp_erp_action_cleanup(erp_action, result);
|
|
|
|
kref_put(&adapter->ref, zfcp_adapter_release);
|
|
return result;
|
|
}
|
|
|
|
static int zfcp_erp_thread(void *data)
|
|
{
|
|
struct zfcp_adapter *adapter = (struct zfcp_adapter *) data;
|
|
struct list_head *next;
|
|
struct zfcp_erp_action *act;
|
|
unsigned long flags;
|
|
|
|
for (;;) {
|
|
wait_event_interruptible(adapter->erp_ready_wq,
|
|
!list_empty(&adapter->erp_ready_head) ||
|
|
kthread_should_stop());
|
|
|
|
if (kthread_should_stop())
|
|
break;
|
|
|
|
write_lock_irqsave(&adapter->erp_lock, flags);
|
|
next = adapter->erp_ready_head.next;
|
|
write_unlock_irqrestore(&adapter->erp_lock, flags);
|
|
|
|
if (next != &adapter->erp_ready_head) {
|
|
act = list_entry(next, struct zfcp_erp_action, list);
|
|
|
|
/* there is more to come after dismission, no notify */
|
|
if (zfcp_erp_strategy(act) != ZFCP_ERP_DISMISSED)
|
|
zfcp_erp_wakeup(adapter);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_thread_setup - Start ERP thread for adapter
|
|
* @adapter: Adapter to start the ERP thread for
|
|
*
|
|
* Return: 0 on success, or error code from kthread_run().
|
|
*/
|
|
int zfcp_erp_thread_setup(struct zfcp_adapter *adapter)
|
|
{
|
|
struct task_struct *thread;
|
|
|
|
thread = kthread_run(zfcp_erp_thread, adapter, "zfcperp%s",
|
|
dev_name(&adapter->ccw_device->dev));
|
|
if (IS_ERR(thread)) {
|
|
dev_err(&adapter->ccw_device->dev,
|
|
"Creating an ERP thread for the FCP device failed.\n");
|
|
return PTR_ERR(thread);
|
|
}
|
|
|
|
adapter->erp_thread = thread;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_thread_kill - Stop ERP thread.
|
|
* @adapter: Adapter where the ERP thread should be stopped.
|
|
*
|
|
* The caller of this routine ensures that the specified adapter has
|
|
* been shut down and that this operation has been completed. Thus,
|
|
* there are no pending erp_actions which would need to be handled
|
|
* here.
|
|
*/
|
|
void zfcp_erp_thread_kill(struct zfcp_adapter *adapter)
|
|
{
|
|
kthread_stop(adapter->erp_thread);
|
|
adapter->erp_thread = NULL;
|
|
WARN_ON(!list_empty(&adapter->erp_ready_head));
|
|
WARN_ON(!list_empty(&adapter->erp_running_head));
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_wait - wait for completion of error recovery on an adapter
|
|
* @adapter: adapter for which to wait for completion of its error recovery
|
|
*/
|
|
void zfcp_erp_wait(struct zfcp_adapter *adapter)
|
|
{
|
|
wait_event(adapter->erp_done_wqh,
|
|
!(atomic_read(&adapter->status) &
|
|
ZFCP_STATUS_ADAPTER_ERP_PENDING));
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_set_adapter_status - set adapter status bits
|
|
* @adapter: adapter to change the status
|
|
* @mask: status bits to change
|
|
*
|
|
* Changes in common status bits are propagated to attached ports and LUNs.
|
|
*/
|
|
void zfcp_erp_set_adapter_status(struct zfcp_adapter *adapter, u32 mask)
|
|
{
|
|
struct zfcp_port *port;
|
|
struct scsi_device *sdev;
|
|
unsigned long flags;
|
|
u32 common_mask = mask & ZFCP_COMMON_FLAGS;
|
|
|
|
atomic_or(mask, &adapter->status);
|
|
|
|
if (!common_mask)
|
|
return;
|
|
|
|
read_lock_irqsave(&adapter->port_list_lock, flags);
|
|
list_for_each_entry(port, &adapter->port_list, list)
|
|
atomic_or(common_mask, &port->status);
|
|
read_unlock_irqrestore(&adapter->port_list_lock, flags);
|
|
|
|
/*
|
|
* if `scsi_host` is missing, xconfig/xport data has never completed
|
|
* yet, so we can't access it, but there are also no SDEVs yet
|
|
*/
|
|
if (adapter->scsi_host == NULL)
|
|
return;
|
|
|
|
spin_lock_irqsave(adapter->scsi_host->host_lock, flags);
|
|
__shost_for_each_device(sdev, adapter->scsi_host)
|
|
atomic_or(common_mask, &sdev_to_zfcp(sdev)->status);
|
|
spin_unlock_irqrestore(adapter->scsi_host->host_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_clear_adapter_status - clear adapter status bits
|
|
* @adapter: adapter to change the status
|
|
* @mask: status bits to change
|
|
*
|
|
* Changes in common status bits are propagated to attached ports and LUNs.
|
|
*/
|
|
void zfcp_erp_clear_adapter_status(struct zfcp_adapter *adapter, u32 mask)
|
|
{
|
|
struct zfcp_port *port;
|
|
struct scsi_device *sdev;
|
|
unsigned long flags;
|
|
u32 common_mask = mask & ZFCP_COMMON_FLAGS;
|
|
u32 clear_counter = mask & ZFCP_STATUS_COMMON_ERP_FAILED;
|
|
|
|
atomic_andnot(mask, &adapter->status);
|
|
|
|
if (!common_mask)
|
|
return;
|
|
|
|
if (clear_counter)
|
|
atomic_set(&adapter->erp_counter, 0);
|
|
|
|
read_lock_irqsave(&adapter->port_list_lock, flags);
|
|
list_for_each_entry(port, &adapter->port_list, list) {
|
|
atomic_andnot(common_mask, &port->status);
|
|
if (clear_counter)
|
|
atomic_set(&port->erp_counter, 0);
|
|
}
|
|
read_unlock_irqrestore(&adapter->port_list_lock, flags);
|
|
|
|
/*
|
|
* if `scsi_host` is missing, xconfig/xport data has never completed
|
|
* yet, so we can't access it, but there are also no SDEVs yet
|
|
*/
|
|
if (adapter->scsi_host == NULL)
|
|
return;
|
|
|
|
spin_lock_irqsave(adapter->scsi_host->host_lock, flags);
|
|
__shost_for_each_device(sdev, adapter->scsi_host) {
|
|
atomic_andnot(common_mask, &sdev_to_zfcp(sdev)->status);
|
|
if (clear_counter)
|
|
atomic_set(&sdev_to_zfcp(sdev)->erp_counter, 0);
|
|
}
|
|
spin_unlock_irqrestore(adapter->scsi_host->host_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_set_port_status - set port status bits
|
|
* @port: port to change the status
|
|
* @mask: status bits to change
|
|
*
|
|
* Changes in common status bits are propagated to attached LUNs.
|
|
*/
|
|
void zfcp_erp_set_port_status(struct zfcp_port *port, u32 mask)
|
|
{
|
|
struct scsi_device *sdev;
|
|
u32 common_mask = mask & ZFCP_COMMON_FLAGS;
|
|
unsigned long flags;
|
|
|
|
atomic_or(mask, &port->status);
|
|
|
|
if (!common_mask)
|
|
return;
|
|
|
|
spin_lock_irqsave(port->adapter->scsi_host->host_lock, flags);
|
|
__shost_for_each_device(sdev, port->adapter->scsi_host)
|
|
if (sdev_to_zfcp(sdev)->port == port)
|
|
atomic_or(common_mask,
|
|
&sdev_to_zfcp(sdev)->status);
|
|
spin_unlock_irqrestore(port->adapter->scsi_host->host_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_clear_port_status - clear port status bits
|
|
* @port: adapter to change the status
|
|
* @mask: status bits to change
|
|
*
|
|
* Changes in common status bits are propagated to attached LUNs.
|
|
*/
|
|
void zfcp_erp_clear_port_status(struct zfcp_port *port, u32 mask)
|
|
{
|
|
struct scsi_device *sdev;
|
|
u32 common_mask = mask & ZFCP_COMMON_FLAGS;
|
|
u32 clear_counter = mask & ZFCP_STATUS_COMMON_ERP_FAILED;
|
|
unsigned long flags;
|
|
|
|
atomic_andnot(mask, &port->status);
|
|
|
|
if (!common_mask)
|
|
return;
|
|
|
|
if (clear_counter)
|
|
atomic_set(&port->erp_counter, 0);
|
|
|
|
spin_lock_irqsave(port->adapter->scsi_host->host_lock, flags);
|
|
__shost_for_each_device(sdev, port->adapter->scsi_host)
|
|
if (sdev_to_zfcp(sdev)->port == port) {
|
|
atomic_andnot(common_mask,
|
|
&sdev_to_zfcp(sdev)->status);
|
|
if (clear_counter)
|
|
atomic_set(&sdev_to_zfcp(sdev)->erp_counter, 0);
|
|
}
|
|
spin_unlock_irqrestore(port->adapter->scsi_host->host_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_set_lun_status - set lun status bits
|
|
* @sdev: SCSI device / lun to set the status bits
|
|
* @mask: status bits to change
|
|
*/
|
|
void zfcp_erp_set_lun_status(struct scsi_device *sdev, u32 mask)
|
|
{
|
|
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
|
|
|
|
atomic_or(mask, &zfcp_sdev->status);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_clear_lun_status - clear lun status bits
|
|
* @sdev: SCSi device / lun to clear the status bits
|
|
* @mask: status bits to change
|
|
*/
|
|
void zfcp_erp_clear_lun_status(struct scsi_device *sdev, u32 mask)
|
|
{
|
|
struct zfcp_scsi_dev *zfcp_sdev = sdev_to_zfcp(sdev);
|
|
|
|
atomic_andnot(mask, &zfcp_sdev->status);
|
|
|
|
if (mask & ZFCP_STATUS_COMMON_ERP_FAILED)
|
|
atomic_set(&zfcp_sdev->erp_counter, 0);
|
|
}
|
|
|
|
/**
|
|
* zfcp_erp_adapter_reset_sync() - Really reopen adapter and wait.
|
|
* @adapter: Pointer to zfcp_adapter to reopen.
|
|
* @dbftag: Trace tag string of length %ZFCP_DBF_TAG_LEN.
|
|
*/
|
|
void zfcp_erp_adapter_reset_sync(struct zfcp_adapter *adapter, char *dbftag)
|
|
{
|
|
zfcp_erp_set_adapter_status(adapter, ZFCP_STATUS_COMMON_RUNNING);
|
|
zfcp_erp_adapter_reopen(adapter, ZFCP_STATUS_COMMON_ERP_FAILED, dbftag);
|
|
zfcp_erp_wait(adapter);
|
|
}
|