// SPDX-License-Identifier: GPL-2.0+ /* * Virtual concat MTD device driver * * Copyright (C) 2018 Bernhard Frauendienst * Author: Bernhard Frauendienst */ #include #include #include "mtdcore.h" #include #include #include #include #include #define CONCAT_PROP "part-concat-next" #define CONCAT_POSTFIX "concat" #define MIN_DEV_PER_CONCAT 1 static LIST_HEAD(concat_node_list); /** * struct mtd_virt_concat_node - components of a concatenation * @head: List handle * @count: Number of nodes * @nodes: Pointer to the nodes (partitions) to concatenate * @concat: Concatenation container */ struct mtd_virt_concat_node { struct list_head head; unsigned int count; struct mtd_concat *concat; struct device_node *nodes[] __counted_by(count); }; /** * mtd_is_part_concat - Check if the device is already part * of a concatenated device * @dev: pointer to 'device_node' * * Return: true if the device is already part of a concatenation, * false otherwise. */ static bool mtd_is_part_concat(struct device_node *dev) { struct mtd_virt_concat_node *item; int idx; list_for_each_entry(item, &concat_node_list, head) { for (idx = 0; idx < item->count; idx++) { if (item->nodes[idx] == dev) return true; } } return false; } static void mtd_virt_concat_put_mtd_devices(struct mtd_concat *concat) { int i; for (i = 0; i < concat->num_subdev; i++) put_mtd_device(concat->subdev[i]); } void mtd_virt_concat_destroy_joins(void) { struct mtd_virt_concat_node *item, *tmp; struct mtd_info *mtd; list_for_each_entry_safe(item, tmp, &concat_node_list, head) { mtd = &item->concat->mtd; if (item->concat) { mtd_device_unregister(mtd); kfree(mtd->name); mtd_concat_destroy(mtd); mtd_virt_concat_put_mtd_devices(item->concat); } } } /** * mtd_virt_concat_destroy - Destroy the concat that includes the mtd object * @mtd: pointer to 'mtd_info' * * Return: 0 on success, -error otherwise. */ int mtd_virt_concat_destroy(struct mtd_info *mtd) { struct mtd_info *child, *master = mtd_get_master(mtd); struct mtd_virt_concat_node *item, *tmp; struct mtd_concat *concat; int idx, ret = 0; bool is_mtd_found; list_for_each_entry_safe(item, tmp, &concat_node_list, head) { is_mtd_found = false; /* Find the concat item that hold the mtd device */ for (idx = 0; idx < item->count; idx++) { if (item->nodes[idx] == mtd->dev.of_node) { is_mtd_found = true; break; } } if (!is_mtd_found) continue; concat = item->concat; /* * Since this concatenated device is being removed, retrieve * all MTD devices that are part of it and register them * individually. */ for (idx = 0; idx < concat->num_subdev; idx++) { child = concat->subdev[idx]; if (child->dev.of_node != mtd->dev.of_node) { ret = add_mtd_device(child); if (ret) goto out; } } /* Destroy the concat */ if (concat->mtd.name) { del_mtd_device(&concat->mtd); kfree(concat->mtd.name); mtd_concat_destroy(&concat->mtd); mtd_virt_concat_put_mtd_devices(item->concat); } for (idx = 0; idx < item->count; idx++) of_node_put(item->nodes[idx]); kfree(item); } return 0; out: mutex_lock(&master->master.partitions_lock); list_del(&child->part.node); mutex_unlock(&master->master.partitions_lock); kfree(mtd->name); kfree(mtd); return ret; } /** * mtd_virt_concat_create_item - Create a concat item * @parts: pointer to 'device_node' * @count: number of mtd devices that make up * the concatenated device. * * Return: 0 on success, -error otherwise. */ static int mtd_virt_concat_create_item(struct device_node *parts, unsigned int count) { struct mtd_virt_concat_node *item; struct mtd_concat *concat; int i; for (i = 0; i < (count - 1); i++) { if (mtd_is_part_concat(of_parse_phandle(parts, CONCAT_PROP, i))) return 0; } item = kzalloc_flex(*item, nodes, count, GFP_KERNEL); if (!item) return -ENOMEM; item->count = count; /* * The partition in which "part-concat-next" property * is defined is the first device in the list of concat * devices. */ item->nodes[0] = parts; for (i = 1; i < count; i++) item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1)); concat = kzalloc_flex(*concat, subdev, count, GFP_KERNEL); if (!concat) { kfree(item); return -ENOMEM; } item->concat = concat; list_add_tail(&item->head, &concat_node_list); return 0; } void mtd_virt_concat_destroy_items(void) { struct mtd_virt_concat_node *item, *temp; int i; list_for_each_entry_safe(item, temp, &concat_node_list, head) { for (i = 0; i < item->count; i++) of_node_put(item->nodes[i]); kfree(item); } } /** * mtd_virt_concat_add - Add a mtd device to the concat list * @mtd: pointer to 'mtd_info' * * Return: true on success, false otherwise. */ bool mtd_virt_concat_add(struct mtd_info *mtd) { struct mtd_virt_concat_node *item; struct mtd_concat *concat; int idx; list_for_each_entry(item, &concat_node_list, head) { concat = item->concat; for (idx = 0; idx < item->count; idx++) { if (item->nodes[idx] == mtd->dev.of_node) { concat->subdev[concat->num_subdev++] = mtd; return true; } } } return false; } /** * mtd_virt_concat_node_create - List all the concatenations found in DT * * Return: 0 on success, -error otherwise. */ int mtd_virt_concat_node_create(void) { struct device_node *parts = NULL; int ret = 0, count = 0; /* List all the concatenations found in DT */ do { parts = of_find_node_with_property(parts, CONCAT_PROP); if (!of_device_is_available(parts)) continue; if (mtd_is_part_concat(parts)) continue; count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL); if (count < MIN_DEV_PER_CONCAT) continue; /* * The partition in which "part-concat-next" property is defined * is also part of the concat device, so increament count by 1. */ count++; ret = mtd_virt_concat_create_item(parts, count); if (ret) { of_node_put(parts); goto destroy_items; } } while (parts); return ret; destroy_items: mtd_virt_concat_destroy_items(); return ret; } /** * mtd_virt_concat_create_join - Create and register the concatenated * MTD device. * * Return: 0 on success, -error otherwise. */ int mtd_virt_concat_create_join(void) { struct mtd_virt_concat_node *item; struct mtd_concat *concat; struct mtd_info *mtd; ssize_t name_sz; int ret, idx; char *name; list_for_each_entry(item, &concat_node_list, head) { concat = item->concat; /* * Check if item->count != concat->num_subdev, it indicates * that the MTD information for all devices included in the * concatenation are not handy, concat MTD device can't be * created hence switch to next concat device. */ if (item->count != concat->num_subdev) { continue; } else { /* Calculate the legth of the name of the virtual device */ for (idx = 0, name_sz = 0; idx < concat->num_subdev; idx++) name_sz += (strlen(concat->subdev[idx]->name) + 1); name_sz += strlen(CONCAT_POSTFIX); name = kmalloc(name_sz + 1, GFP_KERNEL); if (!name) { mtd_virt_concat_put_mtd_devices(concat); return -ENOMEM; } ret = 0; for (idx = 0; idx < concat->num_subdev; idx++) { ret += sprintf((name + ret), "%s-", concat->subdev[idx]->name); } sprintf((name + ret), CONCAT_POSTFIX); if (concat->mtd.name) { ret = memcmp(concat->mtd.name, name, name_sz); if (ret == 0) continue; } mtd = mtd_concat_create(concat->subdev, concat->num_subdev, name); if (!mtd) { kfree(name); return -ENXIO; } concat->mtd = *mtd; /* Arbitrary set the first device as parent */ concat->mtd.dev.parent = concat->subdev[0]->dev.parent; concat->mtd.dev = concat->subdev[0]->dev; /* Add the mtd device */ ret = add_mtd_device(&concat->mtd); if (ret) goto destroy_concat; } } return 0; destroy_concat: mtd_concat_destroy(mtd); return ret; }