summaryrefslogtreecommitdiffstats
path: root/drivers/s390/scsi/zfcp_erp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/scsi/zfcp_erp.c')
-rw-r--r--drivers/s390/scsi/zfcp_erp.c1866
1 files changed, 1866 insertions, 0 deletions
diff --git a/drivers/s390/scsi/zfcp_erp.c b/drivers/s390/scsi/zfcp_erp.c
new file mode 100644
index 000000000..78d52a4c5
--- /dev/null
+++ b/drivers/s390/scsi/zfcp_erp.c
@@ -0,0 +1,1866 @@
+// 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);
+}