// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2026 LeapIO Tech Inc. * * LeapRAID storage and RAID controller driver. */ #include #include #include #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) { 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; 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); 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; 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); 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); adapter = NULL; rc = 0; out_put: 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); }