/*- * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SU_USAGE_OFF(bp, offset) \ ((struct nandfs_segment_usage *)((bp)->b_data + offset)) static int nandfs_seg_usage_blk_offset(struct nandfs_device *fsdev, uint64_t seg, uint64_t *blk, uint64_t *offset) { uint64_t off; uint16_t seg_size; seg_size = fsdev->nd_fsdata.f_segment_usage_size; off = roundup(sizeof(struct nandfs_sufile_header), seg_size); off += (seg * seg_size); *blk = off / fsdev->nd_blocksize; *offset = off % fsdev->nd_blocksize; return (0); } /* Alloc new segment */ int nandfs_alloc_segment(struct nandfs_device *fsdev, uint64_t *seg) { struct nandfs_node *su_node; struct nandfs_sufile_header *su_header; struct nandfs_segment_usage *su_usage; struct buf *bp_header, *bp; uint64_t blk, vblk, offset, i, rest, nsegments; uint16_t seg_size; int error, found; seg_size = fsdev->nd_fsdata.f_segment_usage_size; nsegments = fsdev->nd_fsdata.f_nsegments; su_node = fsdev->nd_su_node; ASSERT_VOP_LOCKED(NTOV(su_node), __func__); /* Read header buffer */ error = nandfs_bread(su_node, 0, NOCRED, 0, &bp_header); if (error) { brelse(bp_header); return (error); } su_header = (struct nandfs_sufile_header *)bp_header->b_data; /* Get last allocated segment */ i = su_header->sh_last_alloc + 1; found = 0; bp = NULL; while (!found) { nandfs_seg_usage_blk_offset(fsdev, i, &blk, &offset); if(blk != 0) { error = nandfs_bmap_lookup(su_node, blk, &vblk); if (error) { nandfs_error("%s: cannot find vblk for blk " "blk:%jx\n", __func__, blk); return (error); } if (vblk) error = nandfs_bread(su_node, blk, NOCRED, 0, &bp); else error = nandfs_bcreate(su_node, blk, NOCRED, 0, &bp); if (error) { nandfs_error("%s: cannot create/read " "vblk:%jx\n", __func__, vblk); if (bp) brelse(bp); return (error); } su_usage = SU_USAGE_OFF(bp, offset); } else { su_usage = SU_USAGE_OFF(bp_header, offset); bp = bp_header; } rest = (fsdev->nd_blocksize - offset) / seg_size; /* Go through all su usage in block */ while (rest) { /* When last check start from beggining */ if (i == nsegments) break; if (!su_usage->su_flags) { su_usage->su_flags = 1; found = 1; break; } su_usage++; i++; /* If all checked return error */ if (i == su_header->sh_last_alloc) { DPRINTF(SEG, ("%s: cannot allocate segment \n", __func__)); brelse(bp_header); if (blk != 0) brelse(bp); return (1); } rest--; } if (!found) { /* Otherwise read another block */ if (blk != 0) brelse(bp); if (i == nsegments) { blk = 0; i = 0; } else blk++; offset = 0; } } if (found) { *seg = i; su_header->sh_last_alloc = i; su_header->sh_ncleansegs--; su_header->sh_ndirtysegs++; fsdev->nd_super.s_free_blocks_count = su_header->sh_ncleansegs * fsdev->nd_fsdata.f_blocks_per_segment; fsdev->nd_clean_segs--; /* * It is mostly called from syncer() so we want to force * making buf dirty. */ error = nandfs_dirty_buf(bp_header, 1); if (error) { if (bp && bp != bp_header) brelse(bp); return (error); } if (bp && bp != bp_header) nandfs_dirty_buf(bp, 1); DPRINTF(SEG, ("%s: seg:%#jx\n", __func__, (uintmax_t)i)); return (0); } DPRINTF(SEG, ("%s: failed\n", __func__)); return (1); } /* * Make buffer dirty, it will be updated soon but first it need to be * gathered by syncer. */ int nandfs_touch_segment(struct nandfs_device *fsdev, uint64_t seg) { struct nandfs_node *su_node; struct buf *bp; uint64_t blk, offset; int error; su_node = fsdev->nd_su_node; ASSERT_VOP_LOCKED(NTOV(su_node), __func__); nandfs_seg_usage_blk_offset(fsdev, seg, &blk, &offset); error = nandfs_bread(su_node, blk, NOCRED, 0, &bp); if (error) { brelse(bp); nandfs_error("%s: cannot preallocate new segment\n", __func__); return (error); } else nandfs_dirty_buf(bp, 1); DPRINTF(SEG, ("%s: seg:%#jx\n", __func__, (uintmax_t)seg)); return (error); } /* Update block count of segment */ int nandfs_update_segment(struct nandfs_device *fsdev, uint64_t seg, uint32_t nblks) { struct nandfs_node *su_node; struct nandfs_segment_usage *su_usage; struct buf *bp; uint64_t blk, offset; int error; su_node = fsdev->nd_su_node; ASSERT_VOP_LOCKED(NTOV(su_node), __func__); nandfs_seg_usage_blk_offset(fsdev, seg, &blk, &offset); error = nandfs_bread(su_node, blk, NOCRED, 0, &bp); if (error) { nandfs_error("%s: read block:%jx to update\n", __func__, blk); brelse(bp); return (error); } su_usage = SU_USAGE_OFF(bp, offset); su_usage->su_lastmod = fsdev->nd_ts.tv_sec; su_usage->su_flags = NANDFS_SEGMENT_USAGE_DIRTY; su_usage->su_nblocks += nblks; DPRINTF(SEG, ("%s: seg:%#jx inc:%#x cur:%#x\n", __func__, (uintmax_t)seg, nblks, su_usage->su_nblocks)); nandfs_dirty_buf(bp, 1); return (0); } /* Make segment free */ int nandfs_free_segment(struct nandfs_device *fsdev, uint64_t seg) { struct nandfs_node *su_node; struct nandfs_sufile_header *su_header; struct nandfs_segment_usage *su_usage; struct buf *bp_header, *bp; uint64_t blk, offset; int error; su_node = fsdev->nd_su_node; ASSERT_VOP_LOCKED(NTOV(su_node), __func__); /* Read su header */ error = nandfs_bread(su_node, 0, NOCRED, 0, &bp_header); if (error) { brelse(bp_header); return (error); } su_header = (struct nandfs_sufile_header *)bp_header->b_data; nandfs_seg_usage_blk_offset(fsdev, seg, &blk, &offset); /* Read su usage block if other than su header block */ if (blk != 0) { error = nandfs_bread(su_node, blk, NOCRED, 0, &bp); if (error) { brelse(bp); brelse(bp_header); return (error); } } else bp = bp_header; /* Reset su usage data */ su_usage = SU_USAGE_OFF(bp, offset); su_usage->su_lastmod = fsdev->nd_ts.tv_sec; su_usage->su_nblocks = 0; su_usage->su_flags = 0; /* Update clean/dirty counter in header */ su_header->sh_ncleansegs++; su_header->sh_ndirtysegs--; /* * Make buffers dirty, called by cleaner * so force dirty even if no much space left * on device */ nandfs_dirty_buf(bp_header, 1); if (bp != bp_header) nandfs_dirty_buf(bp, 1); /* Update free block count */ fsdev->nd_super.s_free_blocks_count = su_header->sh_ncleansegs * fsdev->nd_fsdata.f_blocks_per_segment; fsdev->nd_clean_segs++; DPRINTF(SEG, ("%s: seg:%#jx\n", __func__, (uintmax_t)seg)); return (0); } static int nandfs_bad_segment(struct nandfs_device *fsdev, uint64_t seg) { struct nandfs_node *su_node; struct nandfs_segment_usage *su_usage; struct buf *bp; uint64_t blk, offset; int error; su_node = fsdev->nd_su_node; ASSERT_VOP_LOCKED(NTOV(su_node), __func__); nandfs_seg_usage_blk_offset(fsdev, seg, &blk, &offset); error = nandfs_bread(su_node, blk, NOCRED, 0, &bp); if (error) { brelse(bp); return (error); } su_usage = SU_USAGE_OFF(bp, offset); su_usage->su_lastmod = fsdev->nd_ts.tv_sec; su_usage->su_flags = NANDFS_SEGMENT_USAGE_ERROR; DPRINTF(SEG, ("%s: seg:%#jx\n", __func__, (uintmax_t)seg)); nandfs_dirty_buf(bp, 1); return (0); } int nandfs_markgc_segment(struct nandfs_device *fsdev, uint64_t seg) { struct nandfs_node *su_node; struct nandfs_segment_usage *su_usage; struct buf *bp; uint64_t blk, offset; int error; su_node = fsdev->nd_su_node; VOP_LOCK(NTOV(su_node), LK_EXCLUSIVE); nandfs_seg_usage_blk_offset(fsdev, seg, &blk, &offset); error = nandfs_bread(su_node, blk, NOCRED, 0, &bp); if (error) { brelse(bp); VOP_UNLOCK(NTOV(su_node), 0); return (error); } su_usage = SU_USAGE_OFF(bp, offset); MPASS((su_usage->su_flags & NANDFS_SEGMENT_USAGE_GC) == 0); su_usage->su_flags |= NANDFS_SEGMENT_USAGE_GC; brelse(bp); VOP_UNLOCK(NTOV(su_node), 0); DPRINTF(SEG, ("%s: seg:%#jx\n", __func__, (uintmax_t)seg)); return (0); } int nandfs_clear_segment(struct nandfs_device *fsdev, uint64_t seg) { uint64_t offset, segsize; uint32_t bps, bsize; int error = 0; bps = fsdev->nd_fsdata.f_blocks_per_segment; bsize = fsdev->nd_blocksize; segsize = bsize * bps; nandfs_get_segment_range(fsdev, seg, &offset, NULL); offset *= bsize; DPRINTF(SEG, ("%s: seg:%#jx\n", __func__, (uintmax_t)seg)); /* Erase it and mark it bad when fail */ if (nandfs_erase(fsdev, offset, segsize)) error = nandfs_bad_segment(fsdev, seg); if (error) return (error); /* Mark it free */ error = nandfs_free_segment(fsdev, seg); return (error); } int nandfs_get_seg_stat(struct nandfs_device *nandfsdev, struct nandfs_seg_stat *nss) { struct nandfs_sufile_header *suhdr; struct nandfs_node *su_node; struct buf *bp; int err; su_node = nandfsdev->nd_su_node; NANDFS_WRITELOCK(nandfsdev); VOP_LOCK(NTOV(su_node), LK_SHARED); err = nandfs_bread(nandfsdev->nd_su_node, 0, NOCRED, 0, &bp); if (err) { brelse(bp); VOP_UNLOCK(NTOV(su_node), 0); NANDFS_WRITEUNLOCK(nandfsdev); return (-1); } suhdr = (struct nandfs_sufile_header *)bp->b_data; nss->nss_nsegs = nandfsdev->nd_fsdata.f_nsegments; nss->nss_ncleansegs = suhdr->sh_ncleansegs; nss->nss_ndirtysegs = suhdr->sh_ndirtysegs; nss->nss_ctime = 0; nss->nss_nongc_ctime = nandfsdev->nd_ts.tv_sec; nss->nss_prot_seq = nandfsdev->nd_seg_sequence; brelse(bp); VOP_UNLOCK(NTOV(su_node), 0); NANDFS_WRITEUNLOCK(nandfsdev); return (0); } int nandfs_get_segment_info_ioctl(struct nandfs_device *fsdev, struct nandfs_argv *nargv) { struct nandfs_suinfo *nsi; int error; if (nargv->nv_nmembs > NANDFS_SEGMENTS_MAX) return (EINVAL); nsi = malloc(sizeof(struct nandfs_suinfo) * nargv->nv_nmembs, M_NANDFSTEMP, M_WAITOK | M_ZERO); error = nandfs_get_segment_info(fsdev, nsi, nargv->nv_nmembs, nargv->nv_index); if (error == 0) error = copyout(nsi, (void *)(uintptr_t)nargv->nv_base, sizeof(struct nandfs_suinfo) * nargv->nv_nmembs); free(nsi, M_NANDFSTEMP); return (error); } int nandfs_get_segment_info(struct nandfs_device *fsdev, struct nandfs_suinfo *nsi, uint32_t nmembs, uint64_t segment) { return (nandfs_get_segment_info_filter(fsdev, nsi, nmembs, segment, NULL, 0, 0)); } int nandfs_get_segment_info_filter(struct nandfs_device *fsdev, struct nandfs_suinfo *nsi, uint32_t nmembs, uint64_t segment, uint64_t *nsegs, uint32_t filter, uint32_t nfilter) { struct nandfs_segment_usage *su; struct nandfs_node *su_node; struct buf *bp; uint64_t curr, blocknr, blockoff, i; uint32_t flags; int err = 0; curr = ~(0); lockmgr(&fsdev->nd_seg_const, LK_EXCLUSIVE, NULL); su_node = fsdev->nd_su_node; VOP_LOCK(NTOV(su_node), LK_SHARED); bp = NULL; if (nsegs != NULL) *nsegs = 0; for (i = 0; i < nmembs; segment++) { if (segment == fsdev->nd_fsdata.f_nsegments) break; nandfs_seg_usage_blk_offset(fsdev, segment, &blocknr, &blockoff); if (i == 0 || curr != blocknr) { if (bp != NULL) brelse(bp); err = nandfs_bread(su_node, blocknr, NOCRED, 0, &bp); if (err) { goto out; } curr = blocknr; } su = SU_USAGE_OFF(bp, blockoff); flags = su->su_flags; if (segment == fsdev->nd_seg_num || segment == fsdev->nd_next_seg_num) flags |= NANDFS_SEGMENT_USAGE_ACTIVE; if (nfilter != 0 && (flags & nfilter) != 0) continue; if (filter != 0 && (flags & filter) == 0) continue; nsi->nsi_num = segment; nsi->nsi_lastmod = su->su_lastmod; nsi->nsi_blocks = su->su_nblocks; nsi->nsi_flags = flags; nsi++; i++; if (nsegs != NULL) (*nsegs)++; } out: if (bp != NULL) brelse(bp); VOP_UNLOCK(NTOV(su_node), 0); lockmgr(&fsdev->nd_seg_const, LK_RELEASE, NULL); return (err); }