blob: 4ba8a9c1ffee60420fcf0644535b718c169567f0 [file] [edit]
/*
* Copyright 2016, NICTA
*
* This software may be distributed and modified according to the terms of
* the GNU General Public License version 2. Note that NO WARRANTY is provided.
* See "LICENSE_GPLv2.txt" for details.
*
* @TAG(NICTA_GPL)
*/
#include <bilbyfs.h>
int wbuf_init(struct bilbyfs_info *bi)
{
bi->rbuf.buf = vmalloc(bi->super.leb_size);
if (bi->rbuf.buf) {
bi->rbuf.size = bi->super.leb_size;
return 0;
}
wbuf_clean(bi);
return -ENOMEM;
}
void wbuf_clean(struct bilbyfs_info *bi)
{
vfree(bi->rbuf.buf);
memset(&bi->rbuf, 0, sizeof(bi->rbuf));
}
void wbuf_start(struct bilbyfs_info *bi, struct bilbyfs_wbuf *wbuf)
{
bilbyfs_assert(bi->super.leb_size == bi->vi.usable_leb_size);
wbuf->used = 0;
wbuf->sync_offs = 0;
wbuf->avail = bi->super.leb_size;
wbuf->size = bi->super.leb_size;
}
int wbuf_write_obj(struct bilbyfs_info *bi, void *obj, int objlen, struct bilbyfs_wbuf *wbuf)
{
bilbyfs_assert(objlen % BILBYFS_OBJ_PADDING == 0);
bilbyfs_debug("wbuf_write_obj({lnum=%u,offs=%u,len=%u,oid=%llx}) = %d\n",
fsm_get_lnum(bi), wbuf->used, objlen,
((struct obj_ch *)obj)->type >= BILBYFS_SUP_OBJ ? 0 : get_obj_id(obj),
(wbuf->avail < objlen ? -EINVAL : 0));
if (wbuf->avail < objlen)
return -EINVAL;
memcpy(wbuf->buf + wbuf->used, obj, objlen);
wbuf->avail -= objlen;
wbuf->used += objlen;
return 0;
}
int wbuf_prepare_commit(struct bilbyfs_info *bi, u32 *padding_sz, struct bilbyfs_wbuf *wbuf)
{
struct obj_ch *ch;
int pad_sz;
/* Ensure non-empty transactions */
bilbyfs_assert(wbuf->used > 0);
if (wbuf->avail == 0)
return wbuf->used;
/* padding to the next flash page */
pad_sz = ALIGN(wbuf->used, bi->super.min_io_size) - wbuf->used;
if (pad_sz < BILBYFS_CH_SZ) {
memset(wbuf->buf + wbuf->used, BILBYFS_PAD_BYTE, pad_sz);
} else {
ch = wbuf->buf + wbuf->used;
pack_obj_pad(ch, pad_sz);
pack_obj_header(ch, next_sqnum(bi), BILBYFS_TRANS_ATOM);
}
if (padding_sz)
*padding_sz = pad_sz;
wbuf->avail -= pad_sz;
wbuf->used += pad_sz;
return wbuf->used;
}
int wbuf_atom_leb_commit(struct bilbyfs_info *bi, int lnum, struct bilbyfs_wbuf *wbuf)
{
int err;
/* Ensure that a LEB is big enough to store the data */
bilbyfs_assert(bi->super.leb_size >= wbuf->used);
/* Ensure that we do not commit an empty transaction */
bilbyfs_assert(wbuf->used > 0);
/* Ensure that we called wbuf_prepare_commit() */
bilbyfs_assert(wbuf->used == ALIGN(wbuf->used, bi->super.min_io_size));
err = ubi_leb_change(bi->ubi, lnum, wbuf->buf, wbuf->used);
bilbyfs_debug("ubi_leb_change(ubi, %d, %p, %d) = %d\n",
lnum, wbuf->buf, wbuf->used, err);
return err;
}
int wbuf_commit(struct bilbyfs_info *bi, u32 lnum, struct bilbyfs_wbuf *wbuf)
{
int err;
/* Ensure that sync is within used range */
bilbyfs_assert(wbuf->sync_offs < wbuf->used);
/* Ensure that sync is aligned to min-io-size */
bilbyfs_assert(wbuf->sync_offs == ALIGN(wbuf->sync_offs, bi->super.min_io_size));
/* Ensure that buffer contains enough room for synchronizing the
* remaining data */
bilbyfs_assert(wbuf->size >= wbuf->used);
/* Ensure that we called wbuf_prepare_commit() */
bilbyfs_assert(wbuf->used == ALIGN(wbuf->used, bi->super.min_io_size));
err = ubi_leb_write(bi->ubi, lnum, wbuf->buf + wbuf->sync_offs, wbuf->sync_offs, wbuf->used - wbuf->sync_offs);
bilbyfs_debug("ubi_leb_write(ubi, %d, %p, %d, %d) = %d\n",
lnum, wbuf->buf + wbuf->sync_offs, wbuf->sync_offs, wbuf->used - wbuf->sync_offs, err);
if (!err)
wbuf->sync_offs = wbuf->used;
return err;
}
static int wbuf_read_obj_pages(struct bilbyfs_info *bi, struct obj_addr *addr,
struct bilbyfs_rbuf *rbuf)
{
int min_io_size = bi->super.min_io_size;
int max_io_size = bi->super.max_io_size;
int offs_in_page = addr->offs % min_io_size;
int aligned_offs = addr->offs - offs_in_page;
int aligned_end = ALIGN(addr->offs + addr->len, min_io_size);
int toread;
int offs;
int err;
offs = 0;
toread = aligned_end - aligned_offs;
bilbyfs_assert(aligned_offs == ALIGN(aligned_offs, min_io_size));
bilbyfs_assert(toread == ALIGN(toread, min_io_size));
while (toread > max_io_size) {
err = ubi_read(bi->ubi, addr->lnum, rbuf->buf + offs,
aligned_offs + offs, max_io_size);
if (err)
return err;
offs += max_io_size;
toread -= max_io_size;
}
while (toread >= min_io_size) {
err = ubi_read(bi->ubi, addr->lnum, rbuf->buf + offs,
aligned_offs + offs, min_io_size);
if (err)
return err;
offs += min_io_size;
toread -= min_io_size;
}
bilbyfs_assert(toread <= 0);
/* offset from where we started to read */
rbuf->offs = aligned_offs;
return offs_in_page;
}
int wbuf_read_obj(struct bilbyfs_info *bi, void *buf, struct obj_addr *addr)
{
int err;
int offs_in_page;
err = wbuf_read_obj_pages(bi, addr, &bi->rbuf);
if (err < 0)
return err;
offs_in_page = err;
memcpy(buf, bi->rbuf.buf + offs_in_page, addr->len);
return 0;
}
int wbuf_read_sum(struct bilbyfs_info *bi, int lnum, struct bilbyfs_rbuf *rbuf, u32 *sum_offs_ret)
{
int io_sz = bi->super.min_io_size;
int sum_offs;
int nb_read;
int offs = bi->super.leb_size - io_sz;
int err;
int i;
struct obj_sum *sum;
/* struct timeval st, stp; */
bilbyfs_assert(rbuf->size == bi->super.leb_size);
/* bilbyfs_err("reading 1 page from erase-block %x\n", lnum); */
bi->nb_pages += 1;
/*
do_gettimeofday(&st);
*/
err = ubi_read(bi->ubi, lnum, rbuf->buf + offs, offs, io_sz);
if (err)
return err;
/*
do_gettimeofday(&stp);
pr_err("timed ubi_read() : %ld.%ld\n", stp.tv_sec - st.tv_sec, stp. tv_usec - st.tv_usec);
*/
sum_offs = le32_to_cpu(*(u32*)(rbuf->buf + bi->super.leb_size - BILBYFS_OBJ_SUM_OFFS_SZ));
if (sum_offs >= (bi->super.leb_size - BILBYFS_OBJ_SUM_OFFS_SZ))
return -ENOENT;
offs = sum_offs - sum_offs % io_sz;
nb_read = (bi->super.leb_size - offs) / io_sz;
/* bilbyfs_err("reading more pages (%u) from erase-block %x\n", nb_read - 1, lnum); */
bi->nb_extra_pages += nb_read - 1;
for (i = 0; i < nb_read - 1; i++) {
/*
do_gettimeofday(&st);
*/
err = ubi_read(bi->ubi, lnum, rbuf->buf + offs, offs, io_sz);
if (err)
return err;
/*
do_gettimeofday(&stp);
pr_err("timed ubi_read() e : %ld.%ld\n", stp.tv_sec - st.tv_sec, stp. tv_usec - st.tv_usec);
*/
offs += io_sz;
}
sum= rbuf->buf + sum_offs;
bilbyfs_debug("summary (.lnum=%d, .nb_read=%d, .sum_sz=%u, io_sz=%d, sum.size=%u, sum.nb_entry=%u)\n", lnum, nb_read, bi->super.leb_size - sum_offs, io_sz, le32_to_cpu(sum->ch.len), le32_to_cpu(sum->nb_sum_entry));
rbuf->offs = 0;
*sum_offs_ret = sum_offs;
return 0;
}
/* Read an entires LEB but stops when encountering a block that starts by 0xffff
* and assumes that the remaining pages are full of 0xff too.
*/
int wbuf_read_leb_fast(struct bilbyfs_info *bi, int lnum, struct bilbyfs_rbuf *rbuf)
{
int io_sz = bi->super.min_io_size;
int nb_read = bi->super.leb_size / io_sz;
int offs = 0;
int err;
int i;
bilbyfs_assert(rbuf->size == bi->super.leb_size);
for (i = 0; i < nb_read; i++) {
err = ubi_read(bi->ubi, lnum, rbuf->buf + offs, offs, io_sz);
if (err)
return err;
if (*(u64*)(rbuf->buf + offs) == 0xffffffffffffffff) {
break;
}
offs += io_sz;
}
rbuf->offs = 0;
return 0;
}
int wbuf_read_leb(struct bilbyfs_info *bi, int lnum, struct bilbyfs_rbuf *rbuf)
{
int io_sz = bi->super.min_io_size;
int nb_read = bi->super.leb_size / io_sz;
int offs = 0;
int err;
int i;
bilbyfs_assert(rbuf->size == bi->super.leb_size);
for (i = 0; i < nb_read; i++) {
err = ubi_read(bi->ubi, lnum, rbuf->buf + offs, offs, io_sz);
if (err)
return err;
offs += io_sz;
}
rbuf->offs = 0;
return 0;
}
int wbuf_erase(struct bilbyfs_info *bi, int lnum)
{
int err = ubi_leb_erase(bi->ubi, lnum);
bilbyfs_debug("ubi_leb_erase(lnum=%d) = %d\n", lnum, err);
return err;
}
void *wbuf_next_obj_addr(struct bilbyfs_info *bi, struct obj_addr *addr,
struct bilbyfs_rbuf *rbuf)
{
struct obj_ch *obj;
u32 leb_offs;
u32 rbuf_offs;
int err;
/* Ensures that rbuf covers addr */
bilbyfs_assert(rbuf->offs <= addr->offs);
/* Note: This function also works if addr->len = 0, it will
* just check that there exists a valid object at addr->offs
* and store its length in addr->len.
*/
leb_offs = addr->offs + addr->len;
if (leb_offs >= (rbuf->offs + rbuf->size))
return ERR_PTR(-ENOENT);
/* Make offs an offset in the rbuf */
rbuf_offs = leb_offs - rbuf->offs;
/* Get a pointer to obj in rbuf */
obj = rbuf->buf + rbuf_offs;
err = check_obj_header(obj, rbuf->size - rbuf_offs);
if (!err) {
addr->offs = leb_offs;
addr->len = le32_to_cpu(obj->len);
addr->sqnum = le64_to_cpu(obj->sqnum);
return obj;
}
/* Scan for pad byte */
if (err == -ENOENT) {
if (*((u8 *) obj) == BILBYFS_PAD_BYTE) {
addr->offs = leb_offs;
addr->len = 8;
return wbuf_next_obj_addr(bi, addr, rbuf);
}
}
return ERR_PTR(err);
}