blob: ca8f132e03aee26017b686bd5a72673c413144e0 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Broadcom MPI3 Storage Controllers
*
* Copyright (C) 2017-2023 Broadcom Inc.
* (mailto: mpi3mr-linuxdrv.pdl@broadcom.com)
*
*/
#include "mpi3mr.h"
#include <linux/idr.h>
/* global driver scop variables */
LIST_HEAD(mrioc_list);
DEFINE_SPINLOCK(mrioc_list_lock);
static DEFINE_IDA(mrioc_ida);
static int warn_non_secure_ctlr;
atomic64_t event_counter;
MODULE_AUTHOR(MPI3MR_DRIVER_AUTHOR);
MODULE_DESCRIPTION(MPI3MR_DRIVER_DESC);
MODULE_LICENSE(MPI3MR_DRIVER_LICENSE);
MODULE_VERSION(MPI3MR_DRIVER_VERSION);
/* Module parameters*/
int prot_mask = -1;
module_param(prot_mask, int, 0);
MODULE_PARM_DESC(prot_mask, "Host protection capabilities mask, def=0x07");
static int prot_guard_mask = 3;
module_param(prot_guard_mask, int, 0);
MODULE_PARM_DESC(prot_guard_mask, " Host protection guard mask, def=3");
static int logging_level;
module_param(logging_level, int, 0);
MODULE_PARM_DESC(logging_level,
" bits for enabling additional logging info (default=0)");
static int max_sgl_entries = MPI3MR_DEFAULT_SGL_ENTRIES;
module_param(max_sgl_entries, int, 0444);
MODULE_PARM_DESC(max_sgl_entries,
"Preferred max number of SG entries to be used for a single I/O\n"
"The actual value will be determined by the driver\n"
"(Minimum=256, Maximum=2048, default=256)");
/* Forward declarations*/
static void mpi3mr_send_event_ack(struct mpi3mr_ioc *mrioc, u8 event,
struct mpi3mr_drv_cmd *cmdparam, u32 event_ctx);
#define MPI3MR_DRIVER_EVENT_TG_QD_REDUCTION (0xFFFF)
#define MPI3_EVENT_WAIT_FOR_DEVICES_TO_REFRESH (0xFFFE)
/**
* mpi3mr_host_tag_for_scmd - Get host tag for a scmd
* @mrioc: Adapter instance reference
* @scmd: SCSI command reference
*
* Calculate the host tag based on block tag for a given scmd.
*
* Return: Valid host tag or MPI3MR_HOSTTAG_INVALID.
*/
static u16 mpi3mr_host_tag_for_scmd(struct mpi3mr_ioc *mrioc,
struct scsi_cmnd *scmd)
{
struct scmd_priv *priv = NULL;
u32 unique_tag;
u16 host_tag, hw_queue;
unique_tag = blk_mq_unique_tag(scsi_cmd_to_rq(scmd));
hw_queue = blk_mq_unique_tag_to_hwq(unique_tag);
if (hw_queue >= mrioc->num_op_reply_q)
return MPI3MR_HOSTTAG_INVALID;
host_tag = blk_mq_unique_tag_to_tag(unique_tag);
if (WARN_ON(host_tag >= mrioc->max_host_ios))
return MPI3MR_HOSTTAG_INVALID;
priv = scsi_cmd_priv(scmd);
/*host_tag 0 is invalid hence incrementing by 1*/
priv->host_tag = host_tag + 1;
priv->scmd = scmd;
priv->in_lld_scope = 1;
priv->req_q_idx = hw_queue;
priv->meta_chain_idx = -1;
priv->chain_idx = -1;
priv->meta_sg_valid = 0;
return priv->host_tag;
}
/**
* mpi3mr_scmd_from_host_tag - Get SCSI command from host tag
* @mrioc: Adapter instance reference
* @host_tag: Host tag
* @qidx: Operational queue index
*
* Identify the block tag from the host tag and queue index and
* retrieve associated scsi command using scsi_host_find_tag().
*
* Return: SCSI command reference or NULL.
*/
static struct scsi_cmnd *mpi3mr_scmd_from_host_tag(
struct mpi3mr_ioc *mrioc, u16 host_tag, u16 qidx)
{
struct scsi_cmnd *scmd = NULL;
struct scmd_priv *priv = NULL;
u32 unique_tag = host_tag - 1;
if (WARN_ON(host_tag > mrioc->max_host_ios))
goto out;
unique_tag |= (qidx << BLK_MQ_UNIQUE_TAG_BITS);
scmd = scsi_host_find_tag(mrioc->shost, unique_tag);
if (scmd) {
priv = scsi_cmd_priv(scmd);
if (!priv->in_lld_scope)
scmd = NULL;
}
out:
return scmd;
}
/**
* mpi3mr_clear_scmd_priv - Cleanup SCSI command private date
* @mrioc: Adapter instance reference
* @scmd: SCSI command reference
*
* Invalidate the SCSI command private data to mark the command
* is not in LLD scope anymore.
*
* Return: Nothing.
*/
static void mpi3mr_clear_scmd_priv(struct mpi3mr_ioc *mrioc,
struct scsi_cmnd *scmd)
{
struct scmd_priv *priv = NULL;
priv = scsi_cmd_priv(scmd);
if (WARN_ON(priv->in_lld_scope == 0))
return;
priv->host_tag = MPI3MR_HOSTTAG_INVALID;
priv->req_q_idx = 0xFFFF;
priv->scmd = NULL;
priv->in_lld_scope = 0;
priv->meta_sg_valid = 0;
if (priv->chain_idx >= 0) {
clear_bit(priv->chain_idx, mrioc->chain_bitmap);
priv->chain_idx = -1;
}
if (priv->meta_chain_idx >= 0) {
clear_bit(priv->meta_chain_idx, mrioc->chain_bitmap);
priv->meta_chain_idx = -1;
}
}
static void mpi3mr_dev_rmhs_send_tm(struct mpi3mr_ioc *mrioc, u16 handle,
struct mpi3mr_drv_cmd *cmdparam, u8 iou_rc);
static void mpi3mr_fwevt_worker(struct work_struct *work);
/**
* mpi3mr_fwevt_free - firmware event memory dealloctor
* @r: k reference pointer of the firmware event
*
* Free firmware event memory when no reference.
*/
static void mpi3mr_fwevt_free(struct kref *r)
{
kfree(container_of(r, struct mpi3mr_fwevt, ref_count));
}
/**
* mpi3mr_fwevt_get - k reference incrementor
* @fwevt: Firmware event reference
*
* Increment firmware event reference count.
*/
static void mpi3mr_fwevt_get(struct mpi3mr_fwevt *fwevt)
{
kref_get(&fwevt->ref_count);
}
/**
* mpi3mr_fwevt_put - k reference decrementor
* @fwevt: Firmware event reference
*
* decrement firmware event reference count.
*/
static void mpi3mr_fwevt_put(struct mpi3mr_fwevt *fwevt)
{
kref_put(&fwevt->ref_count, mpi3mr_fwevt_free);
}
/**
* mpi3mr_alloc_fwevt - Allocate firmware event
* @len: length of firmware event data to allocate
*
* Allocate firmware event with required length and initialize
* the reference counter.
*
* Return: firmware event reference.
*/
static struct mpi3mr_fwevt *mpi3mr_alloc_fwevt(int len)
{
struct mpi3mr_fwevt *fwevt;
fwevt = kzalloc(sizeof(*fwevt) + len, GFP_ATOMIC);
if (!fwevt)
return NULL;
kref_init(&fwevt->ref_count);
return fwevt;
}
/**
* mpi3mr_fwevt_add_to_list - Add firmware event to the list
* @mrioc: Adapter instance reference
* @fwevt: Firmware event reference
*
* Add the given firmware event to the firmware event list.
*
* Return: Nothing.
*/
static void mpi3mr_fwevt_add_to_list(struct mpi3mr_ioc *mrioc,
struct mpi3mr_fwevt *fwevt)
{
unsigned long flags;
if (!mrioc->fwevt_worker_thread)
return;
spin_lock_irqsave(&mrioc->fwevt_lock, flags);
/* get fwevt reference count while adding it to fwevt_list */
mpi3mr_fwevt_get(fwevt);
INIT_LIST_HEAD(&fwevt->list);
list_add_tail(&fwevt->list, &mrioc->fwevt_list);
INIT_WORK(&fwevt->work, mpi3mr_fwevt_worker);
/* get fwevt reference count while enqueueing it to worker queue */
mpi3mr_fwevt_get(fwevt);
queue_work(mrioc->fwevt_worker_thread, &fwevt->work);
spin_unlock_irqrestore(&mrioc->fwevt_lock, flags);
}
/**
* mpi3mr_hdb_trigger_data_event - Add hdb trigger data event to
* the list
* @mrioc: Adapter instance reference
* @event_data: Event data
*
* Add the given hdb trigger data event to the firmware event
* list.
*
* Return: Nothing.
*/
void mpi3mr_hdb_trigger_data_event(struct mpi3mr_ioc *mrioc,
struct trigger_event_data *event_data)
{
struct mpi3mr_fwevt *fwevt;
u16 sz = sizeof(*event_data);
fwevt = mpi3mr_alloc_fwevt(sz);
if (!fwevt) {
ioc_warn(mrioc, "failed to queue hdb trigger data event\n");
return;
}
fwevt->mrioc = mrioc;
fwevt->event_id = MPI3MR_DRIVER_EVENT_PROCESS_TRIGGER;
fwevt->send_ack = 0;
fwevt->process_evt = 1;
fwevt->evt_ctx = 0;
fwevt->event_data_size = sz;
memcpy(fwevt->event_data, event_data, sz);
mpi3mr_fwevt_add_to_list(mrioc, fwevt);
}
/**
* mpi3mr_fwevt_del_from_list - Delete firmware event from list
* @mrioc: Adapter instance reference
* @fwevt: Firmware event reference
*
* Delete the given firmware event from the firmware event list.
*
* Return: Nothing.
*/
static void mpi3mr_fwevt_del_from_list(struct mpi3mr_ioc *mrioc,
struct mpi3mr_fwevt *fwevt)
{
unsigned long flags;
spin_lock_irqsave(&mrioc->fwevt_lock, flags);
if (!list_empty(&fwevt->list)) {
list_del_init(&fwevt->list);
/*
* Put fwevt reference count after
* removing it from fwevt_list
*/
mpi3mr_fwevt_put(fwevt);
}
spin_unlock_irqrestore(&mrioc->fwevt_lock, flags);
}
/**
* mpi3mr_dequeue_fwevt - Dequeue firmware event from the list
* @mrioc: Adapter instance reference
*
* Dequeue a firmware event from the firmware event list.
*
* Return: firmware event.
*/
static struct mpi3mr_fwevt *mpi3mr_dequeue_fwevt(
struct mpi3mr_ioc *mrioc)
{
unsigned long flags;
struct mpi3mr_fwevt *fwevt = NULL;
spin_lock_irqsave(&mrioc->fwevt_lock, flags);
if (!list_empty(&mrioc->fwevt_list)) {
fwevt = list_first_entry(&mrioc->fwevt_list,
struct mpi3mr_fwevt, list);
list_del_init(&fwevt->list);
/*
* Put fwevt reference count after
* removing it from fwevt_list
*/
mpi3mr_fwevt_put(fwevt);
}
spin_unlock_irqrestore(&mrioc->fwevt_lock, flags);
return fwevt;
}
/**
* mpi3mr_cancel_work - cancel firmware event
* @fwevt: fwevt object which needs to be canceled
*
* Return: Nothing.
*/
static void mpi3mr_cancel_work(struct mpi3mr_fwevt *fwevt)
{
/*
* Wait on the fwevt to complete. If this returns 1, then
* the event was never executed.
*
* If it did execute, we wait for it to finish, and the put will
* happen from mpi3mr_process_fwevt()
*/
if (cancel_work_sync(&fwevt->work)) {
/*
* Put fwevt reference count after
* dequeuing it from worker queue
*/
mpi3mr_fwevt_put(fwevt);
/*
* Put fwevt reference count to neutralize
* kref_init increment
*/
mpi3mr_fwevt_put(fwevt);
}
}
/**
* mpi3mr_cleanup_fwevt_list - Cleanup firmware event list
* @mrioc: Adapter instance reference
*
* Flush all pending firmware events from the firmware event
* list.
*
* Return: Nothing.
*/
void mpi3mr_cleanup_fwevt_list(struct mpi3mr_ioc *mrioc)
{
struct mpi3mr_fwevt *fwevt = NULL;
if ((list_empty(&mrioc->fwevt_list) && !mrioc->current_event) ||
!mrioc->fwevt_worker_thread)
return;
while ((fwevt = mpi3mr_dequeue_fwevt(mrioc)))
mpi3mr_cancel_work(fwevt);
if (mrioc->current_event) {
fwevt = mrioc->current_event;
/*
* Don't call cancel_work_sync() API for the
* fwevt work if the controller reset is
* get called as part of processing the
* same fwevt work (or) when worker thread is
* waiting for device add/remove APIs to complete.
* Otherwise we will see deadlock.
*/
if (current_work() == &fwevt->work || fwevt->pending_at_sml) {
fwevt->discard = 1;
return;
}
mpi3mr_cancel_work(fwevt);
}
}
/**
* mpi3mr_queue_qd_reduction_event - Queue TG QD reduction event
* @mrioc: Adapter instance reference
* @tg: Throttle group information pointer
*
* Accessor to queue on synthetically generated driver event to
* the event worker thread, the driver event will be used to
* reduce the QD of all VDs in the TG from the worker thread.
*
* Return: None.
*/
static void mpi3mr_queue_qd_reduction_event(struct mpi3mr_ioc *mrioc,
struct mpi3mr_throttle_group_info *tg)
{
struct mpi3mr_fwevt *fwevt;
u16 sz = sizeof(struct mpi3mr_throttle_group_info *);
/*
* If the QD reduction event is already queued due to throttle and if
* the QD is not restored through device info change event
* then dont queue further reduction events
*/
if (tg->fw_qd != tg->modified_qd)
return;
fwevt = mpi3mr_alloc_fwevt(sz);
if (!fwevt) {
ioc_warn(mrioc, "failed to queue TG QD reduction event\n");
return;
}
*(struct mpi3mr_throttle_group_info **)fwevt->event_data = tg;
fwevt->mrioc = mrioc;
fwevt->event_id = MPI3MR_DRIVER_EVENT_TG_QD_REDUCTION;
fwevt->send_ack = 0;
fwevt->process_evt = 1;
fwevt->evt_ctx = 0;
fwevt->event_data_size = sz;
tg->modified_qd = max_t(u16, (tg->fw_qd * tg->qd_reduction) / 10, 8);
dprint_event_bh(mrioc, "qd reduction event queued for tg_id(%d)\n",
tg->id);
mpi3mr_fwevt_add_to_list(mrioc, fwevt);
}
/**
* mpi3mr_invalidate_devhandles -Invalidate device handles
* @mrioc: Adapter instance reference
*
* Invalidate the device handles in the target device structures
* . Called post reset prior to reinitializing the controller.
*
* Return: Nothing.
*/
void mpi3mr_invalidate_devhandles(struct mpi3mr_ioc *mrioc)
{
struct mpi3mr_tgt_dev *tgtdev;
struct mpi3mr_stgt_priv_data *tgt_priv;
list_for_each_entry(tgtdev, &mrioc->tgtdev_list, list) {
tgtdev->dev_handle = MPI3MR_INVALID_DEV_HANDLE;
if (tgtdev->starget && tgtdev->starget->hostdata) {
tgt_priv = tgtdev->starget->hostdata;
tgt_priv->dev_handle = MPI3MR_INVALID_DEV_HANDLE;
tgt_priv->io_throttle_enabled = 0;
tgt_priv->io_divert = 0;
tgt_priv->throttle_group = NULL;
tgt_priv->wslen = 0;
if (tgtdev->host_exposed)
atomic_set(&tgt_priv->block_io, 1);
}
}
}
/**
* mpi3mr_print_scmd - print individual SCSI command
* @rq: Block request
* @data: Adapter instance reference
*
* Print the SCSI command details if it is in LLD scope.
*
* Return: true always.
*/
static bool mpi3mr_print_scmd(struct request *rq, void *data)
{
struct mpi3mr_ioc *mrioc = (struct mpi3mr_ioc *)data;
struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(rq);
struct scmd_priv *priv = NULL;
if (scmd) {
priv = scsi_cmd_priv(scmd);
if (!priv->in_lld_scope)
goto out;
ioc_info(mrioc, "%s :Host Tag = %d, qid = %d\n",
__func__, priv->host_tag, priv->req_q_idx + 1);
scsi_print_command(scmd);
}
out:
return(true);
}
/**
* mpi3mr_flush_scmd - Flush individual SCSI command
* @rq: Block request
* @data: Adapter instance reference
*
* Return the SCSI command to the upper layers if it is in LLD
* scope.
*
* Return: true always.
*/
static bool mpi3mr_flush_scmd(struct request *rq, void *data)
{
struct mpi3mr_ioc *mrioc = (struct mpi3mr_ioc *)data;
struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(rq);
struct scmd_priv *priv = NULL;
if (scmd) {
priv = scsi_cmd_priv(scmd);
if (!priv->in_lld_scope)
goto out;
if (priv->meta_sg_valid)
dma_unmap_sg(&mrioc->pdev->dev, scsi_prot_sglist(scmd),
scsi_prot_sg_count(scmd), scmd->sc_data_direction);
mpi3mr_clear_scmd_priv(mrioc, scmd);
scsi_dma_unmap(scmd);
scmd->result = DID_RESET << 16;
scsi_print_command(scmd);
scsi_done(scmd);
mrioc->flush_io_count++;
}
out:
return(true);
}
/**
* mpi3mr_count_dev_pending - Count commands pending for a lun
* @rq: Block request
* @data: SCSI device reference
*
* This is an iterator function called for each SCSI command in
* a host and if the command is pending in the LLD for the
* specific device(lun) then device specific pending I/O counter
* is updated in the device structure.
*
* Return: true always.
*/
static bool mpi3mr_count_dev_pending(struct request *rq, void *data)
{
struct scsi_device *sdev = (struct scsi_device *)data;
struct mpi3mr_sdev_priv_data *sdev_priv_data = sdev->hostdata;
struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(rq);
struct scmd_priv *priv;
if (scmd) {
priv = scsi_cmd_priv(scmd);
if (!priv->in_lld_scope)
goto out;
if (scmd->device == sdev)
sdev_priv_data->pend_count++;
}
out:
return true;
}
/**
* mpi3mr_count_tgt_pending - Count commands pending for target
* @rq: Block request
* @data: SCSI target reference
*
* This is an iterator function called for each SCSI command in
* a host and if the command is pending in the LLD for the
* specific target then target specific pending I/O counter is
* updated in the target structure.
*
* Return: true always.
*/
static bool mpi3mr_count_tgt_pending(struct request *rq, void *data)
{
struct scsi_target *starget = (struct scsi_target *)data;
struct mpi3mr_stgt_priv_data *stgt_priv_data = starget->hostdata;
struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(rq);
struct scmd_priv *priv;
if (scmd) {
priv = scsi_cmd_priv(scmd);
if (!priv->in_lld_scope)
goto out;
if (scmd->device && (scsi_target(scmd->device) == starget))
stgt_priv_data->pend_count++;
}
out:
return true;
}
/**
* mpi3mr_flush_host_io - Flush host I/Os
* @mrioc: Adapter instance reference
*
* Flush all of the pending I/Os by calling
* blk_mq_tagset_busy_iter() for each possible tag. This is
* executed post controller reset
*
* Return: Nothing.
*/
void mpi3mr_flush_host_io(struct mpi3mr_ioc *mrioc)
{
struct Scsi_Host *shost = mrioc->shost;
mrioc->flush_io_count = 0;
ioc_info(mrioc, "%s :Flushing Host I/O cmds post reset\n", __func__);
blk_mq_tagset_busy_iter(&shost->tag_set,
mpi3mr_flush_scmd, (void *)mrioc);
ioc_info(mrioc, "%s :Flushed %d Host I/O cmds\n", __func__,
mrioc->flush_io_count);
}
/**
* mpi3mr_flush_cmds_for_unrecovered_controller - Flush all pending cmds
* @mrioc: Adapter instance reference
*
* This function waits for currently running IO poll threads to
* exit and then flushes all host I/Os and any internal pending
* cmds. This is executed after controller is marked as
* unrecoverable.
*
* Return: Nothing.
*/
void mpi3mr_flush_cmds_for_unrecovered_controller(struct mpi3mr_ioc *mrioc)
{
struct Scsi_Host *shost = mrioc->shost;
int i;
if (!mrioc->unrecoverable)
return;
if (mrioc->op_reply_qinfo) {
for (i = 0; i < mrioc->num_queues; i++) {
while (atomic_read(&mrioc->op_reply_qinfo[i].in_use))
udelay(500);
atomic_set(&mrioc->op_reply_qinfo[i].pend_ios, 0);
}
}
mrioc->flush_io_count = 0;
blk_mq_tagset_busy_iter(&shost->tag_set,
mpi3mr_flush_scmd, (void *)mrioc);
mpi3mr_flush_delayed_cmd_lists(mrioc);
mpi3mr_flush_drv_cmds(mrioc);
}
/**
* mpi3mr_alloc_tgtdev - target device allocator
*
* Allocate target device instance and initialize the reference
* count
*
* Return: target device instance.
*/
static struct mpi3mr_tgt_dev *mpi3mr_alloc_tgtdev(void)
{
struct mpi3mr_tgt_dev *tgtdev;
tgtdev = kzalloc(sizeof(*tgtdev), GFP_ATOMIC);
if (!tgtdev)
return NULL;
kref_init(&tgtdev->ref_count);
return tgtdev;
}
/**
* mpi3mr_tgtdev_add_to_list -Add tgtdevice to the list
* @mrioc: Adapter instance reference
* @tgtdev: Target device
*
* Add the target device to the target device list
*
* Return: Nothing.
*/
static void mpi3mr_tgtdev_add_to_list(struct mpi3mr_ioc *mrioc,
struct mpi3mr_tgt_dev *tgtdev)
{
unsigned long flags;
spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
mpi3mr_tgtdev_get(tgtdev);
INIT_LIST_HEAD(&tgtdev->list);
list_add_tail(&tgtdev->list, &mrioc->tgtdev_list);
tgtdev->state = MPI3MR_DEV_CREATED;
spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);
}
/**
* mpi3mr_tgtdev_del_from_list -Delete tgtdevice from the list
* @mrioc: Adapter instance reference
* @tgtdev: Target device
* @must_delete: Must delete the target device from the list irrespective
* of the device state.
*
* Remove the target device from the target device list
*
* Return: Nothing.
*/
static void mpi3mr_tgtdev_del_from_list(struct mpi3mr_ioc *mrioc,
struct mpi3mr_tgt_dev *tgtdev, bool must_delete)
{
unsigned long flags;
spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
if ((tgtdev->state == MPI3MR_DEV_REMOVE_HS_STARTED) || (must_delete == true)) {
if (!list_empty(&tgtdev->list)) {
list_del_init(&tgtdev->list);
tgtdev->state = MPI3MR_DEV_DELETED;
mpi3mr_tgtdev_put(tgtdev);
}
}
spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);
}
/**
* __mpi3mr_get_tgtdev_by_handle -Get tgtdev from device handle
* @mrioc: Adapter instance reference
* @handle: Device handle
*
* Accessor to retrieve target device from the device handle.
* Non Lock version
*
* Return: Target device reference.
*/
static struct mpi3mr_tgt_dev *__mpi3mr_get_tgtdev_by_handle(
struct mpi3mr_ioc *mrioc, u16 handle)
{
struct mpi3mr_tgt_dev *tgtdev;
assert_spin_locked(&mrioc->tgtdev_lock);
list_for_each_entry(tgtdev, &mrioc->tgtdev_list, list)
if (tgtdev->dev_handle == handle)
goto found_tgtdev;
return NULL;
found_tgtdev:
mpi3mr_tgtdev_get(tgtdev);
return tgtdev;
}
/**
* mpi3mr_get_tgtdev_by_handle -Get tgtdev from device handle
* @mrioc: Adapter instance reference
* @handle: Device handle
*
* Accessor to retrieve target device from the device handle.
* Lock version
*
* Return: Target device reference.
*/
struct mpi3mr_tgt_dev *mpi3mr_get_tgtdev_by_handle(
struct mpi3mr_ioc *mrioc, u16 handle)
{
struct mpi3mr_tgt_dev *tgtdev;
unsigned long flags;
spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
tgtdev = __mpi3mr_get_tgtdev_by_handle(mrioc, handle);
spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);
return tgtdev;
}
/**
* __mpi3mr_get_tgtdev_by_perst_id -Get tgtdev from persist ID
* @mrioc: Adapter instance reference
* @persist_id: Persistent ID
*
* Accessor to retrieve target device from the Persistent ID.
* Non Lock version
*
* Return: Target device reference.
*/
static struct mpi3mr_tgt_dev *__mpi3mr_get_tgtdev_by_perst_id(
struct mpi3mr_ioc *mrioc, u16 persist_id)
{
struct mpi3mr_tgt_dev *tgtdev;
assert_spin_locked(&mrioc->tgtdev_lock);
list_for_each_entry(tgtdev, &mrioc->tgtdev_list, list)
if (tgtdev->perst_id == persist_id)
goto found_tgtdev;
return NULL;
found_tgtdev:
mpi3mr_tgtdev_get(tgtdev);
return tgtdev;
}
/**
* mpi3mr_get_tgtdev_by_perst_id -Get tgtdev from persistent ID
* @mrioc: Adapter instance reference
* @persist_id: Persistent ID
*
* Accessor to retrieve target device from the Persistent ID.
* Lock version
*
* Return: Target device reference.
*/
static struct mpi3mr_tgt_dev *mpi3mr_get_tgtdev_by_perst_id(
struct mpi3mr_ioc *mrioc, u16 persist_id)
{
struct mpi3mr_tgt_dev *tgtdev;
unsigned long flags;
spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
tgtdev = __mpi3mr_get_tgtdev_by_perst_id(mrioc, persist_id);
spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);
return tgtdev;
}
/**
* __mpi3mr_get_tgtdev_from_tgtpriv -Get tgtdev from tgt private
* @mrioc: Adapter instance reference
* @tgt_priv: Target private data
*
* Accessor to return target device from the target private
* data. Non Lock version
*
* Return: Target device reference.
*/
static struct mpi3mr_tgt_dev *__mpi3mr_get_tgtdev_from_tgtpriv(
struct mpi3mr_ioc *mrioc, struct mpi3mr_stgt_priv_data *tgt_priv)
{
struct mpi3mr_tgt_dev *tgtdev;
assert_spin_locked(&mrioc->tgtdev_lock);
tgtdev = tgt_priv->tgt_dev;
if (tgtdev)
mpi3mr_tgtdev_get(tgtdev);
return tgtdev;
}
/**
* mpi3mr_set_io_divert_for_all_vd_in_tg -set divert for TG VDs
* @mrioc: Adapter instance reference
* @tg: Throttle group information pointer
* @divert_value: 1 or 0
*
* Accessor to set io_divert flag for each device associated
* with the given throttle group with the given value.
*
* Return: None.
*/
static void mpi3mr_set_io_divert_for_all_vd_in_tg(struct mpi3mr_ioc *mrioc,
struct mpi3mr_throttle_group_info *tg, u8 divert_value)
{
unsigned long flags;
struct mpi3mr_tgt_dev *tgtdev;
struct mpi3mr_stgt_priv_data *tgt_priv;
spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
list_for_each_entry(tgtdev, &mrioc->tgtdev_list, list) {
if (tgtdev->starget && tgtdev->starget->hostdata) {
tgt_priv = tgtdev->starget->hostdata;
if (tgt_priv->throttle_group == tg)
tgt_priv->io_divert = divert_value;
}
}
spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);
}
/**
* mpi3mr_print_device_event_notice - print notice related to post processing of
* device event after controller reset.
*
* @mrioc: Adapter instance reference
* @device_add: true for device add event and false for device removal event
*
* Return: None.
*/
void mpi3mr_print_device_event_notice(struct mpi3mr_ioc *mrioc,
bool device_add)
{
ioc_notice(mrioc, "Device %s was in progress before the reset and\n",
(device_add ? "addition" : "removal"));
ioc_notice(mrioc, "completed after reset, verify whether the exposed devices\n");
ioc_notice(mrioc, "are matched with attached devices for correctness\n");
}
/**
* mpi3mr_remove_tgtdev_from_host - Remove dev from upper layers
* @mrioc: Adapter instance reference
* @tgtdev: Target device structure
*
* Checks whether the device is exposed to upper layers and if it
* is then remove the device from upper layers by calling
* scsi_remove_target().
*
* Return: 0 on success, non zero on failure.
*/
void mpi3mr_remove_tgtdev_from_host(struct mpi3mr_ioc *mrioc,
struct mpi3mr_tgt_dev *tgtdev)
{
struct mpi3mr_stgt_priv_data *tgt_priv;
ioc_info(mrioc, "%s :Removing handle(0x%04x), wwid(0x%016llx)\n",
__func__, tgtdev->dev_handle, (unsigned long long)tgtdev->wwid);
if (tgtdev->starget && tgtdev->starget->hostdata) {
tgt_priv = tgtdev->starget->hostdata;
atomic_set(&tgt_priv->block_io, 0);
tgt_priv->dev_handle = MPI3MR_INVALID_DEV_HANDLE;
}
if (!mrioc->sas_transport_enabled || (tgtdev->dev_type !=
MPI3_DEVICE_DEVFORM_SAS_SATA) || tgtdev->non_stl) {
if (tgtdev->starget) {
if (mrioc->current_event)
mrioc->current_event->pending_at_sml = 1;
scsi_remove_target(&tgtdev->starget->dev);
tgtdev->host_exposed = 0;
if (mrioc->current_event) {
mrioc->current_event->pending_at_sml = 0;
if (mrioc->current_event->discard) {
mpi3mr_print_device_event_notice(mrioc,
false);
return;
}
}
}
} else
mpi3mr_remove_tgtdev_from_sas_transport(mrioc, tgtdev);
mpi3mr_global_trigger(mrioc,
MPI3_DRIVER2_GLOBALTRIGGER_DEVICE_REMOVAL_ENABLED);
ioc_info(mrioc, "%s :Removed handle(0x%04x), wwid(0x%016llx)\n",
__func__, tgtdev->dev_handle, (unsigned long long)tgtdev->wwid);
}
/**
* mpi3mr_report_tgtdev_to_host - Expose device to upper layers
* @mrioc: Adapter instance reference
* @perst_id: Persistent ID of the device
*
* Checks whether the device can be exposed to upper layers and
* if it is not then expose the device to upper layers by
* calling scsi_scan_target().
*
* Return: 0 on success, non zero on failure.
*/
static int mpi3mr_report_tgtdev_to_host(struct mpi3mr_ioc *mrioc,
u16 perst_id)
{
int retval = 0;
struct mpi3mr_tgt_dev *tgtdev;
if (mrioc->reset_in_progress || mrioc->pci_err_recovery)
return -1;
tgtdev = mpi3mr_get_tgtdev_by_perst_id(mrioc, perst_id);
if (!tgtdev) {
retval = -1;
goto out;
}
if (tgtdev->is_hidden || tgtdev->host_exposed) {
retval = -1;
goto out;
}
if (!mrioc->sas_transport_enabled || (tgtdev->dev_type !=
MPI3_DEVICE_DEVFORM_SAS_SATA) || tgtdev->non_stl){
tgtdev->host_exposed = 1;
if (mrioc->current_event)
mrioc->current_event->pending_at_sml = 1;
scsi_scan_target(&mrioc->shost->shost_gendev,
mrioc->scsi_device_channel, tgtdev->perst_id,
SCAN_WILD_CARD, SCSI_SCAN_INITIAL);
if (!tgtdev->starget)
tgtdev->host_exposed = 0;
if (mrioc->current_event) {
mrioc->current_event->pending_at_sml = 0;
if (mrioc->current_event->discard) {
mpi3mr_print_device_event_notice(mrioc, true);
goto out;
}
}
} else
mpi3mr_report_tgtdev_to_sas_transport(mrioc, tgtdev);
out:
if (tgtdev)
mpi3mr_tgtdev_put(tgtdev);
return retval;
}
/**
* mpi3mr_change_queue_depth- Change QD callback handler
* @sdev: SCSI device reference
* @q_depth: Queue depth
*
* Validate and limit QD and call scsi_change_queue_depth.
*
* Return: return value of scsi_change_queue_depth
*/
static int mpi3mr_change_queue_depth(struct scsi_device *sdev,
int q_depth)
{
struct scsi_target *starget = scsi_target(sdev);
struct Scsi_Host *shost = dev_to_shost(&starget->dev);
int retval = 0;
if (!sdev->tagged_supported)
q_depth = 1;
if (q_depth > shost->can_queue)
q_depth = shost->can_queue;
else if (!q_depth)
q_depth = MPI3MR_DEFAULT_SDEV_QD;
retval = scsi_change_queue_depth(sdev, q_depth);
sdev->max_queue_depth = sdev->queue_depth;
return retval;
}
static void mpi3mr_configure_nvme_dev(struct mpi3mr_tgt_dev *tgt_dev,
struct queue_limits *lim)
{
u8 pgsz = tgt_dev->dev_spec.pcie_inf.pgsz ? : MPI3MR_DEFAULT_PGSZEXP;
lim->max_hw_sectors = tgt_dev->dev_spec.pcie_inf.mdts / 512;
lim->virt_boundary_mask = (1 << pgsz) - 1;
}
static void mpi3mr_configure_tgt_dev(struct mpi3mr_tgt_dev *tgt_dev,
struct queue_limits *lim)
{
if (tgt_dev->dev_type == MPI3_DEVICE_DEVFORM_PCIE &&
(tgt_dev->dev_spec.pcie_inf.dev_info &
MPI3_DEVICE0_PCIE_DEVICE_INFO_TYPE_MASK) ==
MPI3_DEVICE0_PCIE_DEVICE_INFO_TYPE_NVME_DEVICE)
mpi3mr_configure_nvme_dev(tgt_dev, lim);
}
/**
* mpi3mr_update_sdev - Update SCSI device information
* @sdev: SCSI device reference
* @data: target device reference
*
* This is an iterator function called for each SCSI device in a
* target to update the target specific information into each
* SCSI device.
*
* Return: Nothing.
*/
static void
mpi3mr_update_sdev(struct scsi_device *sdev, void *data)
{
struct mpi3mr_tgt_dev *tgtdev;
struct queue_limits lim;
tgtdev = (struct mpi3mr_tgt_dev *)data;
if (!tgtdev)
return;
mpi3mr_change_queue_depth(sdev, tgtdev->q_depth);
lim = queue_limits_start_update(sdev->request_queue);
mpi3mr_configure_tgt_dev(tgtdev, &lim);
WARN_ON_ONCE(queue_limits_commit_update(sdev->request_queue, &lim));
}
/**
* mpi3mr_refresh_tgtdevs - Refresh target device exposure
* @mrioc: Adapter instance reference
*
* This is executed post controller reset to identify any
* missing devices during reset and remove from the upper layers
* or expose any newly detected device to the upper layers.
*
* Return: Nothing.
*/
static void mpi3mr_refresh_tgtdevs(struct mpi3mr_ioc *mrioc)
{
struct mpi3mr_tgt_dev *tgtdev, *tgtdev_next;
struct mpi3mr_stgt_priv_data *tgt_priv;
dprint_reset(mrioc, "refresh target devices: check for removals\n");
list_for_each_entry_safe(tgtdev, tgtdev_next, &mrioc->tgtdev_list,
list) {
if (((tgtdev->dev_handle == MPI3MR_INVALID_DEV_HANDLE) ||
tgtdev->is_hidden) &&
tgtdev->host_exposed && tgtdev->starget &&
tgtdev->starget->hostdata) {
tgt_priv = tgtdev->starget->hostdata;
tgt_priv->dev_removed = 1;
atomic_set(&tgt_priv->block_io, 0);
}
}
list_for_each_entry_safe(tgtdev, tgtdev_next, &mrioc->tgtdev_list,
list) {
if (tgtdev->dev_handle == MPI3MR_INVALID_DEV_HANDLE) {
dprint_reset(mrioc, "removing target device with perst_id(%d)\n",
tgtdev->perst_id);
if (tgtdev->host_exposed)
mpi3mr_remove_tgtdev_from_host(mrioc, tgtdev);
mpi3mr_tgtdev_del_from_list(mrioc, tgtdev, true);
mpi3mr_tgtdev_put(tgtdev);
} else if (tgtdev->is_hidden & tgtdev->host_exposed) {
dprint_reset(mrioc, "hiding target device with perst_id(%d)\n",
tgtdev->perst_id);
mpi3mr_remove_tgtdev_from_host(mrioc, tgtdev);
}
}
tgtdev = NULL;
list_for_each_entry(tgtdev, &mrioc->tgtdev_list, list) {
if ((tgtdev->dev_handle != MPI3MR_INVALID_DEV_HANDLE) &&
!tgtdev->is_hidden) {
if (!tgtdev->host_exposed)
mpi3mr_report_tgtdev_to_host(mrioc,
tgtdev->perst_id);
else if (tgtdev->starget)
starget_for_each_device(tgtdev->starget,
(void *)tgtdev, mpi3mr_update_sdev);
}
}
}
/**
* mpi3mr_update_tgtdev - DevStatusChange evt bottomhalf
* @mrioc: Adapter instance reference
* @tgtdev: Target device internal structure
* @dev_pg0: New device page0
* @is_added: Flag to indicate the device is just added
*
* Update the information from the device page0 into the driver
* cached target device structure.
*
* Return: Nothing.
*/
static void mpi3mr_update_tgtdev(struct mpi3mr_ioc *mrioc,
struct mpi3mr_tgt_dev *tgtdev, struct mpi3_device_page0 *dev_pg0,
bool is_added)
{
u16 flags = 0;
struct mpi3mr_stgt_priv_data *scsi_tgt_priv_data = NULL;
struct mpi3mr_enclosure_node *enclosure_dev = NULL;
u8 prot_mask = 0;
tgtdev->perst_id = le16_to_cpu(dev_pg0->persistent_id);
tgtdev->dev_handle = le16_to_cpu(dev_pg0->dev_handle);
tgtdev->dev_type = dev_pg0->device_form;
tgtdev->io_unit_port = dev_pg0->io_unit_port;
tgtdev->encl_handle = le16_to_cpu(dev_pg0->enclosure_handle);
tgtdev->parent_handle = le16_to_cpu(dev_pg0->parent_dev_handle);
tgtdev->slot = le16_to_cpu(dev_pg0->slot);
tgtdev->q_depth = le16_to_cpu(dev_pg0->queue_depth);
tgtdev->wwid = le64_to_cpu(dev_pg0->wwid);
tgtdev->devpg0_flag = le16_to_cpu(dev_pg0->flags);
if (tgtdev->encl_handle)
enclosure_dev = mpi3mr_enclosure_find_by_handle(mrioc,
tgtdev->encl_handle);
if (enclosure_dev)
tgtdev->enclosure_logical_id = le64_to_cpu(
enclosure_dev->pg0.enclosure_logical_id);
flags = tgtdev->devpg0_flag;
tgtdev->is_hidden = (flags & MPI3_DEVICE0_FLAGS_HIDDEN);
if (is_added == true)
tgtdev->io_throttle_enabled =
(flags & MPI3_DEVICE0_FLAGS_IO_THROTTLING_REQUIRED) ? 1 : 0;
switch (flags & MPI3_DEVICE0_FLAGS_MAX_WRITE_SAME_MASK) {
case MPI3_DEVICE0_FLAGS_MAX_WRITE_SAME_256_LB:
tgtdev->wslen = MPI3MR_WRITE_SAME_MAX_LEN_256_BLKS;
break;
case MPI3_DEVICE0_FLAGS_MAX_WRITE_SAME_2048_LB:
tgtdev->wslen = MPI3MR_WRITE_SAME_MAX_LEN_2048_BLKS;
break;
case MPI3_DEVICE0_FLAGS_MAX_WRITE_SAME_NO_LIMIT:
default:
tgtdev->wslen = 0;
break;
}
if (tgtdev->starget && tgtdev->starget->hostdata) {
scsi_tgt_priv_data = (struct mpi3mr_stgt_priv_data *)
tgtdev->starget->hostdata;
scsi_tgt_priv_data->perst_id = tgtdev->perst_id;
scsi_tgt_priv_data->dev_handle = tgtdev->dev_handle;
scsi_tgt_priv_data->dev_type = tgtdev->dev_type;
scsi_tgt_priv_data->io_throttle_enabled =
tgtdev->io_throttle_enabled;
if (is_added == true)
atomic_set(&scsi_tgt_priv_data->block_io, 0);
scsi_tgt_priv_data->wslen = tgtdev->wslen;
}
switch (dev_pg0->access_status) {
case MPI3_DEVICE0_ASTATUS_NO_ERRORS:
case MPI3_DEVICE0_ASTATUS_PREPARE:
case MPI3_DEVICE0_ASTATUS_NEEDS_INITIALIZATION:
case MPI3_DEVICE0_ASTATUS_DEVICE_MISSING_DELAY:
break;
default:
tgtdev->is_hidden = 1;
break;
}
switch (tgtdev->dev_type) {
case MPI3_DEVICE_DEVFORM_SAS_SATA:
{
struct mpi3_device0_sas_sata_format *sasinf =
&dev_pg0->device_specific.sas_sata_format;
u16 dev_info = le16_to_cpu(sasinf->device_info);
tgtdev->dev_spec.sas_sata_inf.dev_info = dev_info;
tgtdev->dev_spec.sas_sata_inf.sas_address =
le64_to_cpu(sasinf->sas_address);
tgtdev->dev_spec.sas_sata_inf.phy_id = sasinf->phy_num;
tgtdev->dev_spec.sas_sata_inf.attached_phy_id =
sasinf->attached_phy_identifier;
if ((dev_info & MPI3_SAS_DEVICE_INFO_DEVICE_TYPE_MASK) !=
MPI3_SAS_DEVICE_INFO_DEVICE_TYPE_END_DEVICE)
tgtdev->is_hidden = 1;
else if (!(dev_info & (MPI3_SAS_DEVICE_INFO_STP_SATA_TARGET |
MPI3_SAS_DEVICE_INFO_SSP_TARGET)))
tgtdev->is_hidden = 1;
if (((tgtdev->devpg0_flag &
MPI3_DEVICE0_FLAGS_ATT_METHOD_DIR_ATTACHED)
&& (tgtdev->devpg0_flag &
MPI3_DEVICE0_FLAGS_ATT_METHOD_VIRTUAL)) ||
(tgtdev->parent_handle == 0xFFFF))
tgtdev->non_stl = 1;
if (tgtdev->dev_spec.sas_sata_inf.hba_port)
tgtdev->dev_spec.sas_sata_inf.hba_port->port_id =
dev_pg0->io_unit_port;
break;
}
case MPI3_DEVICE_DEVFORM_PCIE:
{
struct mpi3_device0_pcie_format *pcieinf =
&dev_pg0->device_specific.pcie_format;
u16 dev_info = le16_to_cpu(pcieinf->device_info);
tgtdev->dev_spec.pcie_inf.dev_info = dev_info;
tgtdev->dev_spec.pcie_inf.capb =
le32_to_cpu(pcieinf->capabilities);
tgtdev->dev_spec.pcie_inf.mdts = MPI3MR_DEFAULT_MDTS;
/* 2^12 = 4096 */
tgtdev->dev_spec.pcie_inf.pgsz = 12;
if (dev_pg0->access_status == MPI3_DEVICE0_ASTATUS_NO_ERRORS) {
tgtdev->dev_spec.pcie_inf.mdts =
le32_to_cpu(pcieinf->maximum_data_transfer_size);
tgtdev->dev_spec.pcie_inf.pgsz = pcieinf->page_size;
tgtdev->dev_spec.pcie_inf.reset_to =
max_t(u8, pcieinf->controller_reset_to,
MPI3MR_INTADMCMD_TIMEOUT);
tgtdev->dev_spec.pcie_inf.abort_to =
max_t(u8, pcieinf->nvme_abort_to,
MPI3MR_INTADMCMD_TIMEOUT);
}
if (tgtdev->dev_spec.pcie_inf.mdts > (1024 * 1024))
tgtdev->dev_spec.pcie_inf.mdts = (1024 * 1024);
if (((dev_info & MPI3_DEVICE0_PCIE_DEVICE_INFO_TYPE_MASK) !=
MPI3_DEVICE0_PCIE_DEVICE_INFO_TYPE_NVME_DEVICE) &&
((dev_info & MPI3_DEVICE0_PCIE_DEVICE_INFO_TYPE_MASK) !=
MPI3_DEVICE0_PCIE_DEVICE_INFO_TYPE_SCSI_DEVICE))
tgtdev->is_hidden = 1;
tgtdev->non_stl = 1;
if (!mrioc->shost)
break;
prot_mask = scsi_host_get_prot(mrioc->shost);
if (prot_mask & SHOST_DIX_TYPE0_PROTECTION) {
scsi_host_set_prot(mrioc->shost, prot_mask & 0x77);
ioc_info(mrioc,
"%s : Disabling DIX0 prot capability\n", __func__);
ioc_info(mrioc,
"because HBA does not support DIX0 operation on NVME drives\n");
}
break;
}
case MPI3_DEVICE_DEVFORM_VD:
{
struct mpi3_device0_vd_format *vdinf =
&dev_pg0->device_specific.vd_format;
struct mpi3mr_throttle_group_info *tg = NULL;
u16 vdinf_io_throttle_group =
le16_to_cpu(vdinf->io_throttle_group);
tgtdev->dev_spec.vd_inf.state = vdinf->vd_state;
if (vdinf->vd_state == MPI3_DEVICE0_VD_STATE_OFFLINE)
tgtdev->is_hidden = 1;
tgtdev->non_stl = 1;
tgtdev->dev_spec.vd_inf.tg_id = vdinf_io_throttle_group;
tgtdev->dev_spec.vd_inf.tg_high =
le16_to_cpu(vdinf->io_throttle_group_high) * 2048;
tgtdev->dev_spec.vd_inf.tg_low =
le16_to_cpu(vdinf->io_throttle_group_low) * 2048;
if (vdinf_io_throttle_group < mrioc->num_io_throttle_group) {
tg = mrioc->throttle_groups + vdinf_io_throttle_group;
tg->id = vdinf_io_throttle_group;
tg->high = tgtdev->dev_spec.vd_inf.tg_high;
tg->low = tgtdev->dev_spec.vd_inf.tg_low;
tg->qd_reduction =
tgtdev->dev_spec.vd_inf.tg_qd_reduction;
if (is_added == true)
tg->fw_qd = tgtdev->q_depth;
tg->modified_qd = tgtdev->q_depth;
}
tgtdev->dev_spec.vd_inf.tg = tg;
if (scsi_tgt_priv_data)
scsi_tgt_priv_data->throttle_group = tg;
break;
}
default:
break;
}
}
/**
* mpi3mr_devstatuschg_evt_bh - DevStatusChange evt bottomhalf
* @mrioc: Adapter instance reference
* @fwevt: Firmware event information.
*
* Process Device status Change event and based on device's new
* information, either expose the device to the upper layers, or
* remove the device from upper layers.
*
* Return: Nothing.
*/
static void mpi3mr_devstatuschg_evt_bh(struct mpi3mr_ioc *mrioc,
struct mpi3mr_fwevt *fwevt)
{
u16 dev_handle = 0;
u8 uhide = 0, delete = 0, cleanup = 0;
struct mpi3mr_tgt_dev *tgtdev = NULL;
struct mpi3_event_data_device_status_change *evtdata =
(struct mpi3_event_data_device_status_change *)fwevt->event_data;
dev_handle = le16_to_cpu(evtdata->dev_handle);
ioc_info(mrioc,
"%s :device status change: handle(0x%04x): reason code(0x%x)\n",
__func__, dev_handle, evtdata->reason_code);
switch (evtdata->reason_code) {
case MPI3_EVENT_DEV_STAT_RC_HIDDEN:
delete = 1;
break;
case MPI3_EVENT_DEV_STAT_RC_NOT_HIDDEN:
uhide = 1;
break;
case MPI3_EVENT_DEV_STAT_RC_VD_NOT_RESPONDING:
delete = 1;
cleanup = 1;
break;
default:
ioc_info(mrioc, "%s :Unhandled reason code(0x%x)\n", __func__,
evtdata->reason_code);
break;
}
tgtdev = mpi3mr_get_tgtdev_by_handle(mrioc, dev_handle);
if (!tgtdev)
goto out;
if (uhide) {
tgtdev->is_hidden = 0;
if (!tgtdev->host_exposed)
mpi3mr_report_tgtdev_to_host(mrioc, tgtdev->perst_id);
}
if (delete)
mpi3mr_remove_tgtdev_from_host(mrioc, tgtdev);
if (cleanup) {
mpi3mr_tgtdev_del_from_list(mrioc, tgtdev, false);
mpi3mr_tgtdev_put(tgtdev);
}
out:
if (tgtdev)
mpi3mr_tgtdev_put(tgtdev);
}
/**
* mpi3mr_devinfochg_evt_bh - DeviceInfoChange evt bottomhalf
* @mrioc: Adapter instance reference
* @dev_pg0: New device page0
*
* Process Device Info Change event and based on device's new
* information, either expose the device to the upper layers, or
* remove the device from upper layers or update the details of
* the device.
*
* Return: Nothing.
*/
static void mpi3mr_devinfochg_evt_bh(struct mpi3mr_ioc *mrioc,
struct mpi3_device_page0 *dev_pg0)
{
struct mpi3mr_tgt_dev *tgtdev = NULL;
u16 dev_handle = 0, perst_id = 0;
perst_id = le16_to_cpu(dev_pg0->persistent_id);
dev_handle = le16_to_cpu(dev_pg0->dev_handle);
ioc_info(mrioc,
"%s :Device info change: handle(0x%04x): persist_id(0x%x)\n",
__func__, dev_handle, perst_id);
tgtdev = mpi3mr_get_tgtdev_by_handle(mrioc, dev_handle);
if (!tgtdev)
goto out;
mpi3mr_update_tgtdev(mrioc, tgtdev, dev_pg0, false);
if (!tgtdev->is_hidden && !tgtdev->host_exposed)
mpi3mr_report_tgtdev_to_host(mrioc, perst_id);
if (tgtdev->is_hidden && tgtdev->host_exposed)
mpi3mr_remove_tgtdev_from_host(mrioc, tgtdev);
if (!tgtdev->is_hidden && tgtdev->host_exposed && tgtdev->starget)
starget_for_each_device(tgtdev->starget, (void *)tgtdev,
mpi3mr_update_sdev);
out:
if (tgtdev)
mpi3mr_tgtdev_put(tgtdev);
}
/**
* mpi3mr_free_enclosure_list - release enclosures
* @mrioc: Adapter instance reference
*
* Free memory allocated during encloure add.
*
* Return nothing.
*/
void mpi3mr_free_enclosure_list(struct mpi3mr_ioc *mrioc)
{
struct mpi3mr_enclosure_node *enclosure_dev, *enclosure_dev_next;
list_for_each_entry_safe(enclosure_dev,
enclosure_dev_next, &mrioc->enclosure_list, list) {
list_del(&enclosure_dev->list);
kfree(enclosure_dev);
}
}
/**
* mpi3mr_enclosure_find_by_handle - enclosure search by handle
* @mrioc: Adapter instance reference
* @handle: Firmware device handle of the enclosure
*
* This searches for enclosure device based on handle, then returns the
* enclosure object.
*
* Return: Enclosure object reference or NULL
*/
struct mpi3mr_enclosure_node *mpi3mr_enclosure_find_by_handle(
struct mpi3mr_ioc *mrioc, u16 handle)
{
struct mpi3mr_enclosure_node *enclosure_dev, *r = NULL;
list_for_each_entry(enclosure_dev, &mrioc->enclosure_list, list) {
if (le16_to_cpu(enclosure_dev->pg0.enclosure_handle) != handle)
continue;
r = enclosure_dev;
goto out;
}
out:
return r;
}
/**
* mpi3mr_process_trigger_data_event_bh - Process trigger event
* data
* @mrioc: Adapter instance reference
* @event_data: Event data
*
* This function releases diage buffers or issues diag fault
* based on trigger conditions
*
* Return: Nothing
*/
static void mpi3mr_process_trigger_data_event_bh(struct mpi3mr_ioc *mrioc,
struct trigger_event_data *event_data)
{
struct diag_buffer_desc *trace_hdb = event_data->trace_hdb;
struct diag_buffer_desc *fw_hdb = event_data->fw_hdb;
unsigned long flags;
int retval = 0;
u8 trigger_type = event_data->trigger_type;
union mpi3mr_trigger_data *trigger_data =
&event_data->trigger_specific_data;
if (event_data->snapdump) {
if (trace_hdb)
mpi3mr_set_trigger_data_in_hdb(trace_hdb, trigger_type,
trigger_data, 1);
if (fw_hdb)
mpi3mr_set_trigger_data_in_hdb(fw_hdb, trigger_type,
trigger_data, 1);
mpi3mr_soft_reset_handler(mrioc,
MPI3MR_RESET_FROM_TRIGGER, 1);
return;
}
if (trace_hdb) {
retval = mpi3mr_issue_diag_buf_release(mrioc, trace_hdb);
if (!retval) {
mpi3mr_set_trigger_data_in_hdb(trace_hdb, trigger_type,
trigger_data, 1);
}
spin_lock_irqsave(&mrioc->trigger_lock, flags);
mrioc->trace_release_trigger_active = false;
spin_unlock_irqrestore(&mrioc->trigger_lock, flags);
}
if (fw_hdb) {
retval = mpi3mr_issue_diag_buf_release(mrioc, fw_hdb);
if (!retval) {
mpi3mr_set_trigger_data_in_hdb(fw_hdb, trigger_type,
trigger_data, 1);
}
spin_lock_irqsave(&mrioc->trigger_lock, flags);
mrioc->fw_release_trigger_active = false;
spin_unlock_irqrestore(&mrioc->trigger_lock, flags);
}
}
/**
* mpi3mr_encldev_add_chg_evt_debug - debug for enclosure event
* @mrioc: Adapter instance reference
* @encl_pg0: Enclosure page 0.
* @is_added: Added event or not
*
* Return nothing.
*/
static void mpi3mr_encldev_add_chg_evt_debug(struct mpi3mr_ioc *mrioc,
struct mpi3_enclosure_page0 *encl_pg0, u8 is_added)
{
char *reason_str = NULL;
if (!(mrioc->logging_level & MPI3_DEBUG_EVENT_WORK_TASK))
return;
if (is_added)
reason_str = "enclosure added";
else
reason_str = "enclosure dev status changed";
ioc_info(mrioc,
"%s: handle(0x%04x), enclosure logical id(0x%016llx)\n",
reason_str, le16_to_cpu(encl_pg0->enclosure_handle),
(unsigned long long)le64_to_cpu(encl_pg0->enclosure_logical_id));
ioc_info(mrioc,
"number of slots(%d), port(%d), flags(0x%04x), present(%d)\n",
le16_to_cpu(encl_pg0->num_slots), encl_pg0->io_unit_port,
le16_to_cpu(encl_pg0->flags),
((le16_to_cpu(encl_pg0->flags) &
MPI3_ENCLS0_FLAGS_ENCL_DEV_PRESENT_MASK) >> 4));
}
/**
* mpi3mr_encldev_add_chg_evt_bh - Enclosure evt bottomhalf
* @mrioc: Adapter instance reference
* @fwevt: Firmware event reference
*
* Prints information about the Enclosure device status or
* Enclosure add events if logging is enabled and add or remove
* the enclosure from the controller's internal list of
* enclosures.
*
* Return: Nothing.
*/
static void mpi3mr_encldev_add_chg_evt_bh(struct mpi3mr_ioc *mrioc,
struct mpi3mr_fwevt *fwevt)
{
struct mpi3mr_enclosure_node *enclosure_dev = NULL;
struct mpi3_enclosure_page0 *encl_pg0;
u16 encl_handle;
u8 added, present;
encl_pg0 = (struct mpi3_enclosure_page0 *) fwevt->event_data;
added = (fwevt->event_id == MPI3_EVENT_ENCL_DEVICE_ADDED) ? 1 : 0;
mpi3mr_encldev_add_chg_evt_debug(mrioc, encl_pg0, added);
encl_handle = le16_to_cpu(encl_pg0->enclosure_handle);
present = ((le16_to_cpu(encl_pg0->flags) &
MPI3_ENCLS0_FLAGS_ENCL_DEV_PRESENT_MASK) >> 4);
if (encl_handle)
enclosure_dev = mpi3mr_enclosure_find_by_handle(mrioc,
encl_handle);
if (!enclosure_dev && present) {
enclosure_dev =
kzalloc(sizeof(struct mpi3mr_enclosure_node),
GFP_KERNEL);
if (!enclosure_dev)
return;
list_add_tail(&enclosure_dev->list,
&mrioc->enclosure_list);
}
if (enclosure_dev) {
if (!present) {
list_del(&enclosure_dev->list);
kfree(enclosure_dev);
} else
memcpy(&enclosure_dev->pg0, encl_pg0,
sizeof(enclosure_dev->pg0));
}
}
/**
* mpi3mr_sastopochg_evt_debug - SASTopoChange details
* @mrioc: Adapter instance reference
* @event_data: SAS topology change list event data
*
* Prints information about the SAS topology change event.
*
* Return: Nothing.
*/
static void
mpi3mr_sastopochg_evt_debug(struct mpi3mr_ioc *mrioc,
struct mpi3_event_data_sas_topology_change_list *event_data)
{
int i;
u16 handle;
u8 reason_code, phy_number;
char *status_str = NULL;
u8 link_rate, prev_link_rate;
switch (event_data->exp_status) {
case MPI3_EVENT_SAS_TOPO_ES_NOT_RESPONDING:
status_str = "remove";
break;
case MPI3_EVENT_SAS_TOPO_ES_RESPONDING:
status_str = "responding";
break;
case MPI3_EVENT_SAS_TOPO_ES_DELAY_NOT_RESPONDING:
status_str = "remove delay";
break;
case MPI3_EVENT_SAS_TOPO_ES_NO_EXPANDER:
status_str = "direct attached";
break;
default:
status_str = "unknown status";
break;
}
ioc_info(mrioc, "%s :sas topology change: (%s)\n",
__func__, status_str);
ioc_info(mrioc,
"%s :\texpander_handle(0x%04x), port(%d), enclosure_handle(0x%04x) start_phy(%02d), num_entries(%d)\n",
__func__, le16_to_cpu(event_data->expander_dev_handle),
event_data->io_unit_port,
le16_to_cpu(event_data->enclosure_handle),
event_data->start_phy_num, event_data->num_entries);
for (i = 0; i < event_data->num_entries; i++) {
handle = le16_to_cpu(event_data->phy_entry[i].attached_dev_handle);
if (!handle)
continue;
phy_number = event_data->start_phy_num + i;
reason_code = event_data->phy_entry[i].status &
MPI3_EVENT_SAS_TOPO_PHY_RC_MASK;
switch (reason_code) {
case MPI3_EVENT_SAS_TOPO_PHY_RC_TARG_NOT_RESPONDING:
status_str = "target remove";
break;
case MPI3_EVENT_SAS_TOPO_PHY_RC_DELAY_NOT_RESPONDING:
status_str = "delay target remove";
break;
case MPI3_EVENT_SAS_TOPO_PHY_RC_PHY_CHANGED:
status_str = "link status change";
break;
case MPI3_EVENT_SAS_TOPO_PHY_RC_NO_CHANGE:
status_str = "link status no change";
break;
case MPI3_EVENT_SAS_TOPO_PHY_RC_RESPONDING:
status_str = "target responding";
break;
default:
status_str = "unknown";
break;
}
link_rate = event_data->phy_entry[i].link_rate >> 4;
prev_link_rate = event_data->phy_entry[i].link_rate & 0xF;
ioc_info(mrioc,
"%s :\tphy(%02d), attached_handle(0x%04x): %s: link rate: new(0x%02x), old(0x%02x)\n",
__func__, phy_number, handle, status_str, link_rate,
prev_link_rate);
}
}
/**
* mpi3mr_sastopochg_evt_bh - SASTopologyChange evt bottomhalf
* @mrioc: Adapter instance reference
* @fwevt: Firmware event reference
*
* Prints information about the SAS topology change event and
* for "not responding" event code, removes the device from the
* upper layers.
*
* Return: Nothing.
*/
static void mpi3mr_sastopochg_evt_bh(struct mpi3mr_ioc *mrioc,
struct mpi3mr_fwevt *fwevt)
{
struct mpi3_event_data_sas_topology_change_list *event_data =
(struct mpi3_event_data_sas_topology_change_list *)fwevt->event_data;
int i;
u16 handle;
u8 reason_code;
u64 exp_sas_address = 0, parent_sas_address = 0;
struct mpi3mr_hba_port *hba_port = NULL;
struct mpi3mr_tgt_dev *tgtdev = NULL;
struct mpi3mr_sas_node *sas_expander = NULL;
unsigned long flags;
u8 link_rate, prev_link_rate, parent_phy_number;
mpi3mr_sastopochg_evt_debug(mrioc, event_data);
if (mrioc->sas_transport_enabled) {
hba_port = mpi3mr_get_hba_port_by_id(mrioc,
event_data->io_unit_port);
if (le16_to_cpu(event_data->expander_dev_handle)) {
spin_lock_irqsave(&mrioc->sas_node_lock, flags);
sas_expander = __mpi3mr_expander_find_by_handle(mrioc,
le16_to_cpu(event_data->expander_dev_handle));
if (sas_expander) {
exp_sas_address = sas_expander->sas_address;
hba_port = sas_expander->hba_port;
}
spin_unlock_irqrestore(&mrioc->sas_node_lock, flags);
parent_sas_address = exp_sas_address;
} else
parent_sas_address = mrioc->sas_hba.sas_address;
}
for (i = 0; i < event_data->num_entries; i++) {
if (fwevt->discard)
return;
handle = le16_to_cpu(event_data->phy_entry[i].attached_dev_handle);
if (!handle)
continue;
tgtdev = mpi3mr_get_tgtdev_by_handle(mrioc, handle);
if (!tgtdev)
continue;
reason_code = event_data->phy_entry[i].status &
MPI3_EVENT_SAS_TOPO_PHY_RC_MASK;
switch (reason_code) {
case MPI3_EVENT_SAS_TOPO_PHY_RC_TARG_NOT_RESPONDING:
if (tgtdev->host_exposed)
mpi3mr_remove_tgtdev_from_host(mrioc, tgtdev);
mpi3mr_tgtdev_del_from_list(mrioc, tgtdev, false);
mpi3mr_tgtdev_put(tgtdev);
break;
case MPI3_EVENT_SAS_TOPO_PHY_RC_RESPONDING:
case MPI3_EVENT_SAS_TOPO_PHY_RC_PHY_CHANGED:
case MPI3_EVENT_SAS_TOPO_PHY_RC_NO_CHANGE:
{
if (!mrioc->sas_transport_enabled || tgtdev->non_stl
|| tgtdev->is_hidden)
break;
link_rate = event_data->phy_entry[i].link_rate >> 4;
prev_link_rate = event_data->phy_entry[i].link_rate & 0xF;
if (link_rate == prev_link_rate)
break;
if (!parent_sas_address)
break;
parent_phy_number = event_data->start_phy_num + i;
mpi3mr_update_links(mrioc, parent_sas_address, handle,
parent_phy_number, link_rate, hba_port);
break;
}
default:
break;
}
if (tgtdev)
mpi3mr_tgtdev_put(tgtdev);
}
if (mrioc->sas_transport_enabled && (event_data->exp_status ==
MPI3_EVENT_SAS_TOPO_ES_NOT_RESPONDING)) {
if (sas_expander)
mpi3mr_expander_remove(mrioc, exp_sas_address,
hba_port);
}
}
/**
* mpi3mr_pcietopochg_evt_debug - PCIeTopoChange details
* @mrioc: Adapter instance reference
* @event_data: PCIe topology change list event data
*
* Prints information about the PCIe topology change event.
*
* Return: Nothing.
*/
static void
mpi3mr_pcietopochg_evt_debug(struct mpi3mr_ioc *mrioc,
struct mpi3_event_data_pcie_topology_change_list *event_data)
{
int i;
u16 handle;
u16 reason_code;
u8 port_number;
char *status_str = NULL;
u8 link_rate, prev_link_rate;
switch (event_data->switch_status) {
case MPI3_EVENT_PCIE_TOPO_SS_NOT_RESPONDING:
status_str = "remove";
break;
case MPI3_EVENT_PCIE_TOPO_SS_RESPONDING:
status_str = "responding";
break;
case MPI3_EVENT_PCIE_TOPO_SS_DELAY_NOT_RESPONDING:
status_str = "remove delay";
break;
case MPI3_EVENT_PCIE_TOPO_SS_NO_PCIE_SWITCH:
status_str = "direct attached";
break;
default:
status_str = "unknown status";
break;
}
ioc_info(mrioc, "%s :pcie topology change: (%s)\n",
__func__, status_str);
ioc_info(mrioc,
"%s :\tswitch_handle(0x%04x), enclosure_handle(0x%04x) start_port(%02d), num_entries(%d)\n",
__func__, le16_to_cpu(event_data->switch_dev_handle),
le16_to_cpu(event_data->enclosure_handle),
event_data->start_port_num, event_data->num_entries);
for (i = 0; i < event_data->num_entries; i++) {
handle =
le16_to_cpu(event_data->port_entry[i].attached_dev_handle);
if (!handle)
continue;
port_number = event_data->start_port_num + i;
reason_code = event_data->port_entry[i].port_status;
switch (reason_code) {
case MPI3_EVENT_PCIE_TOPO_PS_NOT_RESPONDING:
status_str = "target remove";
break;
case MPI3_EVENT_PCIE_TOPO_PS_DELAY_NOT_RESPONDING:
status_str = "delay target remove";
break;
case MPI3_EVENT_PCIE_TOPO_PS_PORT_CHANGED:
status_str = "link status change";
break;
case MPI3_EVENT_PCIE_TOPO_PS_NO_CHANGE:
status_str = "link status no change";
break;
case MPI3_EVENT_PCIE_TOPO_PS_RESPONDING:
status_str = "target responding";
break;
default:
status_str = "unknown";
break;
}
link_rate = event_data->port_entry[i].current_port_info &
MPI3_EVENT_PCIE_TOPO_PI_RATE_MASK;
prev_link_rate = event_data->port_entry[i].previous_port_info &
MPI3_EVENT_PCIE_TOPO_PI_RATE_MASK;
ioc_info(mrioc,
"%s :\tport(%02d), attached_handle(0x%04x): %s: link rate: new(0x%02x), old(0x%02x)\n",
__func__, port_number, handle, status_str, link_rate,
prev_link_rate);
}
}
/**
* mpi3mr_pcietopochg_evt_bh - PCIeTopologyChange evt bottomhalf
* @mrioc: Adapter instance reference
* @fwevt: Firmware event reference
*
* Prints information about the PCIe topology change event and
* for "not responding" event code, removes the device from the
* upper layers.
*
* Return: Nothing.
*/
static void mpi3mr_pcietopochg_evt_bh(struct mpi3mr_ioc *mrioc,
struct mpi3mr_fwevt *fwevt)
{
struct mpi3_event_data_pcie_topology_change_list *event_data =
(struct mpi3_event_data_pcie_topology_change_list *)fwevt->event_data;
int i;
u16 handle;
u8 reason_code;
struct mpi3mr_tgt_dev *tgtdev = NULL;
mpi3mr_pcietopochg_evt_debug(mrioc, event_data);
for (i = 0; i < event_data->num_entries; i++) {
if (fwevt->discard)
return;
handle =
le16_to_cpu(event_data->port_entry[i].attached_dev_handle);
if (!handle)
continue;
tgtdev = mpi3mr_get_tgtdev_by_handle(mrioc, handle);
if (!tgtdev)
continue;
reason_code = event_data->port_entry[i].port_status;
switch (reason_code) {
case MPI3_EVENT_PCIE_TOPO_PS_NOT_RESPONDING:
if (tgtdev->host_exposed)
mpi3mr_remove_tgtdev_from_host(mrioc, tgtdev);
mpi3mr_tgtdev_del_from_list(mrioc, tgtdev, false);
mpi3mr_tgtdev_put(tgtdev);
break;
default:
break;
}
if (tgtdev)
mpi3mr_tgtdev_put(tgtdev);
}
}
/**
* mpi3mr_logdata_evt_bh - Log data event bottomhalf
* @mrioc: Adapter instance reference
* @fwevt: Firmware event reference
*
* Extracts the event data and calls application interfacing
* function to process the event further.
*
* Return: Nothing.
*/
static void mpi3mr_logdata_evt_bh(struct mpi3mr_ioc *mrioc,
struct mpi3mr_fwevt *fwevt)
{
mpi3mr_app_save_logdata(mrioc, fwevt->event_data,
fwevt->event_data_size);
}
/**
* mpi3mr_update_sdev_qd - Update SCSI device queue depath
* @sdev: SCSI device reference
* @data: Queue depth reference
*
* This is an iterator function called for each SCSI device in a
* target to update the QD of each SCSI device.
*
* Return: Nothing.
*/
static void mpi3mr_update_sdev_qd(struct scsi_device *sdev, void *data)
{
u16 *q_depth = (u16 *)data;
scsi_change_queue_depth(sdev, (int)*q_depth);
sdev->max_queue_depth = sdev->queue_depth;
}
/**
* mpi3mr_set_qd_for_all_vd_in_tg -set QD for TG VDs
* @mrioc: Adapter instance reference
* @tg: Throttle group information pointer
*
* Accessor to reduce QD for each device associated with the
* given throttle group.
*
* Return: None.
*/
static void mpi3mr_set_qd_for_all_vd_in_tg(struct mpi3mr_ioc *mrioc,
struct mpi3mr_throttle_group_info *tg)
{
unsigned long flags;
struct mpi3mr_tgt_dev *tgtdev;
struct mpi3mr_stgt_priv_data *tgt_priv;
spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
list_for_each_entry(tgtdev, &mrioc->tgtdev_list, list) {
if (tgtdev->starget && tgtdev->starget->hostdata) {
tgt_priv = tgtdev->starget->hostdata;
if (tgt_priv->throttle_group == tg) {
dprint_event_bh(mrioc,
"updating qd due to throttling for persist_id(%d) original_qd(%d), reduced_qd (%d)\n",
tgt_priv->perst_id, tgtdev->q_depth,
tg->modified_qd);
starget_for_each_device(tgtdev->starget,
(void *)&tg->modified_qd,
mpi3mr_update_sdev_qd);
}
}
}
spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);
}
/**
* mpi3mr_fwevt_bh - Firmware event bottomhalf handler
* @mrioc: Adapter instance reference
* @fwevt: Firmware event reference
*
* Identifies the firmware event and calls corresponding bottomg
* half handler and sends event acknowledgment if required.
*
* Return: Nothing.
*/
static void mpi3mr_fwevt_bh(struct mpi3mr_ioc *mrioc,
struct mpi3mr_fwevt *fwevt)
{
struct mpi3_device_page0 *dev_pg0 = NULL;
u16 perst_id, handle, dev_info;
struct mpi3_device0_sas_sata_format *sasinf = NULL;
unsigned int timeout;
mpi3mr_fwevt_del_from_list(mrioc, fwevt);
mrioc->current_event = fwevt;
if (mrioc->stop_drv_processing)
goto out;
if (mrioc->unrecoverable) {
dprint_event_bh(mrioc,
"ignoring event(0x%02x) in bottom half handler due to unrecoverable controller\n",
fwevt->event_id);
goto out;
}
if (!fwevt->process_evt)
goto evt_ack;
switch (fwevt->event_id) {
case MPI3_EVENT_DEVICE_ADDED:
{
dev_pg0 = (struct mpi3_device_page0 *)fwevt->event_data;
perst_id = le16_to_cpu(dev_pg0->persistent_id);
handle = le16_to_cpu(dev_pg0->dev_handle);
if (perst_id != MPI3_DEVICE0_PERSISTENTID_INVALID)
mpi3mr_report_tgtdev_to_host(mrioc, perst_id);
else if (mrioc->sas_transport_enabled &&
(dev_pg0->device_form == MPI3_DEVICE_DEVFORM_SAS_SATA)) {
sasinf = &dev_pg0->device_specific.sas_sata_format;
dev_info = le16_to_cpu(sasinf->device_info);
if (!mrioc->sas_hba.num_phys)
mpi3mr_sas_host_add(mrioc);
else
mpi3mr_sas_host_refresh(mrioc);
if (mpi3mr_is_expander_device(dev_info))
mpi3mr_expander_add(mrioc, handle);
}
break;
}
case MPI3_EVENT_DEVICE_INFO_CHANGED:
{
dev_pg0 = (struct mpi3_device_page0 *)fwevt->event_data;
perst_id = le16_to_cpu(dev_pg0->persistent_id);
if (perst_id != MPI3_DEVICE0_PERSISTENTID_INVALID)
mpi3mr_devinfochg_evt_bh(mrioc, dev_pg0);
break;
}
case MPI3_EVENT_DEVICE_STATUS_CHANGE:
{
mpi3mr_devstatuschg_evt_bh(mrioc, fwevt);
break;
}
case MPI3_EVENT_ENCL_DEVICE_ADDED:
case MPI3_EVENT_ENCL_DEVICE_STATUS_CHANGE:
{
mpi3mr_encldev_add_chg_evt_bh(mrioc, fwevt);
break;
}
case MPI3_EVENT_SAS_TOPOLOGY_CHANGE_LIST:
{
mpi3mr_sastopochg_evt_bh(mrioc, fwevt);
break;
}
case MPI3_EVENT_PCIE_TOPOLOGY_CHANGE_LIST:
{
mpi3mr_pcietopochg_evt_bh(mrioc, fwevt);
break;
}
case MPI3_EVENT_LOG_DATA:
{
mpi3mr_logdata_evt_bh(mrioc, fwevt);
break;
}
case MPI3MR_DRIVER_EVENT_TG_QD_REDUCTION:
{
struct mpi3mr_throttle_group_info *tg;
tg = *(struct mpi3mr_throttle_group_info **)fwevt->event_data;
dprint_event_bh(mrioc,
"qd reduction event processed for tg_id(%d) reduction_needed(%d)\n",
tg->id, tg->need_qd_reduction);
if (tg->need_qd_reduction) {
mpi3mr_set_qd_for_all_vd_in_tg(mrioc, tg);
tg->need_qd_reduction = 0;
}
break;
}
case MPI3_EVENT_WAIT_FOR_DEVICES_TO_REFRESH:
{
timeout = MPI3MR_RESET_TIMEOUT * 2;
while ((mrioc->device_refresh_on || mrioc->block_on_pci_err) &&
!mrioc->unrecoverable && !mrioc->pci_err_recovery) {
msleep(500);
if (!timeout--) {
mrioc->unrecoverable = 1;
break;
}
}
if (mrioc->unrecoverable || mrioc->pci_err_recovery)
break;
dprint_event_bh(mrioc,
"scan for non responding and newly added devices after soft reset started\n");
if (mrioc->sas_transport_enabled) {
mpi3mr_refresh_sas_ports(mrioc);
mpi3mr_refresh_expanders(mrioc);
}
mpi3mr_refresh_tgtdevs(mrioc);
ioc_info(mrioc,
"scan for non responding and newly added devices after soft reset completed\n");
break;
}
case MPI3MR_DRIVER_EVENT_PROCESS_TRIGGER:
{
mpi3mr_process_trigger_data_event_bh(mrioc,
(struct trigger_event_data *)fwevt->event_data);
break;
}
default:
break;
}
evt_ack:
if (fwevt->send_ack)
mpi3mr_process_event_ack(mrioc, fwevt->event_id,
fwevt->evt_ctx);
out:
/* Put fwevt reference count to neutralize kref_init increment */
mpi3mr_fwevt_put(fwevt);
mrioc->current_event = NULL;
}
/**
* mpi3mr_fwevt_worker - Firmware event worker
* @work: Work struct containing firmware event
*
* Extracts the firmware event and calls mpi3mr_fwevt_bh.
*
* Return: Nothing.
*/
static void mpi3mr_fwevt_worker(struct work_struct *work)
{
struct mpi3mr_fwevt *fwevt = container_of(work, struct mpi3mr_fwevt,
work);
mpi3mr_fwevt_bh(fwevt->mrioc, fwevt);
/*
* Put fwevt reference count after
* dequeuing it from worker queue
*/
mpi3mr_fwevt_put(fwevt);
}
/**
* mpi3mr_create_tgtdev - Create and add a target device
* @mrioc: Adapter instance reference
* @dev_pg0: Device Page 0 data
*
* If the device specified by the device page 0 data is not
* present in the driver's internal list, allocate the memory
* for the device, populate the data and add to the list, else
* update the device data. The key is persistent ID.
*
* Return: 0 on success, -ENOMEM on memory allocation failure
*/
static int mpi3mr_create_tgtdev(struct mpi3mr_ioc *mrioc,
struct mpi3_device_page0 *dev_pg0)
{
int retval = 0;
struct mpi3mr_tgt_dev *tgtdev = NULL;
u16 perst_id = 0;
unsigned long flags;
perst_id = le16_to_cpu(dev_pg0->persistent_id);
if (perst_id == MPI3_DEVICE0_PERSISTENTID_INVALID)
return retval;
spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
tgtdev = __mpi3mr_get_tgtdev_by_perst_id(mrioc, perst_id);
if (tgtdev)
tgtdev->state = MPI3MR_DEV_CREATED;
spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);
if (tgtdev) {
mpi3mr_update_tgtdev(mrioc, tgtdev, dev_pg0, true);
mpi3mr_tgtdev_put(tgtdev);
} else {
tgtdev = mpi3mr_alloc_tgtdev();
if (!tgtdev)
return -ENOMEM;
mpi3mr_update_tgtdev(mrioc, tgtdev, dev_pg0, true);
mpi3mr_tgtdev_add_to_list(mrioc, tgtdev);
}
return retval;
}
/**
* mpi3mr_flush_delayed_cmd_lists - Flush pending commands
* @mrioc: Adapter instance reference
*
* Flush pending commands in the delayed lists due to a
* controller reset or driver removal as a cleanup.
*
* Return: Nothing
*/
void mpi3mr_flush_delayed_cmd_lists(struct mpi3mr_ioc *mrioc)
{
struct delayed_dev_rmhs_node *_rmhs_node;
struct delayed_evt_ack_node *_evtack_node;
dprint_reset(mrioc, "flushing delayed dev_remove_hs commands\n");
while (!list_empty(&mrioc->delayed_rmhs_list)) {
_rmhs_node = list_entry(mrioc->delayed_rmhs_list.next,
struct delayed_dev_rmhs_node, list);
list_del(&_rmhs_node->list);
kfree(_rmhs_node);
}
dprint_reset(mrioc, "flushing delayed event ack commands\n");
while (!list_empty(&mrioc->delayed_evtack_cmds_list)) {
_evtack_node = list_entry(mrioc->delayed_evtack_cmds_list.next,
struct delayed_evt_ack_node, list);
list_del(&_evtack_node->list);
kfree(_evtack_node);
}
}
/**
* mpi3mr_dev_rmhs_complete_iou - Device removal IOUC completion
* @mrioc: Adapter instance reference
* @drv_cmd: Internal command tracker
*
* Issues a target reset TM to the firmware from the device
* removal TM pend list or retry the removal handshake sequence
* based on the IOU control request IOC status.
*
* Return: Nothing
*/
static void mpi3mr_dev_rmhs_complete_iou(struct mpi3mr_ioc *mrioc,
struct mpi3mr_drv_cmd *drv_cmd)
{
u16 cmd_idx = drv_cmd->host_tag - MPI3MR_HOSTTAG_DEVRMCMD_MIN;
struct delayed_dev_rmhs_node *delayed_dev_rmhs = NULL;
if (drv_cmd->state & MPI3MR_CMD_RESET)
goto clear_drv_cmd;
ioc_info(mrioc,
"%s :dev_rmhs_iouctrl_complete:handle(0x%04x), ioc_status(0x%04x), loginfo(0x%08x)\n",
__func__, drv_cmd->dev_handle, drv_cmd->ioc_status,
drv_cmd->ioc_loginfo);
if (drv_cmd->ioc_status != MPI3_IOCSTATUS_SUCCESS) {
if (drv_cmd->retry_count < MPI3MR_DEV_RMHS_RETRY_COUNT) {
drv_cmd->retry_count++;
ioc_info(mrioc,
"%s :dev_rmhs_iouctrl_complete: handle(0x%04x)retrying handshake retry=%d\n",
__func__, drv_cmd->dev_handle,
drv_cmd->retry_count);
mpi3mr_dev_rmhs_send_tm(mrioc, drv_cmd->dev_handle,
drv_cmd, drv_cmd->iou_rc);
return;
}
ioc_err(mrioc,
"%s :dev removal handshake failed after all retries: handle(0x%04x)\n",
__func__, drv_cmd->dev_handle);
} else {
ioc_info(mrioc,
"%s :dev removal handshake completed successfully: handle(0x%04x)\n",
__func__, drv_cmd->dev_handle);
clear_bit(drv_cmd->dev_handle, mrioc->removepend_bitmap);
}
if (!list_empty(&mrioc->delayed_rmhs_list)) {
delayed_dev_rmhs = list_entry(mrioc->delayed_rmhs_list.next,
struct delayed_dev_rmhs_node, list);
drv_cmd->dev_handle = delayed_dev_rmhs->handle;
drv_cmd->retry_count = 0;
drv_cmd->iou_rc = delayed_dev_rmhs->iou_rc;
ioc_info(mrioc,
"%s :dev_rmhs_iouctrl_complete: processing delayed TM: handle(0x%04x)\n",
__func__, drv_cmd->dev_handle);
mpi3mr_dev_rmhs_send_tm(mrioc, drv_cmd->dev_handle, drv_cmd,
drv_cmd->iou_rc);
list_del(&delayed_dev_rmhs->list);
kfree(delayed_dev_rmhs);
return;
}
clear_drv_cmd:
drv_cmd->state = MPI3MR_CMD_NOTUSED;
drv_cmd->callback = NULL;
drv_cmd->retry_count = 0;
drv_cmd->dev_handle = MPI3MR_INVALID_DEV_HANDLE;
clear_bit(cmd_idx, mrioc->devrem_bitmap);
}
/**
* mpi3mr_dev_rmhs_complete_tm - Device removal TM completion
* @mrioc: Adapter instance reference
* @drv_cmd: Internal command tracker
*
* Issues a target reset TM to the firmware from the device
* removal TM pend list or issue IO unit control request as
* part of device removal or hidden acknowledgment handshake.
*
* Return: Nothing
*/
static void mpi3mr_dev_rmhs_complete_tm(struct mpi3mr_ioc *mrioc,
struct mpi3mr_drv_cmd *drv_cmd)
{
struct mpi3_iounit_control_request iou_ctrl;
u16 cmd_idx = drv_cmd->host_tag - MPI3MR_HOSTTAG_DEVRMCMD_MIN;
struct mpi3_scsi_task_mgmt_reply *tm_reply = NULL;
int retval;
if (drv_cmd->state & MPI3MR_CMD_RESET)
goto clear_drv_cmd;
if (drv_cmd->state & MPI3MR_CMD_REPLY_VALID)
tm_reply = (struct mpi3_scsi_task_mgmt_reply *)drv_cmd->reply;
if (tm_reply)
pr_info(IOCNAME
"dev_rmhs_tr_complete:handle(0x%04x), ioc_status(0x%04x), loginfo(0x%08x), term_count(%d)\n",
mrioc->name, drv_cmd->dev_handle, drv_cmd->ioc_status,
drv_cmd->ioc_loginfo,
le32_to_cpu(tm_reply->termination_count));
pr_info(IOCNAME "Issuing IOU CTL: handle(0x%04x) dev_rmhs idx(%d)\n",
mrioc->name, drv_cmd->dev_handle, cmd_idx);
memset(&iou_ctrl, 0, sizeof(iou_ctrl));
drv_cmd->state = MPI3MR_CMD_PENDING;
drv_cmd->is_waiting = 0;
drv_cmd->callback = mpi3mr_dev_rmhs_complete_iou;
iou_ctrl.operation = drv_cmd->iou_rc;
iou_ctrl.param16[0] = cpu_to_le16(drv_cmd->dev_handle);
iou_ctrl.host_tag = cpu_to_le16(drv_cmd->host_tag);
iou_ctrl.function = MPI3_FUNCTION_IO_UNIT_CONTROL;
retval = mpi3mr_admin_request_post(mrioc, &iou_ctrl, sizeof(iou_ctrl),
1);
if (retval) {
pr_err(IOCNAME "Issue DevRmHsTMIOUCTL: Admin post failed\n",
mrioc->name);
goto clear_drv_cmd;
}
return;
clear_drv_cmd:
drv_cmd->state = MPI3MR_CMD_NOTUSED;
drv_cmd->callback = NULL;
drv_cmd->dev_handle = MPI3MR_INVALID_DEV_HANDLE;
drv_cmd->retry_count = 0;
clear_bit(cmd_idx, mrioc->devrem_bitmap);
}
/**
* mpi3mr_dev_rmhs_send_tm - Issue TM for device removal
* @mrioc: Adapter instance reference
* @handle: Device handle
* @cmdparam: Internal command tracker
* @iou_rc: IO unit reason code
*
* Issues a target reset TM to the firmware or add it to a pend
* list as part of device removal or hidden acknowledgment
* handshake.
*
* Return: Nothing
*/
static void mpi3mr_dev_rmhs_send_tm(struct mpi3mr_ioc *mrioc, u16 handle,
struct mpi3mr_drv_cmd *cmdparam, u8 iou_rc)
{
struct mpi3_scsi_task_mgmt_request tm_req;
int retval = 0;
u16 cmd_idx = MPI3MR_NUM_DEVRMCMD;
u8 retrycount = 5;
struct mpi3mr_drv_cmd *drv_cmd = cmdparam;
struct delayed_dev_rmhs_node *delayed_dev_rmhs = NULL;
struct mpi3mr_tgt_dev *tgtdev = NULL;
unsigned long flags;
spin_lock_irqsave(&mrioc->tgtdev_lock, flags);
tgtdev = __mpi3mr_get_tgtdev_by_handle(mrioc, handle);
if (tgtdev && (iou_rc == MPI3_CTRL_OP_REMOVE_DEVICE))
tgtdev->state = MPI3MR_DEV_REMOVE_HS_STARTED;
spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags);
if (drv_cmd)
goto issue_cmd;
do {
cmd_idx = find_first_zero_bit(mrioc->devrem_bitmap,
MPI3MR_NUM_DEVRMCMD);
if (cmd_idx < MPI3MR_NUM_DEVRMCMD) {
if (!test_and_set_bit(cmd_idx, mrioc->devrem_bitmap))
break;
cmd_idx = MPI3MR_NUM_DEVRMCMD;
}
} while (retrycount--);
if (cmd_idx >= MPI3MR_NUM_DEVRMCMD) {
delayed_dev_rmhs = kzalloc(sizeof(*delayed_dev_rmhs),
GFP_ATOMIC);
if (!delayed_dev_rmhs)
return;
INIT_LIST_HEAD(&delayed_dev_rmhs->list);
delayed_dev_rmhs->handle = handle;
delayed_dev_rmhs->iou_rc = iou_rc;
list_add_tail(&delayed_dev_rmhs->list,
&mrioc->delayed_rmhs_list);
ioc_info(mrioc, "%s :DevRmHs: tr:handle(0x%04x) is postponed\n",
__func__, handle);
return;
}
drv_cmd = &mrioc->dev_rmhs_cmds[cmd_idx];
issue_cmd:
cmd_idx = drv_cmd->host_tag - MPI3MR_HOSTTAG_DEVRMCMD_MIN;
ioc_info(mrioc,
"%s :Issuing TR TM: for devhandle 0x%04x with dev_rmhs %d\n",
__func__, handle, cmd_idx);
memset(&tm_req, 0, sizeof(tm_req));
if (drv_cmd->state & MPI3MR_CMD_PENDING) {
ioc_err(mrioc, "%s :Issue TM: Command is in use\n", __func__);
goto out;
}
drv_cmd->state = MPI3MR_CMD_PENDING;
drv_cmd->is_waiting = 0;
drv_cmd->callback = mpi3mr_dev_rmhs_complete_tm;
drv_cmd->dev_handle = handle;
drv_cmd->iou_rc = iou_rc;
tm_req.dev_handle = cpu_to_le16(handle);
tm_req.task_type = MPI3_SCSITASKMGMT_TASKTYPE_TARGET_RESET;
tm_req.host_tag = cpu_to_le16(drv_cmd->host_tag);
tm_req.task_host_tag = cpu_to_le16(MPI3MR_HOSTTAG_INVALID);
tm_req.function = MPI3_FUNCTION_SCSI_TASK_MGMT;
set_bit(handle, mrioc->removepend_bitmap);
retval = mpi3mr_admin_request_post(mrioc, &tm_req, sizeof(tm_req), 1);
if (retval) {
ioc_err(mrioc, "%s :Issue DevRmHsTM: Admin Post failed\n",
__func__);
goto out_failed;
}
out:
return;
out_failed:
drv_cmd->state = MPI3MR_CMD_NOTUSED;
drv_cmd->callback = NULL;
drv_cmd->dev_handle = MPI3MR_INVALID_DEV_HANDLE;
drv_cmd->retry_count = 0;
clear_bit(cmd_idx, mrioc->devrem_bitmap);
}
/**
* mpi3mr_complete_evt_ack - event ack request completion
* @mrioc: Adapter instance reference
* @drv_cmd: Internal command tracker
*
* This is the completion handler for non blocking event
* acknowledgment sent to the firmware and this will issue any
* pending event acknowledgment request.
*
* Return: Nothing
*/
static void mpi3mr_complete_evt_ack(struct mpi3mr_ioc *mrioc,
struct mpi3mr_drv_cmd *drv_cmd)
{
u16 cmd_idx = drv_cmd->host_tag - MPI3MR_HOSTTAG_EVTACKCMD_MIN;
struct delayed_evt_ack_node *delayed_evtack = NULL;
if (drv_cmd->state & MPI3MR_CMD_RESET)
goto clear_drv_cmd;
if (drv_cmd->ioc_status != MPI3_IOCSTATUS_SUCCESS) {
dprint_event_th(mrioc,
"immediate event ack failed with ioc_status(0x%04x) log_info(0x%08x)\n",
(drv_cmd->ioc_status & MPI3_IOCSTATUS_STATUS_MASK),
drv_cmd->ioc_loginfo);
}
if (!list_empty(&mrioc->delayed_evtack_cmds_list)) {
delayed_evtack =
list_entry(mrioc->delayed_evtack_cmds_list.next,
struct delayed_evt_ack_node, list);
mpi3mr_send_event_ack(mrioc, delayed_evtack->event, drv_cmd,
delayed_evtack->event_ctx);
list_del(&delayed_evtack->list);
kfree(delayed_evtack);
return;
}
clear_drv_cmd:
drv_cmd->state = MPI3MR_CMD_NOTUSED;
drv_cmd->callback = NULL;
clear_bit(cmd_idx, mrioc->evtack_cmds_bitmap);
}
/**
* mpi3mr_send_event_ack - Issue event acknwoledgment request
* @mrioc: Adapter instance reference
* @event: MPI3 event id
* @cmdparam: Internal command tracker
* @event_ctx: event context
*
* Issues event acknowledgment request to the firmware if there
* is a free command to send the event ack else it to a pend
* list so that it will be processed on a completion of a prior
* event acknowledgment .
*
* Return: Nothing
*/
static void mpi3mr_send_event_ack(struct mpi3mr_ioc *mrioc, u8 event,
struct mpi3mr_drv_cmd *cmdparam, u32 event_ctx)
{
struct mpi3_event_ack_request evtack_req;
int retval = 0;
u8 retrycount = 5;
u16 cmd_idx = MPI3MR_NUM_EVTACKCMD;
struct mpi3mr_drv_cmd *drv_cmd = cmdparam;
struct delayed_evt_ack_node *delayed_evtack = NULL;
if (drv_cmd) {
dprint_event_th(mrioc,
"sending delayed event ack in the top half for event(0x%02x), event_ctx(0x%08x)\n",
event, event_ctx);
goto issue_cmd;
}
dprint_event_th(mrioc,
"sending event ack in the top half for event(0x%02x), event_ctx(0x%08x)\n",
event, event_ctx);
do {
cmd_idx = find_first_zero_bit(mrioc->evtack_cmds_bitmap,
MPI3MR_NUM_EVTACKCMD);
if (cmd_idx < MPI3MR_NUM_EVTACKCMD) {
if (!test_and_set_bit(cmd_idx,
mrioc->evtack_cmds_bitmap))
break;
cmd_idx = MPI3MR_NUM_EVTACKCMD;
}
} while (retrycount--);
if (cmd_idx >= MPI3MR_NUM_EVTACKCMD) {
delayed_evtack = kzalloc(sizeof(*delayed_evtack),
GFP_ATOMIC);
if (!delayed_evtack)
return;
INIT_LIST_HEAD(&delayed_evtack->list);
delayed_evtack->event = event;
delayed_evtack->event_ctx = event_ctx;
list_add_tail(&delayed_evtack->list,
&mrioc->delayed_evtack_cmds_list);
dprint_event_th(mrioc,
"event ack in the top half for event(0x%02x), event_ctx(0x%08x) is postponed\n",
event, event_ctx);
return;
}
drv_cmd = &mrioc->evtack_cmds[cmd_idx];
issue_cmd:
cmd_idx = drv_cmd->host_tag - MPI3MR_HOSTTAG_EVTACKCMD_MIN;
memset(&evtack_req, 0, sizeof(evtack_req));
if (drv_cmd->state & MPI3MR_CMD_PENDING) {
dprint_event_th(mrioc,
"sending event ack failed due to command in use\n");
goto out;
}
drv_cmd->state = MPI3MR_CMD_PENDING;
drv_cmd->is_waiting = 0;
drv_cmd->callback = mpi3mr_complete_evt_ack;
evtack_req.host_tag = cpu_to_le16(drv_cmd->host_tag);
evtack_req.function = MPI3_FUNCTION_EVENT_ACK;
evtack_req.event = event;
evtack_req.event_context = cpu_to_le32(event_ctx);
retval = mpi3mr_admin_request_post(mrioc, &evtack_req,
sizeof(evtack_req), 1);
if (retval) {
dprint_event_th(mrioc,
"posting event ack request is failed\n");
goto out_failed;
}
dprint_event_th(mrioc,
"event ack in the top half for event(0x%02x), event_ctx(0x%08x) is posted\n",
event, event_ctx);
out:
return;
out_failed:
drv_cmd->state = MPI3MR_CMD_NOTUSED;
drv_cmd->callback = NULL;
clear_bit(cmd_idx, mrioc->evtack_cmds_bitmap);
}
/**
* mpi3mr_pcietopochg_evt_th - PCIETopologyChange evt tophalf
* @mrioc: Adapter instance reference
* @event_reply: event data
*
* Checks for the reason code and based on that either block I/O
* to device, or unblock I/O to the device, or start the device
* removal handshake with reason as remove with the firmware for
* PCIe devices.
*
* Return: Nothing
*/
static void mpi3mr_pcietopochg_evt_th(struct mpi3mr_ioc *mrioc,
struct mpi3_event_notification_reply *event_reply)
{
struct mpi3_event_data_pcie_topology_change_list *topo_evt =
(struct mpi3_event_data_pcie_topology_change_list *)event_reply->event_data;
int i;
u16 handle;
u8 reason_code;
struct mpi3mr_tgt_dev *tgtdev = NULL;
struct mpi3mr_stgt_priv_data *scsi_tgt_priv_data = NULL;
for (i = 0; i < topo_evt->num_entries; i++) {
handle = le16_to_cpu(topo_evt->port_entry[i].attached_dev_handle);
if (!handle)
continue;
reason_code = topo_evt->port_entry[i].port_status;
scsi_tgt_priv_data = NULL;
tgtdev = mpi3mr_get_tgtdev_by_handle(mrioc, handle);
if (tgtdev && tgtdev->starget && tgtdev->starget->hostdata)
scsi_tgt_priv_data = (struct mpi3mr_stgt_priv_data *)
tgtdev->starget->hostdata;
switch (reason_code) {
case MPI3_EVENT_PCIE_TOPO_PS_NOT_RESPONDING:
if (scsi_tgt_priv_data) {
scsi_tgt_priv_data->dev_removed = 1;
scsi_tgt_priv_data->dev_removedelay = 0;
atomic_set(&scsi_tgt_priv_data->block_io, 0);
}
mpi3mr_dev_rmhs_send_tm(mrioc, handle, NULL,
MPI3_CTRL_OP_REMOVE_DEVICE);
break;
case MPI3_EVENT_PCIE_TOPO_PS_DELAY_NOT_RESPONDING:
if (scsi_tgt_priv_data) {
scsi_tgt_priv_data->dev_removedelay = 1;
atomic_inc(&scsi_tgt_priv_data->block_io);
}
break;
case MPI3_EVENT_PCIE_TOPO_PS_RESPONDING:
if (scsi_tgt_priv_data &&
scsi_tgt_priv_data->dev_removedelay) {
scsi_tgt_priv_data->dev_removedelay = 0;
atomic_dec_if_positive
(&scsi_tgt_priv_data->block_io);
}
break;
case MPI3_EVENT_PCIE_TOPO_PS_PORT_CHANGED:
default:
break;
}
if (tgtdev)
mpi3mr_tgtdev_put(tgtdev);
}
}
/**
* mpi3mr_sastopochg_evt_th - SASTopologyChange evt tophalf
* @mrioc: Adapter instance reference
* @event_reply: event data
*
* Checks for the reason code and based on that either block I/O
* to device, or unblock I/O to the device, or start the device
* removal handshake with reason as remove with the firmware for
* SAS/SATA devices.
*
* Return: Nothing
*/
static void mpi3mr_sastopochg_evt_th(struct mpi3mr_ioc *mrioc,
struct mpi3_event_notification_reply *event_reply)
{
struct mpi3_event_data_sas_topology_change_list *topo_evt =
(struct mpi3_event_data_sas_topology_change_list *)event_reply->event_data;
int i;
u16 handle;
u8 reason_code;
struct mpi3mr_tgt_dev *tgtdev = NULL;
struct mpi3mr_stgt_priv_data *scsi_tgt_priv_data = NULL;
for (i = 0; i < topo_evt->num_entries; i++) {
handle = le16_to_cpu(topo_evt->phy_entry[i].attached_dev_handle);
if (!handle)
continue;
reason_code = topo_evt->phy_entry[i].status &
MPI3_EVENT_SAS_TOPO_PHY_RC_MASK;
scsi_tgt_priv_data = NULL;
tgtdev = mpi3mr_get_tgtdev_by_handle(mrioc, handle);
if (tgtdev && tgtdev->starget && tgtdev->starget->hostdata)
scsi_tgt_priv_data = (struct mpi3mr_stgt_priv_data *)
tgtdev->starget->hostdata;
switch (reason_code) {
case MPI3_EVENT_SAS_TOPO_PHY_RC_TARG_NOT_RESPONDING:
if (scsi_tgt_priv_data) {
scsi_tgt_priv_data->dev_removed = 1;
scsi_tgt_priv_data->dev_removedelay = 0;
atomic_set(&scsi_tgt_priv_data->block_io, 0);
}
mpi3mr_dev_rmhs_send_tm(mrioc, handle, NULL,
MPI3_CTRL_OP_REMOVE_DEVICE);
break;
case MPI3_EVENT_SAS_TOPO_PHY_RC_DELAY_NOT_RESPONDING:
if (scsi_tgt_priv_data) {
scsi_tgt_priv_data->dev_removedelay = 1;
atomic_inc(&scsi_tgt_priv_data->block_io);
}
break;
case MPI3_EVENT_SAS_TOPO_PHY_RC_RESPONDING:
if (scsi_tgt_priv_data &&
scsi_tgt_priv_data->dev_removedelay) {
scsi_tgt_priv_data->dev_removedelay = 0;
atomic_dec_if_positive
(&scsi_tgt_priv_data->block_io);
}
break;
case MPI3_EVENT_SAS_TOPO_PHY_RC_PHY_CHANGED:
default:
break;
}
if (tgtdev)
mpi3mr_tgtdev_put(tgtdev);
}
}
/**
* mpi3mr_devstatuschg_evt_th - DeviceStatusChange evt tophalf
* @mrioc: Adapter instance reference
* @event_reply: event data
*
* Checks for the reason code and based on that either block I/O
* to device, or unblock I/O to the device, or start the device
* removal handshake with reason as remove/hide acknowledgment
* with the firmware.
*
* Return: Nothing
*/
static void mpi3mr_devstatuschg_evt_th(struct mpi3mr_ioc *mrioc,
struct mpi3_event_notification_reply *event_reply)
{
u16 dev_handle = 0;
u8 ublock = 0, block = 0, hide = 0, delete = 0, remove = 0;
struct mpi3mr_tgt_dev *tgtdev = NULL;
struct mpi3mr_stgt_priv_data *scsi_tgt_priv_data = NULL;
struct mpi3_event_data_device_status_change *evtdata =
(struct mpi3_event_data_device_status_change *)event_reply->event_data;
if (mrioc->stop_drv_processing)
goto out;
dev_handle = le16_to_cpu(evtdata->dev_handle);
switch (evtdata->reason_code) {
case MPI3_EVENT_DEV_STAT_RC_INT_DEVICE_RESET_STRT:
case MPI3_EVENT_DEV_STAT_RC_INT_IT_NEXUS_RESET_STRT:
block = 1;
break;
case MPI3_EVENT_DEV_STAT_RC_HIDDEN:
delete = 1;
hide = 1;
break;
case MPI3_EVENT_DEV_STAT_RC_VD_NOT_RESPONDING:
delete = 1;
remove = 1;
break;
case MPI3_EVENT_DEV_STAT_RC_INT_DEVICE_RESET_CMP:
case MPI3_EVENT_DEV_STAT_RC_INT_IT_NEXUS_RESET_CMP:
ublock = 1;
break;
default:
break;
}
tgtdev = mpi3mr_get_tgtdev_by_handle(mrioc, dev_handle);
if (!tgtdev)
goto out;
if (hide)
tgtdev->is_hidden = hide;
if (tgtdev->starget && tgtdev->starget->hostdata) {
scsi_tgt_priv_data = (struct mpi3mr_stgt_priv_data *)
tgtdev->starget->hostdata;
if (block)
atomic_inc(&scsi_tgt_priv_data->block_io);
if (delete)
scsi_tgt_priv_data->dev_removed = 1;
if (ublock)
atomic_dec_if_positive(&scsi_tgt_priv_data->block_io);
}
if (remove)
mpi3mr_dev_rmhs_send_tm(mrioc, dev_handle, NULL,
MPI3_CTRL_OP_REMOVE_DEVICE);
if (hide)
mpi3mr_dev_rmhs_send_tm(mrioc, dev_handle, NULL,
MPI3_CTRL_OP_HIDDEN_ACK);
out:
if (tgtdev)
mpi3mr_tgtdev_put(tgtdev);
}
/**
* mpi3mr_preparereset_evt_th - Prepare for reset event tophalf
* @mrioc: Adapter instance reference
* @event_reply: event data
*
* Blocks and unblocks host level I/O based on the reason code
*
* Return: Nothing
*/
static void mpi3mr_preparereset_evt_th(struct mpi3mr_ioc *mrioc,
struct mpi3_event_notification_reply *event_reply)
{
struct mpi3_event_data_prepare_for_reset *evtdata =
(struct mpi3_event_data_prepare_for_reset *)event_reply->event_data;
if (evtdata->reason_code == MPI3_EVENT_PREPARE_RESET_RC_START) {
dprint_event_th(mrioc,
"prepare for reset event top half with rc=start\n");
if (mrioc->prepare_for_reset)
return;
mrioc->prepare_for_reset = 1;
mrioc->prepare_for_reset_timeout_counter = 0;
} else if (evtdata->reason_code == MPI3_EVENT_PREPARE_RESET_RC_ABORT) {
dprint_event_th(mrioc,
"prepare for reset top half with rc=abort\n");
mrioc->prepare_for_reset = 0;
mrioc->prepare_for_reset_timeout_counter = 0;
}
if ((event_reply->msg_flags & MPI3_EVENT_NOTIFY_MSGFLAGS_ACK_MASK)
== MPI3_EVENT_NOTIFY_MSGFLAGS_ACK_REQUIRED)
mpi3mr_send_event_ack(mrioc, event_reply->event, NULL,
le32_to_cpu(event_reply->event_context));
}
/**
* mpi3mr_energypackchg_evt_th - Energy pack change evt tophalf
* @mrioc: Adapter instance reference
* @event_reply: event data
*
* Identifies the new shutdown timeout value and update.
*
* Return: Nothing
*/
static void mpi3mr_energypackchg_evt_th(struct mpi3mr_ioc *mrioc,
struct mpi3_event_notification_reply *event_reply)
{
struct mpi3_event_data_energy_pack_change *evtdata =
(struct mpi3_event_data_energy_pack_change *)event_reply->event_data;
u16 shutdown_timeout = le16_to_cpu(evtdata->shutdown_timeout);
if (shutdown_timeout <= 0) {
ioc_warn(mrioc,
"%s :Invalid Shutdown Timeout received = %d\n",
__func__, shutdown_timeout);
return;
}
ioc_info(mrioc,
"%s :Previous Shutdown Timeout Value = %d New Shutdown Timeout Value = %d\n",
__func__, mrioc->facts.shutdown_timeout, shutdown_timeout);
mrioc->facts.shutdown_timeout = shutdown_timeout;
}
/**
* mpi3mr_cablemgmt_evt_th - Cable management event tophalf
* @mrioc: Adapter instance reference
* @event_reply: event data
*
* Displays Cable manegemt event details.
*
* Return: Nothing
*/
static void mpi3mr_cablemgmt_evt_th(struct mpi3mr_ioc *mrioc,
struct mpi3_event_notification_reply *event_reply)
{
struct mpi3_event_data_cable_management *evtdata =
(struct mpi3_event_data_cable_management *)event_reply->event_data;
switch (evtdata->status) {
case MPI3_EVENT_CABLE_MGMT_STATUS_INSUFFICIENT_POWER:
{
ioc_info(mrioc, "An active cable with receptacle_id %d cannot be powered.\n"
"Devices connected to this cable are not detected.\n"
"This cable requires %d mW of power.\n",
evtdata->receptacle_id,
le32_to_cpu(evtdata->active_cable_power_requirement));
break;
}
case MPI3_EVENT_CABLE_MGMT_STATUS_DEGRADED:
{
ioc_info(mrioc, "A cable with receptacle_id %d is not running at optimal speed\n",
evtdata->receptacle_id);
break;
}
default:
break;
}
}
/**
* mpi3mr_add_event_wait_for_device_refresh - Add Wait for Device Refresh Event
* @mrioc: Adapter instance reference
*
* Add driver specific event to make sure that the driver won't process the
* events until all the devices are refreshed during soft reset.
*
* Return: Nothing
*/
void mpi3mr_add_event_wait_for_device_refresh(struct mpi3mr_ioc *mrioc)
{
struct mpi3mr_fwevt *fwevt = NULL;
fwevt = mpi3mr_alloc_fwevt(0);
if (!fwevt) {
dprint_event_th(mrioc,
"failed to schedule bottom half handler for event(0x%02x)\n",
MPI3_EVENT_WAIT_FOR_DEVICES_TO_REFRESH);
return;
}
fwevt->mrioc = mrioc;
fwevt->event_id = MPI3_EVENT_WAIT_FOR_DEVICES_TO_REFRESH;
fwevt->send_ack = 0;
fwevt->process_evt = 1;
fwevt->evt_ctx = 0;
fwevt->event_data_size = 0;
mpi3mr_fwevt_add_to_list(mrioc, fwevt);
}
/**
* mpi3mr_os_handle_events - Firmware event handler
* @mrioc: Adapter instance reference
* @event_reply: event data
*
* Identify whteher the event has to handled and acknowledged
* and either process the event in the tophalf and/or schedule a
* bottom half through mpi3mr_fwevt_worker.
*
* Return: Nothing
*/
void mpi3mr_os_handle_events(struct mpi3mr_ioc *mrioc,
struct mpi3_event_notification_reply *event_reply)
{
u16 evt_type, sz;
struct mpi3mr_fwevt *fwevt = NULL;
bool ack_req = 0, process_evt_bh = 0;
if (mrioc->stop_drv_processing)
return;
if ((event_reply->msg_flags & MPI3_EVENT_NOTIFY_MSGFLAGS_ACK_MASK)
== MPI3_EVENT_NOTIFY_MSGFLAGS_ACK_REQUIRED)
ack_req = 1;
evt_type = event_reply->event;
mpi3mr_event_trigger(mrioc, event_reply->event);
switch (evt_type) {
case MPI3_EVENT_DEVICE_ADDED:
{
struct mpi3_device_page0 *dev_pg0 =
(struct mpi3_device_page0 *)event_reply->event_data;
if (mpi3mr_create_tgtdev(mrioc, dev_pg0))
ioc_err(mrioc,
"%s :Failed to add device in the device add event\n",
__func__);
else
process_evt_bh = 1;
break;
}
case MPI3_EVENT_DEVICE_STATUS_CHANGE:
{
process_evt_bh = 1;
mpi3mr_devstatuschg_evt_th(mrioc, event_reply);
break;
}
case MPI3_EVENT_SAS_TOPOLOGY_CHANGE_LIST:
{
process_evt_bh = 1;
mpi3mr_sastopochg_evt_th(mrioc, event_reply);
break;
}
case MPI3_EVENT_PCIE_TOPOLOGY_CHANGE_LIST:
{
process_evt_bh = 1;
mpi3mr_pcietopochg_evt_th(mrioc, event_reply);
break;
}
case MPI3_EVENT_PREPARE_FOR_RESET:
{
mpi3mr_preparereset_evt_th(mrioc, event_reply);
ack_req = 0;
break;
}
case MPI3_EVENT_DIAGNOSTIC_BUFFER_STATUS_CHANGE:
{
mpi3mr_hdbstatuschg_evt_th(mrioc, event_reply);
break;
}
case MPI3_EVENT_DEVICE_INFO_CHANGED:
case MPI3_EVENT_LOG_DATA:
case MPI3_EVENT_ENCL_DEVICE_STATUS_CHANGE:
case MPI3_EVENT_ENCL_DEVICE_ADDED:
{
process_evt_bh = 1;
break;
}
case MPI3_EVENT_ENERGY_PACK_CHANGE:
{
mpi3mr_energypackchg_evt_th(mrioc, event_reply);
break;
}
case MPI3_EVENT_CABLE_MGMT:
{
mpi3mr_cablemgmt_evt_th(mrioc, event_reply);
break;
}
case MPI3_EVENT_SAS_DISCOVERY:
case MPI3_EVENT_SAS_DEVICE_DISCOVERY_ERROR:
case MPI3_EVENT_SAS_BROADCAST_PRIMITIVE:
case MPI3_EVENT_PCIE_ENUMERATION:
break;
default:
ioc_info(mrioc, "%s :event 0x%02x is not handled\n",
__func__, evt_type);
break;
}
if (process_evt_bh || ack_req) {
sz = event_reply->event_data_length * 4;
fwevt = mpi3mr_alloc_fwevt(sz);
if (!fwevt) {
ioc_info(mrioc, "%s :failure at %s:%d/%s()!\n",
__func__, __FILE__, __LINE__, __func__);
return;
}
memcpy(fwevt->event_data, event_reply->event_data, sz);
fwevt->mrioc = mrioc;
fwevt->event_id = evt_type;
fwevt->send_ack = ack_req;
fwevt->process_evt = process_evt_bh;
fwevt->evt_ctx = le32_to_cpu(event_reply->event_context);
mpi3mr_fwevt_add_to_list(mrioc, fwevt);
}
}
/**
* mpi3mr_setup_eedp - Setup EEDP information in MPI3 SCSI IO
* @mrioc: Adapter instance reference
* @scmd: SCSI command reference
* @scsiio_req: MPI3 SCSI IO request
*
* Identifies the protection information flags from the SCSI
* command and set appropriate flags in the MPI3 SCSI IO
* request.
*
* Return: Nothing
*/
static void mpi3mr_setup_eedp(struct mpi3mr_ioc *mrioc,
struct scsi_cmnd *scmd, struct mpi3_scsi_io_request *scsiio_req)
{
u16 eedp_flags = 0;
unsigned char prot_op = scsi_get_prot_op(scmd);
switch (prot_op) {
case SCSI_PROT_NORMAL:
return;
case SCSI_PROT_READ_STRIP:
eedp_flags = MPI3_EEDPFLAGS_EEDP_OP_CHECK_REMOVE;
break;
case SCSI_PROT_WRITE_INSERT:
eedp_flags = MPI3_EEDPFLAGS_EEDP_OP_INSERT;
break;
case SCSI_PROT_READ_INSERT:
eedp_flags = MPI3_EEDPFLAGS_EEDP_OP_INSERT;
scsiio_req->msg_flags |= MPI3_SCSIIO_MSGFLAGS_METASGL_VALID;
break;
case SCSI_PROT_WRITE_STRIP:
eedp_flags = MPI3_EEDPFLAGS_EEDP_OP_CHECK_REMOVE;
scsiio_req->msg_flags |= MPI3_SCSIIO_MSGFLAGS_METASGL_VALID;
break;
case SCSI_PROT_READ_PASS:
eedp_flags = MPI3_EEDPFLAGS_EEDP_OP_CHECK;
scsiio_req->msg_flags |= MPI3_SCSIIO_MSGFLAGS_METASGL_VALID;
break;
case SCSI_PROT_WRITE_PASS:
if (scmd->prot_flags & SCSI_PROT_IP_CHECKSUM) {
eedp_flags = MPI3_EEDPFLAGS_EEDP_OP_CHECK_REGEN;
scsiio_req->sgl[0].eedp.application_tag_translation_mask =
0xffff;
} else
eedp_flags = MPI3_EEDPFLAGS_EEDP_OP_CHECK;
scsiio_req->msg_flags |= MPI3_SCSIIO_MSGFLAGS_METASGL_VALID;
break;
default:
return;
}
if (scmd->prot_flags & SCSI_PROT_GUARD_CHECK)
eedp_flags |= MPI3_EEDPFLAGS_CHK_GUARD;
if (scmd->prot_flags & SCSI_PROT_IP_CHECKSUM)
eedp_flags |= MPI3_EEDPFLAGS_HOST_GUARD_IP_CHKSUM;
if (scmd->prot_flags & SCSI_PROT_REF_CHECK) {
eedp_flags |= MPI3_EEDPFLAGS_CHK_REF_TAG |
MPI3_EEDPFLAGS_INCR_PRI_REF_TAG;
scsiio_req->cdb.eedp32.primary_reference_tag =
cpu_to_be32(scsi_prot_ref_tag(scmd));
}
if (scmd->prot_flags & SCSI_PROT_REF_INCREMENT)
eedp_flags |= MPI3_EEDPFLAGS_INCR_PRI_REF_TAG;
eedp_flags |= MPI3_EEDPFLAGS_ESC_MODE_APPTAG_DISABLE;
switch (scsi_prot_interval(scmd)) {
case 512:
scsiio_req->sgl[0].eedp.user_data_size = MPI3_EEDP_UDS_512;
break;
case 520:
scsiio_req->sgl[0].eedp.user_data_size = MPI3_EEDP_UDS_520;
break;
case 4080:
scsiio_req->sgl[0].eedp.user_data_size = MPI3_EEDP_UDS_4080;
break;
case 4088:
scsiio_req->sgl[0].eedp.user_data_size = MPI3_EEDP_UDS_4088;
break;
case 4096:
scsiio_req->sgl[0].eedp.user_data_size = MPI3_EEDP_UDS_4096;
break;
case 4104:
scsiio_req->sgl[0].eedp.user_data_size = MPI3_EEDP_UDS_4104;
break;
case 4160:
scsiio_req->sgl[0].eedp.user_data_size = MPI3_EEDP_UDS_4160;
break;
default:
break;
}
scsiio_req->sgl[0].eedp.eedp_flags = cpu_to_le16(eedp_flags);
scsiio_req->sgl[0].eedp.flags = MPI3_SGE_FLAGS_ELEMENT_TYPE_EXTENDED;
}
/**
* mpi3mr_build_sense_buffer - Map sense information
* @desc: Sense type
* @buf: Sense buffer to populate
* @key: Sense key
* @asc: Additional sense code
* @ascq: Additional sense code qualifier
*
* Maps the given sense information into either descriptor or
* fixed format sense data.
*
* Return: Nothing
*/
static inline void mpi3mr_build_sense_buffer(int desc, u8 *buf, u8 key,
u8 asc, u8 ascq)
{
if (desc) {
buf[0] = 0x72; /* descriptor, current */
buf[1] = key;
buf[2] = asc;
buf[3] = ascq;
buf[7] = 0;
} else {
buf[0] = 0x70; /* fixed, current */
buf[2] = key;
buf[7] = 0xa;
buf[12] = asc;
buf[13] = ascq;
}
}
/**
* mpi3mr_map_eedp_error - Map EEDP errors from IOC status
* @scmd: SCSI command reference
* @ioc_status: status of MPI3 request
*
* Maps the EEDP error status of the SCSI IO request to sense
* data.
*
* Return: Nothing
*/
static void mpi3mr_map_eedp_error(struct scsi_cmnd *scmd,
u16 ioc_status)
{
u8 ascq = 0;
switch (ioc_status) {
case MPI3_IOCSTATUS_EEDP_GUARD_ERROR:
ascq = 0x01;
break;
case MPI3_IOCSTATUS_EEDP_APP_TAG_ERROR:
ascq = 0x02;
break;
case MPI3_IOCSTATUS_EEDP_REF_TAG_ERROR:
ascq = 0x03;
break;
default:
ascq = 0x00;
break;
}
mpi3mr_build_sense_buffer(0, scmd->sense_buffer, ILLEGAL_REQUEST,
0x10, ascq);
scmd->result = (DID_ABORT << 16) | SAM_STAT_CHECK_CONDITION;
}
/**
* mpi3mr_process_op_reply_desc - reply descriptor handler
* @mrioc: Adapter instance reference
* @reply_desc: Operational reply descriptor
* @reply_dma: place holder for reply DMA address
* @qidx: Operational queue index
*
* Process the operational reply descriptor and identifies the
* descriptor type. Based on the descriptor map the MPI3 request
* status to a SCSI command status and calls scsi_done call
* back.
*