From patchwork Thu Mar 17 18:28:55 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Calixto X-Patchwork-Id: 641831 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id p2HITJ0v031188 for ; Thu, 17 Mar 2011 18:29:20 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755128Ab1CQS27 (ORCPT ); Thu, 17 Mar 2011 14:28:59 -0400 Received: from 63-147-6-160.dia.static.qwest.net ([63.147.6.160]:25586 "EHLO MODEX01.modsystems.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755088Ab1CQS25 convert rfc822-to-8bit (ORCPT ); Thu, 17 Mar 2011 14:28:57 -0400 Received: from MODEX01.modsystems.com ([10.1.0.4]) by modex01 ([10.1.0.4]) with mapi; Thu, 17 Mar 2011 11:28:56 -0700 From: John Calixto To: "linux-mmc@vger.kernel.org" CC: "cjb@laptop.org" Date: Thu, 17 Mar 2011 11:28:55 -0700 Subject: [PATCH resend] mmc: Added ioctl to let userspace apps send ACMDs Thread-Topic: [PATCH resend] mmc: Added ioctl to let userspace apps send ACMDs Thread-Index: AQHL5NEs8wPhYMJu8ki7Sy/K5qtfOg== Message-ID: <203F41F6E33F954E8E8B02559FDC906F743176BFD2@modex01> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: acceptlanguage: en-US MIME-Version: 1.0 Sender: linux-mmc-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-mmc@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Thu, 17 Mar 2011 18:29:20 +0000 (UTC) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index bfc8a8a..62f742d 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -39,6 +39,7 @@ #include #include +#include #include "queue.h" @@ -158,11 +159,159 @@ mmc_blk_getgeo(struct block_device *bdev, struct hd_geometry *geo) return 0; } +static int mmc_blk_ioctl(struct block_device *bdev, fmode_t mode, unsigned int ioc_cmd, unsigned long ioc_arg) +{ + struct sd_ioc_cmd sdic = {0}; + struct mmc_blk_data *md = NULL; + struct mmc_host *host = NULL; + struct mmc_card *card = NULL; + struct mmc_command cmd = {0}; + struct mmc_data data = {0}; + int err = 0; + struct mmc_request mrq = {0}; + struct scatterlist sg = {0}; + unsigned char *blocks = NULL; + size_t data_bytes = 0; +#ifdef CONFIG_MMC_DEBUG + char dbgbuf[64] = {0}; +#endif + + /* + * This is primarily used for application specific commands (ACMD), so + * the current ioc_cmd validation is trivial. + */ + if (ioc_cmd != SD_IOC_ACMD) + return -EINVAL; + + if (copy_from_user(&sdic, (void __user *) ioc_arg, sizeof(struct sd_ioc_cmd))) { + printk(KERN_ERR "%s: error reading ioctl arg from userspace\n", __func__); + return -EFAULT; + } + + if (sdic.struct_version != SD_IOC_CMD_STRUCT_VERSION) + return -EINVAL; + + /* Find the mmc structures based on the bdev. */ + md = mmc_blk_get(bdev->bd_disk); + if (!md) + return -EINVAL; + + card = md->queue.card; +#ifdef CONFIG_MMC_DEBUG + printk(KERN_DEBUG "%s: card = %p\n", __func__, card); +#endif + if (IS_ERR(card)) + return PTR_ERR(card); + +#ifdef CONFIG_MMC_DEBUG + printk(KERN_DEBUG "%s: host = %p\n", __func__, card->host); +#endif + host = card->host; + BUG_ON(!host); + + mmc_claim_host(host); + + err = mmc_app_cmd(host, card); + if (err) { + dev_err(mmc_dev(host), "%s: CMD%d error\n", __func__, MMC_APP_CMD); + goto ioctl_done; + } + + mrq.cmd = &cmd; + mrq.data = &data; + + cmd.opcode = sdic.opcode; + cmd.arg = sdic.arg; + cmd.flags = sdic.flags; + + data.sg = &sg; + data.sg_len = 1; + data.blksz = sdic.blksz; + data.blocks = sdic.blocks; + + data_bytes = data.blksz * data.blocks; + blocks = (unsigned char *) kzalloc(data_bytes, GFP_KERNEL); + if (!blocks) { + err = -ENOMEM; + goto ioctl_done; + } + sg_init_one(data.sg, blocks, data_bytes); + + + if (copy_from_user(blocks, sdic.data, data_bytes)) { + dev_err(mmc_dev(host), "%s: error reading userspace buffer\n", __func__); + err = -EFAULT; + goto ioctl_done; + } + if (sdic.write_flag) { + data.flags = MMC_DATA_WRITE; + } else { + data.flags = MMC_DATA_READ; + } + + /* data.flags must already be set before doing this. */ + mmc_set_data_timeout(&data, card); + /* Allow overriding the timeout_ns for empirical tuning. */ + if (sdic.force_timeout_ns) + data.timeout_ns = sdic.force_timeout_ns; + +#ifdef CONFIG_MMC_DEBUG + hex_dump_to_buffer(blocks, data_bytes, 16, 1, dbgbuf, sizeof(dbgbuf), 0); + dev_dbg(mmc_dev(host), "%s: first bytes of pre data\n%s\n", __func__, dbgbuf); +#endif + + mmc_wait_for_req(host, &mrq); + + if (cmd.error) { + dev_err(mmc_dev(host), "%s: cmd error %d\n", __func__, cmd.error); + err = cmd.error; + goto ioctl_done; + } + if (data.error) { + dev_err(mmc_dev(host), "%s: data error %d\n", __func__, data.error); + err = data.error; + goto ioctl_done; + } + + /* + * According to the SD specs, some commands require a delay after + * issuing the command. + */ + if (sdic.postsleep_us) + udelay(sdic.postsleep_us); + + if (copy_to_user(&(((struct sd_ioc_cmd *) ioc_arg)->response), cmd.resp, sizeof(u32) * 4)) { + dev_err(mmc_dev(host), "%s: error copying response\n", __func__); + err = -EFAULT; + goto ioctl_done; + } + +#ifdef CONFIG_MMC_DEBUG + hex_dump_to_buffer(blocks, data_bytes, 16, 1, dbgbuf, sizeof(dbgbuf), 0); + dev_dbg(mmc_dev(host), "%s: first bytes of post data\n%s\n", __func__, dbgbuf); +#endif + if (!sdic.write_flag) { + if (copy_to_user(sdic.data, blocks, data_bytes)) { + dev_err(mmc_dev(host), "%s: error copying data\n", __func__); + err = -EFAULT; + goto ioctl_done; + } + } + +ioctl_done: + if (blocks) + kfree(blocks); + mmc_release_host(host); + mmc_blk_put(md); + return err; +} + static const struct block_device_operations mmc_bdops = { .open = mmc_blk_open, .release = mmc_blk_release, .getgeo = mmc_blk_getgeo, .owner = THIS_MODULE, + .ioctl = mmc_blk_ioctl, }; struct mmc_blk_request { diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index 797cdb5..0453dcd 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -20,7 +20,7 @@ #include "core.h" #include "sd_ops.h" -static int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card) +int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card) { int err; struct mmc_command cmd; @@ -48,6 +48,7 @@ static int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card) return 0; } +EXPORT_SYMBOL(mmc_app_cmd); /** * mmc_wait_for_app_cmd - start an application command and wait for diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 64e013f..1adda405 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -133,6 +133,7 @@ struct mmc_card; extern void mmc_wait_for_req(struct mmc_host *, struct mmc_request *); extern int mmc_wait_for_cmd(struct mmc_host *, struct mmc_command *, int); +extern int mmc_app_cmd(struct mmc_host *, struct mmc_card *); extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *, struct mmc_command *, int); diff --git a/include/linux/mmc/sd.h b/include/linux/mmc/sd.h index 3fd85e0..a031cba 100644 --- a/include/linux/mmc/sd.h +++ b/include/linux/mmc/sd.h @@ -12,6 +12,8 @@ #ifndef MMC_SD_H #define MMC_SD_H +#include + /* SD commands type argument response */ /* class 0 */ /* This is basically the same command as for MMC with some quirks. */ @@ -84,5 +86,21 @@ #define SD_SWITCH_ACCESS_DEF 0 #define SD_SWITCH_ACCESS_HS 1 +struct sd_ioc_cmd { + unsigned int struct_version; +#define SD_IOC_CMD_STRUCT_VERSION 0 + int write_flag; /* implies direction of data. true = write, false = read */ + unsigned int opcode; + unsigned int arg; + unsigned int flags; + unsigned int postsleep_us; /* apply usecond delay *after* issuing command */ + unsigned int force_timeout_ns; /* force timeout to be force_timeout_ns ns */ + unsigned int response[4]; /* CMD response */ + unsigned int blksz; + unsigned int blocks; + unsigned char *data; /* DAT buffer */ +}; +#define SD_IOC_ACMD _IOWR(MMC_BLOCK_MAJOR, 0, struct sd_ioc_cmd *) + #endif