/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2026 Google LLC * Author: Chao Yu */ #ifndef _LINUX_F2FS_CACHE_H #define _LINUX_F2FS_CACHE_H #include #include #include #include #include #include #include struct f2fs_rwsem; struct f2fs_io_info; enum page_type; /* Represents a single cached block (meta, node or compress) */ struct f2fs_cached_block { struct list_head list; /* LRU list head */ struct f2fs_cached_block_list *cache; /* parent cache list */ union { struct f2fs_cached_block *next_entry;/* chain for merged BIO */ nid_t ino; /* inode number for compress cache */ }; unsigned long index; /* key in radix tree, (meta/compress: pba, node: nid) */ unsigned long state; /* cache entry state (e.g., Dirty, UpToDate) */ void *data; /* blocksize-aligned memory (4KB or 16KB) */ atomic_t refcount; /* reference count */ }; struct f2fs_sb_info; enum f2fs_cache_type { F2FS_META_CACHE, F2FS_NODE_CACHE, F2FS_COMPRESS_CACHE, NR_CACHE_TYPES, }; /* Main cache control structure (per sb_info) */ struct f2fs_cached_block_list { struct f2fs_sb_info *sbi; /* Pointer to f2fs_sb_info */ struct radix_tree_root root; /* Radix tree for cache lookup */ spinlock_t tree_lock; /* Lock for radix tree */ struct list_head lru_list; /* Single global LRU list */ spinlock_t list_lock; /* Lock for LRU list */ enum f2fs_cache_type type; /* Cache type (Node, Meta, Compress) */ unsigned long num_entries; /* Current number of entries */ }; #define IS_META_CACHE(cache) ((cache)->type == F2FS_META_CACHE) #define IS_NODE_CACHE(cache) ((cache)->type == F2FS_NODE_CACHE) #define IS_COMPRESS_CACHE(cache) ((cache)->type == F2FS_COMPRESS_CACHE) /* Flags for f2fs_cached_block state */ enum f2fs_cached_state { F2FS_BLOCK_LOCKED, /* cache entry is locked */ F2FS_BLOCK_UPTODATE, /* cache data is valid */ F2FS_BLOCK_DIRTY, /* cache data is dirty, need to writeback the data */ F2FS_BLOCK_WRITEBACK, /* cache data is writeback state */ F2FS_BLOCK_REFERENCED, /* cache was accessed recently, shrinker will skip it for once */ F2FS_BLOCK_INLINE_DATA, /* indicate inline data */ }; enum { __F2FS_CACHE_CREATE, /* create the cache if there is no cache entry */ __F2FS_CACHE_LOCK, /* get and lock the cache entry */ __F2FS_CACHE_NOFAIL, /* do not allow failure */ __F2FS_CACHE_ACCESS, /* give a chance to add referenced tag */ }; enum f2fs_cache_request_flag { F2FS_CACHE_CREATE = 1 << __F2FS_CACHE_CREATE, F2FS_CACHE_LOCK = 1 << __F2FS_CACHE_LOCK, F2FS_CACHE_NOFAIL = 1 << __F2FS_CACHE_NOFAIL, F2FS_CACHE_ACCESS = 1 << __F2FS_CACHE_ACCESS, }; #define F2FS_CACHE_LOCK_CREATE (F2FS_CACHE_LOCK | F2FS_CACHE_CREATE) #define F2FS_ONSTACK_CACHES (32) #define F2FS_CACHE_FLAG_TEST_FUNC(name, flagname) \ static inline bool f2fs_cache_test_##name( \ const struct f2fs_cached_block *entry) \ { \ return test_bit(F2FS_BLOCK_##flagname, &entry->state); \ } \ #define F2FS_CACHE_FLAG_SET_FUNC(name, flagname) \ static inline void f2fs_cache_set_##name( \ struct f2fs_cached_block *entry) \ { \ set_bit(F2FS_BLOCK_##flagname, &entry->state); \ } \ #define F2FS_CACHE_FLAG_CLEAR_FUNC(name, flagname) \ static inline void f2fs_cache_clear_##name( \ struct f2fs_cached_block *entry) \ { \ clear_bit(F2FS_BLOCK_##flagname, &entry->state); \ } \ #define F2FS_CACHE_FLAG_TEST_AND_SET_FUNC(name, flagname) \ static inline bool f2fs_cache_test_and_set_##name( \ struct f2fs_cached_block *entry) \ { \ return test_and_set_bit(F2FS_BLOCK_##flagname, &entry->state); \ } \ #define F2FS_CACHE_FLAG_TEST_AND_CLEAR_FUNC(name, flagname) \ static inline bool f2fs_cache_test_and_clear_##name( \ struct f2fs_cached_block *entry) \ { \ return test_and_clear_bit(F2FS_BLOCK_##flagname, &entry->state);\ } \ F2FS_CACHE_FLAG_TEST_FUNC(locked, LOCKED); F2FS_CACHE_FLAG_SET_FUNC(locked, LOCKED); F2FS_CACHE_FLAG_CLEAR_FUNC(locked, LOCKED); F2FS_CACHE_FLAG_TEST_FUNC(uptodate, UPTODATE); F2FS_CACHE_FLAG_SET_FUNC(uptodate, UPTODATE); F2FS_CACHE_FLAG_CLEAR_FUNC(uptodate, UPTODATE); F2FS_CACHE_FLAG_TEST_FUNC(dirty, DIRTY); F2FS_CACHE_FLAG_SET_FUNC(dirty, DIRTY); F2FS_CACHE_FLAG_CLEAR_FUNC(dirty, DIRTY); F2FS_CACHE_FLAG_TEST_AND_SET_FUNC(dirty, DIRTY); F2FS_CACHE_FLAG_TEST_AND_CLEAR_FUNC(dirty, DIRTY); F2FS_CACHE_FLAG_TEST_FUNC(writeback, WRITEBACK); F2FS_CACHE_FLAG_SET_FUNC(writeback, WRITEBACK); F2FS_CACHE_FLAG_CLEAR_FUNC(writeback, WRITEBACK); F2FS_CACHE_FLAG_TEST_FUNC(inline, INLINE_DATA); F2FS_CACHE_FLAG_SET_FUNC(inline, INLINE_DATA); F2FS_CACHE_FLAG_CLEAR_FUNC(inline, INLINE_DATA); F2FS_CACHE_FLAG_TEST_FUNC(referenced, REFERENCED); F2FS_CACHE_FLAG_SET_FUNC(referenced, REFERENCED); F2FS_CACHE_FLAG_TEST_AND_CLEAR_FUNC(referenced, REFERENCED); static inline void *cache_address(const struct f2fs_cached_block *entry) { return entry->data; } #define CACHED_NODE(entry) ((struct f2fs_node *)(cache_address(entry))) static inline struct folio *cache_folio(const struct f2fs_cached_block *entry) { return virt_to_folio(entry->data); } int f2fs_init_cache(struct f2fs_sb_info *sbi, struct f2fs_cached_block_list *cache, enum f2fs_cache_type type); void f2fs_destroy_cache(struct f2fs_cached_block_list *cache); void f2fs_cache_get(struct f2fs_cached_block *entry); struct f2fs_cached_block *f2fs_find_cache( struct f2fs_cached_block_list *cache, unsigned long index, enum f2fs_cache_request_flag rflag); #define F2FS_CACHE_TAG_NONE 0 #define F2FS_CACHE_TAG_DIRTY 1 #define F2FS_CACHE_TAG_WRITEBACK 2 bool f2fs_trylock_cache(struct f2fs_cached_block *entry); void f2fs_lock_cache(struct f2fs_cached_block *entry); void f2fs_unlock_cache(struct f2fs_cached_block *entry); bool f2fs_put_cache(struct f2fs_cached_block *entry, bool unlock); bool f2fs_mark_cache_dirty(struct f2fs_cached_block *entry); void f2fs_drop_cache_dirty(struct f2fs_cached_block *entry); void f2fs_start_cache_writeback(struct f2fs_cached_block *entry); void f2fs_end_cache_writeback(struct f2fs_cached_block *entry); unsigned int f2fs_cache_gang_lookup(struct f2fs_cached_block_list *cache, struct f2fs_cached_block **entries, pgoff_t *index, unsigned long end); unsigned int f2fs_cache_gang_lookup_tag(struct f2fs_cached_block_list *cache, struct f2fs_cached_block **results, pgoff_t *first_index, unsigned int max_items, int tag); void f2fs_cache_gang_release(struct f2fs_cached_block **entries, unsigned int nr_entries); void f2fs_cache_wait_on_all_writeback(struct f2fs_cached_block_list *cache); void f2fs_cache_wait_writeback_cond(struct f2fs_cached_block *entry, enum page_type type); void f2fs_cache_wait_writeback(struct f2fs_cached_block *entry); void f2fs_cache_update_tag(struct f2fs_cached_block *entry, unsigned int clear_from, unsigned int set_to); struct f2fs_cached_block *f2fs_grab_cache(struct f2fs_cached_block_list *cache, unsigned long index, int flags); void f2fs_truncate_locked_cache(struct f2fs_cached_block *entry, bool drop_dirty); void f2fs_truncate_cache(struct f2fs_cached_block *entry, bool drop_dirty); void f2fs_drop_cache_range(struct f2fs_cached_block_list *cache, unsigned long start, unsigned long len, bool drop_dirty); #define META_CACHE(sbi) (&(sbi)->meta_blocks) #define NODE_CACHE(sbi) (&(sbi)->node_blocks) #define COMPRESS_CACHE(sbi) (&(sbi)->compress_blocks) #define f2fs_find_meta_cache(sbi, blkaddr) \ f2fs_find_cache(META_CACHE(sbi), blkaddr, 0) #define f2fs_invalidate_meta_caches(sbi, start, len) \ f2fs_drop_cache_range(META_CACHE(sbi), start, len, false) #define f2fs_truncate_meta_caches(sbi, start, len) \ f2fs_drop_cache_range(META_CACHE(sbi), start, len, true) #define f2fs_grab_node_cache(sbi, blkaddr) \ f2fs_grab_cache(NODE_CACHE(sbi), blkaddr, \ F2FS_CACHE_LOCK_CREATE) #define f2fs_find_node_cache(sbi, blkaddr) \ f2fs_find_cache(NODE_CACHE(sbi), blkaddr, F2FS_CACHE_ACCESS) #define f2fs_invalidate_node_cache(sbi, blkaddr) \ f2fs_drop_cache_range(NODE_CACHE(sbi), blkaddr, 1, false) #define f2fs_truncate_node_caches(sbi, start, len) \ f2fs_drop_cache_range(NODE_CACHE(sbi), start, len, true) unsigned long f2fs_shrink_cache(struct f2fs_sb_info *sbi, unsigned long nr_to_scan); #define DEF_DIRTY_CACHE_TIMEOUT 5000 #define MIN_DIRTY_CACHE_TIMEOUT 100 #define MAX_DIRTY_CACHE_TIMEOUT 30000 struct f2fs_cache_kthread { struct task_struct *cache_wb_task; wait_queue_head_t cache_wb_wq; unsigned int cache_wb_interval; }; int f2fs_start_cache_wb_thread(struct f2fs_sb_info *sbi); void f2fs_stop_cache_wb_thread(struct f2fs_sb_info *sbi); #endif /* _LINUX_F2FS_CACHE_H */