mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
leapraid_fw_log_exit() waits for mmap_refcnt to reach zero before it frees
the firmware log buffer. leapraid_fw_mmap() checks host_removing, but it
does not increment mmap_refcnt until after dma_mmap_coherent() succeeds and
the VMA open callback runs.
Removal can set host_removing and observe a zero mmap_refcnt between the
check and the VMA open. It can then free the coherent buffer while the
mmap path is still establishing a userspace mapping of it.
Claim a temporary mmap reference while looking up the adapter under
leapraid_adapter_lock. Removal deletes the adapter from the same locked
list after setting host_removing, so a mapping is either rejected or
included in the count that removal waits for. Drop the temporary reference
on the common exit path, after a successful VMA open has acquired the
reference covering the VMA lifetime.
Fixes: 5597088c9e ("scsi: leapraid: Add new SCSI driver")
Signed-off-by: Linmao Li <lilinmao@kylinos.cn>
Reviewed-by: Dongdong Hao <doubled@leap-io-kernel.com>
Link: https://patch.msgid.link/20260814033845.2971706-3-lilinmao@kylinos.cn
Signed-off-by: Martin K. Petersen (Oracle) <mkp@kernel.org>
810 lines
21 KiB
C
810 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2026 LeapIO Tech Inc.
|
|
*
|
|
* LeapRAID storage and RAID controller driver.
|
|
*/
|
|
#include <linux/compat.h>
|
|
#include <linux/module.h>
|
|
#include <linux/miscdevice.h>
|
|
|
|
#include "leapraid_func.h"
|
|
|
|
/* IOCTL device file name. */
|
|
#define LEAPRAID_DEV_NAME "leapraid_ctl"
|
|
|
|
/* IOCTL version. */
|
|
#define LEAPRAID_IOCTL_VERSION 0x07
|
|
|
|
/* IOCTL commands. */
|
|
#define LEAPRAID_ADAPTER_INFO 17
|
|
#define LEAPRAID_COMMAND 20
|
|
#define LEAPRAID_EVENTQUERY 21
|
|
#define LEAPRAID_EVENTREPORT 23
|
|
|
|
/**
|
|
* struct leapraid_ioctl_header - IOCTL command header
|
|
*
|
|
* @adapter_id : Adapter identifier.
|
|
* @port_number: Port identifier.
|
|
* @max_data_size: Maximum data size for transfer.
|
|
*/
|
|
struct leapraid_ioctl_header {
|
|
u32 adapter_id;
|
|
u32 port_number;
|
|
u32 max_data_size;
|
|
};
|
|
|
|
/**
|
|
* struct leapraid_ioctl_diag_reset - Diagnostic reset request
|
|
*
|
|
* @hdr: Common IOCTL header.
|
|
*/
|
|
struct leapraid_ioctl_diag_reset {
|
|
struct leapraid_ioctl_header hdr;
|
|
};
|
|
|
|
/**
|
|
* struct leapraid_ioctl_pci_info - PCI device information
|
|
*
|
|
* @u: Union holding PCI bus/device/function information.
|
|
* @u.bits.dev: PCI device number.
|
|
* @u.bits.func: PCI function number.
|
|
* @u.bits.bus: PCI bus number.
|
|
* @u.word: Combined representation of PCI BDF.
|
|
* @seg_id: PCI segment identifier.
|
|
*/
|
|
struct leapraid_ioctl_pci_info {
|
|
union {
|
|
struct {
|
|
u32 dev:5;
|
|
u32 func:3;
|
|
u32 bus:24;
|
|
} bits;
|
|
u32 word;
|
|
} u;
|
|
u32 seg_id;
|
|
};
|
|
|
|
/**
|
|
* struct leapraid_ioctl_adapter_info - Adapter information for IOCTL
|
|
*
|
|
* @hdr: IOCTL header.
|
|
* @adapter_type: Adapter type identifier.
|
|
* @port_number: Port number.
|
|
* @pci_id: PCI device ID.
|
|
* @revision: Revision number.
|
|
* @sub_dev: Subsystem device ID.
|
|
* @sub_vendor: Subsystem vendor ID.
|
|
* @r0: Reserved.
|
|
* @fw_ver: Firmware version.
|
|
* @bios_ver: BIOS version.
|
|
* @driver_ver: Driver version.
|
|
* @r1: Reserved.
|
|
* @scsi_id: SCSI ID.
|
|
* @r2: Reserved.
|
|
* @pci_info: PCI information structure.
|
|
*/
|
|
struct leapraid_ioctl_adapter_info {
|
|
struct leapraid_ioctl_header hdr;
|
|
u32 adapter_type;
|
|
u32 port_number;
|
|
u32 pci_id;
|
|
u32 revision;
|
|
u32 sub_dev;
|
|
u32 sub_vendor;
|
|
u32 r0;
|
|
u32 fw_ver;
|
|
u32 bios_ver;
|
|
u8 driver_ver[32];
|
|
u8 r1;
|
|
u8 scsi_id;
|
|
u16 r2;
|
|
struct leapraid_ioctl_pci_info pci_info;
|
|
};
|
|
|
|
/**
|
|
* struct leapraid_ioctl_command - IOCTL command structure
|
|
*
|
|
* @hdr: IOCTL header.
|
|
* @timeout: Command timeout.
|
|
* @rep_msg_buf_ptr: User pointer to reply message buffer.
|
|
* @c2h_buf_ptr: User pointer to card-to-host data buffer.
|
|
* @h2c_buf_ptr: User pointer to host-to-card data buffer.
|
|
* @sense_data_ptr: User pointer to sense data buffer.
|
|
* @max_rep_bytes: Maximum reply bytes.
|
|
* @c2h_size: Card-to-host data size.
|
|
* @h2c_size: Host-to-card data size.
|
|
* @max_sense_bytes: Maximum sense data bytes.
|
|
* @data_sge_offset: Data SGE offset.
|
|
* @mf: Message frame data (flexible array).
|
|
*/
|
|
struct leapraid_ioctl_command {
|
|
struct leapraid_ioctl_header hdr;
|
|
u32 timeout;
|
|
void __user *rep_msg_buf_ptr;
|
|
void __user *c2h_buf_ptr;
|
|
void __user *h2c_buf_ptr;
|
|
void __user *sense_data_ptr;
|
|
u32 max_rep_bytes;
|
|
u32 c2h_size;
|
|
u32 h2c_size;
|
|
u32 max_sense_bytes;
|
|
u32 data_sge_offset;
|
|
u8 mf[];
|
|
};
|
|
|
|
static int leapraid_ctl_validate_sge_offset(struct leapraid_adapter *adapter,
|
|
const struct leapraid_req *req,
|
|
u32 offset_bytes,
|
|
size_t h2c_size,
|
|
size_t c2h_size)
|
|
{
|
|
size_t sge_bytes;
|
|
|
|
switch (req->func) {
|
|
case LEAPRAID_FUNC_SCSIIO:
|
|
case LEAPRAID_FUNC_SCSIIO_RAID_PASSTHROUGH:
|
|
case LEAPRAID_FUNC_SMP_PASSTHROUGH:
|
|
case LEAPRAID_FUNC_SCSIIO_SATA_PASSTHROUGH:
|
|
case LEAPRAID_FUNC_FW_DOWNLOAD:
|
|
case LEAPRAID_FUNC_FW_UPLOAD:
|
|
sge_bytes = LEAPRAID_IEEE_SGE64_ENTRY_SIZE;
|
|
break;
|
|
default:
|
|
sge_bytes = adapter->adapter_attr.use_32_dma_mask ?
|
|
sizeof(struct leapraid_sge_simple32) :
|
|
sizeof(struct leapraid_sge_simple64);
|
|
break;
|
|
}
|
|
|
|
if (h2c_size && c2h_size)
|
|
sge_bytes *= 2;
|
|
|
|
if (offset_bytes > LEAPRAID_REQUEST_SIZE - sge_bytes) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Invalid offset_bytes=%u for func=0x%x\n",
|
|
__func__, offset_bytes, req->func);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct leapraid_adapter *leapraid_ctl_lookup_adapter(int adapter_id,
|
|
bool track_mmap)
|
|
{
|
|
struct leapraid_adapter *adapter;
|
|
struct Scsi_Host *shost;
|
|
|
|
spin_lock(&leapraid_adapter_lock);
|
|
list_for_each_entry(adapter, &leapraid_adapter_list, list) {
|
|
if (adapter->adapter_attr.id == adapter_id) {
|
|
if (READ_ONCE(adapter->access_ctrl.host_removing))
|
|
break;
|
|
shost = adapter->shost;
|
|
if (!shost || !scsi_host_get(shost))
|
|
break;
|
|
if (track_mmap)
|
|
atomic_inc(&adapter->fw_log_desc.mmap_refcnt);
|
|
spin_unlock(&leapraid_adapter_lock);
|
|
return adapter;
|
|
}
|
|
}
|
|
spin_unlock(&leapraid_adapter_lock);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void leapraid_ctl_put_adapter(struct leapraid_adapter *adapter)
|
|
{
|
|
if (adapter && adapter->shost)
|
|
scsi_host_put(adapter->shost);
|
|
}
|
|
|
|
static void leapraid_ctl_scsiio_cmd(struct leapraid_adapter *adapter,
|
|
void *ctl_sp_mpi_req,
|
|
u16 taskid,
|
|
dma_addr_t h2c_dma_addr, size_t h2c_size,
|
|
dma_addr_t c2h_dma_addr, size_t c2h_size,
|
|
u16 dev_hdl, void *psge)
|
|
{
|
|
struct leapraid_mpi_scsiio_req *scsiio_request = ctl_sp_mpi_req;
|
|
|
|
scsiio_request->sense_buffer_len = SCSI_SENSE_BUFFERSIZE;
|
|
scsiio_request->sense_buffer_low_add =
|
|
leapraid_get_sense_buffer_dma(adapter, taskid);
|
|
memset(&adapter->driver_cmds.ctl_cmd.sense,
|
|
0, SCSI_SENSE_BUFFERSIZE);
|
|
leapraid_build_ieee_sg(adapter, psge, h2c_dma_addr,
|
|
h2c_size, c2h_dma_addr, c2h_size);
|
|
if (scsiio_request->func == LEAPRAID_FUNC_SCSIIO)
|
|
leapraid_fire_scsi_io(adapter, taskid, dev_hdl);
|
|
else
|
|
leapraid_fire_task(adapter, taskid);
|
|
}
|
|
|
|
static int leapraid_ctl_smp_passthrough_cmd(struct leapraid_adapter *adapter,
|
|
void *ctl_sp_req,
|
|
u16 taskid,
|
|
dma_addr_t h2c_dma_addr,
|
|
size_t h2c_size,
|
|
dma_addr_t c2h_dma_addr,
|
|
size_t c2h_size,
|
|
void *psge, void *h2c)
|
|
{
|
|
struct leapraid_smp_passthrough_req *smp_pt_req = ctl_sp_req;
|
|
u8 *data;
|
|
|
|
if (!adapter->adapter_attr.enable_mp)
|
|
smp_pt_req->physical_port = LEAPRAID_DISABLE_MP_PORT_ID;
|
|
if (smp_pt_req->passthrough_flg & LEAPRAID_SMP_PT_FLAG_SGL_PTR)
|
|
data = (u8 *)&smp_pt_req->sgl;
|
|
else
|
|
data = h2c;
|
|
|
|
if (!(smp_pt_req->passthrough_flg & LEAPRAID_SMP_PT_FLAG_SGL_PTR) &&
|
|
h2c_size <= 10) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Invalid request size=%zu\n",
|
|
__func__, h2c_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (data[1] == SMP_PHY_CONTROL &&
|
|
(data[10] == SMP_PHY_CONTROL_LINK_RESET ||
|
|
data[10] == SMP_PHY_CONTROL_HARD_RESET))
|
|
adapter->reset_desc.adapter_link_resetting = 1;
|
|
|
|
leapraid_build_ieee_sg(adapter, psge, h2c_dma_addr,
|
|
h2c_size, c2h_dma_addr, c2h_size);
|
|
leapraid_fire_task(adapter, taskid);
|
|
return 0;
|
|
}
|
|
|
|
static void leapraid_ctl_sas_io_unit_ctrl_cmd(struct leapraid_adapter *adapter,
|
|
void *ctl_sp_req,
|
|
dma_addr_t h2c_dma_addr,
|
|
size_t h2c_size,
|
|
dma_addr_t c2h_dma_addr,
|
|
size_t c2h_size,
|
|
void *psge, u16 taskid)
|
|
{
|
|
struct leapraid_sas_io_unit_ctrl_req *sas_io_unit_ctl_req = ctl_sp_req;
|
|
|
|
if (sas_io_unit_ctl_req->op == LEAPRAID_SAS_OP_PHY_HARD_RESET ||
|
|
sas_io_unit_ctl_req->op == LEAPRAID_SAS_OP_PHY_LINK_RESET)
|
|
adapter->reset_desc.adapter_link_resetting = 1;
|
|
leapraid_build_mpi_sg(adapter, psge, h2c_dma_addr,
|
|
h2c_size, c2h_dma_addr, c2h_size);
|
|
leapraid_fire_task(adapter, taskid);
|
|
}
|
|
|
|
static int leapraid_ctl_do_command(struct leapraid_adapter *adapter,
|
|
struct leapraid_ioctl_command *karg,
|
|
void __user *mf)
|
|
{
|
|
struct leapraid_req *leap_mpi_req;
|
|
struct leapraid_req *ctl_sp_mpi_req;
|
|
u16 taskid;
|
|
void *h2c = NULL;
|
|
size_t h2c_size = 0;
|
|
dma_addr_t h2c_dma_addr = 0;
|
|
void *c2h = NULL;
|
|
size_t c2h_size = 0;
|
|
dma_addr_t c2h_dma_addr = 0;
|
|
void *psge;
|
|
unsigned long timeout;
|
|
u16 dev_hdl = LEAPRAID_INVALID_DEV_HANDLE;
|
|
bool issue_reset = false;
|
|
u32 data_sge_offset_bytes;
|
|
u32 sz;
|
|
int rc;
|
|
|
|
rc = leapraid_check_adapter_is_op(adapter, LEAPRAID_DB_WAIT_OP_SHORT,
|
|
__func__);
|
|
if (rc)
|
|
return rc;
|
|
|
|
leap_mpi_req = kzalloc(LEAPRAID_REQUEST_SIZE, GFP_KERNEL);
|
|
if (!leap_mpi_req)
|
|
return -ENOMEM;
|
|
|
|
if (karg->data_sge_offset > (UINT_MAX / LEAPRAID_SGE_OFFSET_SIZE)) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Invalid data_sge_offset=%u\n",
|
|
__func__, karg->data_sge_offset);
|
|
rc = -EINVAL;
|
|
goto out_cleanup;
|
|
}
|
|
|
|
data_sge_offset_bytes = karg->data_sge_offset *
|
|
LEAPRAID_SGE_OFFSET_SIZE;
|
|
if (data_sge_offset_bytes > LEAPRAID_REQUEST_SIZE) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Invalid data_sge_offset=%u\n",
|
|
__func__, karg->data_sge_offset);
|
|
rc = -EINVAL;
|
|
goto out_cleanup;
|
|
}
|
|
|
|
if (copy_from_user(leap_mpi_req, mf, data_sge_offset_bytes)) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Failed to copy request message from user\n",
|
|
__func__);
|
|
rc = -EFAULT;
|
|
goto out_cleanup;
|
|
}
|
|
|
|
h2c_size = karg->h2c_size;
|
|
c2h_size = karg->c2h_size;
|
|
rc = leapraid_ctl_validate_sge_offset(adapter, leap_mpi_req,
|
|
data_sge_offset_bytes,
|
|
h2c_size, c2h_size);
|
|
if (rc)
|
|
goto out_cleanup;
|
|
|
|
taskid = adapter->driver_cmds.ctl_cmd.taskid;
|
|
|
|
adapter->driver_cmds.ctl_cmd.status = LEAPRAID_CMD_PENDING;
|
|
memset(&adapter->driver_cmds.ctl_cmd.reply, 0, LEAPRAID_REPLY_SIZE);
|
|
ctl_sp_mpi_req = leapraid_get_task_desc(adapter, taskid);
|
|
memset(ctl_sp_mpi_req, 0, LEAPRAID_REQUEST_SIZE);
|
|
memcpy(ctl_sp_mpi_req, leap_mpi_req, data_sge_offset_bytes);
|
|
|
|
if (ctl_sp_mpi_req->func == LEAPRAID_FUNC_SCSIIO ||
|
|
ctl_sp_mpi_req->func == LEAPRAID_FUNC_SCSIIO_RAID_PASSTHROUGH ||
|
|
ctl_sp_mpi_req->func == LEAPRAID_FUNC_SCSIIO_SATA_PASSTHROUGH) {
|
|
dev_hdl = le16_to_cpu(ctl_sp_mpi_req->func_dep1);
|
|
if (!dev_hdl ||
|
|
dev_hdl > adapter->adapter_attr.features.max_dev_handle) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Invalid device handle\n", __func__);
|
|
rc = -EINVAL;
|
|
goto out_cleanup;
|
|
}
|
|
}
|
|
|
|
if (WARN_ON(ctl_sp_mpi_req->func == LEAPRAID_FUNC_SCSI_TMF)) {
|
|
rc = -EINVAL;
|
|
goto out_cleanup;
|
|
}
|
|
|
|
if (h2c_size) {
|
|
h2c = dma_alloc_coherent(&adapter->pdev->dev, h2c_size,
|
|
&h2c_dma_addr, GFP_KERNEL);
|
|
if (!h2c) {
|
|
rc = -ENOMEM;
|
|
goto out_cleanup;
|
|
}
|
|
if (copy_from_user(h2c, karg->h2c_buf_ptr, h2c_size)) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Failed to copy h2c from user\n",
|
|
__func__);
|
|
rc = -EFAULT;
|
|
goto out_cleanup;
|
|
}
|
|
}
|
|
if (c2h_size) {
|
|
c2h = dma_alloc_coherent(&adapter->pdev->dev, c2h_size,
|
|
&c2h_dma_addr, GFP_KERNEL);
|
|
if (!c2h) {
|
|
rc = -ENOMEM;
|
|
goto out_cleanup;
|
|
}
|
|
}
|
|
|
|
psge = (void *)ctl_sp_mpi_req + data_sge_offset_bytes;
|
|
init_completion(&adapter->driver_cmds.ctl_cmd.done);
|
|
|
|
switch (ctl_sp_mpi_req->func) {
|
|
case LEAPRAID_FUNC_SCSIIO:
|
|
case LEAPRAID_FUNC_SCSIIO_RAID_PASSTHROUGH:
|
|
leapraid_ctl_scsiio_cmd(adapter, ctl_sp_mpi_req, taskid,
|
|
h2c_dma_addr, h2c_size,
|
|
c2h_dma_addr, c2h_size,
|
|
dev_hdl, psge);
|
|
break;
|
|
case LEAPRAID_FUNC_SMP_PASSTHROUGH:
|
|
if (!h2c) {
|
|
rc = -EINVAL;
|
|
goto out_cleanup;
|
|
}
|
|
rc = leapraid_ctl_smp_passthrough_cmd(adapter,
|
|
ctl_sp_mpi_req, taskid,
|
|
h2c_dma_addr, h2c_size,
|
|
c2h_dma_addr, c2h_size,
|
|
psge, h2c);
|
|
if (rc)
|
|
goto out_cleanup;
|
|
break;
|
|
case LEAPRAID_FUNC_SCSIIO_SATA_PASSTHROUGH:
|
|
leapraid_build_ieee_sg(adapter, psge, h2c_dma_addr, h2c_size,
|
|
c2h_dma_addr, c2h_size);
|
|
leapraid_fire_task(adapter, taskid);
|
|
break;
|
|
case LEAPRAID_FUNC_FW_DOWNLOAD:
|
|
case LEAPRAID_FUNC_FW_UPLOAD:
|
|
leapraid_build_ieee_sg(adapter, psge, h2c_dma_addr, h2c_size,
|
|
c2h_dma_addr, c2h_size);
|
|
leapraid_fire_task(adapter, taskid);
|
|
break;
|
|
case LEAPRAID_FUNC_SAS_IO_UNIT_CTRL:
|
|
leapraid_ctl_sas_io_unit_ctrl_cmd(adapter, ctl_sp_mpi_req,
|
|
h2c_dma_addr, h2c_size,
|
|
c2h_dma_addr, c2h_size,
|
|
psge, taskid);
|
|
break;
|
|
default:
|
|
leapraid_build_mpi_sg(adapter, psge, h2c_dma_addr,
|
|
h2c_size, c2h_dma_addr, c2h_size);
|
|
leapraid_fire_task(adapter, taskid);
|
|
break;
|
|
}
|
|
|
|
timeout = karg->timeout;
|
|
if (timeout < LEAPRAID_CTL_CMD_TIMEOUT)
|
|
timeout = LEAPRAID_CTL_CMD_TIMEOUT;
|
|
if (!wait_for_completion_timeout(&adapter->driver_cmds.ctl_cmd.done,
|
|
timeout * HZ)) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: ctl_cmd timeout, status=0x%x\n",
|
|
__func__, adapter->driver_cmds.ctl_cmd.status);
|
|
leapraid_log_req_context(adapter, taskid, ctl_sp_mpi_req);
|
|
}
|
|
|
|
if ((leap_mpi_req->func == LEAPRAID_FUNC_SMP_PASSTHROUGH ||
|
|
leap_mpi_req->func == LEAPRAID_FUNC_SAS_IO_UNIT_CTRL) &&
|
|
adapter->reset_desc.adapter_link_resetting)
|
|
adapter->reset_desc.adapter_link_resetting = 0;
|
|
|
|
if (!(adapter->driver_cmds.ctl_cmd.status & LEAPRAID_CMD_DONE)) {
|
|
issue_reset =
|
|
leapraid_check_reset(
|
|
adapter->driver_cmds.ctl_cmd.status);
|
|
goto reset;
|
|
}
|
|
|
|
if (c2h_size && copy_to_user(karg->c2h_buf_ptr, c2h, c2h_size)) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Failed to copy c2h to user\n", __func__);
|
|
rc = -ENODATA;
|
|
goto out_cleanup;
|
|
}
|
|
if (karg->max_rep_bytes) {
|
|
sz = min_t(u32, karg->max_rep_bytes, LEAPRAID_REPLY_SIZE);
|
|
if (copy_to_user(karg->rep_msg_buf_ptr,
|
|
&adapter->driver_cmds.ctl_cmd.reply,
|
|
sz)) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Failed to copy reply to user\n",
|
|
__func__);
|
|
rc = -ENODATA;
|
|
goto out_cleanup;
|
|
}
|
|
}
|
|
|
|
if (karg->max_sense_bytes &&
|
|
(leap_mpi_req->func == LEAPRAID_FUNC_SCSIIO ||
|
|
leap_mpi_req->func == LEAPRAID_FUNC_SCSIIO_RAID_PASSTHROUGH)) {
|
|
if (!karg->sense_data_ptr)
|
|
goto out_cleanup;
|
|
|
|
sz = min_t(u32, karg->max_sense_bytes, SCSI_SENSE_BUFFERSIZE);
|
|
if (copy_to_user(karg->sense_data_ptr,
|
|
&adapter->driver_cmds.ctl_cmd.sense,
|
|
sz)) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Failed to copy sense data to user\n",
|
|
__func__);
|
|
rc = -ENODATA;
|
|
goto out_cleanup;
|
|
}
|
|
}
|
|
reset:
|
|
if (issue_reset) {
|
|
rc = -ENODATA;
|
|
if (leap_mpi_req->func == LEAPRAID_FUNC_SCSIIO ||
|
|
leap_mpi_req->func ==
|
|
LEAPRAID_FUNC_SCSIIO_RAID_PASSTHROUGH ||
|
|
leap_mpi_req->func ==
|
|
LEAPRAID_FUNC_SCSIIO_SATA_PASSTHROUGH) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"Fire tgt reset, hdl=0x%04x\n",
|
|
le16_to_cpu(leap_mpi_req->func_dep1));
|
|
leapraid_issue_locked_tm(
|
|
adapter,
|
|
le16_to_cpu(leap_mpi_req->func_dep1), 0, 0, 0,
|
|
LEAPRAID_TM_TASKTYPE_TARGET_RESET, taskid,
|
|
LEAPRAID_TM_MSGFLAGS_LINK_RESET);
|
|
} else {
|
|
dev_info(&adapter->pdev->dev,
|
|
"%s:%d: call hard_reset\n",
|
|
__func__, __LINE__);
|
|
leapraid_hard_reset_handler(adapter, FULL_RESET);
|
|
}
|
|
}
|
|
out_cleanup:
|
|
if (c2h)
|
|
dma_free_coherent(&adapter->pdev->dev, c2h_size,
|
|
c2h, c2h_dma_addr);
|
|
if (h2c)
|
|
dma_free_coherent(&adapter->pdev->dev, h2c_size,
|
|
h2c, h2c_dma_addr);
|
|
kfree(leap_mpi_req);
|
|
adapter->driver_cmds.ctl_cmd.status = LEAPRAID_CMD_NOT_USED;
|
|
return rc;
|
|
}
|
|
|
|
static int leapraid_ctl_get_adapter_info(struct leapraid_adapter *adapter,
|
|
void __user *arg)
|
|
{
|
|
struct leapraid_ioctl_adapter_info *karg;
|
|
u8 revision;
|
|
int rc = 0;
|
|
|
|
karg = kzalloc_obj(*karg);
|
|
if (!karg)
|
|
return -ENOMEM;
|
|
|
|
pci_read_config_byte(adapter->pdev, PCI_CLASS_REVISION, &revision);
|
|
karg->revision = revision;
|
|
karg->pci_id = adapter->pdev->device;
|
|
karg->sub_dev = adapter->pdev->subsystem_device;
|
|
karg->sub_vendor = adapter->pdev->subsystem_vendor;
|
|
karg->pci_info.u.bits.bus = adapter->pdev->bus->number;
|
|
karg->pci_info.u.bits.dev = PCI_SLOT(adapter->pdev->devfn);
|
|
karg->pci_info.u.bits.func = PCI_FUNC(adapter->pdev->devfn);
|
|
karg->pci_info.seg_id = pci_domain_nr(adapter->pdev->bus);
|
|
karg->fw_ver = adapter->adapter_attr.features.fw_version;
|
|
|
|
snprintf(karg->driver_ver, sizeof(karg->driver_ver), "%s-%s",
|
|
LEAPRAID_DRIVER_NAME, LEAPRAID_DRIVER_VERSION);
|
|
|
|
karg->adapter_type = LEAPRAID_IOCTL_VERSION;
|
|
karg->bios_ver = adapter->adapter_attr.bios_version;
|
|
if (copy_to_user(arg, karg,
|
|
sizeof(struct leapraid_ioctl_adapter_info))) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Failed to copy info to user\n", __func__);
|
|
rc = -EFAULT;
|
|
goto free_buf;
|
|
}
|
|
|
|
free_buf:
|
|
kfree(karg);
|
|
return rc;
|
|
}
|
|
|
|
static int leapraid_ctl_ioctl_main(struct file *file, unsigned int cmd,
|
|
void __user *arg)
|
|
{
|
|
struct leapraid_ioctl_header ioctl_header;
|
|
struct leapraid_adapter *adapter = NULL;
|
|
struct leapraid_ioctl_command __user *uarg;
|
|
struct leapraid_ioctl_command karg;
|
|
int rc = -ENOIOCTLCMD;
|
|
|
|
if (copy_from_user(&ioctl_header, arg,
|
|
sizeof(struct leapraid_ioctl_header))) {
|
|
pr_err("%s:%s: Failed to copy header from user\n",
|
|
LEAPRAID_DRIVER_NAME, __func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
adapter = leapraid_ctl_lookup_adapter(ioctl_header.adapter_id, false);
|
|
if (!adapter)
|
|
return -EFAULT;
|
|
|
|
if (atomic_read(&adapter->overheat_desc.thermal_alert)) {
|
|
dev_warn(&adapter->pdev->dev,
|
|
"%s: Failed, thermal_alert=%d\n",
|
|
__func__,
|
|
atomic_read(&adapter->overheat_desc.thermal_alert));
|
|
rc = -EFAULT;
|
|
goto out_put;
|
|
}
|
|
|
|
mutex_lock(&adapter->access_ctrl.pci_access_lock);
|
|
|
|
rc = leapraid_check_adapter_is_op(adapter, LEAPRAID_DB_WAIT_OP_LONG,
|
|
__func__);
|
|
if (rc)
|
|
goto unlock;
|
|
|
|
if (!wait_event_timeout(adapter->access_ctrl.shost_recover_wq,
|
|
!adapter->access_ctrl.shost_recover_async,
|
|
LEAPRAID_WAIT_SHOST_RECOVERY * HZ)) {
|
|
dev_warn(&adapter->pdev->dev,
|
|
"Timeout waiting for shost recovery async\n");
|
|
rc = -EAGAIN;
|
|
goto unlock;
|
|
}
|
|
|
|
if (adapter->access_ctrl.pcie_recovering ||
|
|
adapter->scan_dev_desc.driver_loading ||
|
|
adapter->access_ctrl.host_removing) {
|
|
rc = -EAGAIN;
|
|
goto unlock;
|
|
}
|
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
if (!mutex_trylock(&adapter->driver_cmds.ctl_cmd.mutex)) {
|
|
rc = -EAGAIN;
|
|
goto unlock;
|
|
}
|
|
} else if (mutex_lock_interruptible(&adapter->driver_cmds
|
|
.ctl_cmd.mutex)) {
|
|
rc = -ERESTARTSYS;
|
|
goto unlock;
|
|
}
|
|
|
|
switch (_IOC_NR(cmd)) {
|
|
case LEAPRAID_ADAPTER_INFO:
|
|
if (_IOC_SIZE(cmd) ==
|
|
sizeof(struct leapraid_ioctl_adapter_info))
|
|
rc = leapraid_ctl_get_adapter_info(adapter, arg);
|
|
break;
|
|
case LEAPRAID_COMMAND:
|
|
if (copy_from_user(&karg, arg, sizeof(karg))) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"%s: Failed to copy data from user\n",
|
|
__func__);
|
|
rc = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
if (karg.hdr.adapter_id != ioctl_header.adapter_id) {
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (_IOC_SIZE(cmd) == sizeof(struct leapraid_ioctl_command)) {
|
|
uarg = arg;
|
|
rc = leapraid_ctl_do_command(adapter, &karg,
|
|
&uarg->mf);
|
|
if (rc)
|
|
dev_warn(&adapter->pdev->dev,
|
|
"%s: IOCTL cmd failed rc=%d\n",
|
|
__func__, rc);
|
|
}
|
|
break;
|
|
case LEAPRAID_EVENTQUERY:
|
|
case LEAPRAID_EVENTREPORT:
|
|
rc = 0;
|
|
break;
|
|
default:
|
|
dev_err(&adapter->pdev->dev,
|
|
"Unknown IOCTL opcode=0x%08x\n", cmd);
|
|
break;
|
|
}
|
|
mutex_unlock(&adapter->driver_cmds.ctl_cmd.mutex);
|
|
|
|
unlock:
|
|
mutex_unlock(&adapter->access_ctrl.pci_access_lock);
|
|
out_put:
|
|
leapraid_ctl_put_adapter(adapter);
|
|
return rc;
|
|
}
|
|
|
|
static long leapraid_ctl_ioctl(struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
return leapraid_ctl_ioctl_main(file, cmd, (void __user *)arg);
|
|
}
|
|
|
|
static void leapraid_fw_mmap_open(struct vm_area_struct *vma)
|
|
{
|
|
struct leapraid_adapter *adapter = vma->vm_private_data;
|
|
|
|
if (!adapter)
|
|
return;
|
|
|
|
get_device(&adapter->shost->shost_gendev);
|
|
atomic_inc(&adapter->fw_log_desc.mmap_refcnt);
|
|
}
|
|
|
|
static void leapraid_fw_mmap_close(struct vm_area_struct *vma)
|
|
{
|
|
struct leapraid_adapter *adapter = vma->vm_private_data;
|
|
|
|
if (!adapter)
|
|
return;
|
|
|
|
if (atomic_dec_and_test(&adapter->fw_log_desc.mmap_refcnt))
|
|
wake_up(&adapter->fw_log_desc.mmap_waitq);
|
|
leapraid_ctl_put_adapter(adapter);
|
|
}
|
|
|
|
static const struct vm_operations_struct leapraid_fw_mmap_vm_ops = {
|
|
.open = leapraid_fw_mmap_open,
|
|
.close = leapraid_fw_mmap_close,
|
|
};
|
|
|
|
static int leapraid_fw_mmap(struct file *filp, struct vm_area_struct *vma)
|
|
{
|
|
struct leapraid_adapter *adapter = NULL;
|
|
/* Userspace passes the adapter ID via vma->vm_pgoff. */
|
|
u32 adapter_id = vma->vm_pgoff;
|
|
unsigned long length;
|
|
int rc = -EINVAL;
|
|
|
|
length = vma->vm_end - vma->vm_start;
|
|
|
|
adapter = leapraid_ctl_lookup_adapter(adapter_id, true);
|
|
if (!adapter) {
|
|
pr_err("%s: No adapter found!\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (READ_ONCE(adapter->access_ctrl.host_removing)) {
|
|
rc = -EAGAIN;
|
|
goto out_put;
|
|
}
|
|
|
|
if (length > (LEAPRAID_SYS_LOG_BUF_SIZE +
|
|
LEAPRAID_SYS_LOG_BUF_RESERVE)) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"Requested mapping size is too large!\n");
|
|
rc = -EINVAL;
|
|
goto out_put;
|
|
}
|
|
|
|
if (!adapter->fw_log_desc.fw_log_buffer) {
|
|
dev_err(&adapter->pdev->dev, "No log buffer!\n");
|
|
rc = -EINVAL;
|
|
goto out_put;
|
|
}
|
|
|
|
vma->vm_pgoff = 0;
|
|
|
|
rc = dma_mmap_coherent(&adapter->pdev->dev, vma,
|
|
adapter->fw_log_desc.fw_log_buffer,
|
|
adapter->fw_log_desc.fw_log_buffer_dma,
|
|
length);
|
|
if (rc) {
|
|
dev_err(&adapter->pdev->dev,
|
|
"Failed to map memory to user space!\n");
|
|
goto out_put;
|
|
}
|
|
|
|
vma->vm_private_data = adapter;
|
|
vma->vm_ops = &leapraid_fw_mmap_vm_ops;
|
|
leapraid_fw_mmap_open(vma);
|
|
|
|
rc = 0;
|
|
out_put:
|
|
if (adapter &&
|
|
atomic_dec_and_test(&adapter->fw_log_desc.mmap_refcnt))
|
|
wake_up(&adapter->fw_log_desc.mmap_waitq);
|
|
leapraid_ctl_put_adapter(adapter);
|
|
return rc;
|
|
}
|
|
|
|
static const struct file_operations leapraid_ctl_fops = {
|
|
.owner = THIS_MODULE,
|
|
.unlocked_ioctl = leapraid_ctl_ioctl,
|
|
.mmap = leapraid_fw_mmap,
|
|
};
|
|
|
|
static struct miscdevice leapraid_ctl_dev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = LEAPRAID_DEV_NAME,
|
|
.fops = &leapraid_ctl_fops,
|
|
};
|
|
|
|
int leapraid_ctl_init(void)
|
|
{
|
|
if (misc_register(&leapraid_ctl_dev) < 0) {
|
|
pr_err("%s Can't register misc device\n",
|
|
LEAPRAID_DRIVER_NAME);
|
|
return -ENODEV;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void leapraid_ctl_exit(void)
|
|
{
|
|
misc_deregister(&leapraid_ctl_dev);
|
|
}
|