[v5,4/4] mtd: Add driver for concatenating devices
diff mbox series

Message ID 20191127105522.31445-5-miquel.raynal@bootlin.com
State Changes Requested
Delegated to: Miquel Raynal
Headers show
Series
  • MTD concat
Related show

Commit Message

Miquel Raynal Nov. 27, 2019, 10:55 a.m. UTC
Introduce a generic way to define concatenated MTD devices. This may
be very useful in the case of ie. stacked SPI-NOR. Partitions to
concatenate are described in an additional property of the partitions
subnode:

        flash0 {
                partitions {
                        compatible = "fixed-partitions";
                        part-concat = <&flash0_part1>, <&flash1_part0>;

			part0@0 {
				label = "part0_0";
				reg = <0x0 0x800000>;
			};

			flash0_part1: part1@800000 {
				label = "part0_1";
				reg = <0x800000 0x800000>;
			};
                };
        };

        flash1 {
                partitions {
                        compatible = "fixed-partitions";

			flash0_part1: part1@0 {
				label = "part1_0";
				reg = <0x0 0x800000>;
			};

			part0@800000 {
				label = "part1_1";
				reg = <0x800000 0x800000>;
			};
                };
        };

This is useful for boards where memory range has been extended with
the use of multiple flash chips as memory banks of a single MTD
device, with partitions spanning chip borders.

Suggested-by: Bernhard Frauendienst <kernel@nospam.obeliks.de>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
 drivers/mtd/Kconfig           |   8 ++
 drivers/mtd/Makefile          |   1 +
 drivers/mtd/mtd_virt_concat.c | 240 ++++++++++++++++++++++++++++++++++
 3 files changed, 249 insertions(+)
 create mode 100644 drivers/mtd/mtd_virt_concat.c

Comments

Vignesh Raghavendra Dec. 4, 2019, 9:58 a.m. UTC | #1
Hi Miquel,

On 27/11/19 4:25 pm, Miquel Raynal wrote:
> Introduce a generic way to define concatenated MTD devices. This may
> be very useful in the case of ie. stacked SPI-NOR. Partitions to
> concatenate are described in an additional property of the partitions
> subnode:
> 
>         flash0 {
>                 partitions {
>                         compatible = "fixed-partitions";
>                         part-concat = <&flash0_part1>, <&flash1_part0>;
> 
> 			part0@0 {
> 				label = "part0_0";
> 				reg = <0x0 0x800000>;
> 			};
> 
> 			flash0_part1: part1@800000 {
> 				label = "part0_1";
> 				reg = <0x800000 0x800000>;
> 			};
>                 };
>         };

IIUC flash0 and flash1 are subnodes of a SPI master node?
And I believe flash0 node's compatible is "jedec,spi-nor"?


> 
>         flash1 {
>                 partitions {
>                         compatible = "fixed-partitions";
> 
> 			flash0_part1: part1@0 {

s/flash0_part1/flash1_part0?

> 				label = "part1_0";
> 				reg = <0x0 0x800000>;
> 			};
> 
> 			part0@800000 {
> 				label = "part1_1";
> 				reg = <0x800000 0x800000>;
> 			};
>                 };
>         };
> 

For my understanding, how many /dev/mtdX entries would this create?

Regards
Vignesh

> This is useful for boards where memory range has been extended with
> the use of multiple flash chips as memory banks of a single MTD
> device, with partitions spanning chip borders.
> 
> Suggested-by: Bernhard Frauendienst <kernel@nospam.obeliks.de>
> Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
> ---
>  drivers/mtd/Kconfig           |   8 ++
>  drivers/mtd/Makefile          |   1 +
>  drivers/mtd/mtd_virt_concat.c | 240 ++++++++++++++++++++++++++++++++++
>  3 files changed, 249 insertions(+)
>  create mode 100644 drivers/mtd/mtd_virt_concat.c
> 
> diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
> index 79a8ff542883..3e1e55e7158f 100644
> --- a/drivers/mtd/Kconfig
> +++ b/drivers/mtd/Kconfig
> @@ -276,6 +276,14 @@ config MTD_PARTITIONED_MASTER
>  	  the parent of the partition device be the master device, rather than
>  	  what lies behind the master.
>  
> +config MTD_VIRT_CONCAT
> +	tristate "Virtual concatenated MTD devices"
> +	help
> +	  This driver allows creation of a virtual MTD device, which
> +	  concatenates multiple physical MTD devices into a single one.
> +	  This is useful to create partitions bigger than the underlying
> +	  physical chips by allowing cross-chip boundaries.
> +
>  source "drivers/mtd/chips/Kconfig"
>  
>  source "drivers/mtd/maps/Kconfig"
> diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
> index 58fc327a5276..c7ee13368a66 100644
> --- a/drivers/mtd/Makefile
> +++ b/drivers/mtd/Makefile
> @@ -27,6 +27,7 @@ obj-$(CONFIG_SSFDC)		+= ssfdc.o
>  obj-$(CONFIG_SM_FTL)		+= sm_ftl.o
>  obj-$(CONFIG_MTD_OOPS)		+= mtdoops.o
>  obj-$(CONFIG_MTD_SWAP)		+= mtdswap.o
> +obj-$(CONFIG_MTD_VIRT_CONCAT)	+= mtd_virt_concat.o
>  
>  nftl-objs		:= nftlcore.o nftlmount.o
>  inftl-objs		:= inftlcore.o inftlmount.o
> diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c
> new file mode 100644
> index 000000000000..23c7170ac32f
> --- /dev/null
> +++ b/drivers/mtd/mtd_virt_concat.c
> @@ -0,0 +1,240 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Virtual concat MTD device driver
> + *
> + * Copyright (C) 2018 Bernhard Frauendienst
> + * Author: Bernhard Frauendienst <kernel@nospam.obeliks.de>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/device.h>
> +#include <linux/mtd/concat.h>
> +#include <linux/mtd/mtd.h>
> +#include "mtdcore.h"
> +#include <linux/mtd/partitions.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/slab.h>
> +
> +#define CONCAT_PROP "part-concat"
> +#define MIN_DEV_PER_CONCAT 2
> +
> +/**
> + * struct mtd_virt_concat - concatenation container
> + * @vmtd: Virtual mtd_concat device
> + * @count: Number of physical underlaying devices in @devices
> + * @devices: Array of the physical devices used
> + */
> +struct mtd_virt_concat {
> +	struct mtd_info	*vmtd;
> +	unsigned int count;
> +	struct mtd_info	**devices;
> +};
> +
> +/**
> + * struct mtd_virt_concat_node - components of a concatenation
> + * @head: List handle
> + * @count: Number of nodes
> + * @nodes: Pointer to the nodes (partitions) to concatenate
> + * @concat: Concatenation container
> + */
> +struct mtd_virt_concat_node {
> +	struct list_head head;
> +	unsigned int count;
> +	struct device_node **nodes;
> +	struct mtd_virt_concat *concat;
> +};
> +
> +static LIST_HEAD(concat_list);
> +
> +static void mtd_virt_concat_put_mtd_devices(struct mtd_virt_concat *concat)
> +{
> +	int i;
> +
> +	for (i = 0; i < concat->count; i++)
> +		put_mtd_device(concat->devices[i]);
> +}
> +
> +static int mtd_virt_concat_create_join(struct mtd_virt_concat_node *item)
> +{
> +	struct mtd_virt_concat *concat;
> +	struct mtd_info *mtd;
> +	ssize_t name_sz;
> +	char *name;
> +	int ret, i;
> +
> +	concat = kzalloc(sizeof(*concat), GFP_KERNEL);
> +	if (!concat)
> +		return -ENOMEM;
> +
> +	concat->devices = kcalloc(item->count,
> +				  sizeof(*concat->devices),
> +				  GFP_KERNEL);
> +	if (!concat->devices) {
> +		ret = -ENOMEM;
> +		goto free_concat;
> +	}
> +
> +	/* Aggregate the physical devices */
> +	for (i = 0; i < item->count; i++) {
> +		mtd = get_mtd_device_by_node(item->nodes[i]);
> +		if (IS_ERR(mtd)) {
> +			ret = PTR_ERR(mtd);
> +			goto put_mtd_devices;
> +		}
> +
> +		concat->devices[concat->count++] = mtd;
> +	}
> +
> +	/* Create the virtual device */
> +	name_sz = snprintf(NULL, 0, "%s-%s%s-concat",
> +			   concat->devices[0]->name,
> +			   concat->devices[1]->name,
> +			   concat->count > 2 ? "-+" : "");
> +	name = kmalloc(name_sz, GFP_KERNEL);
> +	if (!name) {
> +		ret = -ENOMEM;
> +		goto put_mtd_devices;
> +	}
> +
> +	sprintf(name, "%s-%s%s-concat",
> +		concat->devices[0]->name,
> +		concat->devices[1]->name,
> +		concat->count > 2 ? "-+" : "");
> +
> +	concat->vmtd = mtd_concat_create(concat->devices, concat->count, name);
> +	if (!concat->vmtd) {
> +		ret = -ENXIO;
> +		goto free_name;
> +	}
> +
> +	/* Arbitrary set the first device as parent */
> +	concat->vmtd->dev.parent = &concat->devices[0]->dev;
> +
> +	/* Register the platform device */
> +	ret = mtd_device_register(concat->vmtd, NULL, 0);
> +	if (ret)
> +		goto destroy_concat;
> +
> +	item->concat = concat;
> +
> +	return 0;
> +
> +destroy_concat:
> +	mtd_concat_destroy(concat->vmtd);
> +free_name:
> +	kfree(name);
> +put_mtd_devices:
> +	mtd_virt_concat_put_mtd_devices(concat);
> +free_concat:
> +	kfree(concat);
> +
> +	return ret;
> +}
> +
> +static void mtd_virt_concat_destroy_joins(void)
> +{
> +	struct mtd_virt_concat_node *item, *tmp;
> +
> +	list_for_each_entry_safe(item, tmp, &concat_list, head) {
> +		if (item->concat) {
> +			mtd_device_unregister(item->concat->vmtd);
> +			kfree(item->concat->vmtd->name);
> +			mtd_concat_destroy(item->concat->vmtd);
> +			mtd_virt_concat_put_mtd_devices(item->concat);
> +		}
> +	}
> +}
> +
> +static int mtd_virt_concat_create_item(struct device_node *parts,
> +				       unsigned int count)
> +{
> +	struct mtd_virt_concat_node *item;
> +	int i;
> +
> +	item = kzalloc(sizeof(*item), GFP_KERNEL);
> +	if (!item)
> +		return -ENOMEM;
> +
> +	item->count = count;
> +	item->nodes = kcalloc(count, sizeof(*item->nodes), GFP_KERNEL);
> +	if (!item->nodes) {
> +		kfree(item);
> +		return -ENOMEM;
> +	}
> +
> +	for (i = 0; i < count; i++)
> +		item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, i);
> +
> +	list_add_tail(&item->head, &concat_list);
> +
> +	return 0;
> +}
> +
> +static void mtd_virt_concat_destroy_items(void)
> +{
> +	struct mtd_virt_concat_node *item, *temp;
> +	int i;
> +
> +	list_for_each_entry_safe(item, temp, &concat_list, head) {
> +		for (i = 0; i < item->count; i++)
> +			of_node_put(item->nodes[i]);
> +
> +		kfree(item->nodes);
> +		kfree(item);
> +	}
> +}
> +
> +static int __init mtd_virt_concat_init(void)
> +{
> +	struct mtd_virt_concat_node *item;
> +	struct device_node *parts = NULL;
> +	int ret = 0, count;
> +
> +	/* List all the concatenations found in DT */
> +	do {
> +		parts = of_find_node_with_property(parts, CONCAT_PROP);
> +		if (!of_device_is_available(parts))
> +			continue;
> +
> +		count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL);
> +		if (count < MIN_DEV_PER_CONCAT)
> +			continue;
> +
> +		ret = mtd_virt_concat_create_item(parts, count);
> +		if (ret) {
> +			of_node_put(parts);
> +			goto destroy_items;
> +		}
> +	} while (parts);
> +
> +	/* TODO: also parse the cmdline */
> +
> +	/* Create the concatenations */
> +	list_for_each_entry(item, &concat_list, head) {
> +		ret = mtd_virt_concat_create_join(item);
> +		if (ret)
> +			goto destroy_joins;
> +	}
> +
> +	return 0;
> +
> +destroy_joins:
> +	mtd_virt_concat_destroy_joins();
> +destroy_items:
> +	mtd_virt_concat_destroy_items();
> +
> +	return ret;
> +}
> +late_initcall(mtd_virt_concat_init);
> +
> +static void __exit mtd_virt_concat_exit(void)
> +{
> +	mtd_virt_concat_destroy_joins();
> +	mtd_virt_concat_destroy_items();
> +}
> +module_exit(mtd_virt_concat_exit);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Bernhard Frauendienst <kernel@nospam.obeliks.de>");
> +MODULE_DESCRIPTION("Virtual concat MTD device driver");
>
Miquel Raynal Dec. 4, 2019, 10:17 a.m. UTC | #2
Hi Vignesh,

Vignesh Raghavendra <vigneshr@ti.com> wrote on Wed, 4 Dec 2019 15:28:46
+0530:

> Hi Miquel,
> 
> On 27/11/19 4:25 pm, Miquel Raynal wrote:
> > Introduce a generic way to define concatenated MTD devices. This may
> > be very useful in the case of ie. stacked SPI-NOR. Partitions to
> > concatenate are described in an additional property of the partitions
> > subnode:
> > 
> >         flash0 {
> >                 partitions {
> >                         compatible = "fixed-partitions";
> >                         part-concat = <&flash0_part1>, <&flash1_part0>;
> > 
> > 			part0@0 {
> > 				label = "part0_0";
> > 				reg = <0x0 0x800000>;
> > 			};
> > 
> > 			flash0_part1: part1@800000 {
> > 				label = "part0_1";
> > 				reg = <0x800000 0x800000>;
> > 			};
> >                 };
> >         };  
> 
> IIUC flash0 and flash1 are subnodes of a SPI master node?
> And I believe flash0 node's compatible is "jedec,spi-nor"?

Indeed this is one possibility (probably the most common) but in theory
this should work for any kind of MTD device, hence I voluntarily
dropped the hardware-specific properties to focus on the partitions
description here.

> 
> 
> > 
> >         flash1 {
> >                 partitions {
> >                         compatible = "fixed-partitions";
> > 
> > 			flash0_part1: part1@0 {  
> 
> s/flash0_part1/flash1_part0?

Right!

> 
> > 				label = "part1_0";
> > 				reg = <0x0 0x800000>;
> > 			};
> > 
> > 			part0@800000 {
> > 				label = "part1_1";
> > 				reg = <0x800000 0x800000>;
> > 			};
> >                 };
> >         };
> >   
> 
> For my understanding, how many /dev/mtdX entries would this create?

If the master is retained (Kconfig option) and thanks to the common
partitioning scheme, we would have:
* flash0 (mtd0)
*   part0_0 (mtd1)
*   part0_1 (mtd2)
* flash1 (mtd3)
*   part1_0 (mtd4)
*   part1_1 (mtd5)

If we enable this driver, we would also get an additional device:
* mtd2-mtd4-concat (or part0_1-part1_0-concat, I don't recall the exact
  name) being mtd6.


Thanks,
Miquèl
Vignesh Raghavendra Dec. 4, 2019, 12:55 p.m. UTC | #3
On 04/12/19 3:47 pm, Miquel Raynal wrote:
> Hi Vignesh,
> 
> Vignesh Raghavendra <vigneshr@ti.com> wrote on Wed, 4 Dec 2019 15:28:46
> +0530:
> 
[...]
>> IIUC flash0 and flash1 are subnodes of a SPI master node?
>> And I believe flash0 node's compatible is "jedec,spi-nor"?
> 
> Indeed this is one possibility (probably the most common) but in theory
> this should work for any kind of MTD device, hence I voluntarily
> dropped the hardware-specific properties to focus on the partitions
> description here.
> 

Ah, make sense...

>>
>>
>>>
>>>         flash1 {
>>>                 partitions {
>>>                         compatible = "fixed-partitions";
>>>
>>> 			flash0_part1: part1@0 {  
>>
>> s/flash0_part1/flash1_part0?
> 
> Right!
> 
>>
>>> 				label = "part1_0";
>>> 				reg = <0x0 0x800000>;
>>> 			};
>>>
>>> 			part0@800000 {
>>> 				label = "part1_1";
>>> 				reg = <0x800000 0x800000>;
>>> 			};
>>>                 };
>>>         };
>>>   
>>
>> For my understanding, how many /dev/mtdX entries would this create?
> 
> If the master is retained (Kconfig option) and thanks to the common
> partitioning scheme, we would have:
> * flash0 (mtd0)
> *   part0_0 (mtd1)
> *   part0_1 (mtd2)
> * flash1 (mtd3)
> *   part1_0 (mtd4)
> *   part1_1 (mtd5)
> 
> If we enable this driver, we would also get an additional device:
> * mtd2-mtd4-concat (or part0_1-part1_0-concat, I don't recall the exact
>   name) being mtd6.

Ok, thanks for the clarification!
Boris Brezillon Dec. 9, 2019, 10:35 a.m. UTC | #4
On Wed, 27 Nov 2019 11:55:22 +0100
Miquel Raynal <miquel.raynal@bootlin.com> wrote:

> Introduce a generic way to define concatenated MTD devices. This may
> be very useful in the case of ie. stacked SPI-NOR. Partitions to
> concatenate are described in an additional property of the partitions
> subnode:
> 
>         flash0 {
>                 partitions {
>                         compatible = "fixed-partitions";
>                         part-concat = <&flash0_part1>, <&flash1_part0>;
> 
> 			part0@0 {
> 				label = "part0_0";
> 				reg = <0x0 0x800000>;
> 			};
> 
> 			flash0_part1: part1@800000 {
> 				label = "part0_1";
> 				reg = <0x800000 0x800000>;

So, flash0_part1 and flash0_part2 will be created even though the user
probably doesn't need them?

> 			};
>                 };
>         };
> 
>         flash1 {
>                 partitions {
>                         compatible = "fixed-partitions";
> 
> 			flash0_part1: part1@0 {
> 				label = "part1_0";
> 				reg = <0x0 0x800000>;
> 			};
> 
> 			part0@800000 {
> 				label = "part1_1";
> 				reg = <0x800000 0x800000>;
> 			};
>                 };
>         };

IMHO this representation is far from intuitive. At first glance it's not
obvious which partitions are linked together and what's the name of the
resulting concatenated part. I definitely prefer the solution where we
have a virtual device describing the concatenation. I also understand
that this goes against the #1 DT rule: "DT only decribes HW blocks, not
how they should be used/configured", but maybe we can find a compromise
here, like moving this description to the /chosen node?

chosen {
	flash-arrays {
		/*
		 * my-flash-array is the MTD name if label is
		 * not present.
		 */
		my-flash-array {
			/*
			 * We could have
			 * compatible = "flash-array";
			 * but we can also do without it.
			 */
			label = "foo";
			flashes = <&flash1 &flash2 ...>;
			partitions {
				/* usual partition description. */
				...
			};
		};
	};
};

Rob, what do you think?

> 
> This is useful for boards where memory range has been extended with
> the use of multiple flash chips as memory banks of a single MTD
> device, with partitions spanning chip borders.
> 
> Suggested-by: Bernhard Frauendienst <kernel@nospam.obeliks.de>
> Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
> ---
>  drivers/mtd/Kconfig           |   8 ++
>  drivers/mtd/Makefile          |   1 +
>  drivers/mtd/mtd_virt_concat.c | 240 ++++++++++++++++++++++++++++++++++
>  3 files changed, 249 insertions(+)
>  create mode 100644 drivers/mtd/mtd_virt_concat.c
> 
> diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
> index 79a8ff542883..3e1e55e7158f 100644
> --- a/drivers/mtd/Kconfig
> +++ b/drivers/mtd/Kconfig
> @@ -276,6 +276,14 @@ config MTD_PARTITIONED_MASTER
>  	  the parent of the partition device be the master device, rather than
>  	  what lies behind the master.
>  
> +config MTD_VIRT_CONCAT
> +	tristate "Virtual concatenated MTD devices"
> +	help
> +	  This driver allows creation of a virtual MTD device, which
> +	  concatenates multiple physical MTD devices into a single one.
> +	  This is useful to create partitions bigger than the underlying
> +	  physical chips by allowing cross-chip boundaries.
> +
>  source "drivers/mtd/chips/Kconfig"
>  
>  source "drivers/mtd/maps/Kconfig"
> diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
> index 58fc327a5276..c7ee13368a66 100644
> --- a/drivers/mtd/Makefile
> +++ b/drivers/mtd/Makefile
> @@ -27,6 +27,7 @@ obj-$(CONFIG_SSFDC)		+= ssfdc.o
>  obj-$(CONFIG_SM_FTL)		+= sm_ftl.o
>  obj-$(CONFIG_MTD_OOPS)		+= mtdoops.o
>  obj-$(CONFIG_MTD_SWAP)		+= mtdswap.o
> +obj-$(CONFIG_MTD_VIRT_CONCAT)	+= mtd_virt_concat.o

Can we move that code to mtdconcat? After, it's just an extension to
the mtdconcat logic that extract the description from a DT instead of
expecting drivers to create the concatenation on their own.


> +
> +static int __init mtd_virt_concat_init(void)
> +{
> +	struct mtd_virt_concat_node *item;
> +	struct device_node *parts = NULL;
> +	int ret = 0, count;
> +
> +	/* List all the concatenations found in DT */
> +	do {
> +		parts = of_find_node_with_property(parts, CONCAT_PROP);
> +		if (!of_device_is_available(parts))
> +			continue;
> +
> +		count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL);
> +		if (count < MIN_DEV_PER_CONCAT)
> +			continue;
> +
> +		ret = mtd_virt_concat_create_item(parts, count);
> +		if (ret) {
> +			of_node_put(parts);
> +			goto destroy_items;
> +		}
> +	} while (parts);
> +
> +	/* TODO: also parse the cmdline */
> +
> +	/* Create the concatenations */
> +	list_for_each_entry(item, &concat_list, head) {
> +		ret = mtd_virt_concat_create_join(item);
> +		if (ret)
> +			goto destroy_joins;
> +	}
> +
> +	return 0;
> +
> +destroy_joins:
> +	mtd_virt_concat_destroy_joins();
> +destroy_items:
> +	mtd_virt_concat_destroy_items();
> +
> +	return ret;
> +}
> +late_initcall(mtd_virt_concat_init);

Right now the solution assumes all drivers are statically linked. I'm
pretty sure we can support other cases if we use MTD notifiers to be
informed of MTD device is addition/removal.

> +
> +static void __exit mtd_virt_concat_exit(void)
> +{
> +	mtd_virt_concat_destroy_joins();
> +	mtd_virt_concat_destroy_items();
> +}
> +module_exit(mtd_virt_concat_exit);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Bernhard Frauendienst <kernel@nospam.obeliks.de>");
> +MODULE_DESCRIPTION("Virtual concat MTD device driver");
Miquel Raynal Jan. 14, 2020, 9:24 a.m. UTC | #5
Hi Rob,

Boris Brezillon <boris.brezillon@collabora.com> wrote on Mon, 9 Dec
2019 11:35:06 +0100:

> On Wed, 27 Nov 2019 11:55:22 +0100
> Miquel Raynal <miquel.raynal@bootlin.com> wrote:
> 
> > Introduce a generic way to define concatenated MTD devices. This may
> > be very useful in the case of ie. stacked SPI-NOR. Partitions to
> > concatenate are described in an additional property of the partitions
> > subnode:
> > 
> >         flash0 {
> >                 partitions {
> >                         compatible = "fixed-partitions";
> >                         part-concat = <&flash0_part1>, <&flash1_part0>;
> > 
> > 			part0@0 {
> > 				label = "part0_0";
> > 				reg = <0x0 0x800000>;
> > 			};
> > 
> > 			flash0_part1: part1@800000 {
> > 				label = "part0_1";
> > 				reg = <0x800000 0x800000>;  
> 
> So, flash0_part1 and flash0_part2 will be created even though the user
> probably doesn't need them?
> 
> > 			};
> >                 };
> >         };
> > 
> >         flash1 {
> >                 partitions {
> >                         compatible = "fixed-partitions";
> > 
> > 			flash0_part1: part1@0 {
> > 				label = "part1_0";
> > 				reg = <0x0 0x800000>;
> > 			};
> > 
> > 			part0@800000 {
> > 				label = "part1_1";
> > 				reg = <0x800000 0x800000>;
> > 			};
> >                 };
> >         };  
> 
> IMHO this representation is far from intuitive. At first glance it's not
> obvious which partitions are linked together and what's the name of the
> resulting concatenated part. I definitely prefer the solution where we
> have a virtual device describing the concatenation. I also understand
> that this goes against the #1 DT rule: "DT only decribes HW blocks, not
> how they should be used/configured", but maybe we can find a compromise
> here, like moving this description to the /chosen node?
> 
> chosen {
> 	flash-arrays {
> 		/*
> 		 * my-flash-array is the MTD name if label is
> 		 * not present.
> 		 */
> 		my-flash-array {
> 			/*
> 			 * We could have
> 			 * compatible = "flash-array";
> 			 * but we can also do without it.
> 			 */
> 			label = "foo";
> 			flashes = <&flash1 &flash2 ...>;
> 			partitions {
> 				/* usual partition description. */
> 				...
> 			};
> 		};
> 	};
> };
> 
> Rob, what do you think?

Rob, I would really welcome your thoughts on this solution, having
something like a flash-array node in the /chosen/ node would avoid
creating dummy devices, keep the declarations of the physical nodes
tidy and have a very simple description.

Hope this compromise could fit!
 
> 
> > 
> > This is useful for boards where memory range has been extended with
> > the use of multiple flash chips as memory banks of a single MTD
> > device, with partitions spanning chip borders.
> > 
> > Suggested-by: Bernhard Frauendienst <kernel@nospam.obeliks.de>
> > Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>

Thanks,
Miquèl
Rob Herring Jan. 14, 2020, 5:46 p.m. UTC | #6
On Mon, Dec 9, 2019 at 4:35 AM Boris Brezillon
<boris.brezillon@collabora.com> wrote:
>
> On Wed, 27 Nov 2019 11:55:22 +0100
> Miquel Raynal <miquel.raynal@bootlin.com> wrote:
>
> > Introduce a generic way to define concatenated MTD devices. This may
> > be very useful in the case of ie. stacked SPI-NOR. Partitions to
> > concatenate are described in an additional property of the partitions
> > subnode:
> >
> >         flash0 {
> >                 partitions {
> >                         compatible = "fixed-partitions";
> >                         part-concat = <&flash0_part1>, <&flash1_part0>;
> >
> >                       part0@0 {
> >                               label = "part0_0";
> >                               reg = <0x0 0x800000>;
> >                       };
> >
> >                       flash0_part1: part1@800000 {
> >                               label = "part0_1";
> >                               reg = <0x800000 0x800000>;
>
> So, flash0_part1 and flash0_part2 will be created even though the user
> probably doesn't need them?

I don't follow?

>
> >                       };
> >                 };
> >         };
> >
> >         flash1 {
> >                 partitions {
> >                         compatible = "fixed-partitions";
> >
> >                       flash0_part1: part1@0 {
> >                               label = "part1_0";
> >                               reg = <0x0 0x800000>;
> >                       };
> >
> >                       part0@800000 {
> >                               label = "part1_1";
> >                               reg = <0x800000 0x800000>;
> >                       };
> >                 };
> >         };
>
> IMHO this representation is far from intuitive. At first glance it's not
> obvious which partitions are linked together and what's the name of the
> resulting concatenated part. I definitely prefer the solution where we
> have a virtual device describing the concatenation. I also understand
> that this goes against the #1 DT rule: "DT only decribes HW blocks, not
> how they should be used/configured", but maybe we can find a compromise
> here, like moving this description to the /chosen node?
>
> chosen {
>         flash-arrays {
>                 /*
>                  * my-flash-array is the MTD name if label is
>                  * not present.
>                  */
>                 my-flash-array {
>                         /*
>                          * We could have
>                          * compatible = "flash-array";
>                          * but we can also do without it.
>                          */
>                         label = "foo";
>                         flashes = <&flash1 &flash2 ...>;
>                         partitions {
>                                 /* usual partition description. */
>                                 ...
>                         };
>                 };
>         };
> };
>
> Rob, what do you think?

I don't think chosen is the right place to put all the partition
information. It's not something the bootloader configures.

This suffers from the same issue I have with the original proposal. It
will not work for existing s/w. There's only 1 logical partition that
concatenated. The rest of the partitions shouldn't need any special
handling. So we really only need some way to say 'link these 2
partitions into 1 logical partition'. Though perhaps one could want to
combine any number of physical partitions into logical partitions, but
then none of the proposals could support that. Then again, maybe
that's a userspace problem like with disks.

To throw out another option, what if the first device contains the
complete partitions for both devices with some property in one or both
devices pointing to the other device? That would make the partitions
in the 1st device still accessible to existing s/w (unless it bounds
checks the partitions).

Rob
Miquel Raynal Jan. 14, 2020, 6:10 p.m. UTC | #7
Hi Rob,

Rob Herring <robh+dt@kernel.org> wrote on Tue, 14 Jan 2020 11:46:18
-0600:

> On Mon, Dec 9, 2019 at 4:35 AM Boris Brezillon
> <boris.brezillon@collabora.com> wrote:
> >
> > On Wed, 27 Nov 2019 11:55:22 +0100
> > Miquel Raynal <miquel.raynal@bootlin.com> wrote:
> >  
> > > Introduce a generic way to define concatenated MTD devices. This may
> > > be very useful in the case of ie. stacked SPI-NOR. Partitions to
> > > concatenate are described in an additional property of the partitions
> > > subnode:
> > >
> > >         flash0 {
> > >                 partitions {
> > >                         compatible = "fixed-partitions";
> > >                         part-concat = <&flash0_part1>, <&flash1_part0>;
> > >
> > >                       part0@0 {
> > >                               label = "part0_0";
> > >                               reg = <0x0 0x800000>;
> > >                       };
> > >
> > >                       flash0_part1: part1@800000 {
> > >                               label = "part0_1";
> > >                               reg = <0x800000 0x800000>;  
> >
> > So, flash0_part1 and flash0_part2 will be created even though the user
> > probably doesn't need them?  
> 
> I don't follow?

Well, one will have to create "fake" partitions in order to concatenate
them with this solution, instead of just concatenating the devices (in
the case where you want to concatenate the entire devices). But the real
debate is below, on the representation.

> 
> >  
> > >                       };
> > >                 };
> > >         };
> > >
> > >         flash1 {
> > >                 partitions {
> > >                         compatible = "fixed-partitions";
> > >
> > >                       flash0_part1: part1@0 {
> > >                               label = "part1_0";
> > >                               reg = <0x0 0x800000>;
> > >                       };
> > >
> > >                       part0@800000 {
> > >                               label = "part1_1";
> > >                               reg = <0x800000 0x800000>;
> > >                       };
> > >                 };
> > >         };  
> >
> > IMHO this representation is far from intuitive. At first glance it's not
> > obvious which partitions are linked together and what's the name of the
> > resulting concatenated part. I definitely prefer the solution where we
> > have a virtual device describing the concatenation. I also understand
> > that this goes against the #1 DT rule: "DT only decribes HW blocks, not
> > how they should be used/configured", but maybe we can find a compromise
> > here, like moving this description to the /chosen node?
> >
> > chosen {
> >         flash-arrays {
> >                 /*
> >                  * my-flash-array is the MTD name if label is
> >                  * not present.
> >                  */
> >                 my-flash-array {
> >                         /*
> >                          * We could have
> >                          * compatible = "flash-array";
> >                          * but we can also do without it.
> >                          */
> >                         label = "foo";
> >                         flashes = <&flash1 &flash2 ...>;
> >                         partitions {
> >                                 /* usual partition description. */
> >                                 ...
> >                         };
> >                 };
> >         };
> > };
> >
> > Rob, what do you think?  
> 
> I don't think chosen is the right place to put all the partition
> information. It's not something the bootloader configures.
> 
> This suffers from the same issue I have with the original proposal. It
> will not work for existing s/w. There's only 1 logical partition that

I don't get why it would not work? Current hardware will just not have
the concatenation support, that's all. How is this a problem?

> concatenated. The rest of the partitions shouldn't need any special
> handling. So we really only need some way to say 'link these 2
> partitions into 1 logical partition'. Though perhaps one could want to
> combine any number of physical partitions into logical partitions, but
> then none of the proposals could support that. Then again, maybe

Yes, the flash-array proposal supports having more than two
partitions/devices concatenated, it is also already supported by the
driver (you don't care about this, but I do :) ).

> that's a userspace problem like with disks.

I see one big issue with this solution: what about bootloaders?

The root cause for such idea is that, in my case, the 2 MTD devices are
too small to contain the images needed to boot. The perfect solution is
to merge the two devices virtually in one single device and let U-Boot
read it like one.

I need to have the same representation both in U-Boot and Linux, hence
a userspace tool and a kernel command line argument do not work, right?

> To throw out another option, what if the first device contains the
> complete partitions for both devices with some property in one or both
> devices pointing to the other device? That would make the partitions
> in the 1st device still accessible to existing s/w (unless it bounds
> checks the partitions).

From a coding perspective this is very difficult as bound checks are
done everywhere and lying about the boundaries is IMHO a bit complex.

Thanks,
Miquèl
Rob Herring Jan. 14, 2020, 9:59 p.m. UTC | #8
On Tue, Jan 14, 2020 at 12:11 PM Miquel Raynal
<miquel.raynal@bootlin.com> wrote:
>
> Hi Rob,
>
> Rob Herring <robh+dt@kernel.org> wrote on Tue, 14 Jan 2020 11:46:18
> -0600:
>
> > On Mon, Dec 9, 2019 at 4:35 AM Boris Brezillon
> > <boris.brezillon@collabora.com> wrote:
> > >
> > > On Wed, 27 Nov 2019 11:55:22 +0100
> > > Miquel Raynal <miquel.raynal@bootlin.com> wrote:
> > >
> > > > Introduce a generic way to define concatenated MTD devices. This may
> > > > be very useful in the case of ie. stacked SPI-NOR. Partitions to
> > > > concatenate are described in an additional property of the partitions
> > > > subnode:
> > > >
> > > >         flash0 {
> > > >                 partitions {
> > > >                         compatible = "fixed-partitions";
> > > >                         part-concat = <&flash0_part1>, <&flash1_part0>;
> > > >
> > > >                       part0@0 {
> > > >                               label = "part0_0";
> > > >                               reg = <0x0 0x800000>;
> > > >                       };
> > > >
> > > >                       flash0_part1: part1@800000 {
> > > >                               label = "part0_1";
> > > >                               reg = <0x800000 0x800000>;
> > >
> > > So, flash0_part1 and flash0_part2 will be created even though the user
> > > probably doesn't need them?
> >
> > I don't follow?
>
> Well, one will have to create "fake" partitions in order to concatenate
> them with this solution, instead of just concatenating the devices (in
> the case where you want to concatenate the entire devices). But the real
> debate is below, on the representation.

So concatenating multiple devices without partitions defined in DT? To
support that, then we need to link flash nodes rather than partition
nodes.

> > > >                       };
> > > >                 };
> > > >         };
> > > >
> > > >         flash1 {
> > > >                 partitions {
> > > >                         compatible = "fixed-partitions";
> > > >
> > > >                       flash0_part1: part1@0 {
> > > >                               label = "part1_0";
> > > >                               reg = <0x0 0x800000>;
> > > >                       };
> > > >
> > > >                       part0@800000 {
> > > >                               label = "part1_1";
> > > >                               reg = <0x800000 0x800000>;
> > > >                       };
> > > >                 };
> > > >         };
> > >
> > > IMHO this representation is far from intuitive. At first glance it's not
> > > obvious which partitions are linked together and what's the name of the
> > > resulting concatenated part. I definitely prefer the solution where we
> > > have a virtual device describing the concatenation. I also understand
> > > that this goes against the #1 DT rule: "DT only decribes HW blocks, not
> > > how they should be used/configured", but maybe we can find a compromise
> > > here, like moving this description to the /chosen node?
> > >
> > > chosen {
> > >         flash-arrays {
> > >                 /*
> > >                  * my-flash-array is the MTD name if label is
> > >                  * not present.
> > >                  */
> > >                 my-flash-array {
> > >                         /*
> > >                          * We could have
> > >                          * compatible = "flash-array";
> > >                          * but we can also do without it.
> > >                          */
> > >                         label = "foo";
> > >                         flashes = <&flash1 &flash2 ...>;
> > >                         partitions {
> > >                                 /* usual partition description. */
> > >                                 ...
> > >                         };
> > >                 };
> > >         };
> > > };
> > >
> > > Rob, what do you think?
> >
> > I don't think chosen is the right place to put all the partition
> > information. It's not something the bootloader configures.
> >
> > This suffers from the same issue I have with the original proposal. It
> > will not work for existing s/w. There's only 1 logical partition that
>
> I don't get why it would not work? Current hardware will just not have
> the concatenation support, that's all. How is this a problem?

No one has multiple flash devices on any h/w already? If I already
have a working system that can load and boot a kernel off an MTD
partition, but could benefit from concatenating other partitions (i.e.
rootfs), why should I have to modify my bootloader(s)?

> > concatenated. The rest of the partitions shouldn't need any special
> > handling. So we really only need some way to say 'link these 2
> > partitions into 1 logical partition'. Though perhaps one could want to
> > combine any number of physical partitions into logical partitions, but
> > then none of the proposals could support that. Then again, maybe
>
> Yes, the flash-array proposal supports having more than two
> partitions/devices concatenated, it is also already supported by the
> driver (you don't care about this, but I do :) ).

I meant for N devices, you'd only have at most N-1 concatenated
partitions as there are N-1 device boundaries. Whereas you could
define any partition could be combined with any other number of
partitions regardless of where they lie. The types of things you can
do with LVM and disk partitions is what I'm thinking of.

> > that's a userspace problem like with disks.
>
> I see one big issue with this solution: what about bootloaders?

Similar to how it works with disks. Bootloaders (at a minimum)
understand physical partitions. Later stages understand logical
partitions.

> The root cause for such idea is that, in my case, the 2 MTD devices are
> too small to contain the images needed to boot. The perfect solution is
> to merge the two devices virtually in one single device and let U-Boot
> read it like one.

Got any actual h/w that the flash devices are smaller than a
kernel+ramdisk yet had the $ and board space to put multiple devices
down?

What about the stages before u-boot? Assume they all use DT, why
require adding this support before a stage that actually needs it.

> I need to have the same representation both in U-Boot and Linux, hence
> a userspace tool and a kernel command line argument do not work, right?

I never said u-boot can't gain support for this, it's just requiring
it to from the start even if you only wanted to have a concatenated
partition for your rootfs or non-boot partition. Only the kernel would
need to understand concatenating partitions vs. every component that
reads the partitions.

Rob

Patch
diff mbox series

diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 79a8ff542883..3e1e55e7158f 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -276,6 +276,14 @@  config MTD_PARTITIONED_MASTER
 	  the parent of the partition device be the master device, rather than
 	  what lies behind the master.
 
+config MTD_VIRT_CONCAT
+	tristate "Virtual concatenated MTD devices"
+	help
+	  This driver allows creation of a virtual MTD device, which
+	  concatenates multiple physical MTD devices into a single one.
+	  This is useful to create partitions bigger than the underlying
+	  physical chips by allowing cross-chip boundaries.
+
 source "drivers/mtd/chips/Kconfig"
 
 source "drivers/mtd/maps/Kconfig"
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 58fc327a5276..c7ee13368a66 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -27,6 +27,7 @@  obj-$(CONFIG_SSFDC)		+= ssfdc.o
 obj-$(CONFIG_SM_FTL)		+= sm_ftl.o
 obj-$(CONFIG_MTD_OOPS)		+= mtdoops.o
 obj-$(CONFIG_MTD_SWAP)		+= mtdswap.o
+obj-$(CONFIG_MTD_VIRT_CONCAT)	+= mtd_virt_concat.o
 
 nftl-objs		:= nftlcore.o nftlmount.o
 inftl-objs		:= inftlcore.o inftlmount.o
diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c
new file mode 100644
index 000000000000..23c7170ac32f
--- /dev/null
+++ b/drivers/mtd/mtd_virt_concat.c
@@ -0,0 +1,240 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Virtual concat MTD device driver
+ *
+ * Copyright (C) 2018 Bernhard Frauendienst
+ * Author: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/mtd/concat.h>
+#include <linux/mtd/mtd.h>
+#include "mtdcore.h"
+#include <linux/mtd/partitions.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+
+#define CONCAT_PROP "part-concat"
+#define MIN_DEV_PER_CONCAT 2
+
+/**
+ * struct mtd_virt_concat - concatenation container
+ * @vmtd: Virtual mtd_concat device
+ * @count: Number of physical underlaying devices in @devices
+ * @devices: Array of the physical devices used
+ */
+struct mtd_virt_concat {
+	struct mtd_info	*vmtd;
+	unsigned int count;
+	struct mtd_info	**devices;
+};
+
+/**
+ * struct mtd_virt_concat_node - components of a concatenation
+ * @head: List handle
+ * @count: Number of nodes
+ * @nodes: Pointer to the nodes (partitions) to concatenate
+ * @concat: Concatenation container
+ */
+struct mtd_virt_concat_node {
+	struct list_head head;
+	unsigned int count;
+	struct device_node **nodes;
+	struct mtd_virt_concat *concat;
+};
+
+static LIST_HEAD(concat_list);
+
+static void mtd_virt_concat_put_mtd_devices(struct mtd_virt_concat *concat)
+{
+	int i;
+
+	for (i = 0; i < concat->count; i++)
+		put_mtd_device(concat->devices[i]);
+}
+
+static int mtd_virt_concat_create_join(struct mtd_virt_concat_node *item)
+{
+	struct mtd_virt_concat *concat;
+	struct mtd_info *mtd;
+	ssize_t name_sz;
+	char *name;
+	int ret, i;
+
+	concat = kzalloc(sizeof(*concat), GFP_KERNEL);
+	if (!concat)
+		return -ENOMEM;
+
+	concat->devices = kcalloc(item->count,
+				  sizeof(*concat->devices),
+				  GFP_KERNEL);
+	if (!concat->devices) {
+		ret = -ENOMEM;
+		goto free_concat;
+	}
+
+	/* Aggregate the physical devices */
+	for (i = 0; i < item->count; i++) {
+		mtd = get_mtd_device_by_node(item->nodes[i]);
+		if (IS_ERR(mtd)) {
+			ret = PTR_ERR(mtd);
+			goto put_mtd_devices;
+		}
+
+		concat->devices[concat->count++] = mtd;
+	}
+
+	/* Create the virtual device */
+	name_sz = snprintf(NULL, 0, "%s-%s%s-concat",
+			   concat->devices[0]->name,
+			   concat->devices[1]->name,
+			   concat->count > 2 ? "-+" : "");
+	name = kmalloc(name_sz, GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto put_mtd_devices;
+	}
+
+	sprintf(name, "%s-%s%s-concat",
+		concat->devices[0]->name,
+		concat->devices[1]->name,
+		concat->count > 2 ? "-+" : "");
+
+	concat->vmtd = mtd_concat_create(concat->devices, concat->count, name);
+	if (!concat->vmtd) {
+		ret = -ENXIO;
+		goto free_name;
+	}
+
+	/* Arbitrary set the first device as parent */
+	concat->vmtd->dev.parent = &concat->devices[0]->dev;
+
+	/* Register the platform device */
+	ret = mtd_device_register(concat->vmtd, NULL, 0);
+	if (ret)
+		goto destroy_concat;
+
+	item->concat = concat;
+
+	return 0;
+
+destroy_concat:
+	mtd_concat_destroy(concat->vmtd);
+free_name:
+	kfree(name);
+put_mtd_devices:
+	mtd_virt_concat_put_mtd_devices(concat);
+free_concat:
+	kfree(concat);
+
+	return ret;
+}
+
+static void mtd_virt_concat_destroy_joins(void)
+{
+	struct mtd_virt_concat_node *item, *tmp;
+
+	list_for_each_entry_safe(item, tmp, &concat_list, head) {
+		if (item->concat) {
+			mtd_device_unregister(item->concat->vmtd);
+			kfree(item->concat->vmtd->name);
+			mtd_concat_destroy(item->concat->vmtd);
+			mtd_virt_concat_put_mtd_devices(item->concat);
+		}
+	}
+}
+
+static int mtd_virt_concat_create_item(struct device_node *parts,
+				       unsigned int count)
+{
+	struct mtd_virt_concat_node *item;
+	int i;
+
+	item = kzalloc(sizeof(*item), GFP_KERNEL);
+	if (!item)
+		return -ENOMEM;
+
+	item->count = count;
+	item->nodes = kcalloc(count, sizeof(*item->nodes), GFP_KERNEL);
+	if (!item->nodes) {
+		kfree(item);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < count; i++)
+		item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, i);
+
+	list_add_tail(&item->head, &concat_list);
+
+	return 0;
+}
+
+static void mtd_virt_concat_destroy_items(void)
+{
+	struct mtd_virt_concat_node *item, *temp;
+	int i;
+
+	list_for_each_entry_safe(item, temp, &concat_list, head) {
+		for (i = 0; i < item->count; i++)
+			of_node_put(item->nodes[i]);
+
+		kfree(item->nodes);
+		kfree(item);
+	}
+}
+
+static int __init mtd_virt_concat_init(void)
+{
+	struct mtd_virt_concat_node *item;
+	struct device_node *parts = NULL;
+	int ret = 0, count;
+
+	/* List all the concatenations found in DT */
+	do {
+		parts = of_find_node_with_property(parts, CONCAT_PROP);
+		if (!of_device_is_available(parts))
+			continue;
+
+		count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL);
+		if (count < MIN_DEV_PER_CONCAT)
+			continue;
+
+		ret = mtd_virt_concat_create_item(parts, count);
+		if (ret) {
+			of_node_put(parts);
+			goto destroy_items;
+		}
+	} while (parts);
+
+	/* TODO: also parse the cmdline */
+
+	/* Create the concatenations */
+	list_for_each_entry(item, &concat_list, head) {
+		ret = mtd_virt_concat_create_join(item);
+		if (ret)
+			goto destroy_joins;
+	}
+
+	return 0;
+
+destroy_joins:
+	mtd_virt_concat_destroy_joins();
+destroy_items:
+	mtd_virt_concat_destroy_items();
+
+	return ret;
+}
+late_initcall(mtd_virt_concat_init);
+
+static void __exit mtd_virt_concat_exit(void)
+{
+	mtd_virt_concat_destroy_joins();
+	mtd_virt_concat_destroy_items();
+}
+module_exit(mtd_virt_concat_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Bernhard Frauendienst <kernel@nospam.obeliks.de>");
+MODULE_DESCRIPTION("Virtual concat MTD device driver");