summaryrefslogtreecommitdiffstats
path: root/drivers/scsi/mpt2sas/mpt2sas_ctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/mpt2sas/mpt2sas_ctl.c')
-rw-r--r--drivers/scsi/mpt2sas/mpt2sas_ctl.c2516
1 files changed, 2516 insertions, 0 deletions
diff --git a/drivers/scsi/mpt2sas/mpt2sas_ctl.c b/drivers/scsi/mpt2sas/mpt2sas_ctl.c
new file mode 100644
index 000000000000..4fbe3f83319b
--- /dev/null
+++ b/drivers/scsi/mpt2sas/mpt2sas_ctl.c
@@ -0,0 +1,2516 @@
+/*
+ * Management Module Support for MPT (Message Passing Technology) based
+ * controllers
+ *
+ * This code is based on drivers/scsi/mpt2sas/mpt2_ctl.c
+ * Copyright (C) 2007-2008 LSI Corporation
+ * (mailto:DL-MPTFusionLinux@lsi.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * NO WARRANTY
+ * THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+ * LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+ * solely responsible for determining the appropriateness of using and
+ * distributing the Program and assumes all risks associated with its
+ * exercise of rights under this Agreement, including but not limited to
+ * the risks and costs of program errors, damage to or loss of data,
+ * programs or equipment, and unavailability or interruption of operations.
+
+ * DISCLAIMER OF LIABILITY
+ * NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+ * HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/smp_lock.h>
+#include <linux/compat.h>
+#include <linux/poll.h>
+
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#include "mpt2sas_base.h"
+#include "mpt2sas_ctl.h"
+
+static struct fasync_struct *async_queue;
+static DECLARE_WAIT_QUEUE_HEAD(ctl_poll_wait);
+
+/**
+ * enum block_state - blocking state
+ * @NON_BLOCKING: non blocking
+ * @BLOCKING: blocking
+ *
+ * These states are for ioctls that need to wait for a response
+ * from firmware, so they probably require sleep.
+ */
+enum block_state {
+ NON_BLOCKING,
+ BLOCKING,
+};
+
+#ifdef CONFIG_SCSI_MPT2SAS_LOGGING
+/**
+ * _ctl_display_some_debug - debug routine
+ * @ioc: per adapter object
+ * @smid: system request message index
+ * @calling_function_name: string pass from calling function
+ * @mpi_reply: reply message frame
+ * Context: none.
+ *
+ * Function for displaying debug info helpfull when debugging issues
+ * in this module.
+ */
+static void
+_ctl_display_some_debug(struct MPT2SAS_ADAPTER *ioc, u16 smid,
+ char *calling_function_name, MPI2DefaultReply_t *mpi_reply)
+{
+ Mpi2ConfigRequest_t *mpi_request;
+ char *desc = NULL;
+
+ if (!(ioc->logging_level & MPT_DEBUG_IOCTL))
+ return;
+
+ mpi_request = mpt2sas_base_get_msg_frame(ioc, smid);
+ switch (mpi_request->Function) {
+ case MPI2_FUNCTION_SCSI_IO_REQUEST:
+ {
+ Mpi2SCSIIORequest_t *scsi_request =
+ (Mpi2SCSIIORequest_t *)mpi_request;
+
+ snprintf(ioc->tmp_string, MPT_STRING_LENGTH,
+ "scsi_io, cmd(0x%02x), cdb_len(%d)",
+ scsi_request->CDB.CDB32[0],
+ le16_to_cpu(scsi_request->IoFlags) & 0xF);
+ desc = ioc->tmp_string;
+ break;
+ }
+ case MPI2_FUNCTION_SCSI_TASK_MGMT:
+ desc = "task_mgmt";
+ break;
+ case MPI2_FUNCTION_IOC_INIT:
+ desc = "ioc_init";
+ break;
+ case MPI2_FUNCTION_IOC_FACTS:
+ desc = "ioc_facts";
+ break;
+ case MPI2_FUNCTION_CONFIG:
+ {
+ Mpi2ConfigRequest_t *config_request =
+ (Mpi2ConfigRequest_t *)mpi_request;
+
+ snprintf(ioc->tmp_string, MPT_STRING_LENGTH,
+ "config, type(0x%02x), ext_type(0x%02x), number(%d)",
+ (config_request->Header.PageType &
+ MPI2_CONFIG_PAGETYPE_MASK), config_request->ExtPageType,
+ config_request->Header.PageNumber);
+ desc = ioc->tmp_string;
+ break;
+ }
+ case MPI2_FUNCTION_PORT_FACTS:
+ desc = "port_facts";
+ break;
+ case MPI2_FUNCTION_PORT_ENABLE:
+ desc = "port_enable";
+ break;
+ case MPI2_FUNCTION_EVENT_NOTIFICATION:
+ desc = "event_notification";
+ break;
+ case MPI2_FUNCTION_FW_DOWNLOAD:
+ desc = "fw_download";
+ break;
+ case MPI2_FUNCTION_FW_UPLOAD:
+ desc = "fw_upload";
+ break;
+ case MPI2_FUNCTION_RAID_ACTION:
+ desc = "raid_action";
+ break;
+ case MPI2_FUNCTION_RAID_SCSI_IO_PASSTHROUGH:
+ {
+ Mpi2SCSIIORequest_t *scsi_request =
+ (Mpi2SCSIIORequest_t *)mpi_request;
+
+ snprintf(ioc->tmp_string, MPT_STRING_LENGTH,
+ "raid_pass, cmd(0x%02x), cdb_len(%d)",
+ scsi_request->CDB.CDB32[0],
+ le16_to_cpu(scsi_request->IoFlags) & 0xF);
+ desc = ioc->tmp_string;
+ break;
+ }
+ case MPI2_FUNCTION_SAS_IO_UNIT_CONTROL:
+ desc = "sas_iounit_cntl";
+ break;
+ case MPI2_FUNCTION_SATA_PASSTHROUGH:
+ desc = "sata_pass";
+ break;
+ case MPI2_FUNCTION_DIAG_BUFFER_POST:
+ desc = "diag_buffer_post";
+ break;
+ case MPI2_FUNCTION_DIAG_RELEASE:
+ desc = "diag_release";
+ break;
+ case MPI2_FUNCTION_SMP_PASSTHROUGH:
+ desc = "smp_passthrough";
+ break;
+ }
+
+ if (!desc)
+ return;
+
+ printk(MPT2SAS_DEBUG_FMT "%s: %s, smid(%d)\n",
+ ioc->name, calling_function_name, desc, smid);
+
+ if (!mpi_reply)
+ return;
+
+ if (mpi_reply->IOCStatus || mpi_reply->IOCLogInfo)
+ printk(MPT2SAS_DEBUG_FMT
+ "\tiocstatus(0x%04x), loginfo(0x%08x)\n",
+ ioc->name, le16_to_cpu(mpi_reply->IOCStatus),
+ le32_to_cpu(mpi_reply->IOCLogInfo));
+
+ if (mpi_request->Function == MPI2_FUNCTION_SCSI_IO_REQUEST ||
+ mpi_request->Function ==
+ MPI2_FUNCTION_RAID_SCSI_IO_PASSTHROUGH) {
+ Mpi2SCSIIOReply_t *scsi_reply =
+ (Mpi2SCSIIOReply_t *)mpi_reply;
+ if (scsi_reply->SCSIState || scsi_reply->SCSIStatus)
+ printk(MPT2SAS_DEBUG_FMT
+ "\tscsi_state(0x%02x), scsi_status"
+ "(0x%02x)\n", ioc->name,
+ scsi_reply->SCSIState,
+ scsi_reply->SCSIStatus);
+ }
+}
+#endif
+
+/**
+ * mpt2sas_ctl_done - ctl module completion routine
+ * @ioc: per adapter object
+ * @smid: system request message index
+ * @VF_ID: virtual function id
+ * @reply: reply message frame(lower 32bit addr)
+ * Context: none.
+ *
+ * The callback handler when using ioc->ctl_cb_idx.
+ *
+ * Return nothing.
+ */
+void
+mpt2sas_ctl_done(struct MPT2SAS_ADAPTER *ioc, u16 smid, u8 VF_ID, u32 reply)
+{
+ MPI2DefaultReply_t *mpi_reply;
+
+ if (ioc->ctl_cmds.status == MPT2_CMD_NOT_USED)
+ return;
+ if (ioc->ctl_cmds.smid != smid)
+ return;
+ ioc->ctl_cmds.status |= MPT2_CMD_COMPLETE;
+ mpi_reply = mpt2sas_base_get_reply_virt_addr(ioc, reply);
+ if (mpi_reply) {
+ memcpy(ioc->ctl_cmds.reply, mpi_reply, mpi_reply->MsgLength*4);
+ ioc->ctl_cmds.status |= MPT2_CMD_REPLY_VALID;
+ }
+#ifdef CONFIG_SCSI_MPT2SAS_LOGGING
+ _ctl_display_some_debug(ioc, smid, "ctl_done", mpi_reply);
+#endif
+ ioc->ctl_cmds.status &= ~MPT2_CMD_PENDING;
+ complete(&ioc->ctl_cmds.done);
+}
+
+/**
+ * _ctl_check_event_type - determines when an event needs logging
+ * @ioc: per adapter object
+ * @event: firmware event
+ *
+ * The bitmask in ioc->event_type[] indicates which events should be
+ * be saved in the driver event_log. This bitmask is set by application.
+ *
+ * Returns 1 when event should be captured, or zero means no match.
+ */
+static int
+_ctl_check_event_type(struct MPT2SAS_ADAPTER *ioc, u16 event)
+{
+ u16 i;
+ u32 desired_event;
+
+ if (event >= 128 || !event || !ioc->event_log)
+ return 0;
+
+ desired_event = (1 << (event % 32));
+ if (!desired_event)
+ desired_event = 1;
+ i = event / 32;
+ return desired_event & ioc->event_type[i];
+}
+
+/**
+ * mpt2sas_ctl_add_to_event_log - add event
+ * @ioc: per adapter object
+ * @mpi_reply: reply message frame
+ *
+ * Return nothing.
+ */
+void
+mpt2sas_ctl_add_to_event_log(struct MPT2SAS_ADAPTER *ioc,
+ Mpi2EventNotificationReply_t *mpi_reply)
+{
+ struct MPT2_IOCTL_EVENTS *event_log;
+ u16 event;
+ int i;
+ u32 sz, event_data_sz;
+ u8 send_aen = 0;
+
+ if (!ioc->event_log)
+ return;
+
+ event = le16_to_cpu(mpi_reply->Event);
+
+ if (_ctl_check_event_type(ioc, event)) {
+
+ /* insert entry into circular event_log */
+ i = ioc->event_context % MPT2SAS_CTL_EVENT_LOG_SIZE;
+ event_log = ioc->event_log;
+ event_log[i].event = event;
+ event_log[i].context = ioc->event_context++;
+
+ event_data_sz = le16_to_cpu(mpi_reply->EventDataLength)*4;
+ sz = min_t(u32, event_data_sz, MPT2_EVENT_DATA_SIZE);
+ memset(event_log[i].data, 0, MPT2_EVENT_DATA_SIZE);
+ memcpy(event_log[i].data, mpi_reply->EventData, sz);
+ send_aen = 1;
+ }
+
+ /* This aen_event_read_flag flag is set until the
+ * application has read the event log.
+ * For MPI2_EVENT_LOG_ENTRY_ADDED, we always notify.
+ */
+ if (event == MPI2_EVENT_LOG_ENTRY_ADDED ||
+ (send_aen && !ioc->aen_event_read_flag)) {
+ ioc->aen_event_read_flag = 1;
+ wake_up_interruptible(&ctl_poll_wait);
+ if (async_queue)
+ kill_fasync(&async_queue, SIGIO, POLL_IN);
+ }
+}
+
+/**
+ * mpt2sas_ctl_event_callback - firmware event handler (called at ISR time)
+ * @ioc: per adapter object
+ * @VF_ID: virtual function id
+ * @reply: reply message frame(lower 32bit addr)
+ * Context: interrupt.
+ *
+ * This function merely adds a new work task into ioc->firmware_event_thread.
+ * The tasks are worked from _firmware_event_work in user context.
+ *
+ * Return nothing.
+ */
+void
+mpt2sas_ctl_event_callback(struct MPT2SAS_ADAPTER *ioc, u8 VF_ID, u32 reply)
+{
+ Mpi2EventNotificationReply_t *mpi_reply;
+
+ mpi_reply = mpt2sas_base_get_reply_virt_addr(ioc, reply);
+ mpt2sas_ctl_add_to_event_log(ioc, mpi_reply);
+}
+
+/**
+ * _ctl_verify_adapter - validates ioc_number passed from application
+ * @ioc: per adapter object
+ * @iocpp: The ioc pointer is returned in this.
+ *
+ * Return (-1) means error, else ioc_number.
+ */
+static int
+_ctl_verify_adapter(int ioc_number, struct MPT2SAS_ADAPTER **iocpp)
+{
+ struct MPT2SAS_ADAPTER *ioc;
+
+ list_for_each_entry(ioc, &ioc_list, list) {
+ if (ioc->id != ioc_number)
+ continue;
+ *iocpp = ioc;
+ return ioc_number;
+ }
+ *iocpp = NULL;
+ return -1;
+}
+
+/**
+ * mpt2sas_ctl_reset_handler - reset callback handler (for ctl)
+ * @ioc: per adapter object
+ * @reset_phase: phase
+ *
+ * The handler for doing any required cleanup or initialization.
+ *
+ * The reset phase can be MPT2_IOC_PRE_RESET, MPT2_IOC_AFTER_RESET,
+ * MPT2_IOC_DONE_RESET
+ */
+void
+mpt2sas_ctl_reset_handler(struct MPT2SAS_ADAPTER *ioc, int reset_phase)
+{
+ switch (reset_phase) {
+ case MPT2_IOC_PRE_RESET:
+ dtmprintk(ioc, printk(MPT2SAS_DEBUG_FMT "%s: "
+ "MPT2_IOC_PRE_RESET\n", ioc->name, __func__));
+ break;
+ case MPT2_IOC_AFTER_RESET:
+ dtmprintk(ioc, printk(MPT2SAS_DEBUG_FMT "%s: "
+ "MPT2_IOC_AFTER_RESET\n", ioc->name, __func__));
+ if (ioc->ctl_cmds.status & MPT2_CMD_PENDING) {
+ ioc->ctl_cmds.status |= MPT2_CMD_RESET;
+ mpt2sas_base_free_smid(ioc, ioc->ctl_cmds.smid);
+ complete(&ioc->ctl_cmds.done);
+ }
+ break;
+ case MPT2_IOC_DONE_RESET:
+ dtmprintk(ioc, printk(MPT2SAS_DEBUG_FMT "%s: "
+ "MPT2_IOC_DONE_RESET\n", ioc->name, __func__));
+ break;
+ }
+}
+
+/**
+ * _ctl_fasync -
+ * @fd -
+ * @filep -
+ * @mode -
+ *
+ * Called when application request fasyn callback handler.
+ */
+static int
+_ctl_fasync(int fd, struct file *filep, int mode)
+{
+ return fasync_helper(fd, filep, mode, &async_queue);
+}
+
+/**
+ * _ctl_release -
+ * @inode -
+ * @filep -
+ *
+ * Called when application releases the fasyn callback handler.
+ */
+static int
+_ctl_release(struct inode *inode, struct file *filep)
+{
+ return fasync_helper(-1, filep, 0, &async_queue);
+}
+
+/**
+ * _ctl_poll -
+ * @file -
+ * @wait -
+ *
+ */
+static unsigned int
+_ctl_poll(struct file *filep, poll_table *wait)
+{
+ struct MPT2SAS_ADAPTER *ioc;
+
+ poll_wait(filep, &ctl_poll_wait, wait);
+
+ list_for_each_entry(ioc, &ioc_list, list) {
+ if (ioc->aen_event_read_flag)
+ return POLLIN | POLLRDNORM;
+ }
+ return 0;
+}
+
+/**
+ * _ctl_do_task_abort - assign an active smid to the abort_task
+ * @ioc: per adapter object
+ * @karg - (struct mpt2_ioctl_command)
+ * @tm_request - pointer to mf from user space
+ *
+ * Returns 0 when an smid if found, else fail.
+ * during failure, the reply frame is filled.
+ */
+static int
+_ctl_do_task_abort(struct MPT2SAS_ADAPTER *ioc, struct mpt2_ioctl_command *karg,
+ Mpi2SCSITaskManagementRequest_t *tm_request)
+{
+ u8 found = 0;
+ u16 i;
+ u16 handle;
+ struct scsi_cmnd *scmd;
+ struct MPT2SAS_DEVICE *priv_data;
+ unsigned long flags;
+ Mpi2SCSITaskManagementReply_t *tm_reply;
+ u32 sz;
+ u32 lun;
+
+ lun = scsilun_to_int((struct scsi_lun *)tm_request->LUN);
+
+ handle = le16_to_cpu(tm_request->DevHandle);
+ spin_lock_irqsave(&ioc->scsi_lookup_lock, flags);
+ for (i = ioc->request_depth; i && !found; i--) {
+ scmd = ioc->scsi_lookup[i - 1].scmd;
+ if (scmd == NULL || scmd->device == NULL ||
+ scmd->device->hostdata == NULL)
+ continue;
+ if (lun != scmd->device->lun)
+ continue;
+ priv_data = scmd->device->hostdata;
+ if (priv_data->sas_target == NULL)
+ continue;
+ if (priv_data->sas_target->handle != handle)
+ continue;
+ tm_request->TaskMID = cpu_to_le16(ioc->scsi_lookup[i - 1].smid);
+ found = 1;
+ }
+ spin_unlock_irqrestore(&ioc->scsi_lookup_lock, flags);
+
+ if (!found) {
+ dctlprintk(ioc, printk(MPT2SAS_DEBUG_FMT "ABORT_TASK: "
+ "DevHandle(0x%04x), lun(%d), no active mid!!\n", ioc->name,
+ tm_request->DevHandle, lun));
+ tm_reply = ioc->ctl_cmds.reply;
+ tm_reply->DevHandle = tm_request->DevHandle;
+ tm_reply->Function = MPI2_FUNCTION_SCSI_TASK_MGMT;
+ tm_reply->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_ABORT_TASK;
+ tm_reply->MsgLength = sizeof(Mpi2SCSITaskManagementReply_t)/4;
+ tm_reply->VP_ID = tm_request->VP_ID;
+ tm_reply->VF_ID = tm_request->VF_ID;
+ sz = min_t(u32, karg->max_reply_bytes, ioc->reply_sz);
+ if (copy_to_user(karg->reply_frame_buf_ptr, ioc->ctl_cmds.reply,
+ sz))
+ printk(KERN_ERR "failure at %s:%d/%s()!\n", __FILE__,
+ __LINE__, __func__);
+ return 1;
+ }
+
+ dctlprintk(ioc, printk(MPT2SAS_DEBUG_FMT "ABORT_TASK: "
+ "DevHandle(0x%04x), lun(%d), smid(%d)\n", ioc->name,
+ tm_request->DevHandle, lun, tm_request->TaskMID));
+ return 0;
+}
+
+/**
+ * _ctl_do_mpt_command - main handler for MPT2COMMAND opcode
+ * @ioc: per adapter object
+ * @karg - (struct mpt2_ioctl_command)
+ * @mf - pointer to mf in user space
+ * @state - NON_BLOCKING or BLOCKING
+ */
+static long
+_ctl_do_mpt_command(struct MPT2SAS_ADAPTER *ioc,
+ struct mpt2_ioctl_command karg, void __user *mf, enum block_state state)
+{
+ MPI2RequestHeader_t *mpi_request;
+ MPI2DefaultReply_t *mpi_reply;
+ u32 ioc_state;
+ u16 ioc_status;
+ u16 smid;
+ unsigned long timeout, timeleft;
+ u8 issue_reset;
+ u32 sz;
+ void *psge;
+ void *priv_sense = NULL;
+ void *data_out = NULL;
+ dma_addr_t data_out_dma;
+ size_t data_out_sz = 0;
+ void *data_in = NULL;
+ dma_addr_t data_in_dma;
+ size_t data_in_sz = 0;
+ u32 sgl_flags;
+ long ret;
+ u16 wait_state_count;
+
+ issue_reset = 0;
+
+ if (state == NON_BLOCKING && !mutex_trylock(&ioc->ctl_cmds.mutex))
+ return -EAGAIN;
+ else if (mutex_lock_interruptible(&ioc->ctl_cmds.mutex))
+ return -ERESTARTSYS;
+
+ if (ioc->ctl_cmds.status != MPT2_CMD_NOT_USED) {
+ printk(MPT2SAS_ERR_FMT "%s: ctl_cmd in use\n",
+ ioc->name, __func__);
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ wait_state_count = 0;
+ ioc_state = mpt2sas_base_get_iocstate(ioc, 1);
+ while (ioc_state != MPI2_IOC_STATE_OPERATIONAL) {
+ if (wait_state_count++ == 10) {
+ printk(MPT2SAS_ERR_FMT
+ "%s: failed due to ioc not operational\n",
+ ioc->name, __func__);
+ ret = -EFAULT;
+ goto out;
+ }
+ ssleep(1);
+ ioc_state = mpt2sas_base_get_iocstate(ioc, 1);
+ printk(MPT2SAS_INFO_FMT "%s: waiting for "
+ "operational state(count=%d)\n", ioc->name,
+ __func__, wait_state_count);
+ }
+ if (wait_state_count)
+ printk(MPT2SAS_INFO_FMT "%s: ioc is operational\n",
+ ioc->name, __func__);
+
+ smid = mpt2sas_base_get_smid(ioc, ioc->ctl_cb_idx);
+ if (!smid) {
+ printk(MPT2SAS_ERR_FMT "%s: failed obtaining a smid\n",
+ ioc->name, __func__);
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ ret = 0;
+ ioc->ctl_cmds.status = MPT2_CMD_PENDING;
+ memset(ioc->ctl_cmds.reply, 0, ioc->reply_sz);
+ mpi_request = mpt2sas_base_get_msg_frame(ioc, smid);
+ ioc->ctl_cmds.smid = smid;
+ data_out_sz = karg.data_out_size;
+ data_in_sz = karg.data_in_size;
+
+ /* copy in request message frame from user */
+ if (copy_from_user(mpi_request, mf, karg.data_sge_offset*4)) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n", __FILE__, __LINE__,
+ __func__);
+ ret = -EFAULT;
+ mpt2sas_base_free_smid(ioc, smid);
+ goto out;
+ }
+
+ if (mpi_request->Function == MPI2_FUNCTION_SCSI_IO_REQUEST ||
+ mpi_request->Function == MPI2_FUNCTION_RAID_SCSI_IO_PASSTHROUGH) {
+ if (!mpi_request->FunctionDependent1 ||
+ mpi_request->FunctionDependent1 >
+ cpu_to_le16(ioc->facts.MaxDevHandle)) {
+ ret = -EINVAL;
+ mpt2sas_base_free_smid(ioc, smid);
+ goto out;
+ }
+ }
+
+ /* obtain dma-able memory for data transfer */
+ if (data_out_sz) /* WRITE */ {
+ data_out = pci_alloc_consistent(ioc->pdev, data_out_sz,
+ &data_out_dma);
+ if (!data_out) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n", __FILE__,
+ __LINE__, __func__);
+ ret = -ENOMEM;
+ mpt2sas_base_free_smid(ioc, smid);
+ goto out;
+ }
+ if (copy_from_user(data_out, karg.data_out_buf_ptr,
+ data_out_sz)) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n", __FILE__,
+ __LINE__, __func__);
+ ret = -EFAULT;
+ mpt2sas_base_free_smid(ioc, smid);
+ goto out;
+ }
+ }
+
+ if (data_in_sz) /* READ */ {
+ data_in = pci_alloc_consistent(ioc->pdev, data_in_sz,
+ &data_in_dma);
+ if (!data_in) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n", __FILE__,
+ __LINE__, __func__);
+ ret = -ENOMEM;
+ mpt2sas_base_free_smid(ioc, smid);
+ goto out;
+ }
+ }
+
+ /* add scatter gather elements */
+ psge = (void *)mpi_request + (karg.data_sge_offset*4);
+
+ if (!data_out_sz && !data_in_sz) {
+ mpt2sas_base_build_zero_len_sge(ioc, psge);
+ } else if (data_out_sz && data_in_sz) {
+ /* WRITE sgel first */
+ sgl_flags = (MPI2_SGE_FLAGS_SIMPLE_ELEMENT |
+ MPI2_SGE_FLAGS_END_OF_BUFFER | MPI2_SGE_FLAGS_HOST_TO_IOC);
+ sgl_flags = sgl_flags << MPI2_SGE_FLAGS_SHIFT;
+ ioc->base_add_sg_single(psge, sgl_flags |
+ data_out_sz, data_out_dma);
+
+ /* incr sgel */
+ psge += ioc->sge_size;
+
+ /* READ sgel last */
+ sgl_flags = (MPI2_SGE_FLAGS_SIMPLE_ELEMENT |
+ MPI2_SGE_FLAGS_LAST_ELEMENT | MPI2_SGE_FLAGS_END_OF_BUFFER |
+ MPI2_SGE_FLAGS_END_OF_LIST);
+ sgl_flags = sgl_flags << MPI2_SGE_FLAGS_SHIFT;
+ ioc->base_add_sg_single(psge, sgl_flags |
+ data_in_sz, data_in_dma);
+ } else if (data_out_sz) /* WRITE */ {
+ sgl_flags = (MPI2_SGE_FLAGS_SIMPLE_ELEMENT |
+ MPI2_SGE_FLAGS_LAST_ELEMENT | MPI2_SGE_FLAGS_END_OF_BUFFER |
+ MPI2_SGE_FLAGS_END_OF_LIST | MPI2_SGE_FLAGS_HOST_TO_IOC);
+ sgl_flags = sgl_flags << MPI2_SGE_FLAGS_SHIFT;
+ ioc->base_add_sg_single(psge, sgl_flags |
+ data_out_sz, data_out_dma);
+ } else if (data_in_sz) /* READ */ {
+ sgl_flags = (MPI2_SGE_FLAGS_SIMPLE_ELEMENT |
+ MPI2_SGE_FLAGS_LAST_ELEMENT | MPI2_SGE_FLAGS_END_OF_BUFFER |
+ MPI2_SGE_FLAGS_END_OF_LIST);
+ sgl_flags = sgl_flags << MPI2_SGE_FLAGS_SHIFT;
+ ioc->base_add_sg_single(psge, sgl_flags |
+ data_in_sz, data_in_dma);
+ }
+
+ /* send command to firmware */
+#ifdef CONFIG_SCSI_MPT2SAS_LOGGING
+ _ctl_display_some_debug(ioc, smid, "ctl_request", NULL);
+#endif
+
+ switch (mpi_request->Function) {
+ case MPI2_FUNCTION_SCSI_IO_REQUEST:
+ case MPI2_FUNCTION_RAID_SCSI_IO_PASSTHROUGH:
+ {
+ Mpi2SCSIIORequest_t *scsiio_request =
+ (Mpi2SCSIIORequest_t *)mpi_request;
+ scsiio_request->SenseBufferLowAddress =
+ (u32)mpt2sas_base_get_sense_buffer_dma(ioc, smid);
+ priv_sense = mpt2sas_base_get_sense_buffer(ioc, smid);
+ memset(priv_sense, 0, SCSI_SENSE_BUFFERSIZE);
+ mpt2sas_base_put_smid_scsi_io(ioc, smid, 0,
+ le16_to_cpu(mpi_request->FunctionDependent1));
+ break;
+ }
+ case MPI2_FUNCTION_SCSI_TASK_MGMT:
+ {
+ Mpi2SCSITaskManagementRequest_t *tm_request =
+ (Mpi2SCSITaskManagementRequest_t *)mpi_request;
+
+ if (tm_request->TaskType ==
+ MPI2_SCSITASKMGMT_TASKTYPE_ABORT_TASK) {
+ if (_ctl_do_task_abort(ioc, &karg, tm_request))
+ goto out;
+ }
+
+ mutex_lock(&ioc->tm_cmds.mutex);
+ mpt2sas_scsih_set_tm_flag(ioc, le16_to_cpu(
+ tm_request->DevHandle));
+ mpt2sas_base_put_smid_hi_priority(ioc, smid,
+ mpi_request->VF_ID);
+ break;
+ }
+ case MPI2_FUNCTION_SMP_PASSTHROUGH:
+ {
+ Mpi2SmpPassthroughRequest_t *smp_request =
+ (Mpi2SmpPassthroughRequest_t *)mpi_request;
+ u8 *data;
+
+ /* ioc determines which port to use */
+ smp_request->PhysicalPort = 0xFF;
+ if (smp_request->PassthroughFlags &
+ MPI2_SMP_PT_REQ_PT_FLAGS_IMMEDIATE)
+ data = (u8 *)&smp_request->SGL;
+ else
+ data = data_out;
+
+ if (data[1] == 0x91 && (data[10] == 1 || data[10] == 2)) {
+ ioc->ioc_link_reset_in_progress = 1;
+ ioc->ignore_loginfos = 1;
+ }
+ mpt2sas_base_put_smid_default(ioc, smid, mpi_request->VF_ID);
+ break;
+ }
+ case MPI2_FUNCTION_SAS_IO_UNIT_CONTROL:
+ {
+ Mpi2SasIoUnitControlRequest_t *sasiounit_request =
+ (Mpi2SasIoUnitControlRequest_t *)mpi_request;
+
+ if (sasiounit_request->Operation == MPI2_SAS_OP_PHY_HARD_RESET
+ || sasiounit_request->Operation ==
+ MPI2_SAS_OP_PHY_LINK_RESET) {
+ ioc->ioc_link_reset_in_progress = 1;
+ ioc->ignore_loginfos = 1;
+ }
+ mpt2sas_base_put_smid_default(ioc, smid, mpi_request->VF_ID);
+ break;
+ }
+ default:
+ mpt2sas_base_put_smid_default(ioc, smid, mpi_request->VF_ID);
+ break;
+ }
+
+ if (karg.timeout < MPT2_IOCTL_DEFAULT_TIMEOUT)
+ timeout = MPT2_IOCTL_DEFAULT_TIMEOUT;
+ else
+ timeout = karg.timeout;
+ timeleft = wait_for_completion_timeout(&ioc->ctl_cmds.done,
+ timeout*HZ);
+ if (mpi_request->Function == MPI2_FUNCTION_SCSI_TASK_MGMT) {
+ Mpi2SCSITaskManagementRequest_t *tm_request =
+ (Mpi2SCSITaskManagementRequest_t *)mpi_request;
+ mutex_unlock(&ioc->tm_cmds.mutex);
+ mpt2sas_scsih_clear_tm_flag(ioc, le16_to_cpu(
+ tm_request->DevHandle));
+ } else if ((mpi_request->Function == MPI2_FUNCTION_SMP_PASSTHROUGH ||
+ mpi_request->Function == MPI2_FUNCTION_SAS_IO_UNIT_CONTROL) &&
+ ioc->ioc_link_reset_in_progress) {
+ ioc->ioc_link_reset_in_progress = 0;
+ ioc->ignore_loginfos = 0;
+ }
+ if (!(ioc->ctl_cmds.status & MPT2_CMD_COMPLETE)) {
+ printk(MPT2SAS_ERR_FMT "%s: timeout\n", ioc->name,
+ __func__);
+ _debug_dump_mf(mpi_request, karg.data_sge_offset);
+ if (!(ioc->ctl_cmds.status & MPT2_CMD_RESET))
+ issue_reset = 1;
+ goto issue_host_reset;
+ }
+
+ mpi_reply = ioc->ctl_cmds.reply;
+ ioc_status = le16_to_cpu(mpi_reply->IOCStatus) & MPI2_IOCSTATUS_MASK;
+
+#ifdef CONFIG_SCSI_MPT2SAS_LOGGING
+ if (mpi_reply->Function == MPI2_FUNCTION_SCSI_TASK_MGMT &&
+ (ioc->logging_level & MPT_DEBUG_TM)) {
+ Mpi2SCSITaskManagementReply_t *tm_reply =
+ (Mpi2SCSITaskManagementReply_t *)mpi_reply;
+
+ printk(MPT2SAS_DEBUG_FMT "TASK_MGMT: "
+ "IOCStatus(0x%04x), IOCLogInfo(0x%08x), "
+ "TerminationCount(0x%08x)\n", ioc->name,
+ tm_reply->IOCStatus, tm_reply->IOCLogInfo,
+ tm_reply->TerminationCount);
+ }
+#endif
+ /* copy out xdata to user */
+ if (data_in_sz) {
+ if (copy_to_user(karg.data_in_buf_ptr, data_in,
+ data_in_sz)) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n", __FILE__,
+ __LINE__, __func__);
+ ret = -ENODATA;
+ goto out;
+ }
+ }
+
+ /* copy out reply message frame to user */
+ if (karg.max_reply_bytes) {
+ sz = min_t(u32, karg.max_reply_bytes, ioc->reply_sz);
+ if (copy_to_user(karg.reply_frame_buf_ptr, ioc->ctl_cmds.reply,
+ sz)) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n", __FILE__,
+ __LINE__, __func__);
+ ret = -ENODATA;
+ goto out;
+ }
+ }
+
+ /* copy out sense to user */
+ if (karg.max_sense_bytes && (mpi_request->Function ==
+ MPI2_FUNCTION_SCSI_IO_REQUEST || mpi_request->Function ==
+ MPI2_FUNCTION_RAID_SCSI_IO_PASSTHROUGH)) {
+ sz = min_t(u32, karg.max_sense_bytes, SCSI_SENSE_BUFFERSIZE);
+ if (copy_to_user(karg.sense_data_ptr, priv_sense, sz)) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n", __FILE__,
+ __LINE__, __func__);
+ ret = -ENODATA;
+ goto out;
+ }
+ }
+
+ issue_host_reset:
+ if (issue_reset) {
+ if ((mpi_request->Function == MPI2_FUNCTION_SCSI_IO_REQUEST ||
+ mpi_request->Function ==
+ MPI2_FUNCTION_RAID_SCSI_IO_PASSTHROUGH)) {
+ printk(MPT2SAS_INFO_FMT "issue target reset: handle "
+ "= (0x%04x)\n", ioc->name,
+ mpi_request->FunctionDependent1);
+ mutex_lock(&ioc->tm_cmds.mutex);
+ mpt2sas_scsih_issue_tm(ioc,
+ mpi_request->FunctionDependent1, 0,
+ MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET, 0, 10);
+ ioc->tm_cmds.status = MPT2_CMD_NOT_USED;
+ mutex_unlock(&ioc->tm_cmds.mutex);
+ } else
+ mpt2sas_base_hard_reset_handler(ioc, CAN_SLEEP,
+ FORCE_BIG_HAMMER);
+ }
+
+ out:
+
+ /* free memory associated with sg buffers */
+ if (data_in)
+ pci_free_consistent(ioc->pdev, data_in_sz, data_in,
+ data_in_dma);
+
+ if (data_out)
+ pci_free_consistent(ioc->pdev, data_out_sz, data_out,
+ data_out_dma);
+
+ ioc->ctl_cmds.status = MPT2_CMD_NOT_USED;
+ mutex_unlock(&ioc->ctl_cmds.mutex);
+ return ret;
+}
+
+/**
+ * _ctl_getiocinfo - main handler for MPT2IOCINFO opcode
+ * @arg - user space buffer containing ioctl content
+ */
+static long
+_ctl_getiocinfo(void __user *arg)
+{
+ struct mpt2_ioctl_iocinfo karg;
+ struct MPT2SAS_ADAPTER *ioc;
+ u8 revision;
+
+ if (copy_from_user(&karg, arg, sizeof(karg))) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n",
+ __FILE__, __LINE__, __func__);
+ return -EFAULT;
+ }
+ if (_ctl_verify_adapter(karg.hdr.ioc_number, &ioc) == -1 || !ioc)
+ return -ENODEV;
+
+ dctlprintk(ioc, printk(MPT2SAS_DEBUG_FMT "%s: enter\n", ioc->name,
+ __func__));
+
+ memset(&karg, 0 , sizeof(karg));
+ karg.adapter_type = MPT2_IOCTL_INTERFACE_SAS2;
+ if (ioc->pfacts)
+ karg.port_number = ioc->pfacts[0].PortNumber;
+ pci_read_config_byte(ioc->pdev, PCI_CLASS_REVISION, &revision);
+ karg.hw_rev = revision;
+ karg.pci_id = ioc->pdev->device;
+ karg.subsystem_device = ioc->pdev->subsystem_device;
+ karg.subsystem_vendor = ioc->pdev->subsystem_vendor;
+ karg.pci_information.u.bits.bus = ioc->pdev->bus->number;
+ karg.pci_information.u.bits.device = PCI_SLOT(ioc->pdev->devfn);
+ karg.pci_information.u.bits.function = PCI_FUNC(ioc->pdev->devfn);
+ karg.pci_information.segment_id = pci_domain_nr(ioc->pdev->bus);
+ karg.firmware_version = ioc->facts.FWVersion.Word;
+ strncpy(karg.driver_version, MPT2SAS_DRIVER_VERSION,
+ MPT2_IOCTL_VERSION_LENGTH);
+ karg.driver_version[MPT2_IOCTL_VERSION_LENGTH - 1] = '\0';
+ karg.bios_version = le32_to_cpu(ioc->bios_pg3.BiosVersion);
+
+ if (copy_to_user(arg, &karg, sizeof(karg))) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n",
+ __FILE__, __LINE__, __func__);
+ return -EFAULT;
+ }
+ return 0;
+}
+
+/**
+ * _ctl_eventquery - main handler for MPT2EVENTQUERY opcode
+ * @arg - user space buffer containing ioctl content
+ */
+static long
+_ctl_eventquery(void __user *arg)
+{
+ struct mpt2_ioctl_eventquery karg;
+ struct MPT2SAS_ADAPTER *ioc;
+
+ if (copy_from_user(&karg, arg, sizeof(karg))) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n",
+ __FILE__, __LINE__, __func__);
+ return -EFAULT;
+ }
+ if (_ctl_verify_adapter(karg.hdr.ioc_number, &ioc) == -1 || !ioc)
+ return -ENODEV;
+
+ dctlprintk(ioc, printk(MPT2SAS_DEBUG_FMT "%s: enter\n", ioc->name,
+ __func__));
+
+ karg.event_entries = MPT2SAS_CTL_EVENT_LOG_SIZE;
+ memcpy(karg.event_types, ioc->event_type,
+ MPI2_EVENT_NOTIFY_EVENTMASK_WORDS * sizeof(u32));
+
+ if (copy_to_user(arg, &karg, sizeof(karg))) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n",
+ __FILE__, __LINE__, __func__);
+ return -EFAULT;
+ }
+ return 0;
+}
+
+/**
+ * _ctl_eventenable - main handler for MPT2EVENTENABLE opcode
+ * @arg - user space buffer containing ioctl content
+ */
+static long
+_ctl_eventenable(void __user *arg)
+{
+ struct mpt2_ioctl_eventenable karg;
+ struct MPT2SAS_ADAPTER *ioc;
+
+ if (copy_from_user(&karg, arg, sizeof(karg))) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n",
+ __FILE__, __LINE__, __func__);
+ return -EFAULT;
+ }
+ if (_ctl_verify_adapter(karg.hdr.ioc_number, &ioc) == -1 || !ioc)
+ return -ENODEV;
+
+ dctlprintk(ioc, printk(MPT2SAS_DEBUG_FMT "%s: enter\n", ioc->name,
+ __func__));
+
+ if (ioc->event_log)
+ return 0;
+ memcpy(ioc->event_type, karg.event_types,
+ MPI2_EVENT_NOTIFY_EVENTMASK_WORDS * sizeof(u32));
+ mpt2sas_base_validate_event_type(ioc, ioc->event_type);
+
+ /* initialize event_log */
+ ioc->event_context = 0;
+ ioc->aen_event_read_flag = 0;
+ ioc->event_log = kcalloc(MPT2SAS_CTL_EVENT_LOG_SIZE,
+ sizeof(struct MPT2_IOCTL_EVENTS), GFP_KERNEL);
+ if (!ioc->event_log) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n",
+ __FILE__, __LINE__, __func__);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/**
+ * _ctl_eventreport - main handler for MPT2EVENTREPORT opcode
+ * @arg - user space buffer containing ioctl content
+ */
+static long
+_ctl_eventreport(void __user *arg)
+{
+ struct mpt2_ioctl_eventreport karg;
+ struct MPT2SAS_ADAPTER *ioc;
+ u32 number_bytes, max_events, max;
+ struct mpt2_ioctl_eventreport __user *uarg = arg;
+
+ if (copy_from_user(&karg, arg, sizeof(karg))) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n",
+ __FILE__, __LINE__, __func__);
+ return -EFAULT;
+ }
+ if (_ctl_verify_adapter(karg.hdr.ioc_number, &ioc) == -1 || !ioc)
+ return -ENODEV;
+
+ dctlprintk(ioc, printk(MPT2SAS_DEBUG_FMT "%s: enter\n", ioc->name,
+ __func__));
+
+ number_bytes = karg.hdr.max_data_size -
+ sizeof(struct mpt2_ioctl_header);
+ max_events = number_bytes/sizeof(struct MPT2_IOCTL_EVENTS);
+ max = min_t(u32, MPT2SAS_CTL_EVENT_LOG_SIZE, max_events);
+
+ /* If fewer than 1 event is requested, there must have
+ * been some type of error.
+ */
+ if (!max || !ioc->event_log)
+ return -ENODATA;
+
+ number_bytes = max * sizeof(struct MPT2_IOCTL_EVENTS);
+ if (copy_to_user(uarg->event_data, ioc->event_log, number_bytes)) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n",
+ __FILE__, __LINE__, __func__);
+ return -EFAULT;
+ }
+
+ /* reset flag so SIGIO can restart */
+ ioc->aen_event_read_flag = 0;
+ return 0;
+}
+
+/**
+ * _ctl_do_reset - main handler for MPT2HARDRESET opcode
+ * @arg - user space buffer containing ioctl content
+ */
+static long
+_ctl_do_reset(void __user *arg)
+{
+ struct mpt2_ioctl_diag_reset karg;
+ struct MPT2SAS_ADAPTER *ioc;
+ int retval;
+
+ if (copy_from_user(&karg, arg, sizeof(karg))) {
+ printk(KERN_ERR "failure at %s:%d/%s()!\n",
+ __FILE__, __LINE__, __func__);
+ return -EFAULT;
+ }
+ if (_ctl_verify_adapter(karg.hdr.ioc_number, &ioc) == -1 || !ioc)
+ return -ENODEV;
+
+ dctlprintk(ioc, printk(MPT2SAS_DEBUG_FMT "%s: enter\n", ioc->name,
+ __func__));
+
+ retval = mpt2sas_base_hard_reset_handler(ioc, CAN_SLEEP,
+ FORCE_BIG_HAMMER);
+ printk(MPT2SAS_INFO_FMT "host reset: %s\n",
+ ioc->name, ((!retval) ? "SUCCESS" : "FAILED"));
+ return 0;
+}
+
+/**
+ * _ctl_btdh_search_sas_device - searching for sas device
+ * @ioc: per adapter object
+ * @btdh: btdh ioctl payload
+ */
+static int
+_ctl_btdh_search_sas_device(struct MPT2SAS_ADAPTER *ioc,
+ struct mpt2_ioctl_btdh_mapping *btdh)
+{
+ struct _sas_device *sas_device;
+ unsigned long flags;
+ int rc = 0;
+
+ if (list_empty(&ioc->sas_device_list))
+ return rc;
+
+ spin_lock_irqsave(&ioc->sas_device_lock, flags);
+ list_for_each_entry(sas_device, &ioc->sas_device_list, list) {
+ if (btdh->bus == 0xFFFFFFFF && btdh->id == 0xFFFFFFFF &&
+ btdh->handle == sas_device->handle) {
+ btdh->bus = sas_device->channel;
+ btdh->id = sas_device->id;
+ rc = 1;
+ goto out;
+ } else if (btdh->bus == sas_device->channel && btdh->id ==
+ sas_device->id && btdh->handle == 0xFFFF) {
+ btdh->handle = sas_device->handle;
+ rc = 1;
+ goto out;
+ }
+ }
+ out:
+ spin_unlock_irqrestore(&ioc->sas_device_lock, flags);
+ return rc;
+}
+
+/**
+ * _ctl_btdh_search_raid_device - searching for raid device
+ * @ioc: per adapter object
+ * @btdh: btdh ioctl payload
+ */
+static int
+_ctl_btdh_search_raid_device(struct MPT2SAS_ADAPTER *ioc,
+ struct mpt2_ioctl_btdh_mapping *btdh)
+{
+ struct _raid_device *raid_device;
+ unsigned long flags;
+ int rc = 0;
+
+ if (list_empty(&ioc->raid_device_list))
+ return rc;
+
+ spin_lock_irqsave(&ioc->raid_device_lock, flags);
+ list_for_each_entry(raid_device, &ioc->raid_device_list, list) {
+ if (btdh->bus == 0xFFFFFFFF && btdh->i