| // 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); |
| } |