On 20 September 2017 at 10:02, Linus Walleij <[email protected]> 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 <[email protected]>
> Signed-off-by: Linus Walleij <[email protected]>
Thanks, applied for next!
Kind regards
Uffe
> ---
> ChangeLog v5->v6:
> - Prefix the bus name with mmc_ so it becomes "mmc_rpmb"
> - Prefix the RPMB-specific symbols with mmc_*
> - Use the ternary operator ( = rpmb ? A : B ) for assigning IOCTL
> enums
> 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 | 283
> +++++++++++++++++++++++++++++++++++++++++++----
> drivers/mmc/core/queue.h | 2 +
> 2 files changed, 263 insertions(+), 22 deletions(-)
>
> diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c
> index 29fc1e662891..6421d06b66bb 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 = "mmc_rpmb",
> +};
> +
> +/**
> + * 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,8 @@ 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;
> + req_to_mmc_queue_req(req)->drv_op =
> + rpmb ? MMC_DRV_OP_IOCTL_RPMB : MMC_DRV_OP_IOCTL;
> 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 +641,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 +673,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 +691,8 @@ 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;
> + req_to_mmc_queue_req(req)->drv_op =
> + rpmb ? MMC_DRV_OP_IOCTL_RPMB : 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 +740,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 +752,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 +1225,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 +1245,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,
> @@ -2071,6 +2124,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);
> @@ -2189,6 +2243,154 @@ static int mmc_blk_alloc_part(struct mmc_card *card,
> return 0;
> }
>
> +/**
> + * mmc_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 mmc_rpmb_ioctl(struct file *filp, unsigned int cmd,
> + unsigned long arg)
> +{
> + 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 mmc_rpmb_ioctl_compat(struct file *filp, unsigned int cmd,
> + unsigned long arg)
> +{
> + return mmc_rpmb_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
> +}
> +#endif
> +
> +static int mmc_rpmb_chrdev_open(struct inode *inode, struct file *filp)
> +{
> + 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 mmc_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 mmc_rpmb_fileops = {
> + .release = mmc_rpmb_chrdev_release,
> + .open = mmc_rpmb_chrdev_open,
> + .owner = THIS_MODULE,
> + .llseek = no_llseek,
> + .unlocked_ioctl = mmc_rpmb_ioctl,
> +#ifdef CONFIG_COMPAT
> + .compat_ioctl = mmc_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, &mmc_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
> @@ -2197,13 +2399,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,
> @@ -2215,7 +2430,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)
> @@ -2252,7 +2467,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);
> @@ -2571,6 +2794,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);
>
> @@ -2578,16 +2812,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;
> }
>
> @@ -2595,6 +2833,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,
> --
> 2.13.5
>