xref: /linux/drivers/mtd/mtd_virt_concat.c (revision 43db6366fc2de02050e66389f5628d3fdc9af10a)
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 }
364