diff options
-rw-r--r-- | drivers/mmc/card/block.c | 225 | ||||
-rw-r--r-- | include/uapi/linux/mmc/ioctl.h | 18 |
2 files changed, 202 insertions, 41 deletions
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 93b9e202bce1..af4c62f99a12 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3,6 +3,7 @@ * * Copyright 2002 Hewlett-Packard Company * Copyright 2005-2008 Pierre Ossman + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. * * Use consistent with the GNU GPL is permitted, * provided that this copyright notice is @@ -375,6 +376,30 @@ out: return ERR_PTR(err); } +static int mmc_blk_ioctl_copy_to_user( + struct mmc_ioc_cmd __user *ic_ptr, + struct mmc_blk_ioc_data *idata) +{ + int err = 0; + + if (copy_to_user(&(ic_ptr->response), idata->ic.response, + sizeof(idata->ic.response))) { + err = -EFAULT; + goto out; + } + if (!idata->ic.write_flag) { + if (copy_to_user((void __user *) + (unsigned long) idata->ic.data_ptr, + idata->buf, idata->buf_bytes)) { + err = -EFAULT; + goto out; + } + } +out: + return err; + +} + static int ioctl_rpmb_card_status_poll(struct mmc_card *card, u32 *status, u32 retries_max) { @@ -407,11 +432,9 @@ static int ioctl_rpmb_card_status_poll(struct mmc_card *card, u32 *status, return err; } -static int mmc_blk_ioctl_cmd(struct block_device *bdev, - struct mmc_ioc_cmd __user *ic_ptr) +static int _mmc_blk_ioctl_cmd_locked(struct mmc_blk_data *md, + struct mmc_blk_ioc_data *idata) { - struct mmc_blk_ioc_data *idata; - struct mmc_blk_data *md; struct mmc_card *card; struct mmc_command cmd = {0}; struct mmc_data data = {0}; @@ -421,19 +444,6 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, int is_rpmb = false; u32 status = 0; - /* - * The caller must have CAP_SYS_RAWIO, and must be calling this on the - * whole block device, not on a partition. This prevents overspray - * between sibling partitions. - */ - if ((!capable(CAP_SYS_RAWIO)) || (bdev != bdev->bd_contains)) - return -EPERM; - - idata = mmc_blk_ioctl_copy_from_user(ic_ptr); - if (IS_ERR(idata)) - return PTR_ERR(idata); - - md = mmc_blk_get(bdev->bd_disk); if (!md) { err = -EINVAL; goto cmd_err; @@ -445,7 +455,7 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, card = md->queue.card; if (IS_ERR(card)) { err = PTR_ERR(card); - goto cmd_done; + goto cmd_err; } cmd.opcode = idata->ic.opcode; @@ -490,23 +500,21 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, mrq.cmd = &cmd; - mmc_claim_host(card->host); - err = mmc_blk_part_switch(card, md); if (err) - goto cmd_rel_host; + goto cmd_err; if (idata->ic.is_acmd) { err = mmc_app_cmd(card->host, card); if (err) - goto cmd_rel_host; + goto cmd_err; } if (is_rpmb) { err = mmc_set_blockcount(card, data.blocks, idata->ic.write_flag & (1 << 31)); if (err) - goto cmd_rel_host; + goto cmd_err; } mmc_wait_for_req(card->host, &mrq); @@ -515,13 +523,13 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, dev_err(mmc_dev(card->host), "%s: cmd error %d\n", __func__, cmd.error); err = cmd.error; - goto cmd_rel_host; + goto cmd_err; } if (data.error) { dev_err(mmc_dev(card->host), "%s: data error %d\n", __func__, data.error); err = data.error; - goto cmd_rel_host; + goto cmd_err; } /* @@ -531,18 +539,7 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, if (idata->ic.postsleep_min_us) usleep_range(idata->ic.postsleep_min_us, idata->ic.postsleep_max_us); - if (copy_to_user(&(ic_ptr->response), cmd.resp, sizeof(cmd.resp))) { - err = -EFAULT; - goto cmd_rel_host; - } - - if (!idata->ic.write_flag) { - if (copy_to_user((void __user *)(unsigned long) idata->ic.data_ptr, - idata->buf, idata->buf_bytes)) { - err = -EFAULT; - goto cmd_rel_host; - } - } + memcpy(&(idata->ic.response), cmd.resp, sizeof(cmd.resp)); if (is_rpmb) { /* @@ -555,10 +552,50 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, "%s: Card Status=0x%08X, error %d\n", __func__, status, err); } +cmd_err: + return err; +} + +static int mmc_blk_ioctl_cmd(struct block_device *bdev, + struct mmc_ioc_cmd __user *ic_ptr) +{ + struct mmc_blk_ioc_data *idata; + struct mmc_blk_data *md; + struct mmc_card *card; + int err; + + /* + * The caller must have CAP_SYS_RAWIO, and must be calling this on the + * whole block device, not on a partition. This prevents overspray + * between sibling partitions. + */ + if ((!capable(CAP_SYS_RAWIO)) || (bdev != bdev->bd_contains)) + return -EPERM; + + idata = mmc_blk_ioctl_copy_from_user(ic_ptr); + if (IS_ERR(idata)) + return PTR_ERR(idata); + + md = mmc_blk_get(bdev->bd_disk); + if (!md) { + err = -EINVAL; + goto cmd_err; + } + + card = md->queue.card; + if (IS_ERR(card)) { + err = PTR_ERR(card); + goto cmd_done; + } + + mmc_claim_host(card->host); + + err = _mmc_blk_ioctl_cmd_locked(md, idata); -cmd_rel_host: mmc_release_host(card->host); + mmc_blk_ioctl_copy_to_user(ic_ptr, idata); + cmd_done: mmc_blk_put(md); cmd_err: @@ -567,12 +604,120 @@ cmd_err: return err; } +static int mmc_blk_ioctl_combo_cmd(struct block_device *bdev, + struct mmc_combo_cmd_info __user *user) +{ + int err, i; + struct mmc_combo_cmd_info mcci = {0}; + u8 num_cmd; + struct mmc_blk_ioc_data **ioc_data = NULL; + u32 usr_ptr; + struct mmc_card *card; + struct mmc_blk_data *md; + + /* + * The caller must have CAP_SYS_RAWIO, and must be calling this on the + * whole block device, not on a partition. This prevents overspray + * between sibling partitions. + */ + if ((!capable(CAP_SYS_RAWIO)) || (bdev != bdev->bd_contains)) + return -EPERM; + + md = mmc_blk_get(bdev->bd_disk); + if (!md) { + err = -EINVAL; + goto out; + } + + card = md->queue.card; + if (IS_ERR(card)) { + err = PTR_ERR(card); + goto cmd_done; + } + + if (copy_from_user(&mcci, user, sizeof(struct mmc_combo_cmd_info))) { + err = -EFAULT; + goto cmd_done; + } + + num_cmd = mcci.num_of_combo_cmds; + if (num_cmd < 1) { + err = -EINVAL; + goto cmd_done; + } + + ioc_data = devm_kzalloc(mmc_dev(card->host), + sizeof(struct mmc_blk_ioc_data) * num_cmd, + GFP_KERNEL); + if (!ioc_data) { + err = -ENOMEM; + goto cmd_done; + } + + for (i = 0; i < num_cmd; i++) { + usr_ptr = (u32)mcci.mmc_ioc_cmd_list + + (i * sizeof(struct mmc_ioc_cmd)); + ioc_data[i] = mmc_blk_ioctl_copy_from_user( + (struct mmc_ioc_cmd __user *)usr_ptr); + if (IS_ERR(ioc_data[i])) { + err = PTR_ERR(ioc_data[i]); + goto free_all; + } + } + + mmc_claim_host(card->host); + for (i = 0; i < num_cmd; i++) { + err = _mmc_blk_ioctl_cmd_locked(md, + ioc_data[i]); + if (err) { + err = -EFAULT; + mmc_release_host(card->host); + goto free_all; + } + } + mmc_release_host(card->host); + + /* copy to user if data and response */ + for (i = 0; i < num_cmd; i++) { + usr_ptr = (u32)mcci.mmc_ioc_cmd_list + + (i * sizeof(struct mmc_ioc_cmd)); + err = mmc_blk_ioctl_copy_to_user( + (struct mmc_ioc_cmd __user *)usr_ptr, + ioc_data[i]); + if (err) + break; + } + +free_all: + for (i = 0; i < num_cmd; i++) { + if (IS_ERR(ioc_data[i])) + break; + kfree(ioc_data[i]->buf); + kfree(ioc_data[i]); + } + devm_kfree(mmc_dev(card->host), ioc_data); +cmd_done: + mmc_blk_put(md); +out: + return err; +} + static int mmc_blk_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg) { int ret = -EINVAL; - if (cmd == MMC_IOC_CMD) - ret = mmc_blk_ioctl_cmd(bdev, (struct mmc_ioc_cmd __user *)arg); + switch (cmd) { + case MMC_COMBO_IOC_CMD: { + ret = mmc_blk_ioctl_combo_cmd(bdev, + (struct mmc_combo_cmd_info __user *)arg); + break; + } + case MMC_IOC_CMD: { + ret = mmc_blk_ioctl_cmd(bdev, + (struct mmc_ioc_cmd __user *)arg); + break; + } + } return ret; } diff --git a/include/uapi/linux/mmc/ioctl.h b/include/uapi/linux/mmc/ioctl.h index 1f5e68923929..35263b31322c 100644 --- a/include/uapi/linux/mmc/ioctl.h +++ b/include/uapi/linux/mmc/ioctl.h @@ -45,8 +45,24 @@ struct mmc_ioc_cmd { }; #define mmc_ioc_cmd_set_data(ic, ptr) ic.data_ptr = (__u64)(unsigned long) ptr -#define MMC_IOC_CMD _IOWR(MMC_BLOCK_MAJOR, 0, struct mmc_ioc_cmd) +/** + * combo command used ioctl cmd id MMC_COMBO_IOC_CMD + */ +struct mmc_combo_cmd_info { + uint8_t num_of_combo_cmds; + struct mmc_ioc_cmd *mmc_ioc_cmd_list; +}; +#define MMC_IOC_CMD _IOWR(MMC_BLOCK_MAJOR, 0, struct mmc_ioc_cmd) +/* + * MMC_COMBO_IOC_CMD: This is used to send a array of eMMC commands. + * each entry in struct mmc_ioc_cmd will have details of + * one eMMC command. + * The mmc driver will issue all commands in array in sequence to card. + * To ensure atomic operations of all commands in array, + * a single lock is acquired. + */ +#define MMC_COMBO_IOC_CMD _IOWR(MMC_BLOCK_MAJOR, 1, struct mmc_combo_cmd_info) /* * Since this ioctl is only meant to enhance (and not replace) normal access * to the mmc bus device, an upper data transfer limit of MMC_IOC_MAX_BYTES |