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 mtd_concat *concat; 35 struct device_node *nodes[] __counted_by(count); 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); 137 } 138 return 0; 139 out: 140 mutex_lock(&master->master.partitions_lock); 141 list_del(&child->part.node); 142 mutex_unlock(&master->master.partitions_lock); 143 kfree(mtd->name); 144 kfree(mtd); 145 146 return ret; 147 } 148 149 /** 150 * mtd_virt_concat_create_item - Create a concat item 151 * @parts: pointer to 'device_node' 152 * @count: number of mtd devices that make up 153 * the concatenated device. 154 * 155 * Return: 0 on success, -error otherwise. 156 */ 157 static int mtd_virt_concat_create_item(struct device_node *parts, 158 unsigned int count) 159 { 160 struct mtd_virt_concat_node *item; 161 struct mtd_concat *concat; 162 int i; 163 164 for (i = 0; i < (count - 1); i++) { 165 if (mtd_is_part_concat(of_parse_phandle(parts, CONCAT_PROP, i))) 166 return 0; 167 } 168 169 item = kzalloc_flex(*item, nodes, count, GFP_KERNEL); 170 if (!item) 171 return -ENOMEM; 172 173 item->count = count; 174 175 /* 176 * The partition in which "part-concat-next" property 177 * is defined is the first device in the list of concat 178 * devices. 179 */ 180 item->nodes[0] = parts; 181 182 for (i = 1; i < count; i++) 183 item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1)); 184 185 concat = kzalloc_flex(*concat, subdev, count, GFP_KERNEL); 186 if (!concat) { 187 kfree(item); 188 return -ENOMEM; 189 } 190 191 item->concat = concat; 192 193 list_add_tail(&item->head, &concat_node_list); 194 195 return 0; 196 } 197 198 void mtd_virt_concat_destroy_items(void) 199 { 200 struct mtd_virt_concat_node *item, *temp; 201 int i; 202 203 list_for_each_entry_safe(item, temp, &concat_node_list, head) { 204 for (i = 0; i < item->count; i++) 205 of_node_put(item->nodes[i]); 206 207 kfree(item); 208 } 209 } 210 211 /** 212 * mtd_virt_concat_add - Add a mtd device to the concat list 213 * @mtd: pointer to 'mtd_info' 214 * 215 * Return: true on success, false otherwise. 216 */ 217 bool mtd_virt_concat_add(struct mtd_info *mtd) 218 { 219 struct mtd_virt_concat_node *item; 220 struct mtd_concat *concat; 221 int idx; 222 223 list_for_each_entry(item, &concat_node_list, head) { 224 concat = item->concat; 225 for (idx = 0; idx < item->count; idx++) { 226 if (item->nodes[idx] == mtd->dev.of_node) { 227 concat->subdev[concat->num_subdev++] = mtd; 228 return true; 229 } 230 } 231 } 232 return false; 233 } 234 235 /** 236 * mtd_virt_concat_node_create - List all the concatenations found in DT 237 * 238 * Return: 0 on success, -error otherwise. 239 */ 240 int mtd_virt_concat_node_create(void) 241 { 242 struct device_node *parts = NULL; 243 int ret = 0, count = 0; 244 245 /* List all the concatenations found in DT */ 246 do { 247 parts = of_find_node_with_property(parts, CONCAT_PROP); 248 if (!of_device_is_available(parts)) 249 continue; 250 251 if (mtd_is_part_concat(parts)) 252 continue; 253 254 count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL); 255 if (count < MIN_DEV_PER_CONCAT) 256 continue; 257 258 /* 259 * The partition in which "part-concat-next" property is defined 260 * is also part of the concat device, so increament count by 1. 261 */ 262 count++; 263 264 ret = mtd_virt_concat_create_item(parts, count); 265 if (ret) { 266 of_node_put(parts); 267 goto destroy_items; 268 } 269 } while (parts); 270 271 return ret; 272 273 destroy_items: 274 mtd_virt_concat_destroy_items(); 275 276 return ret; 277 } 278 279 /** 280 * mtd_virt_concat_create_join - Create and register the concatenated 281 * MTD device. 282 * 283 * Return: 0 on success, -error otherwise. 284 */ 285 int mtd_virt_concat_create_join(void) 286 { 287 struct mtd_virt_concat_node *item; 288 struct mtd_concat *concat; 289 struct mtd_info *mtd; 290 ssize_t name_sz; 291 int ret, idx; 292 char *name; 293 294 list_for_each_entry(item, &concat_node_list, head) { 295 concat = item->concat; 296 /* 297 * Check if item->count != concat->num_subdev, it indicates 298 * that the MTD information for all devices included in the 299 * concatenation are not handy, concat MTD device can't be 300 * created hence switch to next concat device. 301 */ 302 if (item->count != concat->num_subdev) { 303 continue; 304 } else { 305 /* Calculate the legth of the name of the virtual device */ 306 for (idx = 0, name_sz = 0; idx < concat->num_subdev; idx++) 307 name_sz += (strlen(concat->subdev[idx]->name) + 1); 308 name_sz += strlen(CONCAT_POSTFIX); 309 name = kmalloc(name_sz + 1, GFP_KERNEL); 310 if (!name) { 311 mtd_virt_concat_put_mtd_devices(concat); 312 return -ENOMEM; 313 } 314 315 ret = 0; 316 for (idx = 0; idx < concat->num_subdev; idx++) { 317 ret += sprintf((name + ret), "%s-", 318 concat->subdev[idx]->name); 319 } 320 sprintf((name + ret), CONCAT_POSTFIX); 321 322 if (concat->mtd.name) { 323 ret = memcmp(concat->mtd.name, name, name_sz); 324 if (ret == 0) 325 continue; 326 } 327 mtd = mtd_concat_create(concat->subdev, concat->num_subdev, name); 328 if (!mtd) { 329 kfree(name); 330 return -ENXIO; 331 } 332 concat->mtd = *mtd; 333 /* Arbitrary set the first device as parent */ 334 concat->mtd.dev.parent = concat->subdev[0]->dev.parent; 335 concat->mtd.dev = concat->subdev[0]->dev; 336 337 /* Add the mtd device */ 338 ret = add_mtd_device(&concat->mtd); 339 if (ret) 340 goto destroy_concat; 341 } 342 } 343 344 return 0; 345 346 destroy_concat: 347 mtd_concat_destroy(mtd); 348 349 return ret; 350 } 351