diff options
author | gber <gber@FreeBSD.org> | 2012-05-17 10:11:18 +0000 |
---|---|---|
committer | gber <gber@FreeBSD.org> | 2012-05-17 10:11:18 +0000 |
commit | 6f7c7353004e2ff9709b326a4008ce8ea63d9270 (patch) | |
tree | a325137a898341311de8641f7212e28b7d87950e /sys/fs/nandfs/nandfs_cleaner.c | |
parent | 661b9d94414ea6d11d5b7960aef1f172975ce52b (diff) | |
download | FreeBSD-src-6f7c7353004e2ff9709b326a4008ce8ea63d9270.zip FreeBSD-src-6f7c7353004e2ff9709b326a4008ce8ea63d9270.tar.gz |
Import work done under project/nand (@235533) into head.
The NAND Flash environment consists of several distinct components:
- NAND framework (drivers harness for NAND controllers and NAND chips)
- NAND simulator (NANDsim)
- NAND file system (NAND FS)
- Companion tools and utilities
- Documentation (manual pages)
This work is still experimental. Please use with caution.
Obtained from: Semihalf
Supported by: FreeBSD Foundation, Juniper Networks
Diffstat (limited to 'sys/fs/nandfs/nandfs_cleaner.c')
-rw-r--r-- | sys/fs/nandfs/nandfs_cleaner.c | 621 |
1 files changed, 621 insertions, 0 deletions
diff --git a/sys/fs/nandfs/nandfs_cleaner.c b/sys/fs/nandfs/nandfs_cleaner.c new file mode 100644 index 0000000..9257c10 --- /dev/null +++ b/sys/fs/nandfs/nandfs_cleaner.c @@ -0,0 +1,621 @@ +/*- + * Copyright (c) 2010-2012 Semihalf. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mount.h> +#include <sys/mutex.h> +#include <sys/buf.h> +#include <sys/namei.h> +#include <sys/vnode.h> +#include <sys/bio.h> + +#include <fs/nandfs/nandfs_mount.h> +#include <fs/nandfs/nandfs.h> +#include <fs/nandfs/nandfs_subr.h> + +#define NANDFS_CLEANER_KILL 1 + +static void nandfs_cleaner(struct nandfs_device *); +static int nandfs_cleaner_clean_segments(struct nandfs_device *, + struct nandfs_vinfo *, uint32_t, struct nandfs_period *, uint32_t, + struct nandfs_bdesc *, uint32_t, uint64_t *, uint32_t); + +static int +nandfs_process_bdesc(struct nandfs_device *nffsdev, struct nandfs_bdesc *bd, + uint64_t nmembs); + +static void +nandfs_wakeup_wait_cleaner(struct nandfs_device *fsdev, int reason) +{ + + mtx_lock(&fsdev->nd_clean_mtx); + if (reason == NANDFS_CLEANER_KILL) + fsdev->nd_cleaner_exit = 1; + if (fsdev->nd_cleaning == 0) { + fsdev->nd_cleaning = 1; + wakeup(&fsdev->nd_cleaning); + } + cv_wait(&fsdev->nd_clean_cv, &fsdev->nd_clean_mtx); + mtx_unlock(&fsdev->nd_clean_mtx); +} + +int +nandfs_start_cleaner(struct nandfs_device *fsdev) +{ + int error; + + MPASS(fsdev->nd_cleaner == NULL); + + fsdev->nd_cleaner_exit = 0; + + error = kthread_add((void(*)(void *))nandfs_cleaner, fsdev, NULL, + &fsdev->nd_cleaner, 0, 0, "nandfs_cleaner"); + if (error) + printf("nandfs: could not start cleaner: %d\n", error); + + return (error); +} + +int +nandfs_stop_cleaner(struct nandfs_device *fsdev) +{ + + MPASS(fsdev->nd_cleaner != NULL); + nandfs_wakeup_wait_cleaner(fsdev, NANDFS_CLEANER_KILL); + fsdev->nd_cleaner = NULL; + + DPRINTF(CLEAN, ("cleaner stopped\n")); + return (0); +} + +static int +nandfs_cleaner_finished(struct nandfs_device *fsdev) +{ + int exit; + + mtx_lock(&fsdev->nd_clean_mtx); + fsdev->nd_cleaning = 0; + if (!fsdev->nd_cleaner_exit) { + DPRINTF(CLEAN, ("%s: sleep\n", __func__)); + msleep(&fsdev->nd_cleaning, &fsdev->nd_clean_mtx, PRIBIO, "-", + hz * nandfs_cleaner_interval); + } + exit = fsdev->nd_cleaner_exit; + cv_broadcast(&fsdev->nd_clean_cv); + mtx_unlock(&fsdev->nd_clean_mtx); + if (exit) { + DPRINTF(CLEAN, ("%s: no longer active\n", __func__)); + return (1); + } + + return (0); +} + +static void +print_suinfo(struct nandfs_suinfo *suinfo, int nsegs) +{ + int i; + + for (i = 0; i < nsegs; i++) { + DPRINTF(CLEAN, ("%jx %jd %c%c%c %10u\n", + suinfo[i].nsi_num, suinfo[i].nsi_lastmod, + (suinfo[i].nsi_flags & + (NANDFS_SEGMENT_USAGE_ACTIVE) ? 'a' : '-'), + (suinfo[i].nsi_flags & + (NANDFS_SEGMENT_USAGE_DIRTY) ? 'd' : '-'), + (suinfo[i].nsi_flags & + (NANDFS_SEGMENT_USAGE_ERROR) ? 'e' : '-'), + suinfo[i].nsi_blocks)); + } +} + +static int +nandfs_cleaner_vblock_is_alive(struct nandfs_device *fsdev, + struct nandfs_vinfo *vinfo, struct nandfs_cpinfo *cp, uint32_t ncps) +{ + int64_t idx, min, max; + + if (vinfo->nvi_end >= fsdev->nd_last_cno) + return (1); + + if (ncps == 0) + return (0); + + if (vinfo->nvi_end < cp[0].nci_cno || + vinfo->nvi_start > cp[ncps - 1].nci_cno) + return (0); + + idx = min = 0; + max = ncps - 1; + while (min <= max) { + idx = (min + max) / 2; + if (vinfo->nvi_start == cp[idx].nci_cno) + return (1); + if (vinfo->nvi_start < cp[idx].nci_cno) + max = idx - 1; + else + min = idx + 1; + } + + return (vinfo->nvi_end >= cp[idx].nci_cno); +} + +static void +nandfs_cleaner_vinfo_mark_alive(struct nandfs_device *fsdev, + struct nandfs_vinfo *vinfo, uint32_t nmembs, struct nandfs_cpinfo *cp, + uint32_t ncps) +{ + uint32_t i; + + for (i = 0; i < nmembs; i++) + vinfo[i].nvi_alive = + nandfs_cleaner_vblock_is_alive(fsdev, &vinfo[i], cp, ncps); +} + +static int +nandfs_cleaner_bdesc_is_alive(struct nandfs_device *fsdev, + struct nandfs_bdesc *bdesc) +{ + int alive; + + alive = bdesc->bd_oblocknr == bdesc->bd_blocknr; + if (!alive) + MPASS(abs(bdesc->bd_oblocknr - bdesc->bd_blocknr) > 2); + + return (alive); +} + +static void +nandfs_cleaner_bdesc_mark_alive(struct nandfs_device *fsdev, + struct nandfs_bdesc *bdesc, uint32_t nmembs) +{ + uint32_t i; + + for (i = 0; i < nmembs; i++) + bdesc[i].bd_alive = nandfs_cleaner_bdesc_is_alive(fsdev, + &bdesc[i]); +} + +static void +nandfs_cleaner_iterate_psegment(struct nandfs_device *fsdev, + struct nandfs_segment_summary *segsum, union nandfs_binfo *binfo, + nandfs_daddr_t blk, struct nandfs_vinfo **vipp, struct nandfs_bdesc **bdpp) +{ + int i; + + DPRINTF(CLEAN, ("%s nbinfos %x\n", __func__, segsum->ss_nbinfos)); + for (i = 0; i < segsum->ss_nbinfos; i++) { + if (binfo[i].bi_v.bi_ino == NANDFS_DAT_INO) { + (*bdpp)->bd_oblocknr = blk + segsum->ss_nblocks - + segsum->ss_nbinfos + i; + /* + * XXX Hack + */ + if (segsum->ss_flags & NANDFS_SS_SR) + (*bdpp)->bd_oblocknr--; + (*bdpp)->bd_level = binfo[i].bi_dat.bi_level; + (*bdpp)->bd_offset = binfo[i].bi_dat.bi_blkoff; + (*bdpp)++; + } else { + (*vipp)->nvi_ino = binfo[i].bi_v.bi_ino; + (*vipp)->nvi_vblocknr = binfo[i].bi_v.bi_vblocknr; + (*vipp)++; + } + } +} + +static int +nandfs_cleaner_iterate_segment(struct nandfs_device *fsdev, uint64_t segno, + struct nandfs_vinfo **vipp, struct nandfs_bdesc **bdpp, int *select) +{ + struct nandfs_segment_summary *segsum; + union nandfs_binfo *binfo; + struct buf *bp; + uint32_t nblocks; + nandfs_daddr_t curr, start, end; + int error = 0; + + nandfs_get_segment_range(fsdev, segno, &start, &end); + + DPRINTF(CLEAN, ("%s: segno %jx start %jx end %jx\n", __func__, segno, + start, end)); + + *select = 0; + + for (curr = start; curr < end; curr += nblocks) { + error = nandfs_dev_bread(fsdev, curr, NOCRED, 0, &bp); + if (error) { + brelse(bp); + nandfs_error("%s: couldn't load segment summary of %jx: %d\n", + __func__, segno, error); + return (error); + } + + segsum = (struct nandfs_segment_summary *)bp->b_data; + binfo = (union nandfs_binfo *)(bp->b_data + segsum->ss_bytes); + + if (!nandfs_segsum_valid(segsum)) { + brelse(bp); + nandfs_error("nandfs: invalid summary of segment %jx\n", segno); + return (error); + } + + DPRINTF(CLEAN, ("%s: %jx magic %x bytes %x nblocks %x nbinfos " + "%x\n", __func__, segno, segsum->ss_magic, segsum->ss_bytes, + segsum->ss_nblocks, segsum->ss_nbinfos)); + + nandfs_cleaner_iterate_psegment(fsdev, segsum, binfo, curr, + vipp, bdpp); + nblocks = segsum->ss_nblocks; + brelse(bp); + } + + if (error == 0) + *select = 1; + + return (error); +} + +static int +nandfs_cleaner_choose_segment(struct nandfs_device *fsdev, uint64_t **segpp, + uint64_t nsegs, uint64_t *rseg) +{ + struct nandfs_suinfo *suinfo; + uint64_t i, ssegs; + int error; + + suinfo = malloc(sizeof(*suinfo) * nsegs, M_NANDFSTEMP, + M_ZERO | M_WAITOK); + + if (*rseg >= fsdev->nd_fsdata.f_nsegments) + *rseg = 0; + +retry: + error = nandfs_get_segment_info_filter(fsdev, suinfo, nsegs, *rseg, + &ssegs, NANDFS_SEGMENT_USAGE_DIRTY, + NANDFS_SEGMENT_USAGE_ACTIVE | NANDFS_SEGMENT_USAGE_ERROR | + NANDFS_SEGMENT_USAGE_GC); + if (error) { + nandfs_error("%s:%d", __FILE__, __LINE__); + goto out; + } + + if (ssegs == 0 && *rseg != 0) { + *rseg = 0; + goto retry; + } + + print_suinfo(suinfo, ssegs); + + for (i = 0; i < ssegs; i++) { + (**segpp) = suinfo[i].nsi_num; + (*segpp)++; + } + + *rseg = suinfo[i - 1].nsi_num + 1; +out: + free(suinfo, M_NANDFSTEMP); + + return (error); +} + +static int +nandfs_cleaner_body(struct nandfs_device *fsdev, uint64_t *rseg) +{ + struct nandfs_vinfo *vinfo, *vip, *vipi; + struct nandfs_bdesc *bdesc, *bdp, *bdpi; + struct nandfs_cpstat cpstat; + struct nandfs_cpinfo *cpinfo = NULL; + uint64_t *segnums, *segp; + int select, selected; + int error = 0; + int nsegs; + int i; + + nsegs = nandfs_cleaner_segments; + + vip = vinfo = malloc(sizeof(*vinfo) * + fsdev->nd_fsdata.f_blocks_per_segment * nsegs, M_NANDFSTEMP, + M_ZERO | M_WAITOK); + bdp = bdesc = malloc(sizeof(*bdesc) * + fsdev->nd_fsdata.f_blocks_per_segment * nsegs, M_NANDFSTEMP, + M_ZERO | M_WAITOK); + segp = segnums = malloc(sizeof(*segnums) * nsegs, M_NANDFSTEMP, + M_WAITOK); + + error = nandfs_cleaner_choose_segment(fsdev, &segp, nsegs, rseg); + if (error) { + nandfs_error("%s:%d", __FILE__, __LINE__); + goto out; + } + + if (segnums == segp) + goto out; + + selected = 0; + for (i = 0; i < segp - segnums; i++) { + error = nandfs_cleaner_iterate_segment(fsdev, segnums[i], &vip, + &bdp, &select); + if (error) { + /* + * XXX deselect (see below)? + */ + goto out; + } + if (!select) + segnums[i] = NANDFS_NOSEGMENT; + else { + error = nandfs_markgc_segment(fsdev, segnums[i]); + if (error) { + nandfs_error("%s:%d\n", __FILE__, __LINE__); + goto out; + } + selected++; + } + } + + if (selected == 0) { + MPASS(vinfo == vip); + MPASS(bdesc == bdp); + goto out; + } + + error = nandfs_get_cpstat(fsdev->nd_cp_node, &cpstat); + if (error) { + nandfs_error("%s:%d\n", __FILE__, __LINE__); + goto out; + } + + if (cpstat.ncp_nss != 0) { + cpinfo = malloc(sizeof(struct nandfs_cpinfo) * cpstat.ncp_nss, + M_NANDFSTEMP, M_WAITOK); + error = nandfs_get_cpinfo(fsdev->nd_cp_node, 1, NANDFS_SNAPSHOT, + cpinfo, cpstat.ncp_nss, NULL); + if (error) { + nandfs_error("%s:%d\n", __FILE__, __LINE__); + goto out_locked; + } + } + + NANDFS_WRITELOCK(fsdev); + DPRINTF(CLEAN, ("%s: got lock\n", __func__)); + + error = nandfs_get_dat_vinfo(fsdev, vinfo, vip - vinfo); + if (error) { + nandfs_error("%s:%d\n", __FILE__, __LINE__); + goto out_locked; + } + + nandfs_cleaner_vinfo_mark_alive(fsdev, vinfo, vip - vinfo, cpinfo, + cpstat.ncp_nss); + + error = nandfs_get_dat_bdescs(fsdev, bdesc, bdp - bdesc); + if (error) { + nandfs_error("%s:%d\n", __FILE__, __LINE__); + goto out_locked; + } + + nandfs_cleaner_bdesc_mark_alive(fsdev, bdesc, bdp - bdesc); + + DPRINTF(CLEAN, ("got:\n")); + for (vipi = vinfo; vipi < vip; vipi++) { + DPRINTF(CLEAN, ("v ino %jx vblocknr %jx start %jx end %jx " + "alive %d\n", vipi->nvi_ino, vipi->nvi_vblocknr, + vipi->nvi_start, vipi->nvi_end, vipi->nvi_alive)); + } + for (bdpi = bdesc; bdpi < bdp; bdpi++) { + DPRINTF(CLEAN, ("b oblocknr %jx blocknr %jx offset %jx " + "alive %d\n", bdpi->bd_oblocknr, bdpi->bd_blocknr, + bdpi->bd_offset, bdpi->bd_alive)); + } + DPRINTF(CLEAN, ("end list\n")); + + error = nandfs_cleaner_clean_segments(fsdev, vinfo, vip - vinfo, NULL, + 0, bdesc, bdp - bdesc, segnums, segp - segnums); + if (error) + nandfs_error("%s:%d\n", __FILE__, __LINE__); + +out_locked: + NANDFS_WRITEUNLOCK(fsdev); +out: + free(cpinfo, M_NANDFSTEMP); + free(segnums, M_NANDFSTEMP); + free(bdesc, M_NANDFSTEMP); + free(vinfo, M_NANDFSTEMP); + + return (error); +} + +static void +nandfs_cleaner(struct nandfs_device *fsdev) +{ + uint64_t checked_seg = 0; + int error; + + while (!nandfs_cleaner_finished(fsdev)) { + if (!nandfs_cleaner_enable || rebooting) + continue; + + DPRINTF(CLEAN, ("%s: run started\n", __func__)); + + fsdev->nd_cleaning = 1; + + error = nandfs_cleaner_body(fsdev, &checked_seg); + + DPRINTF(CLEAN, ("%s: run finished error %d\n", __func__, + error)); + } + + DPRINTF(CLEAN, ("%s: exiting\n", __func__)); + kthread_exit(); +} + +static int +nandfs_cleaner_clean_segments(struct nandfs_device *nffsdev, + struct nandfs_vinfo *vinfo, uint32_t nvinfo, + struct nandfs_period *pd, uint32_t npd, + struct nandfs_bdesc *bdesc, uint32_t nbdesc, + uint64_t *segments, uint32_t nsegs) +{ + struct nandfs_node *gc; + struct buf *bp; + uint32_t i; + int error = 0; + + gc = nffsdev->nd_gc_node; + + DPRINTF(CLEAN, ("%s: enter\n", __func__)); + + VOP_LOCK(NTOV(gc), LK_EXCLUSIVE); + for (i = 0; i < nvinfo; i++) { + if (!vinfo[i].nvi_alive) + continue; + DPRINTF(CLEAN, ("%s: read vblknr:%#jx blk:%#jx\n", + __func__, (uintmax_t)vinfo[i].nvi_vblocknr, + (uintmax_t)vinfo[i].nvi_blocknr)); + error = nandfs_bread(nffsdev->nd_gc_node, vinfo[i].nvi_blocknr, + NULL, 0, &bp); + if (error) { + nandfs_error("%s:%d", __FILE__, __LINE__); + VOP_UNLOCK(NTOV(gc), 0); + goto out; + } + nandfs_vblk_set(bp, vinfo[i].nvi_vblocknr); + nandfs_buf_set(bp, NANDFS_VBLK_ASSIGNED); + nandfs_dirty_buf(bp, 1); + } + VOP_UNLOCK(NTOV(gc), 0); + + /* Delete checkpoints */ + for (i = 0; i < npd; i++) { + DPRINTF(CLEAN, ("delete checkpoint: %jx\n", + (uintmax_t)pd[i].p_start)); + error = nandfs_delete_cp(nffsdev->nd_cp_node, pd[i].p_start, + pd[i].p_end); + if (error) { + nandfs_error("%s:%d", __FILE__, __LINE__); + goto out; + } + } + + /* Update vblocks */ + for (i = 0; i < nvinfo; i++) { + if (vinfo[i].nvi_alive) + continue; + DPRINTF(CLEAN, ("freeing vblknr: %jx\n", vinfo[i].nvi_vblocknr)); + error = nandfs_vblock_free(nffsdev, vinfo[i].nvi_vblocknr); + if (error) { + nandfs_error("%s:%d", __FILE__, __LINE__); + goto out; + } + } + + error = nandfs_process_bdesc(nffsdev, bdesc, nbdesc); + if (error) { + nandfs_error("%s:%d", __FILE__, __LINE__); + goto out; + } + + /* Add segments to clean */ + if (nffsdev->nd_free_count) { + nffsdev->nd_free_base = realloc(nffsdev->nd_free_base, + (nffsdev->nd_free_count + nsegs) * sizeof(uint64_t), + M_NANDFSTEMP, M_WAITOK | M_ZERO); + memcpy(&nffsdev->nd_free_base[nffsdev->nd_free_count], segments, + nsegs * sizeof(uint64_t)); + nffsdev->nd_free_count += nsegs; + } else { + nffsdev->nd_free_base = malloc(nsegs * sizeof(uint64_t), + M_NANDFSTEMP, M_WAITOK|M_ZERO); + memcpy(nffsdev->nd_free_base, segments, + nsegs * sizeof(uint64_t)); + nffsdev->nd_free_count = nsegs; + } + +out: + + DPRINTF(CLEAN, ("%s: exit error %d\n", __func__, error)); + + return (error); +} + +static int +nandfs_process_bdesc(struct nandfs_device *nffsdev, struct nandfs_bdesc *bd, + uint64_t nmembs) +{ + struct nandfs_node *dat_node; + struct buf *bp; + uint64_t i; + int error; + + dat_node = nffsdev->nd_dat_node; + + VOP_LOCK(NTOV(dat_node), LK_EXCLUSIVE); + + for (i = 0; i < nmembs; i++) { + if (!bd[i].bd_alive) + continue; + DPRINTF(CLEAN, ("%s: idx %jx offset %jx\n", + __func__, i, bd[i].bd_offset)); + if (bd[i].bd_level) { + error = nandfs_bread_meta(dat_node, bd[i].bd_offset, + NULL, 0, &bp); + if (error) { + nandfs_error("%s: cannot read dat node " + "level:%d\n", __func__, bd[i].bd_level); + brelse(bp); + VOP_UNLOCK(NTOV(dat_node), 0); + return (error); + } + nandfs_dirty_buf_meta(bp, 1); + nandfs_bmap_dirty_blocks(VTON(bp->b_vp), bp, 1); + } else { + error = nandfs_bread(dat_node, bd[i].bd_offset, NULL, + 0, &bp); + if (error) { + nandfs_error("%s: cannot read dat node\n", + __func__); + brelse(bp); + VOP_UNLOCK(NTOV(dat_node), 0); + return (error); + } + nandfs_dirty_buf(bp, 1); + } + DPRINTF(CLEAN, ("%s: bp: %p\n", __func__, bp)); + } + + VOP_UNLOCK(NTOV(dat_node), 0); + + return (0); +} |