13bd94003SHeinz Mauelshagen // SPDX-License-Identifier: GPL-2.0-only
26bbc923dSHelen Koike
36bbc923dSHelen Koike /*
46bbc923dSHelen Koike * Copyright (C) 2017 The Chromium OS Authors <chromium-os-dev@chromium.org>
56bbc923dSHelen Koike *
66bbc923dSHelen Koike * This file is released under the GPLv2.
76bbc923dSHelen Koike */
86bbc923dSHelen Koike
96bbc923dSHelen Koike #include <linux/ctype.h>
10035641b0SPeter Korsgaard #include <linux/delay.h>
116bbc923dSHelen Koike #include <linux/device.h>
126bbc923dSHelen Koike #include <linux/device-mapper.h>
136bbc923dSHelen Koike #include <linux/init.h>
146bbc923dSHelen Koike #include <linux/list.h>
156bbc923dSHelen Koike #include <linux/moduleparam.h>
166bbc923dSHelen Koike
176bbc923dSHelen Koike #define DM_MSG_PREFIX "init"
186bbc923dSHelen Koike #define DM_MAX_DEVICES 256
196bbc923dSHelen Koike #define DM_MAX_TARGETS 256
206bbc923dSHelen Koike #define DM_MAX_STR_SIZE 4096
21035641b0SPeter Korsgaard #define DM_MAX_WAITFOR 256
226bbc923dSHelen Koike
236bbc923dSHelen Koike static char *create;
246bbc923dSHelen Koike
25035641b0SPeter Korsgaard static char *waitfor[DM_MAX_WAITFOR];
26035641b0SPeter Korsgaard
276bbc923dSHelen Koike /*
286bbc923dSHelen Koike * Format: dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
296bbc923dSHelen Koike * Table format: <start_sector> <num_sectors> <target_type> <target_args>
30035641b0SPeter Korsgaard * Block devices to wait for to become available before setting up tables:
31035641b0SPeter Korsgaard * dm-mod.waitfor=<device1>[,..,<deviceN>]
326bbc923dSHelen Koike *
336cf2a73cSMauro Carvalho Chehab * See Documentation/admin-guide/device-mapper/dm-init.rst for dm-mod.create="..." format
346bbc923dSHelen Koike * details.
356bbc923dSHelen Koike */
366bbc923dSHelen Koike
376bbc923dSHelen Koike struct dm_device {
386bbc923dSHelen Koike struct dm_ioctl dmi;
396bbc923dSHelen Koike struct dm_target_spec *table[DM_MAX_TARGETS];
406bbc923dSHelen Koike char *target_args_array[DM_MAX_TARGETS];
416bbc923dSHelen Koike struct list_head list;
426bbc923dSHelen Koike };
436bbc923dSHelen Koike
4490e6bf06SDamien Le Moal static const char * const dm_allowed_targets[] __initconst = {
456bbc923dSHelen Koike "crypt",
466bbc923dSHelen Koike "delay",
476bbc923dSHelen Koike "linear",
486bbc923dSHelen Koike "snapshot-origin",
496bbc923dSHelen Koike "striped",
506bbc923dSHelen Koike "verity",
516bbc923dSHelen Koike };
526bbc923dSHelen Koike
dm_verify_target_type(const char * target)536bbc923dSHelen Koike static int __init dm_verify_target_type(const char *target)
546bbc923dSHelen Koike {
556bbc923dSHelen Koike unsigned int i;
566bbc923dSHelen Koike
576bbc923dSHelen Koike for (i = 0; i < ARRAY_SIZE(dm_allowed_targets); i++) {
586bbc923dSHelen Koike if (!strcmp(dm_allowed_targets[i], target))
596bbc923dSHelen Koike return 0;
606bbc923dSHelen Koike }
616bbc923dSHelen Koike return -EINVAL;
626bbc923dSHelen Koike }
636bbc923dSHelen Koike
dm_setup_cleanup(struct list_head * devices)646bbc923dSHelen Koike static void __init dm_setup_cleanup(struct list_head *devices)
656bbc923dSHelen Koike {
666bbc923dSHelen Koike struct dm_device *dev, *tmp;
676bbc923dSHelen Koike unsigned int i;
686bbc923dSHelen Koike
696bbc923dSHelen Koike list_for_each_entry_safe(dev, tmp, devices, list) {
706bbc923dSHelen Koike list_del(&dev->list);
716bbc923dSHelen Koike for (i = 0; i < dev->dmi.target_count; i++) {
726bbc923dSHelen Koike kfree(dev->table[i]);
736bbc923dSHelen Koike kfree(dev->target_args_array[i]);
746bbc923dSHelen Koike }
756bbc923dSHelen Koike kfree(dev);
766bbc923dSHelen Koike }
776bbc923dSHelen Koike }
786bbc923dSHelen Koike
796bbc923dSHelen Koike /**
806bbc923dSHelen Koike * str_field_delimit - delimit a string based on a separator char.
816bbc923dSHelen Koike * @str: the pointer to the string to delimit.
826bbc923dSHelen Koike * @separator: char that delimits the field
836bbc923dSHelen Koike *
846bbc923dSHelen Koike * Find a @separator and replace it by '\0'.
856bbc923dSHelen Koike * Remove leading and trailing spaces.
866bbc923dSHelen Koike * Return the remainder string after the @separator.
876bbc923dSHelen Koike */
str_field_delimit(char ** str,char separator)886bbc923dSHelen Koike static char __init *str_field_delimit(char **str, char separator)
896bbc923dSHelen Koike {
906bbc923dSHelen Koike char *s;
916bbc923dSHelen Koike
926bbc923dSHelen Koike /* TODO: add support for escaped characters */
936bbc923dSHelen Koike *str = skip_spaces(*str);
946bbc923dSHelen Koike s = strchr(*str, separator);
956bbc923dSHelen Koike /* Delimit the field and remove trailing spaces */
966bbc923dSHelen Koike if (s)
976bbc923dSHelen Koike *s = '\0';
986bbc923dSHelen Koike *str = strim(*str);
996bbc923dSHelen Koike return s ? ++s : NULL;
1006bbc923dSHelen Koike }
1016bbc923dSHelen Koike
1026bbc923dSHelen Koike /**
1036bbc923dSHelen Koike * dm_parse_table_entry - parse a table entry
1046bbc923dSHelen Koike * @dev: device to store the parsed information.
1056bbc923dSHelen Koike * @str: the pointer to a string with the format:
1066bbc923dSHelen Koike * <start_sector> <num_sectors> <target_type> <target_args>[, ...]
1076bbc923dSHelen Koike *
1086bbc923dSHelen Koike * Return the remainder string after the table entry, i.e, after the comma which
1096bbc923dSHelen Koike * delimits the entry or NULL if reached the end of the string.
1106bbc923dSHelen Koike */
dm_parse_table_entry(struct dm_device * dev,char * str)1116bbc923dSHelen Koike static char __init *dm_parse_table_entry(struct dm_device *dev, char *str)
1126bbc923dSHelen Koike {
1136bbc923dSHelen Koike const unsigned int n = dev->dmi.target_count - 1;
1146bbc923dSHelen Koike struct dm_target_spec *sp;
1156bbc923dSHelen Koike unsigned int i;
1166bbc923dSHelen Koike /* fields: */
1176bbc923dSHelen Koike char *field[4];
1186bbc923dSHelen Koike char *next;
1196bbc923dSHelen Koike
1206bbc923dSHelen Koike field[0] = str;
1216bbc923dSHelen Koike /* Delimit first 3 fields that are separated by space */
1226bbc923dSHelen Koike for (i = 0; i < ARRAY_SIZE(field) - 1; i++) {
1236bbc923dSHelen Koike field[i + 1] = str_field_delimit(&field[i], ' ');
1246bbc923dSHelen Koike if (!field[i + 1])
1256bbc923dSHelen Koike return ERR_PTR(-EINVAL);
1266bbc923dSHelen Koike }
1276bbc923dSHelen Koike /* Delimit last field that can be terminated by comma */
1286bbc923dSHelen Koike next = str_field_delimit(&field[i], ',');
1296bbc923dSHelen Koike
1306bbc923dSHelen Koike sp = kzalloc(sizeof(*sp), GFP_KERNEL);
1316bbc923dSHelen Koike if (!sp)
1326bbc923dSHelen Koike return ERR_PTR(-ENOMEM);
1336bbc923dSHelen Koike dev->table[n] = sp;
1346bbc923dSHelen Koike
1356bbc923dSHelen Koike /* start_sector */
1366bbc923dSHelen Koike if (kstrtoull(field[0], 0, &sp->sector_start))
1376bbc923dSHelen Koike return ERR_PTR(-EINVAL);
1386bbc923dSHelen Koike /* num_sector */
1396bbc923dSHelen Koike if (kstrtoull(field[1], 0, &sp->length))
1406bbc923dSHelen Koike return ERR_PTR(-EINVAL);
1416bbc923dSHelen Koike /* target_type */
1426bbc923dSHelen Koike strscpy(sp->target_type, field[2], sizeof(sp->target_type));
1436bbc923dSHelen Koike if (dm_verify_target_type(sp->target_type)) {
1446bbc923dSHelen Koike DMERR("invalid type \"%s\"", sp->target_type);
1456bbc923dSHelen Koike return ERR_PTR(-EINVAL);
1466bbc923dSHelen Koike }
1476bbc923dSHelen Koike /* target_args */
148dec7e649SGen Zhang dev->target_args_array[n] = kstrndup(field[3], DM_MAX_STR_SIZE,
149dec7e649SGen Zhang GFP_KERNEL);
1506bbc923dSHelen Koike if (!dev->target_args_array[n])
1516bbc923dSHelen Koike return ERR_PTR(-ENOMEM);
1526bbc923dSHelen Koike
1536bbc923dSHelen Koike return next;
1546bbc923dSHelen Koike }
1556bbc923dSHelen Koike
1566bbc923dSHelen Koike /**
1576bbc923dSHelen Koike * dm_parse_table - parse "dm-mod.create=" table field
1586bbc923dSHelen Koike * @dev: device to store the parsed information.
1596bbc923dSHelen Koike * @str: the pointer to a string with the format:
1606bbc923dSHelen Koike * <table>[,<table>+]
1616bbc923dSHelen Koike */
dm_parse_table(struct dm_device * dev,char * str)1626bbc923dSHelen Koike static int __init dm_parse_table(struct dm_device *dev, char *str)
1636bbc923dSHelen Koike {
1646bbc923dSHelen Koike char *table_entry = str;
1656bbc923dSHelen Koike
1666bbc923dSHelen Koike while (table_entry) {
1676bbc923dSHelen Koike DMDEBUG("parsing table \"%s\"", str);
1688e890c1aSHelen Koike if (++dev->dmi.target_count > DM_MAX_TARGETS) {
1696bbc923dSHelen Koike DMERR("too many targets %u > %d",
1706bbc923dSHelen Koike dev->dmi.target_count, DM_MAX_TARGETS);
1716bbc923dSHelen Koike return -EINVAL;
1726bbc923dSHelen Koike }
1736bbc923dSHelen Koike table_entry = dm_parse_table_entry(dev, table_entry);
1746bbc923dSHelen Koike if (IS_ERR(table_entry)) {
1756bbc923dSHelen Koike DMERR("couldn't parse table");
1766bbc923dSHelen Koike return PTR_ERR(table_entry);
1776bbc923dSHelen Koike }
1786bbc923dSHelen Koike }
1796bbc923dSHelen Koike
1806bbc923dSHelen Koike return 0;
1816bbc923dSHelen Koike }
1826bbc923dSHelen Koike
1836bbc923dSHelen Koike /**
1846bbc923dSHelen Koike * dm_parse_device_entry - parse a device entry
1856bbc923dSHelen Koike * @dev: device to store the parsed information.
1866bbc923dSHelen Koike * @str: the pointer to a string with the format:
1876bbc923dSHelen Koike * name,uuid,minor,flags,table[; ...]
1886bbc923dSHelen Koike *
1896bbc923dSHelen Koike * Return the remainder string after the table entry, i.e, after the semi-colon
1906bbc923dSHelen Koike * which delimits the entry or NULL if reached the end of the string.
1916bbc923dSHelen Koike */
dm_parse_device_entry(struct dm_device * dev,char * str)1926bbc923dSHelen Koike static char __init *dm_parse_device_entry(struct dm_device *dev, char *str)
1936bbc923dSHelen Koike {
1946bbc923dSHelen Koike /* There are 5 fields: name,uuid,minor,flags,table; */
1956bbc923dSHelen Koike char *field[5];
1966bbc923dSHelen Koike unsigned int i;
1976bbc923dSHelen Koike char *next;
1986bbc923dSHelen Koike
1996bbc923dSHelen Koike field[0] = str;
2006bbc923dSHelen Koike /* Delimit first 4 fields that are separated by comma */
2016bbc923dSHelen Koike for (i = 0; i < ARRAY_SIZE(field) - 1; i++) {
2026bbc923dSHelen Koike field[i+1] = str_field_delimit(&field[i], ',');
2036bbc923dSHelen Koike if (!field[i+1])
2046bbc923dSHelen Koike return ERR_PTR(-EINVAL);
2056bbc923dSHelen Koike }
2066bbc923dSHelen Koike /* Delimit last field that can be delimited by semi-colon */
2076bbc923dSHelen Koike next = str_field_delimit(&field[i], ';');
2086bbc923dSHelen Koike
2096bbc923dSHelen Koike /* name */
2106bbc923dSHelen Koike strscpy(dev->dmi.name, field[0], sizeof(dev->dmi.name));
2116bbc923dSHelen Koike /* uuid */
2126bbc923dSHelen Koike strscpy(dev->dmi.uuid, field[1], sizeof(dev->dmi.uuid));
2136bbc923dSHelen Koike /* minor */
2146bbc923dSHelen Koike if (strlen(field[2])) {
215*8b326742SBenjamin Marzinski if (kstrtoull(field[2], 0, &dev->dmi.dev) ||
216*8b326742SBenjamin Marzinski dev->dmi.dev >= (1 << MINORBITS))
2176bbc923dSHelen Koike return ERR_PTR(-EINVAL);
218*8b326742SBenjamin Marzinski dev->dmi.dev = huge_encode_dev((dev_t)dev->dmi.dev);
2196bbc923dSHelen Koike dev->dmi.flags |= DM_PERSISTENT_DEV_FLAG;
2206bbc923dSHelen Koike }
2216bbc923dSHelen Koike /* flags */
2226bbc923dSHelen Koike if (!strcmp(field[3], "ro"))
2236bbc923dSHelen Koike dev->dmi.flags |= DM_READONLY_FLAG;
2246bbc923dSHelen Koike else if (strcmp(field[3], "rw"))
2256bbc923dSHelen Koike return ERR_PTR(-EINVAL);
2266bbc923dSHelen Koike /* table */
2276bbc923dSHelen Koike if (dm_parse_table(dev, field[4]))
2286bbc923dSHelen Koike return ERR_PTR(-EINVAL);
2296bbc923dSHelen Koike
2306bbc923dSHelen Koike return next;
2316bbc923dSHelen Koike }
2326bbc923dSHelen Koike
2336bbc923dSHelen Koike /**
2346bbc923dSHelen Koike * dm_parse_devices - parse "dm-mod.create=" argument
2356bbc923dSHelen Koike * @devices: list of struct dm_device to store the parsed information.
2366bbc923dSHelen Koike * @str: the pointer to a string with the format:
2376bbc923dSHelen Koike * <device>[;<device>+]
2386bbc923dSHelen Koike */
dm_parse_devices(struct list_head * devices,char * str)2396bbc923dSHelen Koike static int __init dm_parse_devices(struct list_head *devices, char *str)
2406bbc923dSHelen Koike {
2416bbc923dSHelen Koike unsigned long ndev = 0;
2426bbc923dSHelen Koike struct dm_device *dev;
2436bbc923dSHelen Koike char *device = str;
2446bbc923dSHelen Koike
2456bbc923dSHelen Koike DMDEBUG("parsing \"%s\"", str);
2466bbc923dSHelen Koike while (device) {
2476bbc923dSHelen Koike dev = kzalloc(sizeof(*dev), GFP_KERNEL);
2486bbc923dSHelen Koike if (!dev)
2496bbc923dSHelen Koike return -ENOMEM;
2506bbc923dSHelen Koike list_add_tail(&dev->list, devices);
2516bbc923dSHelen Koike
2528e890c1aSHelen Koike if (++ndev > DM_MAX_DEVICES) {
2538e890c1aSHelen Koike DMERR("too many devices %lu > %d",
2548e890c1aSHelen Koike ndev, DM_MAX_DEVICES);
2556bbc923dSHelen Koike return -EINVAL;
2566bbc923dSHelen Koike }
2576bbc923dSHelen Koike
2586bbc923dSHelen Koike device = dm_parse_device_entry(dev, device);
2596bbc923dSHelen Koike if (IS_ERR(device)) {
2606bbc923dSHelen Koike DMERR("couldn't parse device");
2616bbc923dSHelen Koike return PTR_ERR(device);
2626bbc923dSHelen Koike }
2636bbc923dSHelen Koike }
2646bbc923dSHelen Koike
2656bbc923dSHelen Koike return 0;
2666bbc923dSHelen Koike }
2676bbc923dSHelen Koike
2686bbc923dSHelen Koike /**
2696bbc923dSHelen Koike * dm_init_init - parse "dm-mod.create=" argument and configure drivers
2706bbc923dSHelen Koike */
dm_init_init(void)2716bbc923dSHelen Koike static int __init dm_init_init(void)
2726bbc923dSHelen Koike {
2736bbc923dSHelen Koike struct dm_device *dev;
2746bbc923dSHelen Koike LIST_HEAD(devices);
2756bbc923dSHelen Koike char *str;
276035641b0SPeter Korsgaard int i, r;
2776bbc923dSHelen Koike
2786bbc923dSHelen Koike if (!create)
2796bbc923dSHelen Koike return 0;
2806bbc923dSHelen Koike
2816bbc923dSHelen Koike if (strlen(create) >= DM_MAX_STR_SIZE) {
28210c9c8e7SStephen Boyd DMERR("Argument is too big. Limit is %d", DM_MAX_STR_SIZE);
2836bbc923dSHelen Koike return -EINVAL;
2846bbc923dSHelen Koike }
285dec7e649SGen Zhang str = kstrndup(create, DM_MAX_STR_SIZE, GFP_KERNEL);
2866bbc923dSHelen Koike if (!str)
2876bbc923dSHelen Koike return -ENOMEM;
2886bbc923dSHelen Koike
2896bbc923dSHelen Koike r = dm_parse_devices(&devices, str);
2906bbc923dSHelen Koike if (r)
2916bbc923dSHelen Koike goto out;
2926bbc923dSHelen Koike
29310c9c8e7SStephen Boyd DMINFO("waiting for all devices to be available before creating mapped devices");
2946bbc923dSHelen Koike wait_for_device_probe();
2956bbc923dSHelen Koike
296035641b0SPeter Korsgaard for (i = 0; i < ARRAY_SIZE(waitfor); i++) {
297035641b0SPeter Korsgaard if (waitfor[i]) {
29849177377SChristoph Hellwig dev_t dev;
29949177377SChristoph Hellwig
300035641b0SPeter Korsgaard DMINFO("waiting for device %s ...", waitfor[i]);
30149177377SChristoph Hellwig while (early_lookup_bdev(waitfor[i], &dev))
302238d991fSHeinz Mauelshagen fsleep(5000);
303035641b0SPeter Korsgaard }
304035641b0SPeter Korsgaard }
305035641b0SPeter Korsgaard
306035641b0SPeter Korsgaard if (waitfor[0])
307035641b0SPeter Korsgaard DMINFO("all devices available");
308035641b0SPeter Korsgaard
3096bbc923dSHelen Koike list_for_each_entry(dev, &devices, list) {
3106bbc923dSHelen Koike if (dm_early_create(&dev->dmi, dev->table,
3116bbc923dSHelen Koike dev->target_args_array))
3126bbc923dSHelen Koike break;
3136bbc923dSHelen Koike }
3146bbc923dSHelen Koike out:
3156bbc923dSHelen Koike kfree(str);
3166bbc923dSHelen Koike dm_setup_cleanup(&devices);
3176bbc923dSHelen Koike return r;
3186bbc923dSHelen Koike }
3196bbc923dSHelen Koike
3206bbc923dSHelen Koike late_initcall(dm_init_init);
3216bbc923dSHelen Koike
3226bbc923dSHelen Koike module_param(create, charp, 0);
3236bbc923dSHelen Koike MODULE_PARM_DESC(create, "Create a mapped device in early boot");
324035641b0SPeter Korsgaard
325035641b0SPeter Korsgaard module_param_array(waitfor, charp, NULL, 0);
326035641b0SPeter Korsgaard MODULE_PARM_DESC(waitfor, "Devices to wait for before setting up tables");
327