blob: 84eef50947aea2d671da410c129fa80d97087a00 [file] [edit]
// 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);
}