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