diff options
| -rw-r--r-- | Documentation/ioctl/ioctl-number.txt | 1 | ||||
| -rw-r--r-- | drivers/mmc/card/block.c | 201 | ||||
| -rw-r--r-- | drivers/mmc/core/sd_ops.c | 3 | ||||
| -rw-r--r-- | include/linux/Kbuild | 1 | ||||
| -rw-r--r-- | include/linux/mmc/Kbuild | 1 | ||||
| -rw-r--r-- | include/linux/mmc/core.h | 1 | ||||
| -rw-r--r-- | include/linux/mmc/ioctl.h | 54 | 
7 files changed, 261 insertions, 1 deletions
diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index a0a5d82b6b0..2a34d822e6d 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -304,6 +304,7 @@ Code  Seq#(hex)	Include File		Comments  0xB0	all	RATIO devices		in development:  					<mailto:vgo@ratio.de>  0xB1	00-1F	PPPoX			<mailto:mostrows@styx.uwaterloo.ca> +0xB3	00	linux/mmc/ioctl.h  0xC0	00-0F	linux/usb/iowarrior.h  0xCB	00-1F	CBM serial IEC bus	in development:  					<mailto:michael.klein@puffin.lb.shuttle.de> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index e7efd6faeaf..407836d5571 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -31,7 +31,11 @@  #include <linux/mutex.h>  #include <linux/scatterlist.h>  #include <linux/string_helpers.h> +#include <linux/delay.h> +#include <linux/capability.h> +#include <linux/compat.h> +#include <linux/mmc/ioctl.h>  #include <linux/mmc/card.h>  #include <linux/mmc/host.h>  #include <linux/mmc/mmc.h> @@ -218,11 +222,208 @@ mmc_blk_getgeo(struct block_device *bdev, struct hd_geometry *geo)  	return 0;  } +struct mmc_blk_ioc_data { +	struct mmc_ioc_cmd ic; +	unsigned char *buf; +	u64 buf_bytes; +}; + +static struct mmc_blk_ioc_data *mmc_blk_ioctl_copy_from_user( +	struct mmc_ioc_cmd __user *user) +{ +	struct mmc_blk_ioc_data *idata; +	int err; + +	idata = kzalloc(sizeof(*idata), GFP_KERNEL); +	if (!idata) { +		err = -ENOMEM; +		goto copy_err; +	} + +	if (copy_from_user(&idata->ic, user, sizeof(idata->ic))) { +		err = -EFAULT; +		goto copy_err; +	} + +	idata->buf_bytes = (u64) idata->ic.blksz * idata->ic.blocks; +	if (idata->buf_bytes > MMC_IOC_MAX_BYTES) { +		err = -EOVERFLOW; +		goto copy_err; +	} + +	idata->buf = kzalloc(idata->buf_bytes, GFP_KERNEL); +	if (!idata->buf) { +		err = -ENOMEM; +		goto copy_err; +	} + +	if (copy_from_user(idata->buf, (void __user *)(unsigned long) +					idata->ic.data_ptr, idata->buf_bytes)) { +		err = -EFAULT; +		goto copy_err; +	} + +	return idata; + +copy_err: +	kfree(idata->buf); +	kfree(idata); +	return ERR_PTR(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; +	struct mmc_command cmd = {0}; +	struct mmc_data data = {0}; +	struct mmc_request mrq = {0}; +	struct scatterlist sg; +	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); + +	cmd.opcode = idata->ic.opcode; +	cmd.arg = idata->ic.arg; +	cmd.flags = idata->ic.flags; + +	data.sg = &sg; +	data.sg_len = 1; +	data.blksz = idata->ic.blksz; +	data.blocks = idata->ic.blocks; + +	sg_init_one(data.sg, idata->buf, idata->buf_bytes); + +	if (idata->ic.write_flag) +		data.flags = MMC_DATA_WRITE; +	else +		data.flags = MMC_DATA_READ; + +	mrq.cmd = &cmd; +	mrq.data = &data; + +	md = mmc_blk_get(bdev->bd_disk); +	if (!md) { +		err = -EINVAL; +		goto cmd_done; +	} + +	card = md->queue.card; +	if (IS_ERR(card)) { +		err = PTR_ERR(card); +		goto cmd_done; +	} + +	mmc_claim_host(card->host); + +	if (idata->ic.is_acmd) { +		err = mmc_app_cmd(card->host, card); +		if (err) +			goto cmd_rel_host; +	} + +	/* data.flags must already be set before doing this. */ +	mmc_set_data_timeout(&data, card); +	/* Allow overriding the timeout_ns for empirical tuning. */ +	if (idata->ic.data_timeout_ns) +		data.timeout_ns = idata->ic.data_timeout_ns; + +	if ((cmd.flags & MMC_RSP_R1B) == MMC_RSP_R1B) { +		/* +		 * Pretend this is a data transfer and rely on the host driver +		 * to compute timeout.  When all host drivers support +		 * cmd.cmd_timeout for R1B, this can be changed to: +		 * +		 *     mrq.data = NULL; +		 *     cmd.cmd_timeout = idata->ic.cmd_timeout_ms; +		 */ +		data.timeout_ns = idata->ic.cmd_timeout_ms * 1000000; +	} + +	mmc_wait_for_req(card->host, &mrq); + +	if (cmd.error) { +		dev_err(mmc_dev(card->host), "%s: cmd error %d\n", +						__func__, cmd.error); +		err = cmd.error; +		goto cmd_rel_host; +	} +	if (data.error) { +		dev_err(mmc_dev(card->host), "%s: data error %d\n", +						__func__, data.error); +		err = data.error; +		goto cmd_rel_host; +	} + +	/* +	 * According to the SD specs, some commands require a delay after +	 * issuing the command. +	 */ +	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; +		} +	} + +cmd_rel_host: +	mmc_release_host(card->host); + +cmd_done: +	mmc_blk_put(md); +	kfree(idata->buf); +	kfree(idata); +	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); +	return ret; +} + +#ifdef CONFIG_COMPAT +static int mmc_blk_compat_ioctl(struct block_device *bdev, fmode_t mode, +	unsigned int cmd, unsigned long arg) +{ +	return mmc_blk_ioctl(bdev, mode, cmd, (unsigned long) compat_ptr(arg)); +} +#endif +  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, +#ifdef CONFIG_COMPAT +	.compat_ioctl		= mmc_blk_compat_ioctl, +#endif  };  struct mmc_blk_request { diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index a206aea5360..021fed15380 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -21,7 +21,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 = {0}; @@ -49,6 +49,7 @@ static int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)  	return 0;  } +EXPORT_SYMBOL_GPL(mmc_app_cmd);  /**   *	mmc_wait_for_app_cmd - start an application command and wait for diff --git a/include/linux/Kbuild b/include/linux/Kbuild index 75cf611641e..ed38527599e 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -4,6 +4,7 @@ header-y += caif/  header-y += dvb/  header-y += hdlc/  header-y += isdn/ +header-y += mmc/  header-y += nfsd/  header-y += raid/  header-y += spi/ diff --git a/include/linux/mmc/Kbuild b/include/linux/mmc/Kbuild new file mode 100644 index 00000000000..1fb26448faa --- /dev/null +++ b/include/linux/mmc/Kbuild @@ -0,0 +1 @@ +header-y += ioctl.h diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index f8e4bcbd284..cbe8d55f64c 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);  extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int); diff --git a/include/linux/mmc/ioctl.h b/include/linux/mmc/ioctl.h new file mode 100644 index 00000000000..5baf2983a12 --- /dev/null +++ b/include/linux/mmc/ioctl.h @@ -0,0 +1,54 @@ +#ifndef LINUX_MMC_IOCTL_H +#define LINUX_MMC_IOCTL_H +struct mmc_ioc_cmd { +	/* Implies direction of data.  true = write, false = read */ +	int write_flag; + +	/* Application-specific command.  true = precede with CMD55 */ +	int is_acmd; + +	__u32 opcode; +	__u32 arg; +	__u32 response[4];  /* CMD response */ +	unsigned int flags; +	unsigned int blksz; +	unsigned int blocks; + +	/* +	 * Sleep at least postsleep_min_us useconds, and at most +	 * postsleep_max_us useconds *after* issuing command.  Needed for +	 * some read commands for which cards have no other way of indicating +	 * they're ready for the next command (i.e. there is no equivalent of +	 * a "busy" indicator for read operations). +	 */ +	unsigned int postsleep_min_us; +	unsigned int postsleep_max_us; + +	/* +	 * Override driver-computed timeouts.  Note the difference in units! +	 */ +	unsigned int data_timeout_ns; +	unsigned int cmd_timeout_ms; + +	/* +	 * For 64-bit machines, the next member, ``__u64 data_ptr``, wants to +	 * be 8-byte aligned.  Make sure this struct is the same size when +	 * built for 32-bit. +	 */ +	__u32 __pad; + +	/* DAT buffer */ +	__u64 data_ptr; +}; +#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) + +/* + * 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 + * is enforced per ioctl call.  For larger data transfers, use the normal + * block device operations. + */ +#define MMC_IOC_MAX_BYTES  (512L * 256) +#endif  /* LINUX_MMC_IOCTL_H */  |