Re: [PATCH v11] dm: add support to directly boot to a mapped device

From: Mike Snitzer
Date: Thu Feb 21 2019 - 10:50:57 EST


On Mon, Feb 18 2019 at 1:18pm -0500,
Helen Koike <helen.koike@xxxxxxxxxxxxx> wrote:

> Add a dm-mod.create= kernel module parameter.
> It allows device-mapper targets to be configured at boot time for use early
> in the boot process (as the root device or otherwise).
>
> Signed-off-by: Will Drewry <wad@xxxxxxxxxxxx>
> Signed-off-by: Kees Cook <keescook@xxxxxxxxxxxx>
> [rework to use dm_ioctl calls]
> Signed-off-by: Enric Balletbo i Serra <enric.balletbo@xxxxxxxxxxxxx>
> [refactored for upstream]
> Signed-off-by: Helen Koike <helen.koike@xxxxxxxxxxxxx>

Can I get confirmation from Will and Kees that this v11 is safe to carry
their Signed-off-by? Are you guys "happy" with this patch?

Helen, the patch header is severly lacking. All the detail you've
provided is outside of the proposed patch header. That needs fixing. I
can write a proper header but if you were to beat me to it, say
today.. hint hint ;) I'd greatly appreciate it. What follows below is
actually quite good (I tweaked slightly to be more suitable, but
hopefully you get the idea, less "name dropping" is still needed though):

> The need to create an initramfs adds another layer of complexity when
> performing tests or preparing a BSP for a board.
> Taking care of initramfs is always a bit painful, it also occupies space and is
> another level of complexity in the stack.
> A practical example as mentioned by Kees is that Chrome OS has a limited amount
> of storage available for the boot image as it is covered by the static root of
> trust signature.
> This feature is already used by Android and Chrome OS in devices already shipped
> to the market/end-users.
>
> Fix this by allowing dm devices to be configured in the kernel command
> line parameter for use early in the boot process without an initramfs.
>
> One of the main difference in this version is that instead of using "dm=",
> I'm using a parameter in the module "dm-mod.create=" (thanks Ezequiel for
> the idea).
>
> The syntax used in the boot param is based on the concise format from the dmsetup
> tool as described in its man page http://man7.org/linux/man-pages/man8/dmsetup.8.html#CONCISE_FORMAT
>
> Which is:
> dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
>
> Where,
> <name> ::= The device name.
> <uuid> ::= xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ""
> <minor> ::= The device minor number | ""
> <flags> ::= "ro" | "rw"
> <table> ::= <start_sector> <num_sectors> <target_type> <target_args>
> <target_type> ::= "verity" | "linear" | ...
>
> Example, the following could be added in the boot parameters.
> dm-mod.create="lroot,,,rw, 0 4096 linear 98:16 0, 4096 4096 linear 98:32 0" root=/dev/dm-0
>
> Please check the patch with the documentation on the format.
>
> The idea to make it compatible with the dmsetup concise format is to make it
> easier for users, allowing just copy & paste from the output of the command:
>
> dmsetup table --concise /dev/mapper/lroot
>
> I refactored the code for this version, instead of pretending to be
> userspace and performing ioctls, the code just parse the cmd line
> argument and create the device directly, which simplified and reduced a
> lot the code (and it is much easier to read).
>
> Also, not all dm targets are allowed. Only the ones that I tested were
> allowed and the ones that doesn't change any block device when the dm is
> create as read-only, i.e. mirror and cache are not allowed, because if
> the user makes a mistake and chose the wrong device to be the mirror or
> choose the wrong device to be the cache, it can corrupt data.
>
> So the only targets allowed are:
> * crypt
> * delay
> * linear
> * snapshot-origin
> * striped
> * verity
>
> I wrote a script to perform several tests using qemu that can be found at
> https://gitlab.collabora.com/koike/dm-cmdline-test
>
> And you can see the result of the tests at:
> https://people.collabora.com/~koike/dm-test-module-param-logs.txt
> At the end of this file:
> "Total successes: 52. Total failures: 0"
>
> And the output of qemu for each run:
> https://people.collabora.com/~koike/dm-test-module-param-logs.tar.gz
>
> I think it would nice to integrate these tests in some CI (maybe kernel
> CI?)
>
> Please let me know your comments.
>
> Changes in v11:
> - Configure the device directly instead of performing in IOCTL (this
> removed a lot of parsing code)
> - Just enable the targets that were tested.
> - Simplify/refactor parsing, as a consequence, escaping characters is not
> allowed (but it wans't properly used in the previous version anyway)
> - don't use sscanf, the size wans't being limited and we could have a
> buffer overflow.
> - change constrained targets list
> - remove code from init/
> - use a module parameter instead of a kernel comand line parameter in
> init/
> - rename dm-boot to dm-init
>
> Changes in v10:
> - https://lore.kernel.org/patchwork/project/lkml/list/?series=371523
> - new file: drivers/md/dm-boot.c
> - most of the parsing code was moved from init/do_mounts_dm.c to drivers/md/dm-boot.c
> - parsing code was in essence replaced by the concise parser from dmsetup
> _create_concise function:
> https://sourceware.org/git/?p=lvm2.git;a=blob;f=libdm/dm-tools/dmsetup.c;h=835fdcdc75e8f0f0f7c4ed46cc9788a6616f58b8;hb=7498f8383397a93db95655ca227257836cbcac82#l1265
> the main reason is that this code is already being used/tested by dmsetup, so
> we can have some level of confidence that it works as expected. Besides this,
> it also looks more efficient.
> - Not all targets are allowed to be used by dm=, as pointed previously, there
> are some risks in creating a mapped device without some validation from
> userspace (see documentation from the patch listing which targets are allowed).
> - Instead of using a simple singly linked list (for devices and tables), use
> the struct list_head. This occupies unnecessary space in the code, but it makes
> the code cleaner and easier to read and less prone to silly errors.
> - Documentation and comments were reviewed and refactored, e.g.:
> * "is to possible" was removed
> * s/specified as a simple string/specified as a string/
> - Added docs above __align function, make it clear that the second parameter @a
> must be a power of two.
> - Clean ups: removal of unnecessary includes, macros, variables, some redundant
> checks and warnings.
> - when calling ioctls, the code was allocating and freeing the same structure
> a couple of times. So instead of executing kzalloc/kfree 3 times, execute
> kmalloc once and reuse the structure after a memset, then finally kfree it once.
> - update commit message
>
> Changes in v9:
> - https://www.redhat.com/archives/linux-lvm/2018-September/msg00016.html
> - Add minor number to make it compatible with dmsetup concise format
>
> Changes in v8:
> - https://www.redhat.com/archives/linux-lvm/2017-May/msg00055.html
> - Fix build error due commit
> e516db4f67 (dm ioctl: add a new DM_DEV_ARM_POLL ioctl)
>
> Changes in v7:
> - http://lkml.iu.edu/hypermail/linux/kernel/1705.2/02657.html
> - Add a new function to issue the equivalent of a DM ioctl programatically.
> - Use the new ioctl interface to create the devices.
> - Use a comma-delimited and semi-colon delimited dmsetup-like commands.
>
> Changes in v6:
> - https://www.redhat.com/archives/dm-devel/2017-April/msg00316.html
>
> Changes in v5:
> - https://www.redhat.com/archives/dm-devel/2016-February/msg00112.html
>
> Documentation/device-mapper/dm-init.txt | 114 +++++++++
> drivers/md/Kconfig | 10 +
> drivers/md/Makefile | 4 +
> drivers/md/dm-init.c | 308 ++++++++++++++++++++++++
> drivers/md/dm-ioctl.c | 103 ++++++++
> include/linux/device-mapper.h | 9 +
> 6 files changed, 548 insertions(+)
> create mode 100644 Documentation/device-mapper/dm-init.txt
> create mode 100644 drivers/md/dm-init.c
>
> diff --git a/Documentation/device-mapper/dm-init.txt b/Documentation/device-mapper/dm-init.txt
> new file mode 100644
> index 000000000000..8464ee7c01b8
> --- /dev/null
> +++ b/Documentation/device-mapper/dm-init.txt
> @@ -0,0 +1,114 @@
> +Early creation of mapped devices
> +====================================
> +
> +It is possible to configure a device-mapper device to act as the root device for
> +your system in two ways.
> +
> +The first is to build an initial ramdisk which boots to a minimal userspace
> +which configures the device, then pivot_root(8) in to it.
> +
> +The second is to create one or more device-mappers using the module parameter
> +"dm-mod.create=" through the kernel boot command line argument.
> +
> +The format is specified as a string of data separated by commas and optionally
> +semi-colons, where:
> + - a comma is used to separate fields like name, uuid, flags and table
> + (specifies one device)
> + - a semi-colon is used to separate devices.
> +
> +So the format will look like this:
> +
> + dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
> +
> +Where,
> + <name> ::= The device name.
> + <uuid> ::= xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ""
> + <minor> ::= The device minor number | ""
> + <flags> ::= "ro" | "rw"
> + <table> ::= <start_sector> <num_sectors> <target_type> <target_args>
> + <target_type> ::= "verity" | "linear" | ... (see list below)
> +
> +The dm line should be equivalent to the one used by the dmsetup tool with the
> +--concise argument.
> +
> +Target types
> +============
> +
> +Not all target types are available as there are serious risks in allowing
> +activation of certain DM targets without first using userspace tools to check
> +the validity of associated metadata.
> +
> + "cache": constrained, userspace should verify cache device
> + "crypt": allowed
> + "delay": allowed
> + "era": constrained, userspace should verify metadata device
> + "flakey": constrained, meant for test
> + "linear": allowed
> + "log-writes": constrained, userspace should verify metadata device
> + "mirror": constrained, userspace should verify main/mirror device
> + "raid": constrained, userspace should verify metadata device
> + "snapshot": constrained, userspace should verify src/dst device
> + "snapshot-origin": allowed
> + "snapshot-merge": constrained, userspace should verify src/dst device
> + "striped": allowed
> + "switch": constrained, userspace should verify dev path
> + "thin": constrained, requires dm target message from userspace
> + "thin-pool": constrained, requires dm target message from userspace
> + "verity": allowed
> + "writecache": constrained, userspace should verify cache device
> + "zero": constrained, not meant for rootfs
> +
> +If the target is not listed above, it is constrained by default (not tested).
> +
> +Examples
> +========
> +An example of booting to a linear array made up of user-mode linux block
> +devices:
> +
> + dm-mod.create="lroot,,,rw, 0 4096 linear 98:16 0, 4096 4096 linear 98:32 0" root=/dev/dm-0
> +
> +This will boot to a rw dm-linear target of 8192 sectors split across two block
> +devices identified by their major:minor numbers. After boot, udev will rename
> +this target to /dev/mapper/lroot (depending on the rules). No uuid was assigned.
> +
> +An example of multiple device-mappers, with the dm-mod.create="..." contents is shown here
> +split on multiple lines for readability:
> +
> + vroot,,,ro,
> + 0 1740800 verity 254:0 254:0 1740800 sha1
> + 76e9be054b15884a9fa85973e9cb274c93afadb6
> + 5b3549d54d6c7a3837b9b81ed72e49463a64c03680c47835bef94d768e5646fe;
> + vram,,,rw,
> + 0 32768 linear 1:0 0,
> + 32768 32768 linear 1:1 0
> +
> +Other examples (per target):
> +
> +"crypt":
> + dm-crypt,,8,ro,
> + 0 1048576 crypt aes-xts-plain64
> + babebabebabebabebabebabebabebabebabebabebabebabebabebabebabebabe 0
> + /dev/sda 0 1 allow_discards
> +
> +"delay":
> + dm-delay,,4,ro,0 409600 delay /dev/sda1 0 500
> +
> +"linear":
> + dm-linear,,,rw,
> + 0 32768 linear /dev/sda1 0,
> + 32768 1024000 linear /dev/sda2 0,
> + 1056768 204800 linear /dev/sda3 0,
> + 1261568 512000 linear /dev/sda4 0
> +
> +"snapshot-origin":
> + dm-snap-orig,,4,ro,0 409600 snapshot-origin 8:2
> +
> +"striped":
> + dm-striped,,4,ro,0 1638400 striped 4 4096
> + /dev/sda1 0 /dev/sda2 0 /dev/sda3 0 /dev/sda4 0
> +
> +"verity":
> + dm-verity,,4,ro,
> + 0 1638400 verity 1 8:1 8:2 4096 4096 204800 1 sha256
> + fb1a5a0f00deb908d8b53cb270858975e76cf64105d412ce764225d53b8f3cfd
> + 51934789604d1b92399c52e7cb149d1b3a1b74bbbcb103b2a0aaacbed5c08584
> diff --git a/drivers/md/Kconfig b/drivers/md/Kconfig
> index 3db222509e44..644e868cc2eb 100644
> --- a/drivers/md/Kconfig
> +++ b/drivers/md/Kconfig
> @@ -436,6 +436,16 @@ config DM_DELAY
>
> If unsure, say N.
>
> +config DM_INIT
> + bool "DM \"dm-mod.create=\" parameter support"
> + depends on BLK_DEV_DM=y
> + ---help---
> + Enable "dm-mod.create=" parameter to create mapped devices at init time.
> + This option is useful to allow mounting rootfs without requiring an
> + initramfs.
> + See Documentation/device-mapper/dm-init.txt for dm-mod.create="..."
> + format
> +
> config DM_UEVENT
> bool "DM uevents"
> depends on BLK_DEV_DM
> diff --git a/drivers/md/Makefile b/drivers/md/Makefile
> index 822f4e8753bc..a52b703e588e 100644
> --- a/drivers/md/Makefile
> +++ b/drivers/md/Makefile
> @@ -69,6 +69,10 @@ obj-$(CONFIG_DM_INTEGRITY) += dm-integrity.o
> obj-$(CONFIG_DM_ZONED) += dm-zoned.o
> obj-$(CONFIG_DM_WRITECACHE) += dm-writecache.o
>
> +ifeq ($(CONFIG_DM_INIT),y)
> +dm-mod-objs += dm-init.o
> +endif
> +
> ifeq ($(CONFIG_DM_UEVENT),y)
> dm-mod-objs += dm-uevent.o
> endif
> diff --git a/drivers/md/dm-init.c b/drivers/md/dm-init.c
> new file mode 100644
> index 000000000000..24ea75911c7c
> --- /dev/null
> +++ b/drivers/md/dm-init.c
> @@ -0,0 +1,308 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/*
> + * dm-init.c
> + * Copyright (C) 2017 The Chromium OS Authors <chromium-os-dev@xxxxxxxxxxxx>
> + *
> + * This file is released under the GPLv2.
> + */
> +
> +#include <linux/ctype.h>
> +#include <linux/device.h>
> +#include <linux/device-mapper.h>
> +#include <linux/init.h>
> +#include <linux/list.h>
> +#include <linux/moduleparam.h>
> +
> +#define DM_MSG_PREFIX "dm"
> +#define DM_MAX_DEVICES 256
> +#define DM_MAX_TARGETS 256
> +#define DM_MAX_STR_SIZE 4096
> +
> +static char *create;
> +
> +/*
> + * Format: dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
> + * Table format: <start_sector> <num_sectors> <target_type> <target_args>
> + *
> + * See Documentation/device-mapper/dm-init.txt for dm-mod.create="..." format
> + * details.
> + */
> +
> +struct dm_device {
> + struct dm_ioctl dmi;
> + struct dm_target_spec *table[DM_MAX_TARGETS];
> + char *target_args_array[DM_MAX_TARGETS];
> + struct list_head list;
> +};
> +
> +const char *dm_allowed_targets[] __initconst = {
> + "crypt",
> + "delay",
> + "linear",
> + "snapshot-origin",
> + "striped",
> + "verity",
> +};
> +
> +static int __init dm_verify_target_type(const char *target)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(dm_allowed_targets); i++) {
> + if (!strcmp(dm_allowed_targets[i], target))
> + return 0;
> + }
> + return -EINVAL;
> +}
> +
> +static void __init dm_setup_cleanup(struct list_head *devices)
> +{
> + struct dm_device *dev, *tmp;
> + unsigned int i;
> +
> + list_for_each_entry_safe(dev, tmp, devices, list) {
> + list_del(&dev->list);
> + for (i = 0; i < dev->dmi.target_count; i++) {
> + kfree(dev->table[i]);
> + kfree(dev->target_args_array[i]);
> + }
> + kfree(dev);
> + }
> +}
> +
> +/**
> + * str_field_delimit - delimit a string based on a separator char.
> + * @str: the pointer to the string to delimit.
> + * @separator: char that delimits the field
> + *
> + * Find a @separator and replace it by '\0'.
> + * Remove leading and trailing spaces.
> + * Return the remainder string after the @separator.
> + */
> +static char __init *str_field_delimit(char **str, char separator)
> +{
> + char *s;
> +
> + /* TODO: add support for escaped characters */
> + *str = skip_spaces(*str);
> + s = strchr(*str, separator);
> + /* Delimit the field and remove trailing spaces */
> + if (s)
> + *s = '\0';
> + *str = strim(*str);
> + return s ? ++s : NULL;
> +}
> +
> +/**
> + * dm_parse_table_entry - parse a table entry
> + * @dev: device to store the parsed information.
> + * @str: the pointer to a string with the format:
> + * <start_sector> <num_sectors> <target_type> <target_args>[, ...]
> + *
> + * Return the remainder string after the table entry, i.e, after the comma which
> + * delimits the entry or NULL if reached the end of the string.
> + */
> +static char __init *dm_parse_table_entry(struct dm_device *dev, char *str)
> +{
> + const unsigned int n = dev->dmi.target_count - 1;
> + struct dm_target_spec *sp;
> + unsigned int i;
> + /* fields: */
> + char *field[4];
> + char *next;
> +
> + field[0] = str;
> + /* Delimit first 3 fields that are separated by space */
> + for (i = 0; i < ARRAY_SIZE(field) - 1; i++) {
> + field[i + 1] = str_field_delimit(&field[i], ' ');
> + if (!field[i + 1])
> + return ERR_PTR(-EINVAL);
> + }
> + /* Delimit last field that can be terminated by comma */
> + next = str_field_delimit(&field[i], ',');
> +
> + sp = kzalloc(sizeof(*sp), GFP_KERNEL);
> + if (!sp)
> + return ERR_PTR(-ENOMEM);
> + dev->table[n] = sp;
> +
> + /* start_sector */
> + if (kstrtoull(field[0], 0, &sp->sector_start))
> + return ERR_PTR(-EINVAL);
> + /* num_sector */
> + if (kstrtoull(field[1], 0, &sp->length))
> + return ERR_PTR(-EINVAL);
> + /* target_type */
> + strscpy(sp->target_type, field[2], sizeof(sp->target_type));
> + if (dm_verify_target_type(sp->target_type)) {
> + DMERR("invalid type \"%s\"", sp->target_type);
> + return ERR_PTR(-EINVAL);
> + }
> + /* target_args */
> + dev->target_args_array[n] = kstrndup(field[3], GFP_KERNEL,
> + DM_MAX_STR_SIZE);
> + if (!dev->target_args_array[n])
> + return ERR_PTR(-ENOMEM);
> +
> + return next;
> +}
> +
> +/**
> + * dm_parse_table - parse "dm-mod.create=" table field
> + * @dev: device to store the parsed information.
> + * @str: the pointer to a string with the format:
> + * <table>[,<table>+]
> + */
> +static int __init dm_parse_table(struct dm_device *dev, char *str)
> +{
> + char *table_entry = str;
> +
> + while (table_entry) {
> + DMDEBUG("parsing table \"%s\"", str);
> + if (++dev->dmi.target_count >= DM_MAX_TARGETS) {
> + DMERR("too many targets %u > %d",
> + dev->dmi.target_count, DM_MAX_TARGETS);
> + return -EINVAL;
> + }
> + table_entry = dm_parse_table_entry(dev, table_entry);
> + if (IS_ERR(table_entry)) {
> + DMERR("couldn't parse table");
> + return PTR_ERR(table_entry);
> + }
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * dm_parse_device_entry - parse a device entry
> + * @dev: device to store the parsed information.
> + * @str: the pointer to a string with the format:
> + * name,uuid,minor,flags,table[; ...]
> + *
> + * Return the remainder string after the table entry, i.e, after the semi-colon
> + * which delimits the entry or NULL if reached the end of the string.
> + */
> +static char __init *dm_parse_device_entry(struct dm_device *dev, char *str)
> +{
> + /* There are 5 fields: name,uuid,minor,flags,table; */
> + char *field[5];
> + unsigned int i;
> + char *next;
> +
> + field[0] = str;
> + /* Delimit first 4 fields that are separated by comma */
> + for (i = 0; i < ARRAY_SIZE(field) - 1; i++) {
> + field[i+1] = str_field_delimit(&field[i], ',');
> + if (!field[i+1])
> + return ERR_PTR(-EINVAL);
> + }
> + /* Delimit last field that can be delimited by semi-colon */
> + next = str_field_delimit(&field[i], ';');
> +
> + /* name */
> + strscpy(dev->dmi.name, field[0], sizeof(dev->dmi.name));
> + /* uuid */
> + strscpy(dev->dmi.uuid, field[1], sizeof(dev->dmi.uuid));
> + /* minor */
> + if (strlen(field[2])) {
> + if (kstrtoull(field[2], 0, &dev->dmi.dev))
> + return ERR_PTR(-EINVAL);
> + dev->dmi.flags |= DM_PERSISTENT_DEV_FLAG;
> + }
> + /* flags */
> + if (!strcmp(field[3], "ro"))
> + dev->dmi.flags |= DM_READONLY_FLAG;
> + else if (strcmp(field[3], "rw"))
> + return ERR_PTR(-EINVAL);
> + /* table */
> + if (dm_parse_table(dev, field[4]))
> + return ERR_PTR(-EINVAL);
> +
> + return next;
> +}
> +
> +/**
> + * dm_parse_devices - parse "dm-mod.create=" argument
> + * @devices: list of struct dm_device to store the parsed information.
> + * @str: the pointer to a string with the format:
> + * <device>[;<device>+]
> + */
> +static int __init dm_parse_devices(struct list_head *devices, char *str)
> +{
> + unsigned long ndev = 0;
> + struct dm_device *dev;
> + char *device = str;
> +
> + DMDEBUG("parsing \"%s\"", str);
> + while (device) {
> + dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> + if (!dev)
> + return -ENOMEM;
> + list_add_tail(&dev->list, devices);
> +
> + if (++ndev >= DM_MAX_DEVICES) {
> + DMERR("too many targets %u > %d",
> + dev->dmi.target_count, DM_MAX_TARGETS);
> + return -EINVAL;
> + }
> +
> + device = dm_parse_device_entry(dev, device);
> + if (IS_ERR(device)) {
> + DMERR("couldn't parse device");
> + return PTR_ERR(device);
> + }
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * dm_init_init - parse "dm-mod.create=" argument and configure drivers
> + */
> +static int __init dm_init_init(void)
> +{
> + struct dm_device *dev;
> + LIST_HEAD(devices);
> + char *str;
> + int r;
> +
> + if (!create)
> + return 0;
> +
> + if (strlen(create) >= DM_MAX_STR_SIZE) {
> + DMERR("Argument is too big. Limit is %d\n", DM_MAX_STR_SIZE);
> + return -EINVAL;
> + }
> + str = kstrndup(create, GFP_KERNEL, DM_MAX_STR_SIZE);
> + if (!str)
> + return -ENOMEM;
> +
> + r = dm_parse_devices(&devices, str);
> + if (r)
> + goto out;
> +
> + DMINFO("waiting for all devices to be available before creating mapped devices\n");
> + wait_for_device_probe();
> +
> + list_for_each_entry(dev, &devices, list) {
> + if (dm_early_create(&dev->dmi, dev->table,
> + dev->target_args_array))
> + break;
> + }
> +out:
> + kfree(str);
> + dm_setup_cleanup(&devices);
> + return r;
> +}
> +
> +late_initcall(dm_init_init);
> +
> +module_param(create, charp, 0);
> +MODULE_PARM_DESC(create, "Create a mapped device when the module is loaded using this argument\n"
> + "This is mostly useful in early boot to allow mounting rootfs without requiring an initramfs\n"
> + "Format: <name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]\n"
> + "Table format: <start_sector> <num_sectors> <target_type> <target_args>\n"
> + "Examples:\n"
> + "dm-mod.create=\"lroot,,,rw, 0 4096 linear 98:16 0, 4096 4096 linear 98:32 0\"\n");
> diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c
> index f666778ad237..c740153b4e52 100644
> --- a/drivers/md/dm-ioctl.c
> +++ b/drivers/md/dm-ioctl.c
> @@ -2018,3 +2018,106 @@ int dm_copy_name_and_uuid(struct mapped_device *md, char *name, char *uuid)
>
> return r;
> }
> +
> +
> +/**
> + * dm_early_create - create a mapped device in early boot.
> + *
> + * @dmi: Contains main information of the device mapping to be created.
> + * @spec_array: array of pointers to struct dm_target_spec. Describes the
> + * mapping table of the device.
> + * @target_params_array: array of strings with the parameters to a specific
> + * target.
> + *
> + * Instead of having the struct dm_target_spec and the parameters for every
> + * target embedded at the end of struct dm_ioctl (as performed in a normal
> + * ioctl), pass them as arguments, so the caller doesn't need to serialize them.
> + * The size of the spec_array and target_params_array is given by
> + * @dmi->target_count.
> + * This function is supposed to be called in early boot, so locking mechanisms
> + * to protect against concurrent loads are not required.
> + */
> +int __init dm_early_create(struct dm_ioctl *dmi,
> + struct dm_target_spec **spec_array,
> + char **target_params_array)
> +{
> + int r, m = DM_ANY_MINOR;
> + struct dm_table *t, *old_map;
> + struct mapped_device *md;
> + unsigned int i;
> +
> + if (!dmi->target_count)
> + return -EINVAL;
> +
> + r = check_name(dmi->name);
> + if (r)
> + return r;
> +
> + if (dmi->flags & DM_PERSISTENT_DEV_FLAG)
> + m = MINOR(huge_decode_dev(dmi->dev));
> +
> + /* alloc dm device */
> + r = dm_create(m, &md);
> + if (r)
> + return r;
> +
> + /* hash insert */
> + r = dm_hash_insert(dmi->name, *dmi->uuid ? dmi->uuid : NULL, md);
> + if (r)
> + goto err_destroy_dm;
> +
> + /* alloc table */
> + r = dm_table_create(&t, get_mode(dmi), dmi->target_count, md);
> + if (r)
> + goto err_destroy_dm;
> +
> + /* add targets */
> + for (i = 0; i < dmi->target_count; i++) {
> + r = dm_table_add_target(t, spec_array[i]->target_type,
> + (sector_t) spec_array[i]->sector_start,
> + (sector_t) spec_array[i]->length,
> + target_params_array[i]);
> + if (r) {
> + DMWARN("error adding target to table");
> + goto err_destroy_table;
> + }
> + }
> +
> + /* finish table */
> + r = dm_table_complete(t);
> + if (r)
> + goto err_destroy_table;
> +
> + md->type = dm_table_get_type(t);
> + /* setup md->queue to reflect md's type (may block) */
> + r = dm_setup_md_queue(md, t);
> + if (r) {
> + DMWARN("unable to set up device queue for new table.");
> + goto err_destroy_table;
> + }
> +
> + /* Set new map */
> + dm_suspend(md, 0);
> + old_map = dm_swap_table(md, t);
> + if (IS_ERR(old_map)) {
> + r = PTR_ERR(old_map);
> + goto err_destroy_table;
> + }
> + set_disk_ro(dm_disk(md), !!(dmi->flags & DM_READONLY_FLAG));
> +
> + /* resume device */
> + r = dm_resume(md);
> + if (r)
> + goto err_destroy_table;
> +
> + DMINFO("%s (%s) is ready", md->disk->disk_name, dmi->name);
> + dm_put(md);
> + return 0;
> +
> +err_destroy_table:
> + dm_table_destroy(t);
> +err_destroy_dm:
> + dm_put(md);
> + dm_destroy(md);
> + return r;
> +}
> diff --git a/include/linux/device-mapper.h b/include/linux/device-mapper.h
> index e528baebad69..8fa618381e78 100644
> --- a/include/linux/device-mapper.h
> +++ b/include/linux/device-mapper.h
> @@ -10,6 +10,7 @@
>
> #include <linux/bio.h>
> #include <linux/blkdev.h>
> +#include <linux/dm-ioctl.h>
> #include <linux/math64.h>
> #include <linux/ratelimit.h>
>
> @@ -431,6 +432,14 @@ void dm_remap_zone_report(struct dm_target *ti, sector_t start,
> struct blk_zone *zones, unsigned int *nr_zones);
> union map_info *dm_get_rq_mapinfo(struct request *rq);
>
> +/*
> + * Device mapper functions to parse and create devices specified by the
> + * parameter "dm-mod.create="
> + */
> +int __init dm_early_create(struct dm_ioctl *dmi,
> + struct dm_target_spec **spec_array,
> + char **target_params_array);
> +
> struct queue_limits *dm_get_queue_limits(struct mapped_device *md);
>
> /*
> --
> 2.20.1
>