mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-25 02:09:42 +00:00
![Artem B. Bityutskiy](/assets/img/avatar_default.png)
UBI (Latin: "where?") manages multiple logical volumes on a single flash device, specifically supporting NAND flash devices. UBI provides a flexible partitioning concept which still allows for wear-levelling across the whole flash device. In a sense, UBI may be compared to the Logical Volume Manager (LVM). Whereas LVM maps logical sector numbers to physical HDD sector numbers, UBI maps logical eraseblocks to physical eraseblocks. More information may be found at http://www.linux-mtd.infradead.org/doc/ubi.html Partitioning/Re-partitioning An UBI volume occupies a certain number of erase blocks. This is limited by a configured maximum volume size, which could also be viewed as the partition size. Each individual UBI volume's size can be changed independently of the other UBI volumes, provided that the sum of all volume sizes doesn't exceed a certain limit. UBI supports dynamic volumes and static volumes. Static volumes are read-only and their contents are protected by CRC check sums. Bad eraseblocks handling UBI transparently handles bad eraseblocks. When a physical eraseblock becomes bad, it is substituted by a good physical eraseblock, and the user does not even notice this. Scrubbing On a NAND flash bit flips can occur on any write operation, sometimes also on read. If bit flips persist on the device, at first they can still be corrected by ECC, but once they accumulate, correction will become impossible. Thus it is best to actively scrub the affected eraseblock, by first copying it to a free eraseblock and then erasing the original. The UBI layer performs this type of scrubbing under the covers, transparently to the UBI volume users. Erase Counts UBI maintains an erase count header per eraseblock. This frees higher-level layers (like file systems) from doing this and allows for centralized erase count management instead. The erase counts are used by the wear-levelling algorithm in the UBI layer. The algorithm itself is exchangeable. Booting from NAND For booting directly from NAND flash the hardware must at least be capable of fetching and executing a small portion of the NAND flash. Some NAND flash controllers have this kind of support. They usually limit the window to a few kilobytes in erase block 0. This "initial program loader" (IPL) must then contain sufficient logic to load and execute the next boot phase. Due to bad eraseblocks, which may be randomly scattered over the flash device, it is problematic to store the "secondary program loader" (SPL) statically. Also, due to bit-flips it may become corrupted over time. UBI allows to solve this problem gracefully by storing the SPL in a small static UBI volume. UBI volumes vs. static partitions UBI volumes are still very similar to static MTD partitions: * both consist of eraseblocks (logical eraseblocks in case of UBI volumes, and physical eraseblocks in case of static partitions; * both support three basic operations - read, write, erase. But UBI volumes have the following advantages over traditional static MTD partitions: * there are no eraseblock wear-leveling constraints in case of UBI volumes, so the user should not care about this; * there are no bit-flips and bad eraseblocks in case of UBI volumes. So, UBI volumes may be considered as flash devices with relaxed restrictions. Where can it be found? Documentation, kernel code and applications can be found in the MTD gits. What are the applications for? The applications help to create binary flash images for two purposes: pfi files (partial flash images) for in-system update of UBI volumes, and plain binary images, with or without OOB data in case of NAND, for a manufacturing step. Furthermore some tools are/and will be created that allow flash content analysis after a system has crashed.. Who did UBI? The original ideas, where UBI is based on, were developed by Andreas Arnez, Frank Haverkamp and Thomas Gleixner. Josh W. Boyer and some others were involved too. The implementation of the kernel layer was done by Artem B. Bityutskiy. The user-space applications and tools were written by Oliver Lohmann with contributions from Frank Haverkamp, Andreas Arnez, and Artem. Joern Engel contributed a patch which modifies JFFS2 so that it can be run on a UBI volume. Thomas Gleixner did modifications to the NAND layer. Alexander Schmidt made some testing work as well as core functionality improvements. Signed-off-by: Artem B. Bityutskiy <dedekind@linutronix.de> Signed-off-by: Frank Haverkamp <haver@vnet.ibm.com>
809 lines
21 KiB
C
809 lines
21 KiB
C
/*
|
|
* Copyright (c) International Business Machines Corp., 2006
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
|
* the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* Author: Artem Bityutskiy (Битюцкий Артём)
|
|
*/
|
|
|
|
/*
|
|
* This file contains implementation of volume creation, deletion, updating and
|
|
* resizing.
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <asm/div64.h>
|
|
#include "ubi.h"
|
|
|
|
#ifdef CONFIG_MTD_UBI_DEBUG_PARANOID
|
|
static void paranoid_check_volumes(struct ubi_device *ubi);
|
|
#else
|
|
#define paranoid_check_volumes(ubi)
|
|
#endif
|
|
|
|
static ssize_t vol_attribute_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf);
|
|
|
|
/* Device attributes corresponding to files in '/<sysfs>/class/ubi/ubiX_Y' */
|
|
static struct device_attribute vol_reserved_ebs =
|
|
__ATTR(reserved_ebs, S_IRUGO, vol_attribute_show, NULL);
|
|
static struct device_attribute vol_type =
|
|
__ATTR(type, S_IRUGO, vol_attribute_show, NULL);
|
|
static struct device_attribute vol_name =
|
|
__ATTR(name, S_IRUGO, vol_attribute_show, NULL);
|
|
static struct device_attribute vol_corrupted =
|
|
__ATTR(corrupted, S_IRUGO, vol_attribute_show, NULL);
|
|
static struct device_attribute vol_alignment =
|
|
__ATTR(alignment, S_IRUGO, vol_attribute_show, NULL);
|
|
static struct device_attribute vol_usable_eb_size =
|
|
__ATTR(usable_eb_size, S_IRUGO, vol_attribute_show, NULL);
|
|
static struct device_attribute vol_data_bytes =
|
|
__ATTR(data_bytes, S_IRUGO, vol_attribute_show, NULL);
|
|
static struct device_attribute vol_upd_marker =
|
|
__ATTR(upd_marker, S_IRUGO, vol_attribute_show, NULL);
|
|
|
|
/*
|
|
* "Show" method for files in '/<sysfs>/class/ubi/ubiX_Y/'.
|
|
*
|
|
* Consider a situation:
|
|
* A. process 1 opens a sysfs file related to volume Y, say
|
|
* /<sysfs>/class/ubi/ubiX_Y/reserved_ebs;
|
|
* B. process 2 removes volume Y;
|
|
* C. process 1 starts reading the /<sysfs>/class/ubi/ubiX_Y/reserved_ebs file;
|
|
*
|
|
* What we want to do in a situation like that is to return error when the file
|
|
* is read. This is done by means of the 'removed' flag and the 'vol_lock' of
|
|
* the UBI volume description object.
|
|
*/
|
|
static ssize_t vol_attribute_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int ret;
|
|
struct ubi_volume *vol = container_of(dev, struct ubi_volume, dev);
|
|
|
|
spin_lock(&vol->ubi->volumes_lock);
|
|
if (vol->removed) {
|
|
spin_unlock(&vol->ubi->volumes_lock);
|
|
return -ENODEV;
|
|
}
|
|
if (attr == &vol_reserved_ebs)
|
|
ret = sprintf(buf, "%d\n", vol->reserved_pebs);
|
|
else if (attr == &vol_type) {
|
|
const char *tp;
|
|
tp = vol->vol_type == UBI_DYNAMIC_VOLUME ? "dynamic" : "static";
|
|
ret = sprintf(buf, "%s\n", tp);
|
|
} else if (attr == &vol_name)
|
|
ret = sprintf(buf, "%s\n", vol->name);
|
|
else if (attr == &vol_corrupted)
|
|
ret = sprintf(buf, "%d\n", vol->corrupted);
|
|
else if (attr == &vol_alignment)
|
|
ret = sprintf(buf, "%d\n", vol->alignment);
|
|
else if (attr == &vol_usable_eb_size) {
|
|
ret = sprintf(buf, "%d\n", vol->usable_leb_size);
|
|
} else if (attr == &vol_data_bytes)
|
|
ret = sprintf(buf, "%lld\n", vol->used_bytes);
|
|
else if (attr == &vol_upd_marker)
|
|
ret = sprintf(buf, "%d\n", vol->upd_marker);
|
|
else
|
|
BUG();
|
|
spin_unlock(&vol->ubi->volumes_lock);
|
|
return ret;
|
|
}
|
|
|
|
/* Release method for volume devices */
|
|
static void vol_release(struct device *dev)
|
|
{
|
|
struct ubi_volume *vol = container_of(dev, struct ubi_volume, dev);
|
|
ubi_assert(vol->removed);
|
|
kfree(vol);
|
|
}
|
|
|
|
/**
|
|
* volume_sysfs_init - initialize sysfs for new volume.
|
|
* @ubi: UBI device description object
|
|
* @vol: volume description object
|
|
*
|
|
* This function returns zero in case of success and a negative error code in
|
|
* case of failure.
|
|
*
|
|
* Note, this function does not free allocated resources in case of failure -
|
|
* the caller does it. This is because this would cause release() here and the
|
|
* caller would oops.
|
|
*/
|
|
static int volume_sysfs_init(struct ubi_device *ubi, struct ubi_volume *vol)
|
|
{
|
|
int err;
|
|
|
|
err = device_create_file(&vol->dev, &vol_reserved_ebs);
|
|
if (err)
|
|
return err;
|
|
err = device_create_file(&vol->dev, &vol_type);
|
|
if (err)
|
|
return err;
|
|
err = device_create_file(&vol->dev, &vol_name);
|
|
if (err)
|
|
return err;
|
|
err = device_create_file(&vol->dev, &vol_corrupted);
|
|
if (err)
|
|
return err;
|
|
err = device_create_file(&vol->dev, &vol_alignment);
|
|
if (err)
|
|
return err;
|
|
err = device_create_file(&vol->dev, &vol_usable_eb_size);
|
|
if (err)
|
|
return err;
|
|
err = device_create_file(&vol->dev, &vol_data_bytes);
|
|
if (err)
|
|
return err;
|
|
err = device_create_file(&vol->dev, &vol_upd_marker);
|
|
if (err)
|
|
return err;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* volume_sysfs_close - close sysfs for a volume.
|
|
* @vol: volume description object
|
|
*/
|
|
static void volume_sysfs_close(struct ubi_volume *vol)
|
|
{
|
|
device_remove_file(&vol->dev, &vol_upd_marker);
|
|
device_remove_file(&vol->dev, &vol_data_bytes);
|
|
device_remove_file(&vol->dev, &vol_usable_eb_size);
|
|
device_remove_file(&vol->dev, &vol_alignment);
|
|
device_remove_file(&vol->dev, &vol_corrupted);
|
|
device_remove_file(&vol->dev, &vol_name);
|
|
device_remove_file(&vol->dev, &vol_type);
|
|
device_remove_file(&vol->dev, &vol_reserved_ebs);
|
|
device_unregister(&vol->dev);
|
|
}
|
|
|
|
/**
|
|
* ubi_create_volume - create volume.
|
|
* @ubi: UBI device description object
|
|
* @req: volume creation request
|
|
*
|
|
* This function creates volume described by @req. If @req->vol_id id
|
|
* %UBI_VOL_NUM_AUTO, this function automatically assigne ID to the new volume
|
|
* and saves it in @req->vol_id. Returns zero in case of success and a negative
|
|
* error code in case of failure.
|
|
*/
|
|
int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
|
|
{
|
|
int i, err, vol_id = req->vol_id;
|
|
struct ubi_volume *vol;
|
|
struct ubi_vtbl_record vtbl_rec;
|
|
uint64_t bytes;
|
|
|
|
if (ubi->ro_mode)
|
|
return -EROFS;
|
|
|
|
vol = kzalloc(sizeof(struct ubi_volume), GFP_KERNEL);
|
|
if (!vol)
|
|
return -ENOMEM;
|
|
|
|
spin_lock(&ubi->volumes_lock);
|
|
|
|
if (vol_id == UBI_VOL_NUM_AUTO) {
|
|
/* Find unused volume ID */
|
|
dbg_msg("search for vacant volume ID");
|
|
for (i = 0; i < ubi->vtbl_slots; i++)
|
|
if (!ubi->volumes[i]) {
|
|
vol_id = i;
|
|
break;
|
|
}
|
|
|
|
if (vol_id == UBI_VOL_NUM_AUTO) {
|
|
dbg_err("out of volume IDs");
|
|
err = -ENFILE;
|
|
goto out_unlock;
|
|
}
|
|
req->vol_id = vol_id;
|
|
}
|
|
|
|
dbg_msg("volume ID %d, %llu bytes, type %d, name %s",
|
|
vol_id, (unsigned long long)req->bytes,
|
|
(int)req->vol_type, req->name);
|
|
|
|
/* Ensure that this volume does not exist */
|
|
err = -EEXIST;
|
|
if (ubi->volumes[vol_id]) {
|
|
dbg_err("volume %d already exists", vol_id);
|
|
goto out_unlock;
|
|
}
|
|
|
|
/* Ensure that the name is unique */
|
|
for (i = 0; i < ubi->vtbl_slots; i++)
|
|
if (ubi->volumes[i] &&
|
|
ubi->volumes[i]->name_len == req->name_len &&
|
|
strcmp(ubi->volumes[i]->name, req->name) == 0) {
|
|
dbg_err("volume \"%s\" exists (ID %d)", req->name, i);
|
|
goto out_unlock;
|
|
}
|
|
|
|
/* Calculate how many eraseblocks are requested */
|
|
vol->usable_leb_size = ubi->leb_size - ubi->leb_size % req->alignment;
|
|
bytes = req->bytes;
|
|
if (do_div(bytes, vol->usable_leb_size))
|
|
vol->reserved_pebs = 1;
|
|
vol->reserved_pebs += bytes;
|
|
|
|
/* Reserve physical eraseblocks */
|
|
if (vol->reserved_pebs > ubi->avail_pebs) {
|
|
dbg_err("not enough PEBs, only %d available", ubi->avail_pebs);
|
|
spin_unlock(&ubi->volumes_lock);
|
|
err = -ENOSPC;
|
|
goto out_unlock;
|
|
}
|
|
ubi->avail_pebs -= vol->reserved_pebs;
|
|
ubi->rsvd_pebs += vol->reserved_pebs;
|
|
|
|
vol->vol_id = vol_id;
|
|
vol->alignment = req->alignment;
|
|
vol->data_pad = ubi->leb_size % vol->alignment;
|
|
vol->vol_type = req->vol_type;
|
|
vol->name_len = req->name_len;
|
|
memcpy(vol->name, req->name, vol->name_len + 1);
|
|
vol->exclusive = 1;
|
|
vol->ubi = ubi;
|
|
ubi->volumes[vol_id] = vol;
|
|
spin_unlock(&ubi->volumes_lock);
|
|
|
|
/*
|
|
* Finish all pending erases because there may be some LEBs belonging
|
|
* to the same volume ID.
|
|
*/
|
|
err = ubi_wl_flush(ubi);
|
|
if (err)
|
|
goto out_acc;
|
|
|
|
vol->eba_tbl = kmalloc(vol->reserved_pebs * sizeof(int), GFP_KERNEL);
|
|
if (!vol->eba_tbl) {
|
|
err = -ENOMEM;
|
|
goto out_acc;
|
|
}
|
|
|
|
for (i = 0; i < vol->reserved_pebs; i++)
|
|
vol->eba_tbl[i] = UBI_LEB_UNMAPPED;
|
|
|
|
if (vol->vol_type == UBI_DYNAMIC_VOLUME) {
|
|
vol->used_ebs = vol->reserved_pebs;
|
|
vol->last_eb_bytes = vol->usable_leb_size;
|
|
vol->used_bytes = vol->used_ebs * vol->usable_leb_size;
|
|
} else {
|
|
bytes = vol->used_bytes;
|
|
vol->last_eb_bytes = do_div(bytes, vol->usable_leb_size);
|
|
vol->used_ebs = bytes;
|
|
if (vol->last_eb_bytes)
|
|
vol->used_ebs += 1;
|
|
else
|
|
vol->last_eb_bytes = vol->usable_leb_size;
|
|
}
|
|
|
|
/* Register character device for the volume */
|
|
cdev_init(&vol->cdev, &ubi_vol_cdev_operations);
|
|
vol->cdev.owner = THIS_MODULE;
|
|
err = cdev_add(&vol->cdev, MKDEV(ubi->major, vol_id + 1), 1);
|
|
if (err) {
|
|
ubi_err("cannot add character device for volume %d", vol_id);
|
|
goto out_mapping;
|
|
}
|
|
|
|
err = ubi_create_gluebi(ubi, vol);
|
|
if (err)
|
|
goto out_cdev;
|
|
|
|
vol->dev.release = vol_release;
|
|
vol->dev.parent = &ubi->dev;
|
|
vol->dev.devt = MKDEV(ubi->major, vol->vol_id + 1);
|
|
vol->dev.class = ubi_class;
|
|
sprintf(&vol->dev.bus_id[0], "%s_%d", ubi->ubi_name, vol->vol_id);
|
|
err = device_register(&vol->dev);
|
|
if (err)
|
|
goto out_gluebi;
|
|
|
|
err = volume_sysfs_init(ubi, vol);
|
|
if (err)
|
|
goto out_sysfs;
|
|
|
|
/* Fill volume table record */
|
|
memset(&vtbl_rec, 0, sizeof(struct ubi_vtbl_record));
|
|
vtbl_rec.reserved_pebs = cpu_to_ubi32(vol->reserved_pebs);
|
|
vtbl_rec.alignment = cpu_to_ubi32(vol->alignment);
|
|
vtbl_rec.data_pad = cpu_to_ubi32(vol->data_pad);
|
|
vtbl_rec.name_len = cpu_to_ubi16(vol->name_len);
|
|
if (vol->vol_type == UBI_DYNAMIC_VOLUME)
|
|
vtbl_rec.vol_type = UBI_VID_DYNAMIC;
|
|
else
|
|
vtbl_rec.vol_type = UBI_VID_STATIC;
|
|
memcpy(vtbl_rec.name, vol->name, vol->name_len + 1);
|
|
|
|
err = ubi_change_vtbl_record(ubi, vol_id, &vtbl_rec);
|
|
if (err)
|
|
goto out_sysfs;
|
|
|
|
spin_lock(&ubi->volumes_lock);
|
|
ubi->vol_count += 1;
|
|
vol->exclusive = 0;
|
|
spin_unlock(&ubi->volumes_lock);
|
|
|
|
paranoid_check_volumes(ubi);
|
|
return 0;
|
|
|
|
out_gluebi:
|
|
err = ubi_destroy_gluebi(vol);
|
|
out_cdev:
|
|
cdev_del(&vol->cdev);
|
|
out_mapping:
|
|
kfree(vol->eba_tbl);
|
|
out_acc:
|
|
spin_lock(&ubi->volumes_lock);
|
|
ubi->rsvd_pebs -= vol->reserved_pebs;
|
|
ubi->avail_pebs += vol->reserved_pebs;
|
|
out_unlock:
|
|
spin_unlock(&ubi->volumes_lock);
|
|
kfree(vol);
|
|
return err;
|
|
|
|
/*
|
|
* We are registered, so @vol is destroyed in the release function and
|
|
* we have to de-initialize differently.
|
|
*/
|
|
out_sysfs:
|
|
err = ubi_destroy_gluebi(vol);
|
|
cdev_del(&vol->cdev);
|
|
kfree(vol->eba_tbl);
|
|
spin_lock(&ubi->volumes_lock);
|
|
ubi->rsvd_pebs -= vol->reserved_pebs;
|
|
ubi->avail_pebs += vol->reserved_pebs;
|
|
spin_unlock(&ubi->volumes_lock);
|
|
volume_sysfs_close(vol);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* ubi_remove_volume - remove volume.
|
|
* @desc: volume descriptor
|
|
*
|
|
* This function removes volume described by @desc. The volume has to be opened
|
|
* in "exclusive" mode. Returns zero in case of success and a negative error
|
|
* code in case of failure.
|
|
*/
|
|
int ubi_remove_volume(struct ubi_volume_desc *desc)
|
|
{
|
|
struct ubi_volume *vol = desc->vol;
|
|
struct ubi_device *ubi = vol->ubi;
|
|
int i, err, vol_id = vol->vol_id, reserved_pebs = vol->reserved_pebs;
|
|
|
|
dbg_msg("remove UBI volume %d", vol_id);
|
|
ubi_assert(desc->mode == UBI_EXCLUSIVE);
|
|
ubi_assert(vol == ubi->volumes[vol_id]);
|
|
|
|
if (ubi->ro_mode)
|
|
return -EROFS;
|
|
|
|
err = ubi_destroy_gluebi(vol);
|
|
if (err)
|
|
return err;
|
|
|
|
err = ubi_change_vtbl_record(ubi, vol_id, NULL);
|
|
if (err)
|
|
return err;
|
|
|
|
for (i = 0; i < vol->reserved_pebs; i++) {
|
|
err = ubi_eba_unmap_leb(ubi, vol_id, i);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
spin_lock(&ubi->volumes_lock);
|
|
vol->removed = 1;
|
|
ubi->volumes[vol_id] = NULL;
|
|
spin_unlock(&ubi->volumes_lock);
|
|
|
|
kfree(vol->eba_tbl);
|
|
vol->eba_tbl = NULL;
|
|
cdev_del(&vol->cdev);
|
|
volume_sysfs_close(vol);
|
|
kfree(desc);
|
|
|
|
spin_lock(&ubi->volumes_lock);
|
|
ubi->rsvd_pebs -= reserved_pebs;
|
|
ubi->avail_pebs += reserved_pebs;
|
|
i = ubi->beb_rsvd_level - ubi->beb_rsvd_pebs;
|
|
if (i > 0) {
|
|
i = ubi->avail_pebs >= i ? i : ubi->avail_pebs;
|
|
ubi->avail_pebs -= i;
|
|
ubi->rsvd_pebs += i;
|
|
ubi->beb_rsvd_pebs += i;
|
|
if (i > 0)
|
|
ubi_msg("reserve more %d PEBs", i);
|
|
}
|
|
ubi->vol_count -= 1;
|
|
spin_unlock(&ubi->volumes_lock);
|
|
|
|
paranoid_check_volumes(ubi);
|
|
module_put(THIS_MODULE);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ubi_resize_volume - re-size volume.
|
|
* @desc: volume descriptor
|
|
* @reserved_pebs: new size in physical eraseblocks
|
|
*
|
|
* This function returns zero in case of success, and a negative error code in
|
|
* case of failure.
|
|
*/
|
|
int ubi_resize_volume(struct ubi_volume_desc *desc, int reserved_pebs)
|
|
{
|
|
int i, err, pebs, *new_mapping;
|
|
struct ubi_volume *vol = desc->vol;
|
|
struct ubi_device *ubi = vol->ubi;
|
|
struct ubi_vtbl_record vtbl_rec;
|
|
int vol_id = vol->vol_id;
|
|
|
|
if (ubi->ro_mode)
|
|
return -EROFS;
|
|
|
|
dbg_msg("re-size volume %d to from %d to %d PEBs",
|
|
vol_id, vol->reserved_pebs, reserved_pebs);
|
|
ubi_assert(desc->mode == UBI_EXCLUSIVE);
|
|
ubi_assert(vol == ubi->volumes[vol_id]);
|
|
|
|
if (vol->vol_type == UBI_STATIC_VOLUME &&
|
|
reserved_pebs < vol->used_ebs) {
|
|
dbg_err("too small size %d, %d LEBs contain data",
|
|
reserved_pebs, vol->used_ebs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* If the size is the same, we have nothing to do */
|
|
if (reserved_pebs == vol->reserved_pebs)
|
|
return 0;
|
|
|
|
new_mapping = kmalloc(reserved_pebs * sizeof(int), GFP_KERNEL);
|
|
if (!new_mapping)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < reserved_pebs; i++)
|
|
new_mapping[i] = UBI_LEB_UNMAPPED;
|
|
|
|
/* Reserve physical eraseblocks */
|
|
pebs = reserved_pebs - vol->reserved_pebs;
|
|
if (pebs > 0) {
|
|
spin_lock(&ubi->volumes_lock);
|
|
if (pebs > ubi->avail_pebs) {
|
|
dbg_err("not enough PEBs: requested %d, available %d",
|
|
pebs, ubi->avail_pebs);
|
|
spin_unlock(&ubi->volumes_lock);
|
|
err = -ENOSPC;
|
|
goto out_free;
|
|
}
|
|
ubi->avail_pebs -= pebs;
|
|
ubi->rsvd_pebs += pebs;
|
|
for (i = 0; i < vol->reserved_pebs; i++)
|
|
new_mapping[i] = vol->eba_tbl[i];
|
|
kfree(vol->eba_tbl);
|
|
vol->eba_tbl = new_mapping;
|
|
spin_unlock(&ubi->volumes_lock);
|
|
}
|
|
|
|
/* Change volume table record */
|
|
memcpy(&vtbl_rec, &ubi->vtbl[vol_id], sizeof(struct ubi_vtbl_record));
|
|
vtbl_rec.reserved_pebs = cpu_to_ubi32(reserved_pebs);
|
|
err = ubi_change_vtbl_record(ubi, vol_id, &vtbl_rec);
|
|
if (err)
|
|
goto out_acc;
|
|
|
|
if (pebs < 0) {
|
|
for (i = 0; i < -pebs; i++) {
|
|
err = ubi_eba_unmap_leb(ubi, vol_id, reserved_pebs + i);
|
|
if (err)
|
|
goto out_acc;
|
|
}
|
|
spin_lock(&ubi->volumes_lock);
|
|
ubi->rsvd_pebs += pebs;
|
|
ubi->avail_pebs -= pebs;
|
|
pebs = ubi->beb_rsvd_level - ubi->beb_rsvd_pebs;
|
|
if (pebs > 0) {
|
|
pebs = ubi->avail_pebs >= pebs ? pebs : ubi->avail_pebs;
|
|
ubi->avail_pebs -= pebs;
|
|
ubi->rsvd_pebs += pebs;
|
|
ubi->beb_rsvd_pebs += pebs;
|
|
if (pebs > 0)
|
|
ubi_msg("reserve more %d PEBs", pebs);
|
|
}
|
|
for (i = 0; i < reserved_pebs; i++)
|
|
new_mapping[i] = vol->eba_tbl[i];
|
|
kfree(vol->eba_tbl);
|
|
vol->eba_tbl = new_mapping;
|
|
spin_unlock(&ubi->volumes_lock);
|
|
}
|
|
|
|
vol->reserved_pebs = reserved_pebs;
|
|
if (vol->vol_type == UBI_DYNAMIC_VOLUME) {
|
|
vol->used_ebs = reserved_pebs;
|
|
vol->last_eb_bytes = vol->usable_leb_size;
|
|
vol->used_bytes = vol->used_ebs * vol->usable_leb_size;
|
|
}
|
|
|
|
paranoid_check_volumes(ubi);
|
|
return 0;
|
|
|
|
out_acc:
|
|
if (pebs > 0) {
|
|
spin_lock(&ubi->volumes_lock);
|
|
ubi->rsvd_pebs -= pebs;
|
|
ubi->avail_pebs += pebs;
|
|
spin_unlock(&ubi->volumes_lock);
|
|
}
|
|
out_free:
|
|
kfree(new_mapping);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* ubi_add_volume - add volume.
|
|
* @ubi: UBI device description object
|
|
* @vol_id: volume ID
|
|
*
|
|
* This function adds an existin volume and initializes all its data
|
|
* structures. Returnes zero in case of success and a negative error code in
|
|
* case of failure.
|
|
*/
|
|
int ubi_add_volume(struct ubi_device *ubi, int vol_id)
|
|
{
|
|
int err;
|
|
struct ubi_volume *vol = ubi->volumes[vol_id];
|
|
|
|
dbg_msg("add volume %d", vol_id);
|
|
ubi_dbg_dump_vol_info(vol);
|
|
ubi_assert(vol);
|
|
|
|
/* Register character device for the volume */
|
|
cdev_init(&vol->cdev, &ubi_vol_cdev_operations);
|
|
vol->cdev.owner = THIS_MODULE;
|
|
err = cdev_add(&vol->cdev, MKDEV(ubi->major, vol->vol_id + 1), 1);
|
|
if (err) {
|
|
ubi_err("cannot add character device for volume %d", vol_id);
|
|
return err;
|
|
}
|
|
|
|
err = ubi_create_gluebi(ubi, vol);
|
|
if (err)
|
|
goto out_cdev;
|
|
|
|
vol->dev.release = vol_release;
|
|
vol->dev.parent = &ubi->dev;
|
|
vol->dev.devt = MKDEV(ubi->major, vol->vol_id + 1);
|
|
vol->dev.class = ubi_class;
|
|
sprintf(&vol->dev.bus_id[0], "%s_%d", ubi->ubi_name, vol->vol_id);
|
|
err = device_register(&vol->dev);
|
|
if (err)
|
|
goto out_gluebi;
|
|
|
|
err = volume_sysfs_init(ubi, vol);
|
|
if (err) {
|
|
cdev_del(&vol->cdev);
|
|
err = ubi_destroy_gluebi(vol);
|
|
volume_sysfs_close(vol);
|
|
return err;
|
|
}
|
|
|
|
paranoid_check_volumes(ubi);
|
|
return 0;
|
|
|
|
out_gluebi:
|
|
err = ubi_destroy_gluebi(vol);
|
|
out_cdev:
|
|
cdev_del(&vol->cdev);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* ubi_free_volume - free volume.
|
|
* @ubi: UBI device description object
|
|
* @vol_id: volume ID
|
|
*
|
|
* This function frees all resources for volume @vol_id but does not remove it.
|
|
* Used only when the UBI device is detached.
|
|
*/
|
|
void ubi_free_volume(struct ubi_device *ubi, int vol_id)
|
|
{
|
|
int err;
|
|
struct ubi_volume *vol = ubi->volumes[vol_id];
|
|
|
|
dbg_msg("free volume %d", vol_id);
|
|
ubi_assert(vol);
|
|
|
|
vol->removed = 1;
|
|
err = ubi_destroy_gluebi(vol);
|
|
ubi->volumes[vol_id] = NULL;
|
|
cdev_del(&vol->cdev);
|
|
volume_sysfs_close(vol);
|
|
}
|
|
|
|
#ifdef CONFIG_MTD_UBI_DEBUG_PARANOID
|
|
|
|
/**
|
|
* paranoid_check_volume - check volume information.
|
|
* @ubi: UBI device description object
|
|
* @vol_id: volume ID
|
|
*/
|
|
static void paranoid_check_volume(const struct ubi_device *ubi, int vol_id)
|
|
{
|
|
int idx = vol_id2idx(ubi, vol_id);
|
|
int reserved_pebs, alignment, data_pad, vol_type, name_len, upd_marker;
|
|
const struct ubi_volume *vol = ubi->volumes[idx];
|
|
long long n;
|
|
const char *name;
|
|
|
|
reserved_pebs = ubi32_to_cpu(ubi->vtbl[vol_id].reserved_pebs);
|
|
|
|
if (!vol) {
|
|
if (reserved_pebs) {
|
|
ubi_err("no volume info, but volume exists");
|
|
goto fail;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (vol->reserved_pebs < 0 || vol->alignment < 0 || vol->data_pad < 0 ||
|
|
vol->name_len < 0) {
|
|
ubi_err("negative values");
|
|
goto fail;
|
|
}
|
|
if (vol->alignment > ubi->leb_size || vol->alignment == 0) {
|
|
ubi_err("bad alignment");
|
|
goto fail;
|
|
}
|
|
|
|
n = vol->alignment % ubi->min_io_size;
|
|
if (vol->alignment != 1 && n) {
|
|
ubi_err("alignment is not multiple of min I/O unit");
|
|
goto fail;
|
|
}
|
|
|
|
n = ubi->leb_size % vol->alignment;
|
|
if (vol->data_pad != n) {
|
|
ubi_err("bad data_pad, has to be %lld", n);
|
|
goto fail;
|
|
}
|
|
|
|
if (vol->vol_type != UBI_DYNAMIC_VOLUME &&
|
|
vol->vol_type != UBI_STATIC_VOLUME) {
|
|
ubi_err("bad vol_type");
|
|
goto fail;
|
|
}
|
|
|
|
if (vol->upd_marker != 0 && vol->upd_marker != 1) {
|
|
ubi_err("bad upd_marker");
|
|
goto fail;
|
|
}
|
|
|
|
if (vol->upd_marker && vol->corrupted) {
|
|
dbg_err("update marker and corrupted simultaneously");
|
|
goto fail;
|
|
}
|
|
|
|
if (vol->reserved_pebs > ubi->good_peb_count) {
|
|
ubi_err("too large reserved_pebs");
|
|
goto fail;
|
|
}
|
|
|
|
n = ubi->leb_size - vol->data_pad;
|
|
if (vol->usable_leb_size != ubi->leb_size - vol->data_pad) {
|
|
ubi_err("bad usable_leb_size, has to be %lld", n);
|
|
goto fail;
|
|
}
|
|
|
|
if (vol->name_len > UBI_VOL_NAME_MAX) {
|
|
ubi_err("too long volume name, max is %d", UBI_VOL_NAME_MAX);
|
|
goto fail;
|
|
}
|
|
|
|
if (!vol->name) {
|
|
ubi_err("NULL volume name");
|
|
goto fail;
|
|
}
|
|
|
|
n = strnlen(vol->name, vol->name_len + 1);
|
|
if (n != vol->name_len) {
|
|
ubi_err("bad name_len %lld", n);
|
|
goto fail;
|
|
}
|
|
|
|
n = vol->used_ebs * vol->usable_leb_size;
|
|
if (vol->vol_type == UBI_DYNAMIC_VOLUME) {
|
|
if (vol->corrupted != 0) {
|
|
ubi_err("corrupted dynamic volume");
|
|
goto fail;
|
|
}
|
|
if (vol->used_ebs != vol->reserved_pebs) {
|
|
ubi_err("bad used_ebs");
|
|
goto fail;
|
|
}
|
|
if (vol->last_eb_bytes != vol->usable_leb_size) {
|
|
ubi_err("bad last_eb_bytes");
|
|
goto fail;
|
|
}
|
|
if (vol->used_bytes != n) {
|
|
ubi_err("bad used_bytes");
|
|
goto fail;
|
|
}
|
|
} else {
|
|
if (vol->corrupted != 0 && vol->corrupted != 1) {
|
|
ubi_err("bad corrupted");
|
|
goto fail;
|
|
}
|
|
if (vol->used_ebs < 0 || vol->used_ebs > vol->reserved_pebs) {
|
|
ubi_err("bad used_ebs");
|
|
goto fail;
|
|
}
|
|
if (vol->last_eb_bytes < 0 ||
|
|
vol->last_eb_bytes > vol->usable_leb_size) {
|
|
ubi_err("bad last_eb_bytes");
|
|
goto fail;
|
|
}
|
|
if (vol->used_bytes < 0 || vol->used_bytes > n ||
|
|
vol->used_bytes < n - vol->usable_leb_size) {
|
|
ubi_err("bad used_bytes");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
alignment = ubi32_to_cpu(ubi->vtbl[vol_id].alignment);
|
|
data_pad = ubi32_to_cpu(ubi->vtbl[vol_id].data_pad);
|
|
name_len = ubi16_to_cpu(ubi->vtbl[vol_id].name_len);
|
|
upd_marker = ubi->vtbl[vol_id].upd_marker;
|
|
name = &ubi->vtbl[vol_id].name[0];
|
|
if (ubi->vtbl[vol_id].vol_type == UBI_VID_DYNAMIC)
|
|
vol_type = UBI_DYNAMIC_VOLUME;
|
|
else
|
|
vol_type = UBI_STATIC_VOLUME;
|
|
|
|
if (alignment != vol->alignment || data_pad != vol->data_pad ||
|
|
upd_marker != vol->upd_marker || vol_type != vol->vol_type ||
|
|
name_len!= vol->name_len || strncmp(name, vol->name, name_len)) {
|
|
ubi_err("volume info is different");
|
|
goto fail;
|
|
}
|
|
|
|
return;
|
|
|
|
fail:
|
|
ubi_err("paranoid check failed");
|
|
ubi_dbg_dump_vol_info(vol);
|
|
ubi_dbg_dump_vtbl_record(&ubi->vtbl[vol_id], vol_id);
|
|
BUG();
|
|
}
|
|
|
|
/**
|
|
* paranoid_check_volumes - check information about all volumes.
|
|
* @ubi: UBI device description object
|
|
*/
|
|
static void paranoid_check_volumes(struct ubi_device *ubi)
|
|
{
|
|
int i;
|
|
|
|
mutex_lock(&ubi->vtbl_mutex);
|
|
spin_lock(&ubi->volumes_lock);
|
|
for (i = 0; i < ubi->vtbl_slots; i++)
|
|
paranoid_check_volume(ubi, i);
|
|
spin_unlock(&ubi->volumes_lock);
|
|
mutex_unlock(&ubi->vtbl_mutex);
|
|
}
|
|
#endif
|