[1/2,v6] mmc: block: Convert RPMB to a character device

Message ID 20170920080201.32438-1-linus.walleij@linaro.org
State New
Headers show
Series
  • [1/2,v6] mmc: block: Convert RPMB to a character device
Related show

Commit Message

Linus Walleij Sept. 20, 2017, 8:02 a.m.
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@intel.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>

---
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(-)

-- 
2.13.5

--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Ulf Hansson Sept. 22, 2017, 9:46 a.m. | #1
On 20 September 2017 at 10:02, Linus Walleij <linus.walleij@linaro.org> 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@intel.com>

> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>


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

>

--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Adrian Hunter Sept. 27, 2017, 11:35 a.m. | #2
BUG when removing, fixed by reverting this patch.

[  346.548512] mmc1: card 0001 removed
[  346.552782] BUG: unable to handle kernel NULL pointer dereference at 0000000000000070
[  346.561539] IP: kernfs_find_ns+0xe/0xc0
[  346.565822] PGD 179dc5067 P4D 179dc5067 PUD 171106067 PMD 0 
[  346.572152] Oops: 0000 [#1] SMP
[  346.575657] Modules linked in:
[  346.579069] CPU: 0 PID: 1393 Comm: sh Not tainted 4.14.0-rc1-00034-g5aa83fb178e3 #1528
[  346.587914] task: ffff899d3a3327c0 task.stack: ffff9e6600888000
[  346.594530] RIP: 0010:kernfs_find_ns+0xe/0xc0
[  346.599395] RSP: 0000:ffff9e660088ba28 EFLAGS: 00010246
[  346.605229] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000000
[  346.613199] RDX: 0000000000000000 RSI: ffffffff90cab2c1 RDI: 0000000000000000
[  346.621168] RBP: ffff9e660088ba48 R08: 0000000000000000 R09: 000000018040003d
[  346.629139] R10: ffff9e660088ba68 R11: ffff899d3b080e00 R12: ffffffff90cab2c1
[  346.637107] R13: 0000000000000000 R14: ffff899d310b04a8 R15: ffff899d39cdcd70
[  346.645079] FS:  0000000000000000(0000) GS:ffff899d3fc00000(0063) knlGS:00000000f7f2d690
[  346.654118] CS:  0010 DS: 002b ES: 002b CR0: 0000000080050033
[  346.660536] CR2: 0000000000000070 CR3: 0000000171173000 CR4: 00000000003406f0
[  346.668504] Call Trace:
[  346.671237]  kernfs_find_and_get_ns+0x2c/0x50
[  346.676106]  sysfs_unmerge_group+0x18/0x60
[  346.680683]  dpm_sysfs_remove+0x1d/0x60
[  346.684969]  device_del+0xef/0x2e0
[  346.688765]  ? cdev_del+0x22/0x30
[  346.692470]  mmc_blk_remove_parts.isra.28+0x6c/0xf0
[  346.697919]  mmc_blk_remove+0x27/0x190
[  346.702106]  mmc_bus_remove+0x18/0x20
[  346.706197]  device_release_driver_internal+0x142/0x200
[  346.712036]  device_release_driver+0xd/0x10
[  346.716709]  bus_remove_device+0xdb/0x120
[  346.721188]  device_del+0x1c3/0x2e0
[  346.725083]  mmc_remove_card+0x5b/0xc0
[  346.729270]  mmc_remove+0x14/0x30
[  346.732974]  mmc_stop_host+0xef/0x1a0
[  346.737065]  mmc_remove_host+0x15/0x40

--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Linus Walleij Sept. 27, 2017, 7:45 p.m. | #3
On Wed, Sep 27, 2017 at 1:35 PM, Adrian Hunter <adrian.hunter@intel.com> wrote:
>

> BUG when removing, fixed by reverting this patch.

>

> [  346.548512] mmc1: card 0001 removed

> [  346.552782] BUG: unable to handle kernel NULL pointer dereference at 0000000000000070


How did you achieve this? I need to reproduce it.

RPMB is only available on eMMC cards and I don't have a removable eMMC
card since by definition they are soldered on.

Does it happen on an ordinary MMC card without RPMB?

I'm sorry if I'm ignorant of something very obvious here...

I will read the code and see if I'm missing something obvious.

Yours,
Linus Walleij
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Shawn Lin Sept. 28, 2017, 3:56 a.m. | #4
On 2017/9/28 3:45, Linus Walleij wrote:
> On Wed, Sep 27, 2017 at 1:35 PM, Adrian Hunter <adrian.hunter@intel.com> wrote:

>>

>> BUG when removing, fixed by reverting this patch.

>>

>> [  346.548512] mmc1: card 0001 removed

>> [  346.552782] BUG: unable to handle kernel NULL pointer dereference at 0000000000000070

> 

> How did you achieve this? I need to reproduce it.

> 

> RPMB is only available on eMMC cards and I don't have a removable eMMC

> card since by definition they are soldered on.

> 

> Does it happen on an ordinary MMC card without RPMB?

> 

> I'm sorry if I'm ignorant of something very obvious here...

> 

> I will read the code and see if I'm missing something obvious.


Haven't closely look at this patch, but btw, don't you need
put_device(&rpmb->dev) for the error path of  mmc_blk_alloc_rpmb_part
and  also do that for mmc_blk_remove_rpmb_part?

And don't you need to check rpmb->md->usage when doing
mmc_blk_remove_rpmb_part ?

> 

> Yours,

> Linus Walleij

> 

> 

> 


--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jisheng Zhang Sept. 28, 2017, 6 a.m. | #5
On Wed, 27 Sep 2017 21:45:58 +0200 Linus Walleij wrote:

> On Wed, Sep 27, 2017 at 1:35 PM, Adrian Hunter <adrian.hunter@intel.com> wrote:

> >

> > BUG when removing, fixed by reverting this patch.

> >

> > [  346.548512] mmc1: card 0001 removed

> > [  346.552782] BUG: unable to handle kernel NULL pointer dereference at 0000000000000070  

> 

> How did you achieve this? I need to reproduce it.

> 

> RPMB is only available on eMMC cards and I don't have a removable eMMC

> card since by definition they are soldered on.


Maybe unbind the sdhci host.
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch

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,