#include "ebh.h"
int nor_create_eb_hdr(struct chfs_eb_hdr *ebhdr, int lnr);
int nand_create_eb_hdr(struct chfs_eb_hdr *ebhdr, int lnr);
int nor_calc_data_offs(struct chfs_ebh *ebh, int pebnr, int offset);
int nand_calc_data_offs(struct chfs_ebh *ebh, int pebnr, int offset);
int nor_read_eb_hdr(struct chfs_ebh *ebh, int pebnr, struct chfs_eb_hdr *ebhdr);
int nand_read_eb_hdr(struct chfs_ebh *ebh, int pebnr, struct chfs_eb_hdr *ebhdr);
int nor_write_eb_hdr(struct chfs_ebh *ebh, int pebnr, struct chfs_eb_hdr *ebhdr);
int nand_write_eb_hdr(struct chfs_ebh *ebh, int pebnr,struct chfs_eb_hdr *ebhdr);
int nor_check_eb_hdr(struct chfs_ebh *ebh, void *buf);
int nand_check_eb_hdr(struct chfs_ebh *ebh, void *buf);
int nor_mark_eb_hdr_dirty_flash(struct chfs_ebh *ebh, int pebnr, int lid);
int nor_invalidate_eb_hdr(struct chfs_ebh *ebh, int pebnr);
int mark_eb_hdr_free(struct chfs_ebh *ebh, int pebnr, int ec);
int ltree_entry_cmp(struct chfs_ltree_entry *le1, struct chfs_ltree_entry *le2);
int peb_in_use_cmp(struct chfs_peb *peb1, struct chfs_peb *peb2);
int peb_free_cmp(struct chfs_peb *peb1, struct chfs_peb *peb2);
int add_peb_to_erase_queue(struct chfs_ebh *ebh, int pebnr, int ec,struct peb_queue *queue);
struct chfs_peb * find_peb_in_use(struct chfs_ebh *ebh, int pebnr);
int add_peb_to_free(struct chfs_ebh *ebh, int pebnr, int ec);
int add_peb_to_in_use(struct chfs_ebh *ebh, int pebnr, int ec);
void erase_callback(struct flash_erase_instruction *ei);
int free_peb(struct chfs_ebh *ebh);
int release_peb(struct chfs_ebh *ebh, int pebnr);
void erase_thread(void *data);
static void erase_thread_start(struct chfs_ebh *ebh);
static void erase_thread_stop(struct chfs_ebh *ebh);
int scan_leb_used_cmp(struct chfs_scan_leb *sleb1, struct chfs_scan_leb *sleb2);
int nor_scan_add_to_used(struct chfs_ebh *ebh, struct chfs_scan_info *si,struct chfs_eb_hdr *ebhdr, int pebnr, int leb_status);
int nor_process_eb(struct chfs_ebh *ebh, struct chfs_scan_info *si,
int pebnr, struct chfs_eb_hdr *ebhdr);
int nand_scan_add_to_used(struct chfs_ebh *ebh, struct chfs_scan_info *si,struct chfs_eb_hdr *ebhdr, int pebnr);
int nand_process_eb(struct chfs_ebh *ebh, struct chfs_scan_info *si,
int pebnr, struct chfs_eb_hdr *ebhdr);
struct chfs_scan_info *chfs_scan(struct chfs_ebh *ebh);
void scan_info_destroy(struct chfs_scan_info *si);
int scan_media(struct chfs_ebh *ebh);
int get_peb(struct chfs_ebh *ebh);
int
nor_create_eb_hdr(struct chfs_eb_hdr *ebhdr, int lnr)
{
ebhdr->u.nor_hdr.lid = htole32(lnr);
return 0;
}
int
nand_create_eb_hdr(struct chfs_eb_hdr *ebhdr, int lnr)
{
ebhdr->u.nand_hdr.lid = htole32(lnr);
return 0;
}
int
nor_calc_data_offs(struct chfs_ebh *ebh, int pebnr, int offset)
{
return pebnr * ebh->flash_if->erasesize + offset +
CHFS_EB_EC_HDR_SIZE + CHFS_EB_HDR_NOR_SIZE;
}
int
nand_calc_data_offs(struct chfs_ebh *ebh, int pebnr, int offset)
{
return pebnr * ebh->flash_if->erasesize + offset +
2 * ebh->flash_if->page_size;
}
int
nor_read_eb_hdr(struct chfs_ebh *ebh,
int pebnr, struct chfs_eb_hdr *ebhdr)
{
int ret;
size_t retlen;
off_t ofs = pebnr * ebh->flash_if->erasesize;
KASSERT(pebnr >= 0 && pebnr < ebh->peb_nr);
ret = flash_read(ebh->flash_dev,
ofs, CHFS_EB_EC_HDR_SIZE,
&retlen, (unsigned char *) &ebhdr->ec_hdr);
if (ret || retlen != CHFS_EB_EC_HDR_SIZE)
return ret;
ofs += CHFS_EB_EC_HDR_SIZE;
ret = flash_read(ebh->flash_dev,
ofs, CHFS_EB_HDR_NOR_SIZE,
&retlen, (unsigned char *) &ebhdr->u.nor_hdr);
if (ret || retlen != CHFS_EB_HDR_NOR_SIZE)
return ret;
return 0;
}
int
nand_read_eb_hdr(struct chfs_ebh *ebh, int pebnr,
struct chfs_eb_hdr *ebhdr)
{
int ret;
size_t retlen;
off_t ofs;
KASSERT(pebnr >= 0 && pebnr < ebh->peb_nr);
ofs = pebnr * ebh->flash_if->erasesize;
ret = flash_read(ebh->flash_dev,
ofs, CHFS_EB_EC_HDR_SIZE, &retlen,
(unsigned char *) &ebhdr->ec_hdr);
if (ret || retlen != CHFS_EB_EC_HDR_SIZE)
return ret;
ofs += ebh->flash_if->page_size;
ret = flash_read(ebh->flash_dev,
ofs, CHFS_EB_HDR_NAND_SIZE, &retlen,
(unsigned char *) &ebhdr->u.nand_hdr);
if (ret || retlen != CHFS_EB_HDR_NAND_SIZE)
return ret;
return 0;
}
int
nor_write_eb_hdr(struct chfs_ebh *ebh, int pebnr, struct chfs_eb_hdr *ebhdr)
{
int ret, crc;
size_t retlen;
off_t ofs = pebnr * ebh->flash_if->erasesize + CHFS_EB_EC_HDR_SIZE;
ebhdr->u.nor_hdr.lid = ebhdr->u.nor_hdr.lid
| htole32(CHFS_LID_NOT_DIRTY_BIT);
crc = crc32(0, (uint8_t *)&ebhdr->u.nor_hdr + 4,
CHFS_EB_HDR_NOR_SIZE - 4);
ebhdr->u.nand_hdr.crc = htole32(crc);
KASSERT(pebnr >= 0 && pebnr < ebh->peb_nr);
ret = flash_write(ebh->flash_dev,
ofs, CHFS_EB_HDR_NOR_SIZE, &retlen,
(unsigned char *) &ebhdr->u.nor_hdr);
if (ret || retlen != CHFS_EB_HDR_NOR_SIZE)
return ret;
return 0;
}
int
nand_write_eb_hdr(struct chfs_ebh *ebh, int pebnr,
struct chfs_eb_hdr *ebhdr)
{
int ret, crc;
size_t retlen;
flash_off_t ofs;
KASSERT(pebnr >= 0 && pebnr < ebh->peb_nr);
ofs = pebnr * ebh->flash_if->erasesize +
ebh->flash_if->page_size;
ebhdr->u.nand_hdr.serial = htole64(++(*ebh->max_serial));
crc = crc32(0, (uint8_t *)&ebhdr->u.nand_hdr + 4,
CHFS_EB_HDR_NAND_SIZE - 4);
ebhdr->u.nand_hdr.crc = htole32(crc);
ret = flash_write(ebh->flash_dev, ofs,
CHFS_EB_HDR_NAND_SIZE, &retlen,
(unsigned char *) &ebhdr->u.nand_hdr);
if (ret || retlen != CHFS_EB_HDR_NAND_SIZE)
return ret;
return 0;
}
int
nor_check_eb_hdr(struct chfs_ebh *ebh, void *buf)
{
uint32_t magic, crc, hdr_crc;
struct chfs_eb_hdr *ebhdr = buf;
le32 lid_save;
if (check_pattern((void *) &ebhdr->ec_hdr,
0xFF, 0, CHFS_EB_EC_HDR_SIZE)) {
dbg_ebh("no header found\n");
return EBHDR_LEB_NO_HDR;
}
magic = le32toh(ebhdr->ec_hdr.magic);
if (magic != CHFS_MAGIC_BITMASK) {
dbg_ebh("bad magic bitmask(exp: %x found %x)\n",
CHFS_MAGIC_BITMASK, magic);
return EBHDR_LEB_BADMAGIC;
}
hdr_crc = le32toh(ebhdr->ec_hdr.crc_ec);
crc = crc32(0, (uint8_t *) &ebhdr->ec_hdr + 8, 4);
if (hdr_crc != crc) {
dbg_ebh("bad crc_ec found\n");
return EBHDR_LEB_BADCRC;
}
if (check_pattern((void *) &ebhdr->u.nor_hdr, 0xFF, 0,
CHFS_EB_HDR_NOR_SIZE)) {
dbg_ebh("free peb found\n");
return EBHDR_LEB_FREE;
}
if (ebhdr->u.nor_hdr.crc == 0 && ebhdr->u.nor_hdr.lid == 0) {
dbg_ebh("invalidated ebhdr found\n");
return EBHDR_LEB_INVALIDATED;
}
hdr_crc = le32toh(ebhdr->u.nor_hdr.crc);
lid_save = ebhdr->u.nor_hdr.lid;
ebhdr->u.nor_hdr.lid = ebhdr->u.nor_hdr.lid | htole32(
CHFS_LID_NOT_DIRTY_BIT);
crc = crc32(0, (uint8_t *) &ebhdr->u.nor_hdr + 4,
CHFS_EB_HDR_NOR_SIZE - 4);
ebhdr->u.nor_hdr.lid = lid_save;
if (crc != hdr_crc) {
dbg_ebh("bad crc found\n");
return EBHDR_LEB_BADCRC;
}
if (!(le32toh(lid_save) & CHFS_LID_NOT_DIRTY_BIT)) {
dbg_ebh("dirty ebhdr found\n");
return EBHDR_LEB_DIRTY;
}
return EBHDR_LEB_OK;
}
int
nand_check_eb_hdr(struct chfs_ebh *ebh, void *buf)
{
uint32_t magic, crc, hdr_crc;
struct chfs_eb_hdr *ebhdr = buf;
if (check_pattern((void *) &ebhdr->ec_hdr,
0xFF, 0, CHFS_EB_EC_HDR_SIZE)) {
dbg_ebh("no header found\n");
return EBHDR_LEB_NO_HDR;
}
magic = le32toh(ebhdr->ec_hdr.magic);
if (magic != CHFS_MAGIC_BITMASK) {
dbg_ebh("bad magic bitmask(exp: %x found %x)\n",
CHFS_MAGIC_BITMASK, magic);
return EBHDR_LEB_BADMAGIC;
}
hdr_crc = le32toh(ebhdr->ec_hdr.crc_ec);
crc = crc32(0, (uint8_t *) &ebhdr->ec_hdr + 8, 4);
if (hdr_crc != crc) {
dbg_ebh("bad crc_ec found\n");
return EBHDR_LEB_BADCRC;
}
if (check_pattern((void *) &ebhdr->u.nand_hdr, 0xFF, 0,
CHFS_EB_HDR_NAND_SIZE)) {
dbg_ebh("free peb found\n");
return EBHDR_LEB_FREE;
}
hdr_crc = le32toh(ebhdr->u.nand_hdr.crc);
crc = crc32(0, (uint8_t *) &ebhdr->u.nand_hdr + 4,
CHFS_EB_HDR_NAND_SIZE - 4);
if (crc != hdr_crc) {
dbg_ebh("bad crc found\n");
return EBHDR_LEB_BADCRC;
}
return EBHDR_LEB_OK;
}
int
nor_mark_eb_hdr_dirty_flash(struct chfs_ebh *ebh, int pebnr, int lid)
{
int ret;
size_t retlen;
off_t ofs;
lid = htole32(lid & CHFS_LID_DIRTY_BIT_MASK);
ofs = pebnr * ebh->flash_if->erasesize + CHFS_EB_EC_HDR_SIZE
+ CHFS_GET_MEMBER_POS(struct chfs_nor_eb_hdr , lid);
ret = flash_write(ebh->flash_dev, ofs, sizeof(lid), &retlen,
(unsigned char *) &lid);
if (ret || retlen != sizeof(lid)) {
chfs_err("can't mark peb dirty");
return ret;
}
return 0;
}
int
nor_invalidate_eb_hdr(struct chfs_ebh *ebh, int pebnr)
{
int ret;
size_t retlen;
off_t ofs;
char zero_buf[CHFS_INVALIDATE_SIZE];
memset(zero_buf, 0x0, CHFS_INVALIDATE_SIZE);
ofs = pebnr * ebh->flash_if->erasesize + CHFS_EB_EC_HDR_SIZE
+ CHFS_GET_MEMBER_POS(struct chfs_nor_eb_hdr, crc);
ret = flash_write(ebh->flash_dev,
ofs, CHFS_INVALIDATE_SIZE, &retlen,
(unsigned char *) &zero_buf);
if (ret || retlen != CHFS_INVALIDATE_SIZE) {
chfs_err("can't invalidate peb");
return ret;
}
return 0;
}
int
mark_eb_hdr_free(struct chfs_ebh *ebh, int pebnr, int ec)
{
int ret, crc;
size_t retlen;
off_t ofs;
struct chfs_eb_hdr *ebhdr;
ebhdr = kmem_alloc(sizeof(struct chfs_eb_hdr), KM_SLEEP);
ebhdr->ec_hdr.magic = htole32(CHFS_MAGIC_BITMASK);
ebhdr->ec_hdr.erase_cnt = htole32(ec);
crc = crc32(0, (uint8_t *) &ebhdr->ec_hdr + 8, 4);
ebhdr->ec_hdr.crc_ec = htole32(crc);
ofs = pebnr * ebh->flash_if->erasesize;
KASSERT(sizeof(ebhdr->ec_hdr) == CHFS_EB_EC_HDR_SIZE);
ret = flash_write(ebh->flash_dev,
ofs, CHFS_EB_EC_HDR_SIZE, &retlen,
(unsigned char *) &ebhdr->ec_hdr);
if (ret || retlen != CHFS_EB_EC_HDR_SIZE) {
chfs_err("can't mark peb as free: %d\n", pebnr);
kmem_free(ebhdr, sizeof(struct chfs_eb_hdr));
return ret;
}
kmem_free(ebhdr, sizeof(struct chfs_eb_hdr));
return 0;
}
int
ltree_entry_cmp(struct chfs_ltree_entry *le1,
struct chfs_ltree_entry *le2)
{
return (le1->lnr - le2->lnr);
}
RB_PROTOTYPE( ltree_rbtree, chfs_ltree_entry, rb, ltree_entry_cmp);
RB_GENERATE( ltree_rbtree, chfs_ltree_entry, rb, ltree_entry_cmp);
static struct chfs_ltree_entry *
ltree_lookup(struct chfs_ebh *ebh, int lnr)
{
struct chfs_ltree_entry le, *result;
le.lnr = lnr;
result = RB_FIND(ltree_rbtree, &ebh->ltree, &le);
return result;
}
static struct chfs_ltree_entry *
ltree_add_entry(struct chfs_ebh *ebh, int lnr)
{
struct chfs_ltree_entry *le, *result;
le = kmem_alloc(sizeof(struct chfs_ltree_entry), KM_SLEEP);
le->lnr = lnr;
le->users = 1;
rw_init(&le->mutex);
mutex_enter(&ebh->ltree_lock);
result = RB_INSERT(ltree_rbtree, &ebh->ltree, le);
if (result) {
result->users++;
kmem_free(le, sizeof(struct chfs_ltree_entry));
}
else {
result = le;
}
mutex_exit(&ebh->ltree_lock);
return result;
}
static int
leb_read_lock(struct chfs_ebh *ebh, int lnr)
{
struct chfs_ltree_entry *le;
le = ltree_add_entry(ebh, lnr);
if (!le)
return ENOMEM;
rw_enter(&le->mutex, RW_READER);
return 0;
}
static void
leb_read_unlock(struct chfs_ebh *ebh, int lnr)
{
struct chfs_ltree_entry *le;
mutex_enter(&ebh->ltree_lock);
le = ltree_lookup(ebh, lnr);
if (!le)
goto out;
le->users -= 1;
KASSERT(le->users >= 0);
rw_exit(&le->mutex);
if (le->users == 0) {
le = RB_REMOVE(ltree_rbtree, &ebh->ltree, le);
if (le) {
rw_destroy(&le->mutex);
kmem_free(le, sizeof(struct chfs_ltree_entry));
}
}
out:
mutex_exit(&ebh->ltree_lock);
}
static int
leb_write_lock(struct chfs_ebh *ebh, int lnr)
{
struct chfs_ltree_entry *le;
le = ltree_add_entry(ebh, lnr);
if (!le)
return ENOMEM;
rw_enter(&le->mutex, RW_WRITER);
return 0;
}
static void
leb_write_unlock(struct chfs_ebh *ebh, int lnr)
{
struct chfs_ltree_entry *le;
mutex_enter(&ebh->ltree_lock);
le = ltree_lookup(ebh, lnr);
if (!le)
goto out;
le->users -= 1;
KASSERT(le->users >= 0);
rw_exit(&le->mutex);
if (le->users == 0) {
RB_REMOVE(ltree_rbtree, &ebh->ltree, le);
rw_destroy(&le->mutex);
kmem_free(le, sizeof(struct chfs_ltree_entry));
}
out:
mutex_exit(&ebh->ltree_lock);
}
int
peb_in_use_cmp(struct chfs_peb *peb1, struct chfs_peb *peb2)
{
return (peb1->pebnr - peb2->pebnr);
}
int
peb_free_cmp(struct chfs_peb *peb1, struct chfs_peb *peb2)
{
int comp;
comp = peb1->erase_cnt - peb2->erase_cnt;
if (0 == comp)
comp = peb1->pebnr - peb2->pebnr;
return comp;
}
RB_PROTOTYPE(peb_in_use_rbtree, chfs_peb, u.rb, peb_in_use_cmp);
RB_GENERATE(peb_in_use_rbtree, chfs_peb, u.rb, peb_in_use_cmp);
RB_PROTOTYPE(peb_free_rbtree, chfs_peb, u.rb, peb_free_cmp);
RB_GENERATE(peb_free_rbtree, chfs_peb, u.rb, peb_free_cmp);
int
add_peb_to_erase_queue(struct chfs_ebh *ebh, int pebnr, int ec,
struct peb_queue *queue)
{
struct chfs_peb *peb;
peb = kmem_alloc(sizeof(struct chfs_peb), KM_SLEEP);
peb->erase_cnt = ec;
peb->pebnr = pebnr;
TAILQ_INSERT_TAIL(queue, peb, u.queue);
return 0;
}
struct chfs_peb *
find_peb_in_use(struct chfs_ebh *ebh, int pebnr)
{
struct chfs_peb peb, *result;
peb.pebnr = pebnr;
result = RB_FIND(peb_in_use_rbtree, &ebh->in_use, &peb);
return result;
}
int
add_peb_to_free(struct chfs_ebh *ebh, int pebnr, int ec)
{
struct chfs_peb *peb, *result;
peb = kmem_alloc(sizeof(struct chfs_peb), KM_SLEEP);
peb->erase_cnt = ec;
peb->pebnr = pebnr;
result = RB_INSERT(peb_free_rbtree, &ebh->free, peb);
if (result) {
kmem_free(peb, sizeof(struct chfs_peb));
return 1;
}
return 0;
}
int
add_peb_to_in_use(struct chfs_ebh *ebh, int pebnr, int ec)
{
struct chfs_peb *peb, *result;
peb = kmem_alloc(sizeof(struct chfs_peb), KM_SLEEP);
peb->erase_cnt = ec;
peb->pebnr = pebnr;
result = RB_INSERT(peb_in_use_rbtree, &ebh->in_use, peb);
if (result) {
kmem_free(peb, sizeof(struct chfs_peb));
return 1;
}
return 0;
}
void
erase_callback(struct flash_erase_instruction *ei)
{
int err;
struct chfs_erase_info_priv *priv = (void *) ei->ei_priv;
struct chfs_ebh *ebh = priv->ebh;
struct chfs_peb *peb = priv->peb;
peb->erase_cnt += 1;
if (ei->ei_state == FLASH_ERASE_DONE) {
err = ebh->ops->mark_eb_hdr_free(ebh,
peb->pebnr, peb->erase_cnt);
if (err) {
chfs_err(
"cannot mark eraseblock as free, PEB: %d\n",
peb->pebnr);
mutex_enter(&ebh->erase_lock);
add_peb_to_erase_queue(ebh, peb->pebnr, peb->erase_cnt,
&ebh->to_erase);
mutex_exit(&ebh->erase_lock);
kmem_free(peb, sizeof(struct chfs_peb));
return;
}
mutex_enter(&ebh->erase_lock);
err = add_peb_to_free(ebh, peb->pebnr, peb->erase_cnt);
mutex_exit(&ebh->erase_lock);
kmem_free(peb, sizeof(struct chfs_peb));
} else {
chfs_err("erase failed, state is: 0x%x\n", ei->ei_state);
add_peb_to_erase_queue(ebh, peb->pebnr, peb->erase_cnt, &ebh->to_erase);
kmem_free(peb, sizeof(struct chfs_peb));
}
}
int
free_peb(struct chfs_ebh *ebh)
{
int err, retries = 0;
off_t ofs;
struct chfs_peb *peb = NULL;
struct flash_erase_instruction *ei;
KASSERT(mutex_owned(&ebh->erase_lock));
if (!TAILQ_EMPTY(&ebh->fully_erased)) {
peb = TAILQ_FIRST(&ebh->fully_erased);
TAILQ_REMOVE(&ebh->fully_erased, peb, u.queue);
err = ebh->ops->mark_eb_hdr_free(ebh,
peb->pebnr, peb->erase_cnt);
if (err) {
goto out_free;
}
err = add_peb_to_free(ebh, peb->pebnr, peb->erase_cnt);
goto out_free;
}
peb = TAILQ_FIRST(&ebh->to_erase);
TAILQ_REMOVE(&ebh->to_erase, peb, u.queue);
mutex_exit(&ebh->erase_lock);
ofs = peb->pebnr * ebh->flash_if->erasesize;
ei = kmem_alloc(sizeof(struct flash_erase_instruction)
+ sizeof(struct chfs_erase_info_priv), KM_SLEEP);
retry:
memset(ei, 0, sizeof(*ei));
ei->ei_addr = ofs;
ei->ei_len = ebh->flash_if->erasesize;
ei->ei_callback = erase_callback;
ei->ei_priv = (unsigned long) (&ei[1]);
((struct chfs_erase_info_priv *) ei->ei_priv)->ebh = ebh;
((struct chfs_erase_info_priv *) ei->ei_priv)->peb = peb;
err = flash_erase(ebh->flash_dev, ei);
dbg_ebh("erased peb: %d\n", peb->pebnr);
KASSERT(err != EINVAL);
if (err) {
dbg_ebh("errno: %d, ei->ei_state: %d\n", err, ei->ei_state);
if (CHFS_MAX_GET_PEB_RETRIES < ++retries &&
ei->ei_state == FLASH_ERASE_FAILED) {
dbg_ebh("ebh markbad! 0x%jx\n", (uintmax_t )ofs);
err = flash_block_markbad(ebh->flash_dev, ofs);
if (!err) {
ebh->peb_nr--;
}
goto out;
}
chfs_err("can not erase PEB: %d, try again\n", peb->pebnr);
goto retry;
}
out:
mutex_enter(&ebh->erase_lock);
return err;
out_free:
kmem_free(peb, sizeof(struct chfs_peb));
return err;
}
int
release_peb(struct chfs_ebh *ebh, int pebnr)
{
int err = 0;
struct chfs_peb *peb;
mutex_enter(&ebh->erase_lock);
peb = find_peb_in_use(ebh, pebnr);
if (!peb) {
chfs_err("LEB is mapped, but is not in the 'in_use' "
"tree of ebh\n");
goto out_unlock;
}
err = add_peb_to_erase_queue(ebh, peb->pebnr, peb->erase_cnt,
&ebh->to_erase);
if (err)
goto out_unlock;
RB_REMOVE(peb_in_use_rbtree, &ebh->in_use, peb);
out_unlock:
mutex_exit(&ebh->erase_lock);
return err;
}
void
erase_thread(void *data) {
dbg_ebh("[EBH THREAD] erase thread started\n");
struct chfs_ebh *ebh = data;
int err;
mutex_enter(&ebh->erase_lock);
while (ebh->bg_erase.eth_running) {
if (TAILQ_EMPTY(&ebh->to_erase) &&
TAILQ_EMPTY(&ebh->fully_erased)) {
cv_timedwait_sig(&ebh->bg_erase.eth_wakeup,
&ebh->erase_lock, mstohz(100));
} else {
err = free_peb(ebh);
mutex_exit(&ebh->erase_lock);
if (err) {
chfs_err("freeing PEB failed in the"
" background thread: %d\n", err);
}
mutex_enter(&ebh->erase_lock);
}
}
mutex_exit(&ebh->erase_lock);
dbg_ebh("[EBH THREAD] erase thread stopped\n");
kthread_exit(0);
}
static void
erase_thread_start(struct chfs_ebh *ebh)
{
cv_init(&ebh->bg_erase.eth_wakeup, "ebheracv");
ebh->bg_erase.eth_running = true;
kthread_create(PRI_NONE, KTHREAD_MPSAFE | KTHREAD_MUSTJOIN, NULL,
erase_thread, ebh, &ebh->bg_erase.eth_thread, "ebherase");
}
static void
erase_thread_stop(struct chfs_ebh *ebh)
{
ebh->bg_erase.eth_running = false;
cv_signal(&ebh->bg_erase.eth_wakeup);
dbg_ebh("[EBH THREAD STOP] signaled\n");
kthread_join(ebh->bg_erase.eth_thread);
#ifdef BROKEN_KTH_JOIN
kpause("chfsebhjointh", false, mstohz(1000), NULL);
#endif
cv_destroy(&ebh->bg_erase.eth_wakeup);
}
int
scan_leb_used_cmp(struct chfs_scan_leb *sleb1, struct chfs_scan_leb *sleb2)
{
return (sleb1->lnr - sleb2->lnr);
}
RB_PROTOTYPE(scan_leb_used_rbtree, chfs_scan_leb, u.rb, scan_leb_used_cmp);
RB_GENERATE(scan_leb_used_rbtree, chfs_scan_leb, u.rb, scan_leb_used_cmp);
static int
scan_add_to_queue(struct chfs_scan_info *si, int pebnr, int erase_cnt,
struct scan_leb_queue *queue)
{
struct chfs_scan_leb *sleb;
sleb = kmem_alloc(sizeof(struct chfs_scan_leb), KM_SLEEP);
sleb->pebnr = pebnr;
sleb->erase_cnt = erase_cnt;
TAILQ_INSERT_TAIL(queue, sleb, u.queue);
return 0;
}
int
nor_scan_add_to_used(struct chfs_ebh *ebh, struct chfs_scan_info *si,
struct chfs_eb_hdr *ebhdr, int pebnr, int leb_status)
{
int err, lnr, ec;
struct chfs_scan_leb *sleb, *old;
lnr = CHFS_GET_LID(ebhdr->u.nor_hdr.lid);
ec = le32toh(ebhdr->ec_hdr.erase_cnt);
sleb = kmem_alloc(sizeof(struct chfs_scan_leb), KM_SLEEP);
sleb->erase_cnt = ec;
sleb->lnr = lnr;
sleb->pebnr = pebnr;
sleb->info = leb_status;
old = RB_INSERT(scan_leb_used_rbtree, &si->used, sleb);
if (old) {
kmem_free(sleb, sizeof(struct chfs_scan_leb));
if (EBHDR_LEB_DIRTY == leb_status &&
EBHDR_LEB_OK == old->info) {
return scan_add_to_queue(si, pebnr, ec, &si->erase);
} else {
err = scan_add_to_queue(si, old->pebnr,
old->erase_cnt, &si->erase);
if (err) {
return err;
}
old->erase_cnt = ec;
old->lnr = lnr;
old->pebnr = pebnr;
old->info = leb_status;
return 0;
}
}
return 0;
}
int
nor_process_eb(struct chfs_ebh *ebh, struct chfs_scan_info *si,
int pebnr, struct chfs_eb_hdr *ebhdr)
{
int err, erase_cnt, leb_status;
err = ebh->ops->read_eb_hdr(ebh, pebnr, ebhdr);
if (err)
return err;
erase_cnt = le32toh(ebhdr->ec_hdr.erase_cnt);
dbg_ebh("erase_cnt: %d\n", erase_cnt);
leb_status = ebh->ops->check_eb_hdr(ebh, ebhdr);
if (EBHDR_LEB_BADMAGIC == leb_status ||
EBHDR_LEB_BADCRC == leb_status) {
err = scan_add_to_queue(si, pebnr, erase_cnt, &si->corrupted);
return err;
}
else if (EBHDR_LEB_FREE == leb_status) {
err = scan_add_to_queue(si, pebnr, erase_cnt, &si->free);
goto count_mean;
}
else if (EBHDR_LEB_NO_HDR == leb_status) {
err = scan_add_to_queue(si, pebnr, erase_cnt, &si->erased);
return err;
}
else if (EBHDR_LEB_INVALIDATED == leb_status) {
err = scan_add_to_queue(si, pebnr, erase_cnt, &si->erase);
return err;
}
err = nor_scan_add_to_used(ebh, si, ebhdr, pebnr, leb_status);
if (err)
return err;
count_mean:
si->sum_of_ec += erase_cnt;
si->num_of_eb++;
return err;
}
int
nand_scan_add_to_used(struct chfs_ebh *ebh, struct chfs_scan_info *si,
struct chfs_eb_hdr *ebhdr, int pebnr)
{
int err, lnr, ec;
struct chfs_scan_leb *sleb, *old;
uint64_t serial = le64toh(ebhdr->u.nand_hdr.serial);
lnr = CHFS_GET_LID(ebhdr->u.nor_hdr.lid);
ec = le32toh(ebhdr->ec_hdr.erase_cnt);
sleb = kmem_alloc(sizeof(struct chfs_scan_leb), KM_SLEEP);
sleb->erase_cnt = ec;
sleb->lnr = lnr;
sleb->pebnr = pebnr;
sleb->info = serial;
old = RB_INSERT(scan_leb_used_rbtree, &si->used, sleb);
if (old) {
kmem_free(sleb, sizeof(struct chfs_scan_leb));
if (serial < old->info)
return scan_add_to_queue(si, pebnr, ec, &si->erase);
else {
err = scan_add_to_queue(si,
old->pebnr, old->erase_cnt, &si->erase);
if (err)
return err;
old->erase_cnt = ec;
old->lnr = lnr;
old->pebnr = pebnr;
old->info = serial;
return 0;
}
}
return 0;
}
int
nand_process_eb(struct chfs_ebh *ebh, struct chfs_scan_info *si,
int pebnr, struct chfs_eb_hdr *ebhdr)
{
int err, erase_cnt, leb_status;
uint64_t max_serial;
bool is_bad;
err = flash_block_isbad(ebh->flash_dev,
pebnr * ebh->flash_if->erasesize, &is_bad);
if (err) {
chfs_err("checking block is bad failed\n");
return err;
}
if (is_bad) {
si->bad_peb_cnt++;
return 0;
}
err = ebh->ops->read_eb_hdr(ebh, pebnr, ebhdr);
if (err)
return err;
erase_cnt = le32toh(ebhdr->ec_hdr.erase_cnt);
leb_status = ebh->ops->check_eb_hdr(ebh, ebhdr);
if (EBHDR_LEB_BADMAGIC == leb_status ||
EBHDR_LEB_BADCRC == leb_status) {
err = scan_add_to_queue(si, pebnr, erase_cnt, &si->corrupted);
return err;
}
else if (EBHDR_LEB_FREE == leb_status) {
err = scan_add_to_queue(si, pebnr, erase_cnt, &si->free);
goto count_mean;
}
else if (EBHDR_LEB_NO_HDR == leb_status) {
err = scan_add_to_queue(si, pebnr, erase_cnt, &si->erased);
return err;
}
err = nand_scan_add_to_used(ebh, si, ebhdr, pebnr);
if (err)
return err;
max_serial = le64toh(ebhdr->u.nand_hdr.serial);
if (max_serial > *ebh->max_serial) {
*ebh->max_serial = max_serial;
}
count_mean:
si->sum_of_ec += erase_cnt;
si->num_of_eb++;
return err;
}
struct chfs_scan_info *
chfs_scan(struct chfs_ebh *ebh)
{
struct chfs_scan_info *si;
struct chfs_eb_hdr *ebhdr;
int pebnr, err;
si = kmem_alloc(sizeof(*si), KM_SLEEP);
TAILQ_INIT(&si->corrupted);
TAILQ_INIT(&si->free);
TAILQ_INIT(&si->erase);
TAILQ_INIT(&si->erased);
RB_INIT(&si->used);
si->bad_peb_cnt = 0;
si->num_of_eb = 0;
si->sum_of_ec = 0;
ebhdr = kmem_alloc(sizeof(*ebhdr), KM_SLEEP);
for (pebnr = 0; pebnr < ebh->peb_nr; pebnr++) {
dbg_ebh("processing PEB %d\n", pebnr);
err = ebh->ops->process_eb(ebh, si, pebnr, ebhdr);
if (err < 0)
goto out_ebhdr;
}
kmem_free(ebhdr, sizeof(*ebhdr));
dbg_ebh("[CHFS_SCAN] scanning information collected\n");
return si;
out_ebhdr:
kmem_free(ebhdr, sizeof(*ebhdr));
kmem_free(si, sizeof(*si));
return NULL;
}
void
scan_info_destroy(struct chfs_scan_info *si)
{
EBH_QUEUE_DESTROY(&si->corrupted,
struct chfs_scan_leb, u.queue);
EBH_QUEUE_DESTROY(&si->erase,
struct chfs_scan_leb, u.queue);
EBH_QUEUE_DESTROY(&si->erased,
struct chfs_scan_leb, u.queue);
EBH_QUEUE_DESTROY(&si->free,
struct chfs_scan_leb, u.queue);
EBH_TREE_DESTROY(scan_leb_used_rbtree,
&si->used, struct chfs_scan_leb);
kmem_free(si, sizeof(*si));
dbg_ebh("[SCAN_INFO_DESTROY] scanning information destroyed\n");
}
int
scan_media(struct chfs_ebh *ebh)
{
int err, i, avg_ec;
struct chfs_scan_info *si;
struct chfs_scan_leb *sleb;
si = chfs_scan(ebh);
mutex_init(&ebh->ltree_lock, MUTEX_DEFAULT, IPL_NONE);
mutex_init(&ebh->erase_lock, MUTEX_DEFAULT, IPL_NONE);
RB_INIT(&ebh->ltree);
RB_INIT(&ebh->free);
RB_INIT(&ebh->in_use);
TAILQ_INIT(&ebh->to_erase);
TAILQ_INIT(&ebh->fully_erased);
mutex_init(&ebh->alc_mutex, MUTEX_DEFAULT, IPL_NONE);
ebh->peb_nr -= si->bad_peb_cnt;
erase_thread_start(ebh);
ebh->lmap = kmem_alloc(ebh->peb_nr * sizeof(int), KM_SLEEP);
for (i = 0; i < ebh->peb_nr; i++) {
ebh->lmap[i] = EBH_LEB_UNMAPPED;
}
if (si->num_of_eb == 0) {
avg_ec = 0;
}
else {
avg_ec = (int) (si->sum_of_ec / si->num_of_eb);
}
dbg_ebh("num_of_eb: %d\n", si->num_of_eb);
mutex_enter(&ebh->erase_lock);
RB_FOREACH(sleb, scan_leb_used_rbtree, &si->used) {
ebh->lmap[sleb->lnr] = sleb->pebnr;
err = add_peb_to_in_use(ebh, sleb->pebnr, sleb->erase_cnt);
if (err)
goto out_free;
}
TAILQ_FOREACH(sleb, &si->erased, u.queue) {
err = add_peb_to_erase_queue(ebh, sleb->pebnr, avg_ec,
&ebh->fully_erased);
if (err)
goto out_free;
}
TAILQ_FOREACH(sleb, &si->erase, u.queue) {
err = add_peb_to_erase_queue(ebh, sleb->pebnr, avg_ec,
&ebh->to_erase);
if (err)
goto out_free;
}
TAILQ_FOREACH(sleb, &si->free, u.queue) {
err = add_peb_to_free(ebh, sleb->pebnr, sleb->erase_cnt);
if (err)
goto out_free;
}
TAILQ_FOREACH(sleb, &si->corrupted, u.queue) {
err = add_peb_to_erase_queue(ebh, sleb->pebnr, avg_ec,
&ebh->to_erase);
if (err)
goto out_free;
}
mutex_exit(&ebh->erase_lock);
scan_info_destroy(si);
return 0;
out_free:
mutex_exit(&ebh->erase_lock);
kmem_free(ebh->lmap, ebh->peb_nr * sizeof(int));
scan_info_destroy(si);
dbg_ebh("[SCAN_MEDIA] returning with error: %d\n", err);
return err;
}
int
ebh_open(struct chfs_ebh *ebh, dev_t dev)
{
int err;
ebh->flash_dev = flash_get_device(dev);
if (!ebh->flash_dev) {
aprint_error("ebh_open: cant get flash device\n");
return ENODEV;
}
ebh->flash_if = flash_get_interface(dev);
if (!ebh->flash_if) {
aprint_error("ebh_open: cant get flash interface\n");
return ENODEV;
}
ebh->flash_size = flash_get_size(dev);
ebh->peb_nr = ebh->flash_size / ebh->flash_if->erasesize;
ebh->ops = kmem_alloc(sizeof(struct chfs_ebh_ops), KM_SLEEP);
switch (ebh->flash_if->type) {
case FLASH_TYPE_NOR:
ebh->eb_size = ebh->flash_if->erasesize -
CHFS_EB_EC_HDR_SIZE - CHFS_EB_HDR_NOR_SIZE;
ebh->ops->read_eb_hdr = nor_read_eb_hdr;
ebh->ops->write_eb_hdr = nor_write_eb_hdr;
ebh->ops->check_eb_hdr = nor_check_eb_hdr;
ebh->ops->mark_eb_hdr_dirty_flash =
nor_mark_eb_hdr_dirty_flash;
ebh->ops->invalidate_eb_hdr = nor_invalidate_eb_hdr;
ebh->ops->mark_eb_hdr_free = mark_eb_hdr_free;
ebh->ops->process_eb = nor_process_eb;
ebh->ops->create_eb_hdr = nor_create_eb_hdr;
ebh->ops->calc_data_offs = nor_calc_data_offs;
ebh->max_serial = NULL;
break;
case FLASH_TYPE_NAND:
ebh->eb_size = ebh->flash_if->erasesize -
2 * ebh->flash_if->page_size;
ebh->ops->read_eb_hdr = nand_read_eb_hdr;
ebh->ops->write_eb_hdr = nand_write_eb_hdr;
ebh->ops->check_eb_hdr = nand_check_eb_hdr;
ebh->ops->mark_eb_hdr_free = mark_eb_hdr_free;
ebh->ops->mark_eb_hdr_dirty_flash = NULL;
ebh->ops->invalidate_eb_hdr = NULL;
ebh->ops->process_eb = nand_process_eb;
ebh->ops->create_eb_hdr = nand_create_eb_hdr;
ebh->ops->calc_data_offs = nand_calc_data_offs;
ebh->max_serial = kmem_alloc(sizeof(uint64_t), KM_SLEEP);
*ebh->max_serial = 0;
break;
default:
return 1;
}
printf("opening ebh: eb_size: %zu\n", ebh->eb_size);
err = scan_media(ebh);
if (err) {
dbg_ebh("Scan failed.");
kmem_free(ebh->ops, sizeof(struct chfs_ebh_ops));
kmem_free(ebh, sizeof(struct chfs_ebh));
return err;
}
return 0;
}
int
ebh_close(struct chfs_ebh *ebh)
{
erase_thread_stop(ebh);
EBH_TREE_DESTROY(peb_free_rbtree, &ebh->free, struct chfs_peb);
EBH_TREE_DESTROY(peb_in_use_rbtree, &ebh->in_use, struct chfs_peb);
EBH_QUEUE_DESTROY(&ebh->fully_erased, struct chfs_peb, u.queue);
EBH_QUEUE_DESTROY(&ebh->to_erase, struct chfs_peb, u.queue);
EBH_TREE_DESTROY_MUTEX(ltree_rbtree, &ebh->ltree,
struct chfs_ltree_entry);
KASSERT(!mutex_owned(&ebh->ltree_lock));
KASSERT(!mutex_owned(&ebh->alc_mutex));
KASSERT(!mutex_owned(&ebh->erase_lock));
mutex_destroy(&ebh->ltree_lock);
mutex_destroy(&ebh->alc_mutex);
mutex_destroy(&ebh->erase_lock);
kmem_free(ebh->ops, sizeof(struct chfs_ebh_ops));
kmem_free(ebh, sizeof(struct chfs_ebh));
return 0;
}
int
ebh_read_leb(struct chfs_ebh *ebh, int lnr, char *buf, uint32_t offset,
size_t len, size_t *retlen)
{
int err, pebnr;
off_t data_offset;
KASSERT(offset + len <= ebh->eb_size);
err = leb_read_lock(ebh, lnr);
if (err)
return err;
pebnr = ebh->lmap[lnr];
if (EBH_LEB_UNMAPPED == pebnr) {
leb_read_unlock(ebh, lnr);
memset(buf, 0xFF, len);
return 0;
}
data_offset = ebh->ops->calc_data_offs(ebh, pebnr, offset);
err = flash_read(ebh->flash_dev, data_offset, len, retlen,
(unsigned char *) buf);
if (err)
goto out_free;
KASSERT(len == *retlen);
out_free:
leb_read_unlock(ebh, lnr);
return err;
}
int
get_peb(struct chfs_ebh *ebh)
{
int err, pebnr;
struct chfs_peb *peb;
retry:
mutex_enter(&ebh->erase_lock);
if (RB_EMPTY(&ebh->free)) {
if (TAILQ_EMPTY(&ebh->to_erase) &&
TAILQ_EMPTY(&ebh->fully_erased)) {
mutex_exit(&ebh->erase_lock);
return ENOSPC;
}
err = free_peb(ebh);
mutex_exit(&ebh->erase_lock);
if (err)
return err;
goto retry;
}
peb = RB_MIN(peb_free_rbtree, &ebh->free);
pebnr = peb->pebnr;
RB_REMOVE(peb_free_rbtree, &ebh->free, peb);
err = add_peb_to_in_use(ebh, peb->pebnr, peb->erase_cnt);
if (err)
pebnr = err;
kmem_free(peb, sizeof(struct chfs_peb));
mutex_exit(&ebh->erase_lock);
return pebnr;
}
int
ebh_write_leb(struct chfs_ebh *ebh, int lnr, char *buf, uint32_t offset,
size_t len, size_t *retlen)
{
int err, pebnr, retries = 0;
off_t data_offset;
struct chfs_eb_hdr *ebhdr;
dbg("offset: %d | len: %zu | (offset+len): %zu "
" | ebsize: %zu\n", offset, len, (offset+len), ebh->eb_size);
KASSERT(offset + len <= ebh->eb_size);
err = leb_write_lock(ebh, lnr);
if (err)
return err;
pebnr = ebh->lmap[lnr];
if (pebnr != EBH_LEB_UNMAPPED) {
data_offset = ebh->ops->calc_data_offs(ebh, pebnr, offset);
err = flash_write(ebh->flash_dev, data_offset, len, retlen,
(unsigned char *) buf);
if (err) {
chfs_err("error %d while writing %zu bytes to PEB "
"%d:%ju, written %zu bytes\n",
err, len, pebnr, (uintmax_t )offset, *retlen);
} else {
KASSERT(len == *retlen);
}
leb_write_unlock(ebh, lnr);
return err;
}
ebhdr = kmem_alloc(sizeof(struct chfs_eb_hdr), KM_SLEEP);
ebh->ops->create_eb_hdr(ebhdr, lnr);
retry:
pebnr = get_peb(ebh);
if (pebnr < 0) {
leb_write_unlock(ebh, lnr);
kmem_free(ebhdr, sizeof(struct chfs_eb_hdr));
return pebnr;
}
err = ebh->ops->write_eb_hdr(ebh, pebnr, ebhdr);
if (err) {
chfs_warn(
"error writing eraseblock header: LEB %d , PEB %d\n",
lnr, pebnr);
goto write_error;
}
if (len) {
data_offset = ebh->ops->calc_data_offs(ebh, pebnr, offset);
err = flash_write(ebh->flash_dev,
data_offset, len, retlen, (unsigned char *) buf);
if (err) {
chfs_err("error %d while writing %zu bytes to PEB "
" %d:%ju, written %zu bytes\n",
err, len, pebnr, (uintmax_t )offset, *retlen);
goto write_error;
}
}
ebh->lmap[lnr] = pebnr;
leb_write_unlock(ebh, lnr);
kmem_free(ebhdr, sizeof(struct chfs_eb_hdr));
return 0;
write_error: err = release_peb(ebh, pebnr);
if (err || CHFS_MAX_GET_PEB_RETRIES < ++retries) {
leb_write_unlock(ebh, lnr);
kmem_free(ebhdr, sizeof(struct chfs_eb_hdr));
return err;
}
goto retry;
}
int
ebh_erase_leb(struct chfs_ebh *ebh, int lnr)
{
int err, pebnr;
leb_write_lock(ebh, lnr);
pebnr = ebh->lmap[lnr];
if (pebnr < 0) {
leb_write_unlock(ebh, lnr);
return EBH_LEB_UNMAPPED;
}
err = release_peb(ebh, pebnr);
if (err)
goto out_unlock;
ebh->lmap[lnr] = EBH_LEB_UNMAPPED;
cv_signal(&ebh->bg_erase.eth_wakeup);
out_unlock:
leb_write_unlock(ebh, lnr);
return err;
}
int
ebh_map_leb(struct chfs_ebh *ebh, int lnr)
{
int err, pebnr, retries = 0;
struct chfs_eb_hdr *ebhdr;
ebhdr = kmem_alloc(sizeof(struct chfs_eb_hdr), KM_SLEEP);
err = leb_write_lock(ebh, lnr);
if (err) {
kmem_free(ebhdr, sizeof(struct chfs_eb_hdr));
return err;
}
retry:
pebnr = get_peb(ebh);
if (pebnr < 0) {
err = pebnr;
goto out_unlock;
}
ebh->ops->create_eb_hdr(ebhdr, lnr);
err = ebh->ops->write_eb_hdr(ebh, pebnr, ebhdr);
if (err) {
chfs_warn(
"error writing eraseblock header: LEB %d , PEB %d\n",
lnr, pebnr);
goto write_error;
}
ebh->lmap[lnr] = pebnr;
out_unlock:
leb_write_unlock(ebh, lnr);
return err;
write_error:
err = release_peb(ebh, pebnr);
if (err || CHFS_MAX_GET_PEB_RETRIES < ++retries) {
leb_write_unlock(ebh, lnr);
kmem_free(ebhdr, sizeof(struct chfs_eb_hdr));
return err;
}
goto retry;
}
int
ebh_unmap_leb(struct chfs_ebh *ebh, int lnr)
{
int err;
if (ebh_is_mapped(ebh, lnr) < 0)
return 0;
err = ebh_erase_leb(ebh, lnr);
return err;
}
int
ebh_is_mapped(struct chfs_ebh *ebh, int lnr)
{
int err, result;
err = leb_read_lock(ebh, lnr);
if (err)
return err;
result = ebh->lmap[lnr];
leb_read_unlock(ebh, lnr);
return result;
}
int
ebh_change_leb(struct chfs_ebh *ebh, int lnr, char *buf, size_t len,
size_t *retlen)
{
int err, pebnr, pebnr_old, retries = 0;
off_t data_offset;
struct chfs_peb *peb = NULL;
struct chfs_eb_hdr *ebhdr;
if (ebh_is_mapped(ebh, lnr) < 0)
return EBH_LEB_UNMAPPED;
if (len == 0) {
err = ebh_unmap_leb(ebh, lnr);
if (err)
return err;
return ebh_map_leb(ebh, lnr);
}
ebhdr = kmem_alloc(sizeof(struct chfs_eb_hdr), KM_SLEEP);
pebnr_old = ebh->lmap[lnr];
mutex_enter(&ebh->alc_mutex);
err = leb_write_lock(ebh, lnr);
if (err)
goto out_mutex;
if (ebh->ops->mark_eb_hdr_dirty_flash) {
err = ebh->ops->mark_eb_hdr_dirty_flash(ebh, pebnr_old, lnr);
if (err)
goto out_unlock;
}
ebh->ops->create_eb_hdr(ebhdr, lnr);
retry:
pebnr = get_peb(ebh);
if (pebnr < 0) {
leb_write_unlock(ebh, lnr);
mutex_exit(&ebh->alc_mutex);
kmem_free(ebhdr, sizeof(struct chfs_eb_hdr));
return pebnr;
}
err = ebh->ops->write_eb_hdr(ebh, pebnr, ebhdr);
if (err) {
chfs_warn(
"error writing eraseblock header: LEB %d , PEB %d",
lnr, pebnr);
goto write_error;
}
data_offset = ebh->ops->calc_data_offs(ebh, pebnr, 0);
err = flash_write(ebh->flash_dev, data_offset, len, retlen,
(unsigned char *) buf);
if (err) {
chfs_err("error %d while writing %zu bytes to PEB %d:%ju,"
" written %zu bytes",
err, len, pebnr, (uintmax_t)data_offset, *retlen);
goto write_error;
}
ebh->lmap[lnr] = pebnr;
if (ebh->ops->invalidate_eb_hdr) {
err = ebh->ops->invalidate_eb_hdr(ebh, pebnr_old);
if (err)
goto out_unlock;
}
peb = find_peb_in_use(ebh, pebnr_old);
err = release_peb(ebh, peb->pebnr);
out_unlock:
leb_write_unlock(ebh, lnr);
out_mutex:
mutex_exit(&ebh->alc_mutex);
kmem_free(ebhdr, sizeof(struct chfs_eb_hdr));
kmem_free(peb, sizeof(struct chfs_peb));
return err;
write_error:
err = release_peb(ebh, pebnr);
if (err || CHFS_MAX_GET_PEB_RETRIES < ++retries) {
leb_write_unlock(ebh, lnr);
mutex_exit(&ebh->alc_mutex);
kmem_free(ebhdr, sizeof(struct chfs_eb_hdr));
return err;
}
goto retry;
}