xref: /openbmc/linux/drivers/md/dm-init.c (revision 49177377)
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])) {
2156bbc923dSHelen Koike 		if (kstrtoull(field[2], 0, &dev->dmi.dev))
2166bbc923dSHelen Koike 			return ERR_PTR(-EINVAL);
2176bbc923dSHelen Koike 		dev->dmi.flags |= DM_PERSISTENT_DEV_FLAG;
2186bbc923dSHelen Koike 	}
2196bbc923dSHelen Koike 	/* flags */
2206bbc923dSHelen Koike 	if (!strcmp(field[3], "ro"))
2216bbc923dSHelen Koike 		dev->dmi.flags |= DM_READONLY_FLAG;
2226bbc923dSHelen Koike 	else if (strcmp(field[3], "rw"))
2236bbc923dSHelen Koike 		return ERR_PTR(-EINVAL);
2246bbc923dSHelen Koike 	/* table */
2256bbc923dSHelen Koike 	if (dm_parse_table(dev, field[4]))
2266bbc923dSHelen Koike 		return ERR_PTR(-EINVAL);
2276bbc923dSHelen Koike 
2286bbc923dSHelen Koike 	return next;
2296bbc923dSHelen Koike }
2306bbc923dSHelen Koike 
2316bbc923dSHelen Koike /**
2326bbc923dSHelen Koike  * dm_parse_devices - parse "dm-mod.create=" argument
2336bbc923dSHelen Koike  * @devices: list of struct dm_device to store the parsed information.
2346bbc923dSHelen Koike  * @str: the pointer to a string with the format:
2356bbc923dSHelen Koike  *	<device>[;<device>+]
2366bbc923dSHelen Koike  */
dm_parse_devices(struct list_head * devices,char * str)2376bbc923dSHelen Koike static int __init dm_parse_devices(struct list_head *devices, char *str)
2386bbc923dSHelen Koike {
2396bbc923dSHelen Koike 	unsigned long ndev = 0;
2406bbc923dSHelen Koike 	struct dm_device *dev;
2416bbc923dSHelen Koike 	char *device = str;
2426bbc923dSHelen Koike 
2436bbc923dSHelen Koike 	DMDEBUG("parsing \"%s\"", str);
2446bbc923dSHelen Koike 	while (device) {
2456bbc923dSHelen Koike 		dev = kzalloc(sizeof(*dev), GFP_KERNEL);
2466bbc923dSHelen Koike 		if (!dev)
2476bbc923dSHelen Koike 			return -ENOMEM;
2486bbc923dSHelen Koike 		list_add_tail(&dev->list, devices);
2496bbc923dSHelen Koike 
2508e890c1aSHelen Koike 		if (++ndev > DM_MAX_DEVICES) {
2518e890c1aSHelen Koike 			DMERR("too many devices %lu > %d",
2528e890c1aSHelen Koike 			      ndev, DM_MAX_DEVICES);
2536bbc923dSHelen Koike 			return -EINVAL;
2546bbc923dSHelen Koike 		}
2556bbc923dSHelen Koike 
2566bbc923dSHelen Koike 		device = dm_parse_device_entry(dev, device);
2576bbc923dSHelen Koike 		if (IS_ERR(device)) {
2586bbc923dSHelen Koike 			DMERR("couldn't parse device");
2596bbc923dSHelen Koike 			return PTR_ERR(device);
2606bbc923dSHelen Koike 		}
2616bbc923dSHelen Koike 	}
2626bbc923dSHelen Koike 
2636bbc923dSHelen Koike 	return 0;
2646bbc923dSHelen Koike }
2656bbc923dSHelen Koike 
2666bbc923dSHelen Koike /**
2676bbc923dSHelen Koike  * dm_init_init - parse "dm-mod.create=" argument and configure drivers
2686bbc923dSHelen Koike  */
dm_init_init(void)2696bbc923dSHelen Koike static int __init dm_init_init(void)
2706bbc923dSHelen Koike {
2716bbc923dSHelen Koike 	struct dm_device *dev;
2726bbc923dSHelen Koike 	LIST_HEAD(devices);
2736bbc923dSHelen Koike 	char *str;
274035641b0SPeter Korsgaard 	int i, r;
2756bbc923dSHelen Koike 
2766bbc923dSHelen Koike 	if (!create)
2776bbc923dSHelen Koike 		return 0;
2786bbc923dSHelen Koike 
2796bbc923dSHelen Koike 	if (strlen(create) >= DM_MAX_STR_SIZE) {
28010c9c8e7SStephen Boyd 		DMERR("Argument is too big. Limit is %d", DM_MAX_STR_SIZE);
2816bbc923dSHelen Koike 		return -EINVAL;
2826bbc923dSHelen Koike 	}
283dec7e649SGen Zhang 	str = kstrndup(create, DM_MAX_STR_SIZE, GFP_KERNEL);
2846bbc923dSHelen Koike 	if (!str)
2856bbc923dSHelen Koike 		return -ENOMEM;
2866bbc923dSHelen Koike 
2876bbc923dSHelen Koike 	r = dm_parse_devices(&devices, str);
2886bbc923dSHelen Koike 	if (r)
2896bbc923dSHelen Koike 		goto out;
2906bbc923dSHelen Koike 
29110c9c8e7SStephen Boyd 	DMINFO("waiting for all devices to be available before creating mapped devices");
2926bbc923dSHelen Koike 	wait_for_device_probe();
2936bbc923dSHelen Koike 
294035641b0SPeter Korsgaard 	for (i = 0; i < ARRAY_SIZE(waitfor); i++) {
295035641b0SPeter Korsgaard 		if (waitfor[i]) {
296*49177377SChristoph Hellwig 			dev_t dev;
297*49177377SChristoph Hellwig 
298035641b0SPeter Korsgaard 			DMINFO("waiting for device %s ...", waitfor[i]);
299*49177377SChristoph Hellwig 			while (early_lookup_bdev(waitfor[i], &dev))
300238d991fSHeinz Mauelshagen 				fsleep(5000);
301035641b0SPeter Korsgaard 		}
302035641b0SPeter Korsgaard 	}
303035641b0SPeter Korsgaard 
304035641b0SPeter Korsgaard 	if (waitfor[0])
305035641b0SPeter Korsgaard 		DMINFO("all devices available");
306035641b0SPeter Korsgaard 
3076bbc923dSHelen Koike 	list_for_each_entry(dev, &devices, list) {
3086bbc923dSHelen Koike 		if (dm_early_create(&dev->dmi, dev->table,
3096bbc923dSHelen Koike 				    dev->target_args_array))
3106bbc923dSHelen Koike 			break;
3116bbc923dSHelen Koike 	}
3126bbc923dSHelen Koike out:
3136bbc923dSHelen Koike 	kfree(str);
3146bbc923dSHelen Koike 	dm_setup_cleanup(&devices);
3156bbc923dSHelen Koike 	return r;
3166bbc923dSHelen Koike }
3176bbc923dSHelen Koike 
3186bbc923dSHelen Koike late_initcall(dm_init_init);
3196bbc923dSHelen Koike 
3206bbc923dSHelen Koike module_param(create, charp, 0);
3216bbc923dSHelen Koike MODULE_PARM_DESC(create, "Create a mapped device in early boot");
322035641b0SPeter Korsgaard 
323035641b0SPeter Korsgaard module_param_array(waitfor, charp, NULL, 0);
324035641b0SPeter Korsgaard MODULE_PARM_DESC(waitfor, "Devices to wait for before setting up tables");
325