forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1866 lines
52 KiB
1866 lines
52 KiB
// 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, &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); |
|
/* lock-free concurrent access with |
|
* zfcp_erp_timeout_handler() |
|
*/ |
|
WRITE_ONCE(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; |
|
|
|
if (fsf_req->status & ZFCP_STATUS_FSFREQ_DISMISSED) |
|
return; |
|
/* lock-free concurrent access with zfcp_erp_strategy_check_fsfreq() */ |
|
act = READ_ONCE(fsf_req->erp_action); |
|
if (!act) |
|
return; |
|
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 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); |
|
act = list_first_entry_or_null(&adapter->erp_ready_head, |
|
struct zfcp_erp_action, list); |
|
write_unlock_irqrestore(&adapter->erp_lock, flags); |
|
|
|
if (act) { |
|
/* 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); |
|
}
|
|
|