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