Skip to content

Commit 43db636

Browse files
Amit Kumar Mahapatramiquelraynal
authored andcommitted
mtd: Add driver for concatenating devices
Introducing CONFIG_MTD_VIRT_CONCAT to separate the legacy flow from the new approach, where only the concatenated partition is registered as an MTD device, while the individual partitions that form it are not registered independently, as they are typically not required by the user. CONFIG_MTD_VIRT_CONCAT is a boolean configuration option that depends on CONFIG_MTD_PARTITIONED_MASTER. When enabled, it allows flash nodes to be exposed as individual MTD devices along with the other partitions. The solution focuses on fixed-partitions description only as it depends on device boundaries. It supports multiple sets of concatenated devices, each comprising two or more partitions. flash@0 { reg = <0>; partitions { compatible = "fixed-partitions"; part0@0 { part-concat-next = <&flash0_part1>; label = "part0_0"; reg = <0x0 0x800000>; }; flash0_part1: part1@800000 { label = "part0_1"; reg = <800000 0x800000>; }; part2@1000000 { part-concat-next = <&flash1_part0>; label = "part0_2"; reg = <0x800000 0x800000>; }; }; }; flash@1 { reg = <1>; partitions { compatible = "fixed-partitions"; flash1_part0: part1@0 { label = "part1_0"; reg = <0x0 0x800000>; }; part1@800000 { label = "part1_1"; reg = <0x800000 0x800000>; }; }; }; The partitions that gets created are flash@0 part0_0-part0_1-concat flash@1 part1_1 part0_2-part1_0-concat Suggested-by: Bernhard Frauendienst <kernel@nospam.obeliks.de> Suggested-by: Miquel Raynal <miquel.raynal@bootlin.com> Signed-off-by: Amit Kumar Mahapatra <amit.kumar-mahapatra@amd.com> Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com> Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
1 parent 59509da commit 43db636

6 files changed

Lines changed: 450 additions & 1 deletion

File tree

drivers/mtd/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,15 @@ config MTD_PARTITIONED_MASTER
206206
the parent of the partition device be the master device, rather than
207207
what lies behind the master.
208208

209+
config MTD_VIRT_CONCAT
210+
bool "Virtual concatenated MTD devices"
211+
depends on MTD_PARTITIONED_MASTER
212+
help
213+
The driver enables the creation of virtual MTD device by
214+
concatenating multiple physical MTD devices into a single
215+
entity. This allows for the creation of partitions larger than
216+
the individual physical chips, extending across chip boundaries.
217+
209218
source "drivers/mtd/chips/Kconfig"
210219

211220
source "drivers/mtd/maps/Kconfig"

drivers/mtd/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# Core functionality.
77
obj-$(CONFIG_MTD) += mtd.o
88
mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o
9+
mtd-$(CONFIG_MTD_VIRT_CONCAT) += mtd_virt_concat.o
910

1011
obj-y += parsers/
1112

drivers/mtd/mtd_virt_concat.c

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
// SPDX-License-Identifier: GPL-2.0+
2+
/*
3+
* Virtual concat MTD device driver
4+
*
5+
* Copyright (C) 2018 Bernhard Frauendienst
6+
* Author: Bernhard Frauendienst <kernel@nospam.obeliks.de>
7+
*/
8+
9+
#include <linux/device.h>
10+
#include <linux/mtd/mtd.h>
11+
#include "mtdcore.h"
12+
#include <linux/mtd/partitions.h>
13+
#include <linux/of.h>
14+
#include <linux/of_platform.h>
15+
#include <linux/slab.h>
16+
#include <linux/mtd/concat.h>
17+
18+
#define CONCAT_PROP "part-concat-next"
19+
#define CONCAT_POSTFIX "concat"
20+
#define MIN_DEV_PER_CONCAT 1
21+
22+
static LIST_HEAD(concat_node_list);
23+
24+
/**
25+
* struct mtd_virt_concat_node - components of a concatenation
26+
* @head: List handle
27+
* @count: Number of nodes
28+
* @nodes: Pointer to the nodes (partitions) to concatenate
29+
* @concat: Concatenation container
30+
*/
31+
struct mtd_virt_concat_node {
32+
struct list_head head;
33+
unsigned int count;
34+
struct device_node **nodes;
35+
struct mtd_concat *concat;
36+
};
37+
38+
/**
39+
* mtd_is_part_concat - Check if the device is already part
40+
* of a concatenated device
41+
* @dev: pointer to 'device_node'
42+
*
43+
* Return: true if the device is already part of a concatenation,
44+
* false otherwise.
45+
*/
46+
static bool mtd_is_part_concat(struct device_node *dev)
47+
{
48+
struct mtd_virt_concat_node *item;
49+
int idx;
50+
51+
list_for_each_entry(item, &concat_node_list, head) {
52+
for (idx = 0; idx < item->count; idx++) {
53+
if (item->nodes[idx] == dev)
54+
return true;
55+
}
56+
}
57+
return false;
58+
}
59+
60+
static void mtd_virt_concat_put_mtd_devices(struct mtd_concat *concat)
61+
{
62+
int i;
63+
64+
for (i = 0; i < concat->num_subdev; i++)
65+
put_mtd_device(concat->subdev[i]);
66+
}
67+
68+
void mtd_virt_concat_destroy_joins(void)
69+
{
70+
struct mtd_virt_concat_node *item, *tmp;
71+
struct mtd_info *mtd;
72+
73+
list_for_each_entry_safe(item, tmp, &concat_node_list, head) {
74+
mtd = &item->concat->mtd;
75+
if (item->concat) {
76+
mtd_device_unregister(mtd);
77+
kfree(mtd->name);
78+
mtd_concat_destroy(mtd);
79+
mtd_virt_concat_put_mtd_devices(item->concat);
80+
}
81+
}
82+
}
83+
84+
/**
85+
* mtd_virt_concat_destroy - Destroy the concat that includes the mtd object
86+
* @mtd: pointer to 'mtd_info'
87+
*
88+
* Return: 0 on success, -error otherwise.
89+
*/
90+
int mtd_virt_concat_destroy(struct mtd_info *mtd)
91+
{
92+
struct mtd_info *child, *master = mtd_get_master(mtd);
93+
struct mtd_virt_concat_node *item, *tmp;
94+
struct mtd_concat *concat;
95+
int idx, ret = 0;
96+
bool is_mtd_found;
97+
98+
list_for_each_entry_safe(item, tmp, &concat_node_list, head) {
99+
is_mtd_found = false;
100+
101+
/* Find the concat item that hold the mtd device */
102+
for (idx = 0; idx < item->count; idx++) {
103+
if (item->nodes[idx] == mtd->dev.of_node) {
104+
is_mtd_found = true;
105+
break;
106+
}
107+
}
108+
if (!is_mtd_found)
109+
continue;
110+
concat = item->concat;
111+
112+
/*
113+
* Since this concatenated device is being removed, retrieve
114+
* all MTD devices that are part of it and register them
115+
* individually.
116+
*/
117+
for (idx = 0; idx < concat->num_subdev; idx++) {
118+
child = concat->subdev[idx];
119+
if (child->dev.of_node != mtd->dev.of_node) {
120+
ret = add_mtd_device(child);
121+
if (ret)
122+
goto out;
123+
}
124+
}
125+
/* Destroy the concat */
126+
if (concat->mtd.name) {
127+
del_mtd_device(&concat->mtd);
128+
kfree(concat->mtd.name);
129+
mtd_concat_destroy(&concat->mtd);
130+
mtd_virt_concat_put_mtd_devices(item->concat);
131+
}
132+
133+
for (idx = 0; idx < item->count; idx++)
134+
of_node_put(item->nodes[idx]);
135+
136+
kfree(item->nodes);
137+
kfree(item);
138+
}
139+
return 0;
140+
out:
141+
mutex_lock(&master->master.partitions_lock);
142+
list_del(&child->part.node);
143+
mutex_unlock(&master->master.partitions_lock);
144+
kfree(mtd->name);
145+
kfree(mtd);
146+
147+
return ret;
148+
}
149+
150+
/**
151+
* mtd_virt_concat_create_item - Create a concat item
152+
* @parts: pointer to 'device_node'
153+
* @count: number of mtd devices that make up
154+
* the concatenated device.
155+
*
156+
* Return: 0 on success, -error otherwise.
157+
*/
158+
static int mtd_virt_concat_create_item(struct device_node *parts,
159+
unsigned int count)
160+
{
161+
struct mtd_virt_concat_node *item;
162+
struct mtd_concat *concat;
163+
int i;
164+
165+
for (i = 0; i < (count - 1); i++) {
166+
if (mtd_is_part_concat(of_parse_phandle(parts, CONCAT_PROP, i)))
167+
return 0;
168+
}
169+
170+
item = kzalloc(sizeof(*item), GFP_KERNEL);
171+
if (!item)
172+
return -ENOMEM;
173+
174+
item->count = count;
175+
item->nodes = kcalloc(count, sizeof(*item->nodes), GFP_KERNEL);
176+
if (!item->nodes) {
177+
kfree(item);
178+
return -ENOMEM;
179+
}
180+
181+
/*
182+
* The partition in which "part-concat-next" property
183+
* is defined is the first device in the list of concat
184+
* devices.
185+
*/
186+
item->nodes[0] = parts;
187+
188+
for (i = 1; i < count; i++)
189+
item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1));
190+
191+
concat = kzalloc(sizeof(*concat), GFP_KERNEL);
192+
if (!concat) {
193+
kfree(item);
194+
return -ENOMEM;
195+
}
196+
197+
concat->subdev = kcalloc(count, sizeof(*concat->subdev), GFP_KERNEL);
198+
if (!concat->subdev) {
199+
kfree(item);
200+
kfree(concat);
201+
return -ENOMEM;
202+
}
203+
item->concat = concat;
204+
205+
list_add_tail(&item->head, &concat_node_list);
206+
207+
return 0;
208+
}
209+
210+
void mtd_virt_concat_destroy_items(void)
211+
{
212+
struct mtd_virt_concat_node *item, *temp;
213+
int i;
214+
215+
list_for_each_entry_safe(item, temp, &concat_node_list, head) {
216+
for (i = 0; i < item->count; i++)
217+
of_node_put(item->nodes[i]);
218+
219+
kfree(item->nodes);
220+
kfree(item);
221+
}
222+
}
223+
224+
/**
225+
* mtd_virt_concat_create_add - Add a mtd device to the concat list
226+
* @mtd: pointer to 'mtd_info'
227+
*
228+
* Return: true on success, false otherwise.
229+
*/
230+
bool mtd_virt_concat_add(struct mtd_info *mtd)
231+
{
232+
struct mtd_virt_concat_node *item;
233+
struct mtd_concat *concat;
234+
int idx;
235+
236+
list_for_each_entry(item, &concat_node_list, head) {
237+
concat = item->concat;
238+
for (idx = 0; idx < item->count; idx++) {
239+
if (item->nodes[idx] == mtd->dev.of_node) {
240+
concat->subdev[concat->num_subdev++] = mtd;
241+
return true;
242+
}
243+
}
244+
}
245+
return false;
246+
}
247+
248+
/**
249+
* mtd_virt_concat_node_create - List all the concatenations found in DT
250+
*
251+
* Return: 0 on success, -error otherwise.
252+
*/
253+
int mtd_virt_concat_node_create(void)
254+
{
255+
struct device_node *parts = NULL;
256+
int ret = 0, count = 0;
257+
258+
/* List all the concatenations found in DT */
259+
do {
260+
parts = of_find_node_with_property(parts, CONCAT_PROP);
261+
if (!of_device_is_available(parts))
262+
continue;
263+
264+
if (mtd_is_part_concat(parts))
265+
continue;
266+
267+
count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL);
268+
if (count < MIN_DEV_PER_CONCAT)
269+
continue;
270+
271+
/*
272+
* The partition in which "part-concat-next" property is defined
273+
* is also part of the concat device, so increament count by 1.
274+
*/
275+
count++;
276+
277+
ret = mtd_virt_concat_create_item(parts, count);
278+
if (ret) {
279+
of_node_put(parts);
280+
goto destroy_items;
281+
}
282+
} while (parts);
283+
284+
return ret;
285+
286+
destroy_items:
287+
mtd_virt_concat_destroy_items();
288+
289+
return ret;
290+
}
291+
292+
/**
293+
* mtd_virt_concat_create_join - Create and register the concatenated
294+
* MTD device.
295+
*
296+
* Return: 0 on success, -error otherwise.
297+
*/
298+
int mtd_virt_concat_create_join(void)
299+
{
300+
struct mtd_virt_concat_node *item;
301+
struct mtd_concat *concat;
302+
struct mtd_info *mtd;
303+
ssize_t name_sz;
304+
int ret, idx;
305+
char *name;
306+
307+
list_for_each_entry(item, &concat_node_list, head) {
308+
concat = item->concat;
309+
/*
310+
* Check if item->count != concat->num_subdev, it indicates
311+
* that the MTD information for all devices included in the
312+
* concatenation are not handy, concat MTD device can't be
313+
* created hence switch to next concat device.
314+
*/
315+
if (item->count != concat->num_subdev) {
316+
continue;
317+
} else {
318+
/* Calculate the legth of the name of the virtual device */
319+
for (idx = 0, name_sz = 0; idx < concat->num_subdev; idx++)
320+
name_sz += (strlen(concat->subdev[idx]->name) + 1);
321+
name_sz += strlen(CONCAT_POSTFIX);
322+
name = kmalloc(name_sz + 1, GFP_KERNEL);
323+
if (!name) {
324+
mtd_virt_concat_put_mtd_devices(concat);
325+
return -ENOMEM;
326+
}
327+
328+
ret = 0;
329+
for (idx = 0; idx < concat->num_subdev; idx++) {
330+
ret += sprintf((name + ret), "%s-",
331+
concat->subdev[idx]->name);
332+
}
333+
sprintf((name + ret), CONCAT_POSTFIX);
334+
335+
if (concat->mtd.name) {
336+
ret = memcmp(concat->mtd.name, name, name_sz);
337+
if (ret == 0)
338+
continue;
339+
}
340+
mtd = mtd_concat_create(concat->subdev, concat->num_subdev, name);
341+
if (!mtd) {
342+
kfree(name);
343+
return -ENXIO;
344+
}
345+
concat->mtd = *mtd;
346+
/* Arbitrary set the first device as parent */
347+
concat->mtd.dev.parent = concat->subdev[0]->dev.parent;
348+
concat->mtd.dev = concat->subdev[0]->dev;
349+
350+
/* Add the mtd device */
351+
ret = add_mtd_device(&concat->mtd);
352+
if (ret)
353+
goto destroy_concat;
354+
}
355+
}
356+
357+
return 0;
358+
359+
destroy_concat:
360+
mtd_concat_destroy(mtd);
361+
362+
return ret;
363+
}

0 commit comments

Comments
 (0)