diff options
Diffstat (limited to 'drivers/mtd/ubi/upd.c')
| -rw-r--r-- | drivers/mtd/ubi/upd.c | 233 |
1 files changed, 158 insertions, 75 deletions
diff --git a/drivers/mtd/ubi/upd.c b/drivers/mtd/ubi/upd.c index 0efc586a832..ec2c2dc1c1c 100644 --- a/drivers/mtd/ubi/upd.c +++ b/drivers/mtd/ubi/upd.c @@ -22,7 +22,8 @@ */ /* - * This file contains implementation of the volume update functionality. + * This file contains implementation of the volume update and atomic LEB change + * functionality. * * The update operation is based on the per-volume update marker which is * stored in the volume table. The update marker is set before the update @@ -38,134 +39,169 @@ */ #include <linux/err.h> -#include <asm/uaccess.h> -#include <asm/div64.h> +#include <linux/uaccess.h> +#include <linux/math64.h> #include "ubi.h" /** * set_update_marker - set update marker. * @ubi: UBI device description object - * @vol_id: volume ID + * @vol: volume description object * - * This function sets the update marker flag for volume @vol_id. Returns zero + * This function sets the update marker flag for volume @vol. Returns zero * in case of success and a negative error code in case of failure. */ -static int set_update_marker(struct ubi_device *ubi, int vol_id) +static int set_update_marker(struct ubi_device *ubi, struct ubi_volume *vol) { int err; struct ubi_vtbl_record vtbl_rec; - struct ubi_volume *vol = ubi->volumes[vol_id]; - dbg_msg("set update marker for volume %d", vol_id); + dbg_gen("set update marker for volume %d", vol->vol_id); if (vol->upd_marker) { - ubi_assert(ubi->vtbl[vol_id].upd_marker); - dbg_msg("already set"); + ubi_assert(ubi->vtbl[vol->vol_id].upd_marker); + dbg_gen("already set"); return 0; } - memcpy(&vtbl_rec, &ubi->vtbl[vol_id], sizeof(struct ubi_vtbl_record)); + vtbl_rec = ubi->vtbl[vol->vol_id]; vtbl_rec.upd_marker = 1; - err = ubi_change_vtbl_record(ubi, vol_id, &vtbl_rec); + mutex_lock(&ubi->device_mutex); + err = ubi_change_vtbl_record(ubi, vol->vol_id, &vtbl_rec); vol->upd_marker = 1; + mutex_unlock(&ubi->device_mutex); return err; } /** * clear_update_marker - clear update marker. * @ubi: UBI device description object - * @vol_id: volume ID + * @vol: volume description object * @bytes: new data size in bytes * - * This function clears the update marker for volume @vol_id, sets new volume + * This function clears the update marker for volume @vol, sets new volume * data size and clears the "corrupted" flag (static volumes only). Returns * zero in case of success and a negative error code in case of failure. */ -static int clear_update_marker(struct ubi_device *ubi, int vol_id, long long bytes) +static int clear_update_marker(struct ubi_device *ubi, struct ubi_volume *vol, + long long bytes) { int err; - uint64_t tmp; struct ubi_vtbl_record vtbl_rec; - struct ubi_volume *vol = ubi->volumes[vol_id]; - dbg_msg("clear update marker for volume %d", vol_id); + dbg_gen("clear update marker for volume %d", vol->vol_id); - memcpy(&vtbl_rec, &ubi->vtbl[vol_id], sizeof(struct ubi_vtbl_record)); + vtbl_rec = ubi->vtbl[vol->vol_id]; ubi_assert(vol->upd_marker && vtbl_rec.upd_marker); vtbl_rec.upd_marker = 0; if (vol->vol_type == UBI_STATIC_VOLUME) { vol->corrupted = 0; - vol->used_bytes = tmp = bytes; - vol->last_eb_bytes = do_div(tmp, vol->usable_leb_size); - vol->used_ebs = tmp; + vol->used_bytes = bytes; + vol->used_ebs = div_u64_rem(bytes, vol->usable_leb_size, + &vol->last_eb_bytes); if (vol->last_eb_bytes) vol->used_ebs += 1; else vol->last_eb_bytes = vol->usable_leb_size; } - err = ubi_change_vtbl_record(ubi, vol_id, &vtbl_rec); + mutex_lock(&ubi->device_mutex); + err = ubi_change_vtbl_record(ubi, vol->vol_id, &vtbl_rec); vol->upd_marker = 0; + mutex_unlock(&ubi->device_mutex); return err; } /** * ubi_start_update - start volume update. * @ubi: UBI device description object - * @vol_id: volume ID + * @vol: volume description object * @bytes: update bytes * * This function starts volume update operation. If @bytes is zero, the volume * is just wiped out. Returns zero in case of success and a negative error code * in case of failure. */ -int ubi_start_update(struct ubi_device *ubi, int vol_id, long long bytes) +int ubi_start_update(struct ubi_device *ubi, struct ubi_volume *vol, + long long bytes) { int i, err; - uint64_t tmp; - struct ubi_volume *vol = ubi->volumes[vol_id]; - dbg_msg("start update of volume %d, %llu bytes", vol_id, bytes); + dbg_gen("start update of volume %d, %llu bytes", vol->vol_id, bytes); + ubi_assert(!vol->updating && !vol->changing_leb); vol->updating = 1; - err = set_update_marker(ubi, vol_id); + err = set_update_marker(ubi, vol); if (err) return err; /* Before updating - wipe out the volume */ for (i = 0; i < vol->reserved_pebs; i++) { - err = ubi_eba_unmap_leb(ubi, vol_id, i); + err = ubi_eba_unmap_leb(ubi, vol, i); if (err) return err; } if (bytes == 0) { - err = clear_update_marker(ubi, vol_id, 0); + err = ubi_wl_flush(ubi, UBI_ALL, UBI_ALL); if (err) return err; - err = ubi_wl_flush(ubi); - if (!err) - vol->updating = 0; + + err = clear_update_marker(ubi, vol, 0); + if (err) + return err; + vol->updating = 0; + return 0; } vol->upd_buf = vmalloc(ubi->leb_size); if (!vol->upd_buf) return -ENOMEM; - tmp = bytes; - vol->upd_ebs = !!do_div(tmp, vol->usable_leb_size); - vol->upd_ebs += tmp; + vol->upd_ebs = div_u64(bytes + vol->usable_leb_size - 1, + vol->usable_leb_size); vol->upd_bytes = bytes; vol->upd_received = 0; return 0; } /** + * ubi_start_leb_change - start atomic LEB change. + * @ubi: UBI device description object + * @vol: volume description object + * @req: operation request + * + * This function starts atomic LEB change operation. Returns zero in case of + * success and a negative error code in case of failure. + */ +int ubi_start_leb_change(struct ubi_device *ubi, struct ubi_volume *vol, + const struct ubi_leb_change_req *req) +{ + ubi_assert(!vol->updating && !vol->changing_leb); + + dbg_gen("start changing LEB %d:%d, %u bytes", + vol->vol_id, req->lnum, req->bytes); + if (req->bytes == 0) + return ubi_eba_atomic_leb_change(ubi, vol, req->lnum, NULL, 0); + + vol->upd_bytes = req->bytes; + vol->upd_received = 0; + vol->changing_leb = 1; + vol->ch_lnum = req->lnum; + + vol->upd_buf = vmalloc(req->bytes); + if (!vol->upd_buf) + return -ENOMEM; + + return 0; +} + +/** * write_leb - write update data. * @ubi: UBI device description object - * @vol_id: volume ID + * @vol: volume description object * @lnum: logical eraseblock number * @buf: data to write * @len: data size @@ -191,26 +227,22 @@ int ubi_start_update(struct ubi_device *ubi, int vol_id, long long bytes) * This function returns zero in case of success and a negative error code in * case of failure. */ -static int write_leb(struct ubi_device *ubi, int vol_id, int lnum, void *buf, - int len, int used_ebs) +static int write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum, + void *buf, int len, int used_ebs) { - int err, l; - struct ubi_volume *vol = ubi->volumes[vol_id]; + int err; if (vol->vol_type == UBI_DYNAMIC_VOLUME) { - l = ALIGN(len, ubi->min_io_size); - memset(buf + len, 0xFF, l - len); + int l = ALIGN(len, ubi->min_io_size); - l = ubi_calc_data_len(ubi, buf, l); - if (l == 0) { - dbg_msg("all %d bytes contain 0xFF - skip", len); + memset(buf + len, 0xFF, l - len); + len = ubi_calc_data_len(ubi, buf, l); + if (len == 0) { + dbg_gen("all %d bytes contain 0xFF - skip", len); return 0; } - if (len != l) - dbg_msg("skip last %d bytes (0xFF)", len - l); - err = ubi_eba_write_leb(ubi, vol_id, lnum, buf, 0, l, - UBI_UNKNOWN); + err = ubi_eba_write_leb(ubi, vol, lnum, buf, 0, len); } else { /* * When writing static volume, and this is the last logical @@ -222,8 +254,7 @@ static int write_leb(struct ubi_device *ubi, int vol_id, int lnum, void *buf, * contain zeros, not random trash. */ memset(buf + len, 0, vol->usable_leb_size - len); - err = ubi_eba_write_leb_st(ubi, vol_id, lnum, buf, len, - UBI_UNKNOWN, used_ebs); + err = ubi_eba_write_leb_st(ubi, vol, lnum, buf, len, used_ebs); } return err; @@ -231,33 +262,29 @@ static int write_leb(struct ubi_device *ubi, int vol_id, int lnum, void *buf, /** * ubi_more_update_data - write more update data. + * @ubi: UBI device description object * @vol: volume description object * @buf: write data (user-space memory buffer) * @count: how much bytes to write * * This function writes more data to the volume which is being updated. It may - * be called arbitrary number of times until all of the update data arrive. - * This function returns %0 in case of success, number of bytes written during - * the last call if the whole volume update was successfully finished, and a + * be called arbitrary number of times until all the update data arriveis. This + * function returns %0 in case of success, number of bytes written during the + * last call if the whole volume update has been successfully finished, and a * negative error code in case of failure. */ -int ubi_more_update_data(struct ubi_device *ubi, int vol_id, +int ubi_more_update_data(struct ubi_device *ubi, struct ubi_volume *vol, const void __user *buf, int count) { - uint64_t tmp; - struct ubi_volume *vol = ubi->volumes[vol_id]; int lnum, offs, err = 0, len, to_write = count; - dbg_msg("write %d of %lld bytes, %lld already passed", + dbg_gen("write %d of %lld bytes, %lld already passed", count, vol->upd_bytes, vol->upd_received); if (ubi->ro_mode) return -EROFS; - tmp = vol->upd_received; - offs = do_div(tmp, vol->usable_leb_size); - lnum = tmp; - + lnum = div_u64_rem(vol->upd_received, vol->usable_leb_size, &offs); if (vol->upd_received + count > vol->upd_bytes) to_write = count = vol->upd_bytes - vol->upd_received; @@ -290,8 +317,8 @@ int ubi_more_update_data(struct ubi_device *ubi, int vol_id, * is the last chunk, it's time to flush the buffer. */ ubi_assert(flush_len <= vol->usable_leb_size); - err = write_leb(ubi, vol_id, lnum, vol->upd_buf, - flush_len, vol->upd_ebs); + err = write_leb(ubi, vol, lnum, vol->upd_buf, flush_len, + vol->upd_ebs); if (err) return err; } @@ -318,8 +345,8 @@ int ubi_more_update_data(struct ubi_device *ubi, int vol_id, if (len == vol->usable_leb_size || vol->upd_received + len == vol->upd_bytes) { - err = write_leb(ubi, vol_id, lnum, vol->upd_buf, len, - vol->upd_ebs); + err = write_leb(ubi, vol, lnum, vol->upd_buf, + len, vol->upd_ebs); if (err) break; } @@ -332,16 +359,72 @@ int ubi_more_update_data(struct ubi_device *ubi, int vol_id, ubi_assert(vol->upd_received <= vol->upd_bytes); if (vol->upd_received == vol->upd_bytes) { + err = ubi_wl_flush(ubi, UBI_ALL, UBI_ALL); + if (err) + return err; /* The update is finished, clear the update marker */ - err = clear_update_marker(ubi, vol_id, vol->upd_bytes); + err = clear_update_marker(ubi, vol, vol->upd_bytes); if (err) return err; - err = ubi_wl_flush(ubi); - if (err == 0) { - err = to_write; - vfree(vol->upd_buf); - vol->updating = 0; - } + vol->updating = 0; + err = to_write; + vfree(vol->upd_buf); + } + + return err; +} + +/** + * ubi_more_leb_change_data - accept more data for atomic LEB change. + * @ubi: UBI device description object + * @vol: volume description object + * @buf: write data (user-space memory buffer) + * @count: how much bytes to write + * + * This function accepts more data to the volume which is being under the + * "atomic LEB change" operation. It may be called arbitrary number of times + * until all data arrives. This function returns %0 in case of success, number + * of bytes written during the last call if the whole "atomic LEB change" + * operation has been successfully finished, and a negative error code in case + * of failure. + */ +int ubi_more_leb_change_data(struct ubi_device *ubi, struct ubi_volume *vol, + const void __user *buf, int count) +{ + int err; + + dbg_gen("write %d of %lld bytes, %lld already passed", + count, vol->upd_bytes, vol->upd_received); + + if (ubi->ro_mode) + return -EROFS; + + if (vol->upd_received + count > vol->upd_bytes) + count = vol->upd_bytes - vol->upd_received; + + err = copy_from_user(vol->upd_buf + vol->upd_received, buf, count); + if (err) + return -EFAULT; + + vol->upd_received += count; + + if (vol->upd_received == vol->upd_bytes) { + int len = ALIGN((int)vol->upd_bytes, ubi->min_io_size); + + memset(vol->upd_buf + vol->upd_bytes, 0xFF, + len - vol->upd_bytes); + len = ubi_calc_data_len(ubi, vol->upd_buf, len); + err = ubi_eba_atomic_leb_change(ubi, vol, vol->ch_lnum, + vol->upd_buf, len); + if (err) + return err; + } + + ubi_assert(vol->upd_received <= vol->upd_bytes); + if (vol->upd_received == vol->upd_bytes) { + vol->changing_leb = 0; + err = count; + vfree(vol->upd_buf); } return err; |
