/** * Triggers a garbage collection cycle. This is implemented as follows: * * (1) The non-scratch area with the lowest garbage collection sequence * number is selected as the "source area." If there are other areas * with the same sequence number, the first one encountered is selected. * * (2) The source area's ID is written to the scratch area's header, * transforming it into a non-scratch ID. The former scratch area is now * known as the "destination area." * * (3) The RAM representation is exhaustively searched for objects which are * resident in the source area. The copy is accomplished as follows: * * For each inode: * (a) If the inode is resident in the source area, copy the inode * record to the destination area. * * (b) Walk the inode's list of data blocks, starting with the last * block in the file. Each block that is resident in the source * area is copied to the destination area. If there is a run of * two or more blocks that are resident in the source area, they * are consolidated and copied to the destination area as a single * new block. * * (4) The source area is reformatted as a scratch sector (i.e., its header * indicates an ID of 0xffff). The area's garbage collection sequence * number is incremented prior to rewriting the header. This area is now * the new scratch sector. * * NOTE: * Garbage collection invalidates all cached data blocks. Whenever this * function is called, all existing nffs_cache_block pointers are rendered * invalid. If you maintain any such pointers, you need to reset them * after calling this function. Cached inodes are not invalidated by * garbage collection. * * If a parent function potentially calls this function, the caller of the * parent function needs to explicitly check if garbage collection * occurred. This is done by inspecting the nffs_gc_count variable before * and after calling the function. * * @param out_area_idx On success, the ID of the cleaned up area gets * written here. Pass null if you do not need * this information. * * @return 0 on success; nonzero on error. */ int nffs_gc(uint8_t *out_area_idx) { struct nffs_hash_entry *entry; struct nffs_hash_entry *next; struct nffs_area *from_area; struct nffs_area *to_area; struct nffs_inode_entry *inode_entry; uint32_t area_offset; uint8_t from_area_idx; uint8_t area_idx; int rc; int i; from_area_idx = nffs_gc_select_area(); from_area = nffs_areas + from_area_idx; to_area = nffs_areas + nffs_scratch_area_idx; rc = nffs_format_from_scratch_area(nffs_scratch_area_idx, from_area->na_id); if (rc != 0) { return rc; } for (i = 0; i < NFFS_HASH_SIZE; i++) { entry = SLIST_FIRST(nffs_hash + i); while (entry != NULL) { next = SLIST_NEXT(entry, nhe_next); if (nffs_hash_id_is_inode(entry->nhe_id)) { /* The inode gets copied if it is in the source area. */ nffs_flash_loc_expand(entry->nhe_flash_loc, &area_idx, &area_offset); inode_entry = (struct nffs_inode_entry *)entry; if (area_idx == from_area_idx) { rc = nffs_gc_copy_inode(inode_entry, nffs_scratch_area_idx); if (rc != 0) { return rc; } } /* If the inode is a file, all constituent data blocks that are * resident in the source area get copied. */ if (nffs_hash_id_is_file(entry->nhe_id)) { rc = nffs_gc_inode_blocks(inode_entry, from_area_idx, nffs_scratch_area_idx, &next); if (rc != 0) { return rc; } } } entry = next; } } /* The amount of written data should never increase as a result of a gc * cycle. */ assert(to_area->na_cur <= from_area->na_cur); /* Turn the source area into the new scratch area. */ from_area->na_gc_seq++; rc = nffs_format_area(from_area_idx, 1); if (rc != 0) { return rc; } if (out_area_idx != NULL) { *out_area_idx = nffs_scratch_area_idx; } nffs_scratch_area_idx = from_area_idx; /* Garbage collection renders the cache invalid: * o All cached blocks are now invalid; drop them. * o Flash locations of inodes may have changed; the cached inodes need * updated to reflect this. */ rc = nffs_cache_inode_refresh(); if (rc != 0) { return rc; } /* Increment the garbage collection counter so that client code knows to * reset its pointers to cached objects. */ nffs_gc_count++; STATS_INC(nffs_stats, nffs_gccnt); return 0; }
/** * Triggers a garbage collection cycle. This is implemented as follows: * * (1) The non-scratch area with the lowest garbage collection sequence * number is selected as the "source area." If there are other areas * with the same sequence number, the first one encountered is selected. * * (2) The source area's ID is written to the scratch area's header, * transforming it into a non-scratch ID. The former scratch area is now * known as the "destination area." * * (3) The RAM representation is exhaustively searched for objects which are * resident in the source area. The copy is accomplished as follows: * * For each inode: * (a) If the inode is resident in the source area, copy the inode * record to the destination area. * * (b) Walk the inode's list of data blocks, starting with the last * block in the file. Each block that is resident in the source * area is copied to the destination area. If there is a run of * two or more blocks that are resident in the source area, they * are consolidated and copied to the destination area as a single * new block. * * (4) The source area is reformatted as a scratch sector (i.e., its header * indicates an ID of 0xffff). The area's garbage collection sequence * number is incremented prior to rewriting the header. This area is now * the new scratch sector. * * @param out_area_idx On success, the ID of the cleaned up area gets * written here. Pass null if you do not need * this information. * * @return 0 on success; nonzero on error. */ int nffs_gc(uint8_t *out_area_idx) { struct nffs_hash_entry *entry; struct nffs_hash_entry *next; struct nffs_area *from_area; struct nffs_area *to_area; struct nffs_inode_entry *inode_entry; uint32_t area_offset; uint8_t from_area_idx; uint8_t area_idx; int rc; int i; from_area_idx = nffs_gc_select_area(); from_area = nffs_areas + from_area_idx; to_area = nffs_areas + nffs_scratch_area_idx; rc = nffs_format_from_scratch_area(nffs_scratch_area_idx, from_area->na_id); if (rc != 0) { return rc; } for (i = 0; i < NFFS_HASH_SIZE; i++) { entry = SLIST_FIRST(nffs_hash + i); while (entry != NULL) { next = SLIST_NEXT(entry, nhe_next); if (nffs_hash_id_is_inode(entry->nhe_id)) { /* The inode gets copied if it is in the source area. */ nffs_flash_loc_expand(entry->nhe_flash_loc, &area_idx, &area_offset); inode_entry = (struct nffs_inode_entry *)entry; if (area_idx == from_area_idx) { rc = nffs_gc_copy_inode(inode_entry, nffs_scratch_area_idx); if (rc != 0) { return rc; } } /* If the inode is a file, all constituent data blocks that are * resident in the source area get copied. */ if (nffs_hash_id_is_file(entry->nhe_id)) { rc = nffs_gc_inode_blocks(inode_entry, from_area_idx, nffs_scratch_area_idx, &next); if (rc != 0) { return rc; } } } entry = next; } } /* The amount of written data should never increase as a result of a gc * cycle. */ assert(to_area->na_cur <= from_area->na_cur); /* Turn the source area into the new scratch area. */ from_area->na_gc_seq++; rc = nffs_format_area(from_area_idx, 1); if (rc != 0) { return rc; } if (out_area_idx != NULL) { *out_area_idx = nffs_scratch_area_idx; } nffs_scratch_area_idx = from_area_idx; return 0; }