/* * Copyright (c) 2018-2020, NVIDIA CORPORATION. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * */ #include /* printk() */ #include /* kmalloc() */ #include /* everything... */ #include /* error codes */ #include /* O_ACCMODE */ #include #include #include #include #include "tegra_vblk.h" #define VBLK_MMC_MAX_IOC_SIZE (256 * 1024) static uint32_t vblk_get_response_type(uint32_t mmc_resp_type) { uint32_t vblk_resp; /* consider only respose types */ switch (mmc_resp_type & 0x1F) { case MMC_RSP_NONE: vblk_resp = RESP_TYPE_NO_RESP; break; case MMC_RSP_R1: /* * MMC_RSP_R1, MMC_RSP_R6, MMC_RSP_R6, MMC_RSP_R7 have same * values */ vblk_resp = RESP_TYPE_R1; break; case MMC_RSP_R1B: vblk_resp = RESP_TYPE_R1B; break; case MMC_RSP_R2: vblk_resp = RESP_TYPE_R2; break; case MMC_RSP_R3: /* MMC_RSP_R3 and MMC_RSP_R4 have same value */ vblk_resp = RESP_TYPE_R3; break; default: vblk_resp = RESP_TYPE_NUM; } return vblk_resp; } int vblk_prep_mmc_multi_ioc(struct vblk_dev *vblkdev, struct vblk_ioctl_req *ioctl_req, void __user *user, uint32_t cmd) { int err = 0; struct combo_info_t *combo_info; struct combo_cmd_t *combo_cmd; int i = 0; uint64_t num_cmd; struct mmc_ioc_cmd ic; struct mmc_ioc_multi_cmd __user *user_cmd; struct mmc_ioc_cmd __user *usr_ptr; uint32_t combo_cmd_size; uint32_t ioctl_bytes = VBLK_MMC_MAX_IOC_SIZE; uint8_t *tmpaddr; void *ioctl_buf; ioctl_buf = vmalloc(ioctl_bytes); if (ioctl_buf == NULL) { return -ENOMEM; } combo_info = (struct combo_info_t *)ioctl_buf; combo_cmd_size = sizeof(uint32_t); if (cmd == MMC_IOC_MULTI_CMD) { user_cmd = (struct mmc_ioc_multi_cmd __user *)user; if (copy_from_user(&num_cmd, &user_cmd->num_of_cmds, sizeof(num_cmd))) { err = -EFAULT; goto free_ioc_buf; } if (num_cmd > MMC_IOC_MAX_CMDS) { err = -EINVAL; goto free_ioc_buf; } usr_ptr = (void * __user)&user_cmd->cmds; } else { num_cmd = 1; usr_ptr = (void * __user)user; } combo_info->count = num_cmd; combo_cmd = (struct combo_cmd_t *)(ioctl_buf + sizeof(struct combo_info_t)); combo_cmd_size = sizeof(struct combo_info_t) + sizeof(struct combo_cmd_t) * combo_info->count; if (combo_cmd_size < sizeof(struct combo_info_t)) { dev_err(vblkdev->device, "combo_cmd_size is overflowing!\n"); err = -EINVAL; goto free_ioc_buf; } if (combo_cmd_size > ioctl_bytes) { dev_err(vblkdev->device, " buffer has no enough space to serve ioctl\n"); err = -EFAULT; goto free_ioc_buf; } tmpaddr = (uint8_t *)⁣ for (i = 0; i < combo_info->count; i++) { if (copy_from_user((void *)tmpaddr, usr_ptr, sizeof(ic))) { err = -EFAULT; goto free_ioc_buf; } combo_cmd->cmd = ic.opcode; combo_cmd->arg = ic.arg; combo_cmd->flags = vblk_get_response_type(ic.flags); combo_cmd->write_flag = (uint32_t)ic.write_flag; combo_cmd->data_len = (uint32_t)(ic.blksz * ic.blocks); combo_cmd->buf_offset = combo_cmd_size; combo_cmd_size += combo_cmd->data_len; if ((combo_cmd_size < combo_cmd->data_len) || (combo_cmd_size > ioctl_bytes)) { dev_err(vblkdev->device, " buffer has no enough space to serve ioctl\n"); err = -EFAULT; goto free_ioc_buf; } if (ic.write_flag && combo_cmd->data_len) { if (copy_from_user(( (void *)ioctl_buf + combo_cmd->buf_offset), (void __user *)(unsigned long)ic.data_ptr, (u64)combo_cmd->data_len)) { dev_err(vblkdev->device, "copy from user failed for data!\n"); err = -EFAULT; goto free_ioc_buf; } } combo_cmd++; usr_ptr++; } ioctl_req->ioctl_id = VBLK_MMC_MULTI_IOC_ID; ioctl_req->ioctl_buf = ioctl_buf; ioctl_req->ioctl_len = ioctl_bytes; free_ioc_buf: if (err && ioctl_buf) vfree(ioctl_buf); return err; } int vblk_complete_mmc_multi_ioc(struct vblk_dev *vblkdev, struct vblk_ioctl_req *ioctl_req, void __user *user, uint32_t cmd) { uint64_t num_cmd; struct mmc_ioc_cmd ic; struct mmc_ioc_cmd *ic_ptr = ⁣ struct mmc_ioc_multi_cmd __user *user_cmd; struct mmc_ioc_cmd __user *usr_ptr; struct combo_cmd_t *combo_cmd; uint32_t i; int err = 0; void *ioctl_buf = ioctl_req->ioctl_buf; if (cmd == MMC_IOC_MULTI_CMD) { user_cmd = (struct mmc_ioc_multi_cmd __user *)user; if (copy_from_user(&num_cmd, &user_cmd->num_of_cmds, sizeof(num_cmd))) { err = -EFAULT; goto free_ioc_buf; } if (num_cmd > MMC_IOC_MAX_CMDS) { err = -EINVAL; goto free_ioc_buf; } usr_ptr = (void * __user)&user_cmd->cmds; } else { usr_ptr = (void * __user)user; num_cmd = 1; } combo_cmd = (struct combo_cmd_t *)(ioctl_buf + sizeof(struct combo_info_t)); for (i = 0; i < num_cmd; i++) { if (copy_from_user((void *)ic_ptr, usr_ptr, sizeof(struct mmc_ioc_cmd))) { err = -EFAULT; goto free_ioc_buf; } if (copy_to_user(&(usr_ptr->response), combo_cmd->response, sizeof(combo_cmd->response))) { err = -EFAULT; goto free_ioc_buf; } if (!ic.write_flag && combo_cmd->data_len) { if (copy_to_user( (void __user *)(unsigned long)ic.data_ptr, (ioctl_buf + combo_cmd->buf_offset), (u64)combo_cmd->data_len)) { dev_err(vblkdev->device, "copy to user of ioctl data failed!\n"); err = -EFAULT; goto free_ioc_buf; } } combo_cmd++; usr_ptr++; } free_ioc_buf: if (ioctl_buf) vfree(ioctl_buf); return err; }