On Sun, 2017-08-20 at 23:39 +0200, Linus Walleij wrote: > The RPMB partition on the eMMC devices is a special area used > for storing cryptographically safe information signed by a > special secret key. To write and read records from this special > area, authentication is needed. > > The RPMB area is *only* and *exclusively* accessed using > ioctl():s from userspace. It is not really a block device, > as blocks cannot be read or written from the device, also > the signed chunks that can be stored on the RPMB are actually > 256 bytes, not 512 making a block device a real bad fit. > > Currently the RPMB partition spawns a separate block device > named /dev/mmcblkNrpmb for each device with an RPMB partition, > including the creation of a block queue with its own kernel > thread and all overhead associated with this. On the Ux500 > HREFv60 platform, for example, the two eMMCs means that two > block queues with separate threads are created for no use > whatsoever. > > I have concluded that this block device design for RPMB is > actually pretty wrong. The RPMB area should have been designed > to be accessed from /dev/mmcblkN directly, using ioctl()s on > the main block device. It is however way too late to change > that, since userspace expects to open an RPMB device in > /dev/mmcblkNrpmb and we cannot break userspace. > > This patch tries to amend the situation using the following > strategy: > > - Stop creating a block device for the RPMB partition/area > > - Instead create a custom, dynamic character device with > the same name. > > - Make this new character device support exactly the same > set of ioctl()s as the old block device. > > - Wrap the requests back to the same ioctl() handlers, but > issue them on the block queue of the main partition/area, > i.e. /dev/mmcblkN > > We need to create a special "rpmb" bus type in order to get > udev and/or busybox hot/coldplug to instantiate the device > node properly. > > Before the patch, this appears in 'ps aux': > > 101 root 0:00 [mmcqd/2rpmb] > 123 root 0:00 [mmcqd/3rpmb] > > After applying the patch these surplus block queue threads > are gone, but RPMB is as usable as ever using the userspace > MMC tools, such as 'mmc rpmb read-counter'. > > We get instead those dynamice devices in /dev: > > brw-rw---- 1 root root 179, 0 Jan 1 2000 mmcblk0 > brw-rw---- 1 root root 179, 1 Jan 1 2000 mmcblk0p1 > brw-rw---- 1 root root 179, 2 Jan 1 2000 mmcblk0p2 > brw-rw---- 1 root root 179, 5 Jan 1 2000 mmcblk0p5 > brw-rw---- 1 root root 179, 8 Jan 1 2000 mmcblk2 > brw-rw---- 1 root root 179, 16 Jan 1 2000 mmcblk2boot0 > brw-rw---- 1 root root 179, 24 Jan 1 2000 mmcblk2boot1 > crw-rw---- 1 root root 248, 0 Jan 1 2000 mmcblk2rpmb > brw-rw---- 1 root root 179, 32 Jan 1 2000 mmcblk3 > brw-rw---- 1 root root 179, 40 Jan 1 2000 mmcblk3boot0 > brw-rw---- 1 root root 179, 48 Jan 1 2000 mmcblk3boot1 > brw-rw---- 1 root root 179, 33 Jan 1 2000 mmcblk3p1 > crw-rw---- 1 root root 248, 1 Jan 1 2000 mmcblk3rpmb > > Notice the (248,0) and (248,1) character devices for RPMB. > > Cc: Tomas Winkler <tomas.winkler@xxxxxxxxx> > Signed-off-by: Linus Walleij <linus.walleij@xxxxxxxxxx> > --- > ChangeLog v1 (RFC) -> v5: > - Rebase. > - Drop discussion comments, let's go for this unless someone > has a better idea. > - Rename rpmb_devt and rpmb_bus_type to mmc_rpmb_devt and > mmc_rpmb_bus_type as requested by Tomas. > - Handle multiple RPMB partitions as requested by Tomas. > - Renumber v5 to keep together with the rest of the series. > --- > drivers/mmc/core/block.c | 286 +++++++++++++++++++++++++++++++++++++++++++---- > drivers/mmc/core/queue.h | 2 + > 2 files changed, 266 insertions(+), 22 deletions(-) > > diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c > index 502068b155b9..27e1cf07a7a2 100644 > --- a/drivers/mmc/core/block.c > +++ b/drivers/mmc/core/block.c > @@ -28,6 +28,7 @@ > #include <linux/hdreg.h> > #include <linux/kdev_t.h> > #include <linux/blkdev.h> > +#include <linux/cdev.h> > #include <linux/mutex.h> > #include <linux/scatterlist.h> > #include <linux/string_helpers.h> > @@ -86,6 +87,7 @@ static int max_devices; > #define MAX_DEVICES 256 > > static DEFINE_IDA(mmc_blk_ida); > +static DEFINE_IDA(mmc_rpmb_ida); > > /* > * There is one mmc_blk_data per slot. > @@ -96,6 +98,7 @@ struct mmc_blk_data { > struct gendisk *disk; > struct mmc_queue queue; > struct list_head part; > + struct list_head rpmbs; > > unsigned int flags; > #define MMC_BLK_CMD23 (1 << 0) /* Can do SET_BLOCK_COUNT for multiblock */ > @@ -121,6 +124,32 @@ struct mmc_blk_data { > int area_type; > }; > > +/* Device type for RPMB character devices */ > +static dev_t mmc_rpmb_devt; > + > +/* Bus type for RPMB character devices */ > +static struct bus_type mmc_rpmb_bus_type = { > + .name = "rpmb", Also need renaming to 'mmc_rpmb', this goes to ABI /sys/bus/mmc_rpmb/ I'm not CCed to whole series so not sure you've updated also Documentation/ABI/testing/sysfs-bus-* > +}; > + > +/** > + * struct mmc_rpmb_data - special RPMB device type for these areas > + * @dev: the device for the RPMB area > + * @chrdev: character device for the RPMB area > + * @id: unique device ID number > + * @part_index: partition index (0 on first) > + * @md: parent MMC block device > + * @node: list item, so we can put this device on a list > + */ > +struct mmc_rpmb_data { > + struct device dev; > + struct cdev chrdev; > + int id; > + unsigned int part_index; > + struct mmc_blk_data *md; > + struct list_head node; > +}; > + > static DEFINE_MUTEX(open_lock); > > module_param(perdev_minors, int, 0444); > @@ -299,6 +328,7 @@ struct mmc_blk_ioc_data { > struct mmc_ioc_cmd ic; > unsigned char *buf; > u64 buf_bytes; > + struct mmc_rpmb_data *rpmb; > }; > > static struct mmc_blk_ioc_data *mmc_blk_ioctl_copy_from_user( > @@ -437,14 +467,25 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, > struct mmc_request mrq = {}; > struct scatterlist sg; > int err; > - bool is_rpmb = false; > + unsigned int target_part; > u32 status = 0; > > if (!card || !md || !idata) > return -EINVAL; > > - if (md->area_type & MMC_BLK_DATA_AREA_RPMB) > - is_rpmb = true; > + /* > + * The RPMB accesses comes in from the character device, so we > + * need to target these explicitly. Else we just target the > + * partition type for the block device the ioctl() was issued > + * on. > + */ > + if (idata->rpmb) { > + /* Support multiple RPMB partitions */ > + target_part = idata->rpmb->part_index; > + target_part |= EXT_CSD_PART_CONFIG_ACC_RPMB; > + } else { > + target_part = md->part_type; > + } > > cmd.opcode = idata->ic.opcode; > cmd.arg = idata->ic.arg; > @@ -488,7 +529,7 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, > > mrq.cmd = &cmd; > > - err = mmc_blk_part_switch(card, md->part_type); > + err = mmc_blk_part_switch(card, target_part); > if (err) > return err; > > @@ -498,7 +539,7 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, > return err; > } > > - if (is_rpmb) { > + if (idata->rpmb) { > err = mmc_set_blockcount(card, data.blocks, > idata->ic.write_flag & (1 << 31)); > if (err) > @@ -538,7 +579,7 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, > > memcpy(&(idata->ic.response), cmd.resp, sizeof(cmd.resp)); > > - if (is_rpmb) { > + if (idata->rpmb) { > /* > * Ensure RPMB command has completed by polling CMD13 > * "Send Status". > @@ -554,7 +595,8 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, > } > > static int mmc_blk_ioctl_cmd(struct mmc_blk_data *md, > - struct mmc_ioc_cmd __user *ic_ptr) > + struct mmc_ioc_cmd __user *ic_ptr, > + struct mmc_rpmb_data *rpmb) > { > struct mmc_blk_ioc_data *idata; > struct mmc_blk_ioc_data *idatas[1]; > @@ -566,6 +608,8 @@ static int mmc_blk_ioctl_cmd(struct mmc_blk_data *md, > idata = mmc_blk_ioctl_copy_from_user(ic_ptr); > if (IS_ERR(idata)) > return PTR_ERR(idata); > + /* This will be NULL on non-RPMB ioctl():s */ > + idata->rpmb = rpmb; > > card = md->queue.card; > if (IS_ERR(card)) { > @@ -581,7 +625,10 @@ static int mmc_blk_ioctl_cmd(struct mmc_blk_data *md, > idata->ic.write_flag ? REQ_OP_DRV_OUT : REQ_OP_DRV_IN, > __GFP_RECLAIM); > idatas[0] = idata; > - req_to_mmc_queue_req(req)->drv_op = MMC_DRV_OP_IOCTL; > + if (rpmb) > + req_to_mmc_queue_req(req)->drv_op = MMC_DRV_OP_IOCTL_RPMB; > + else > + req_to_mmc_queue_req(req)->drv_op = MMC_DRV_OP_IOCTL; maybe neater to use = ?: here, it's used in this module often > req_to_mmc_queue_req(req)->drv_op_data = idatas; > req_to_mmc_queue_req(req)->ioc_count = 1; > blk_execute_rq(mq->queue, NULL, req, 0); > @@ -596,7 +643,8 @@ static int mmc_blk_ioctl_cmd(struct mmc_blk_data *md, > } > > static int mmc_blk_ioctl_multi_cmd(struct mmc_blk_data *md, > - struct mmc_ioc_multi_cmd __user *user) > + struct mmc_ioc_multi_cmd __user *user, > + struct mmc_rpmb_data *rpmb) > { > struct mmc_blk_ioc_data **idata = NULL; > struct mmc_ioc_cmd __user *cmds = user->cmds; > @@ -627,6 +675,8 @@ static int mmc_blk_ioctl_multi_cmd(struct mmc_blk_data *md, > num_of_cmds = i; > goto cmd_err; > } > + /* This will be NULL on non-RPMB ioctl():s */ > + idata[i]->rpmb = rpmb; > } > > card = md->queue.card; > @@ -643,7 +693,10 @@ static int mmc_blk_ioctl_multi_cmd(struct mmc_blk_data *md, > req = blk_get_request(mq->queue, > idata[0]->ic.write_flag ? REQ_OP_DRV_OUT : REQ_OP_DRV_IN, > __GFP_RECLAIM); > - req_to_mmc_queue_req(req)->drv_op = MMC_DRV_OP_IOCTL; > + if (rpmb) > + req_to_mmc_queue_req(req)->drv_op = MMC_DRV_OP_IOCTL_RPMB; > + else > + req_to_mmc_queue_req(req)->drv_op = MMC_DRV_OP_IOCTL; > req_to_mmc_queue_req(req)->drv_op_data = idata; > req_to_mmc_queue_req(req)->ioc_count = num_of_cmds; > blk_execute_rq(mq->queue, NULL, req, 0); > @@ -691,7 +744,8 @@ static int mmc_blk_ioctl(struct block_device *bdev, fmode_t mode, > if (!md) > return -EINVAL; > ret = mmc_blk_ioctl_cmd(md, > - (struct mmc_ioc_cmd __user *)arg); > + (struct mmc_ioc_cmd __user *)arg, > + NULL); > mmc_blk_put(md); > return ret; > case MMC_IOC_MULTI_CMD: > @@ -702,7 +756,8 @@ static int mmc_blk_ioctl(struct block_device *bdev, fmode_t mode, > if (!md) > return -EINVAL; > ret = mmc_blk_ioctl_multi_cmd(md, > - (struct mmc_ioc_multi_cmd __user *)arg); > + (struct mmc_ioc_multi_cmd __user *)arg, > + NULL); > mmc_blk_put(md); > return ret; > default: > @@ -1174,17 +1229,19 @@ static void mmc_blk_issue_drv_op(struct mmc_queue *mq, struct request *req) > struct mmc_queue_req *mq_rq; > struct mmc_card *card = mq->card; > struct mmc_blk_data *md = mq->blkdata; > - struct mmc_blk_data *main_md = dev_get_drvdata(&card->dev); > struct mmc_blk_ioc_data **idata; > + bool rpmb_ioctl; > u8 **ext_csd; > u32 status; > int ret; > int i; > > mq_rq = req_to_mmc_queue_req(req); > + rpmb_ioctl = (mq_rq->drv_op == MMC_DRV_OP_IOCTL_RPMB); > > switch (mq_rq->drv_op) { > case MMC_DRV_OP_IOCTL: > + case MMC_DRV_OP_IOCTL_RPMB: > idata = mq_rq->drv_op_data; > for (i = 0, ret = 0; i < mq_rq->ioc_count; i++) { > ret = __mmc_blk_ioctl_cmd(card, md, idata[i]); > @@ -1192,8 +1249,8 @@ static void mmc_blk_issue_drv_op(struct mmc_queue *mq, struct request *req) > break; > } > /* Always switch back to main area after RPMB access */ > - if (md->area_type & MMC_BLK_DATA_AREA_RPMB) > - mmc_blk_part_switch(card, main_md->part_type); > + if (rpmb_ioctl) > + mmc_blk_part_switch(card, 0); > break; > case MMC_DRV_OP_BOOT_WP: > ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BOOT_WP, > @@ -2022,6 +2079,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, > > spin_lock_init(&md->lock); > INIT_LIST_HEAD(&md->part); > + INIT_LIST_HEAD(&md->rpmbs); > md->usage = 1; > > ret = mmc_init_queue(&md->queue, card, &md->lock, subname); > @@ -2140,6 +2198,153 @@ static int mmc_blk_alloc_part(struct mmc_card *card, > return 0; > } > > +/** > + * rpmb_ioctl() - ioctl handler for the RPMB chardev > + * @filp: the character device file > + * @cmd: the ioctl() command > + * @arg: the argument from userspace > + * > + * This will essentially just redirect the ioctl()s coming in over to > + * the main block device spawning the RPMB character device. > + */ > +static long rpmb_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) will keep the mmc_ prefix also here > +{ > + struct mmc_rpmb_data *rpmb = filp->private_data; > + int ret; > + > + switch (cmd) { > + case MMC_IOC_CMD: > + ret = mmc_blk_ioctl_cmd(rpmb->md, > + (struct mmc_ioc_cmd __user *)arg, > + rpmb); > + break; > + case MMC_IOC_MULTI_CMD: > + ret = mmc_blk_ioctl_multi_cmd(rpmb->md, > + (struct mmc_ioc_multi_cmd __user *)arg, > + rpmb); > + break; > + default: > + ret = -EINVAL; > + break; > + } > + > + return 0; > +} > + > +#ifdef CONFIG_COMPAT > +static long rpmb_ioctl_compat(struct file *filp, unsigned int cmd, > + unsigned long arg) same here, the mmc_ prefix > +{ > + return rpmb_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); > +} > +#endif > + > +static int rpmb_chrdev_open(struct inode *inode, struct file *filp) > +{ prefix > + struct mmc_rpmb_data *rpmb = container_of(inode->i_cdev, > + struct mmc_rpmb_data, chrdev); > + > + get_device(&rpmb->dev); > + filp->private_data = rpmb; > + mutex_lock(&open_lock); > + rpmb->md->usage++; > + mutex_unlock(&open_lock); > + > + return nonseekable_open(inode, filp); > +} > + > +static int rpmb_chrdev_release(struct inode *inode, struct file *filp) > +{ > + struct mmc_rpmb_data *rpmb = container_of(inode->i_cdev, > + struct mmc_rpmb_data, chrdev); > + > + put_device(&rpmb->dev); > + mutex_lock(&open_lock); > + rpmb->md->usage--; > + mutex_unlock(&open_lock); > + > + return 0; > +} > + > +static const struct file_operations rpmb_fileops = { > + .release = rpmb_chrdev_release, > + .open = rpmb_chrdev_open, > + .owner = THIS_MODULE, > + .llseek = no_llseek, > + .unlocked_ioctl = rpmb_ioctl, > +#ifdef CONFIG_COMPAT > + .compat_ioctl = rpmb_ioctl_compat, > +#endif > +}; > + > + > +static int mmc_blk_alloc_rpmb_part(struct mmc_card *card, > + struct mmc_blk_data *md, > + unsigned int part_index, > + sector_t size, > + const char *subname) > +{ > + int devidx, ret; > + char rpmb_name[DISK_NAME_LEN]; > + char cap_str[10]; > + struct mmc_rpmb_data *rpmb; > + > + /* This creates the minor number for the RPMB char device */ > + devidx = ida_simple_get(&mmc_rpmb_ida, 0, max_devices, GFP_KERNEL); > + if (devidx < 0) > + return devidx; > + > + rpmb = kzalloc(sizeof(*rpmb), GFP_KERNEL); > + if (!rpmb) > + return -ENOMEM; > + > + snprintf(rpmb_name, sizeof(rpmb_name), > + "mmcblk%u%s", card->host->index, subname ? subname : ""); > + > + rpmb->id = devidx; > + rpmb->part_index = part_index; > + rpmb->dev.init_name = rpmb_name; > + rpmb->dev.bus = &mmc_rpmb_bus_type; > + rpmb->dev.devt = MKDEV(MAJOR(mmc_rpmb_devt), rpmb->id); > + rpmb->dev.parent = &card->dev; > + device_initialize(&rpmb->dev); > + dev_set_drvdata(&rpmb->dev, rpmb); > + rpmb->md = md; > + > + cdev_init(&rpmb->chrdev, &rpmb_fileops); > + rpmb->chrdev.owner = THIS_MODULE; > + ret = cdev_device_add(&rpmb->chrdev, &rpmb->dev); > + if (ret) { > + pr_err("%s: could not add character device\n", rpmb_name); > + goto out_remove_ida; > + } > + > + list_add(&rpmb->node, &md->rpmbs); > + > + string_get_size((u64)size, 512, STRING_UNITS_2, > + cap_str, sizeof(cap_str)); > + > + pr_info("%s: %s %s partition %u %s, chardev (%d:%d)\n", > + rpmb_name, mmc_card_id(card), > + mmc_card_name(card), EXT_CSD_PART_CONFIG_ACC_RPMB, cap_str, > + MAJOR(mmc_rpmb_devt), rpmb->id); > + > + return 0; > + > +out_remove_ida: > + ida_simple_remove(&mmc_rpmb_ida, rpmb->id); > + kfree(rpmb); > + return ret; > +} > + > +static void mmc_blk_remove_rpmb_part(struct mmc_rpmb_data *rpmb) > +{ > + cdev_device_del(&rpmb->chrdev, &rpmb->dev); > + device_del(&rpmb->dev); > + ida_simple_remove(&mmc_rpmb_ida, rpmb->id); > + kfree(rpmb); > +} > + > /* MMC Physical partitions consist of two boot partitions and > * up to four general purpose partitions. > * For each partition enabled in EXT_CSD a block device will be allocatedi > @@ -2148,13 +2353,26 @@ static int mmc_blk_alloc_part(struct mmc_card *card, > > static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md) > { > - int idx, ret = 0; > + int idx, ret; > > if (!mmc_card_mmc(card)) > return 0; > > for (idx = 0; idx < card->nr_parts; idx++) { > - if (card->part[idx].size) { > + if (card->part[idx].area_type & MMC_BLK_DATA_AREA_RPMB) { > + /* > + * RPMB partitions does not provide block access, they > + * are only accessed using ioctl():s. Thus create > + * special RPMB block devices that do not have a > + * backing block queue for these. > + */ > + ret = mmc_blk_alloc_rpmb_part(card, md, > + card->part[idx].part_cfg, > + card->part[idx].size >> 9, > + card->part[idx].name); > + if (ret) > + return ret; > + } else if (card->part[idx].size) { > ret = mmc_blk_alloc_part(card, md, > card->part[idx].part_cfg, > card->part[idx].size >> 9, > @@ -2166,7 +2384,7 @@ static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md) > } > } > > - return ret; > + return 0; > } > > static void mmc_blk_remove_req(struct mmc_blk_data *md) > @@ -2203,7 +2421,15 @@ static void mmc_blk_remove_parts(struct mmc_card *card, > { > struct list_head *pos, *q; > struct mmc_blk_data *part_md; > + struct mmc_rpmb_data *rpmb; > > + /* Remove RPMB partitions */ > + list_for_each_safe(pos, q, &md->rpmbs) { > + rpmb = list_entry(pos, struct mmc_rpmb_data, node); > + list_del(pos); > + mmc_blk_remove_rpmb_part(rpmb); > + } > + /* Remove block partitions */ > list_for_each_safe(pos, q, &md->part) { > part_md = list_entry(pos, struct mmc_blk_data, part); > list_del(pos); > @@ -2522,6 +2748,17 @@ static int __init mmc_blk_init(void) > { > int res; > > + res = bus_register(&mmc_rpmb_bus_type); > + if (res < 0) { > + pr_err("mmcblk: could not register RPMB bus type\n"); > + return res; > + } > + res = alloc_chrdev_region(&mmc_rpmb_devt, 0, MAX_DEVICES, "rpmb"); > + if (res < 0) { > + pr_err("mmcblk: failed to allocate rpmb chrdev region\n"); > + goto out_bus_unreg; > + } > + > if (perdev_minors != CONFIG_MMC_BLOCK_MINORS) > pr_info("mmcblk: using %d minors per device\n", perdev_minors); > > @@ -2529,16 +2766,20 @@ static int __init mmc_blk_init(void) > > res = register_blkdev(MMC_BLOCK_MAJOR, "mmc"); > if (res) > - goto out; > + goto out_chrdev_unreg; > > res = mmc_register_driver(&mmc_driver); > if (res) > - goto out2; > + goto out_blkdev_unreg; > > return 0; > - out2: > + > +out_blkdev_unreg: > unregister_blkdev(MMC_BLOCK_MAJOR, "mmc"); > - out: > +out_chrdev_unreg: > + unregister_chrdev_region(mmc_rpmb_devt, MAX_DEVICES); > +out_bus_unreg: > + bus_unregister(&mmc_rpmb_bus_type); > return res; > } > > @@ -2546,6 +2787,7 @@ static void __exit mmc_blk_exit(void) > { > mmc_unregister_driver(&mmc_driver); > unregister_blkdev(MMC_BLOCK_MAJOR, "mmc"); > + unregister_chrdev_region(mmc_rpmb_devt, MAX_DEVICES); > } > > module_init(mmc_blk_init); > diff --git a/drivers/mmc/core/queue.h b/drivers/mmc/core/queue.h > index 04fc89360a7a..a2b6a9fcab01 100644 > --- a/drivers/mmc/core/queue.h > +++ b/drivers/mmc/core/queue.h > @@ -35,12 +35,14 @@ struct mmc_blk_request { > /** > * enum mmc_drv_op - enumerates the operations in the mmc_queue_req > * @MMC_DRV_OP_IOCTL: ioctl operation > + * @MMC_DRV_OP_IOCTL_RPMB: RPMB-oriented ioctl operation > * @MMC_DRV_OP_BOOT_WP: write protect boot partitions > * @MMC_DRV_OP_GET_CARD_STATUS: get card status > * @MMC_DRV_OP_GET_EXT_CSD: get the EXT CSD from an eMMC card > */ > enum mmc_drv_op { > MMC_DRV_OP_IOCTL, > + MMC_DRV_OP_IOCTL_RPMB, > MMC_DRV_OP_BOOT_WP, > MMC_DRV_OP_GET_CARD_STATUS, > MMC_DRV_OP_GET_EXT_CSD,��.n��������+%������w��{.n�����{��i��)��jg��������ݢj����G�������j:+v���w�m������w�������h�����٥