| // SPDX-License-Identifier: GPL-2.0-or-later | |
| #include <linux/blk_types.h> | |
| #include "cache.h" | |
| #include "cache_dev.h" | |
| #include "backing_dev.h" | |
| #include "dm_pcache.h" | |
| struct kmem_cache *key_cache; | |
| static inline struct pcache_cache_info *get_cache_info_addr(struct pcache_cache *cache) | |
| { | |
| return (struct pcache_cache_info *)((char *)cache->cache_info_addr + | |
| (size_t)cache->info_index * PCACHE_CACHE_INFO_SIZE); | |
| } | |
| static void cache_info_write(struct pcache_cache *cache) | |
| { | |
| struct pcache_cache_info *cache_info = &cache->cache_info; | |
| cache_info->header.seq++; | |
| cache_info->header.crc = pcache_meta_crc(&cache_info->header, | |
| sizeof(struct pcache_cache_info)); | |
| cache->info_index = (cache->info_index + 1) % PCACHE_META_INDEX_MAX; | |
| memcpy_flushcache(get_cache_info_addr(cache), cache_info, | |
| sizeof(struct pcache_cache_info)); | |
| pmem_wmb(); | |
| } | |
| static void cache_info_init_default(struct pcache_cache *cache); | |
| static int cache_info_init(struct pcache_cache *cache, struct pcache_cache_options *opts) | |
| { | |
| struct dm_pcache *pcache = CACHE_TO_PCACHE(cache); | |
| struct pcache_cache_info *cache_info_addr; | |
| cache_info_addr = pcache_meta_find_latest(&cache->cache_info_addr->header, | |
| sizeof(struct pcache_cache_info), | |
| PCACHE_CACHE_INFO_SIZE, | |
| &cache->cache_info); | |
| if (IS_ERR(cache_info_addr)) | |
| return PTR_ERR(cache_info_addr); | |
| if (cache_info_addr) { | |
| if (opts->data_crc != | |
| (cache->cache_info.flags & PCACHE_CACHE_FLAGS_DATA_CRC)) { | |
| pcache_dev_err(pcache, "invalid option for data_crc: %s, expected: %s", | |
| opts->data_crc ? "true" : "false", | |
| cache->cache_info.flags & PCACHE_CACHE_FLAGS_DATA_CRC ? "true" : "false"); | |
| return -EINVAL; | |
| } | |
| cache->info_index = ((char *)cache_info_addr - (char *)cache->cache_info_addr) / PCACHE_CACHE_INFO_SIZE; | |
| return 0; | |
| } | |
| /* init cache_info for new cache */ | |
| cache_info_init_default(cache); | |
| cache_mode_set(cache, opts->cache_mode); | |
| if (opts->data_crc) | |
| cache->cache_info.flags |= PCACHE_CACHE_FLAGS_DATA_CRC; | |
| return 0; | |
| } | |
| static void cache_info_set_gc_percent(struct pcache_cache_info *cache_info, u8 percent) | |
| { | |
| cache_info->flags &= ~PCACHE_CACHE_FLAGS_GC_PERCENT_MASK; | |
| cache_info->flags |= FIELD_PREP(PCACHE_CACHE_FLAGS_GC_PERCENT_MASK, percent); | |
| } | |
| int pcache_cache_set_gc_percent(struct pcache_cache *cache, u8 percent) | |
| { | |
| if (percent > PCACHE_CACHE_GC_PERCENT_MAX || percent < PCACHE_CACHE_GC_PERCENT_MIN) | |
| return -EINVAL; | |
| mutex_lock(&cache->cache_info_lock); | |
| cache_info_set_gc_percent(&cache->cache_info, percent); | |
| cache_info_write(cache); | |
| mutex_unlock(&cache->cache_info_lock); | |
| return 0; | |
| } | |
| void cache_pos_encode(struct pcache_cache *cache, | |
| struct pcache_cache_pos_onmedia *pos_onmedia_base, | |
| struct pcache_cache_pos *pos, u64 seq, u32 *index) | |
| { | |
| struct pcache_cache_pos_onmedia pos_onmedia; | |
| struct pcache_cache_pos_onmedia *pos_onmedia_addr = pos_onmedia_base + *index; | |
| pos_onmedia.cache_seg_id = pos->cache_seg->cache_seg_id; | |
| pos_onmedia.seg_off = pos->seg_off; | |
| pos_onmedia.header.seq = seq; | |
| pos_onmedia.header.crc = cache_pos_onmedia_crc(&pos_onmedia); | |
| *index = (*index + 1) % PCACHE_META_INDEX_MAX; | |
| memcpy_flushcache(pos_onmedia_addr, &pos_onmedia, sizeof(struct pcache_cache_pos_onmedia)); | |
| pmem_wmb(); | |
| } | |
| int cache_pos_decode(struct pcache_cache *cache, | |
| struct pcache_cache_pos_onmedia *pos_onmedia, | |
| struct pcache_cache_pos *pos, u64 *seq, u32 *index) | |
| { | |
| struct pcache_cache_pos_onmedia latest, *latest_addr; | |
| latest_addr = pcache_meta_find_latest(&pos_onmedia->header, | |
| sizeof(struct pcache_cache_pos_onmedia), | |
| sizeof(struct pcache_cache_pos_onmedia), | |
| &latest); | |
| if (IS_ERR(latest_addr)) | |
| return PTR_ERR(latest_addr); | |
| if (!latest_addr) | |
| return -EIO; | |
| if (!cache_seg_id_valid(cache, latest.cache_seg_id)) | |
| return -EIO; | |
| pos->cache_seg = &cache->segments[latest.cache_seg_id]; | |
| if (latest.seg_off >= pos->cache_seg->segment.data_size) | |
| return -EIO; | |
| pos->seg_off = latest.seg_off; | |
| *seq = latest.header.seq; | |
| *index = (latest_addr - pos_onmedia); | |
| return 0; | |
| } | |
| static inline void cache_info_set_seg_id(struct pcache_cache *cache, u32 seg_id) | |
| { | |
| cache->cache_info.seg_id = seg_id; | |
| } | |
| static int cache_init(struct dm_pcache *pcache) | |
| { | |
| struct pcache_cache *cache = &pcache->cache; | |
| struct pcache_backing_dev *backing_dev = &pcache->backing_dev; | |
| struct pcache_cache_dev *cache_dev = &pcache->cache_dev; | |
| int ret; | |
| cache->segments = kvzalloc_objs(struct pcache_cache_segment, | |
| cache_dev->seg_num); | |
| if (!cache->segments) { | |
| ret = -ENOMEM; | |
| goto err; | |
| } | |
| cache->seg_map = kvcalloc(BITS_TO_LONGS(cache_dev->seg_num), sizeof(unsigned long), GFP_KERNEL); | |
| if (!cache->seg_map) { | |
| ret = -ENOMEM; | |
| goto free_segments; | |
| } | |
| cache->backing_dev = backing_dev; | |
| cache->cache_dev = &pcache->cache_dev; | |
| cache->n_segs = cache_dev->seg_num; | |
| atomic_set(&cache->gc_errors, 0); | |
| atomic_set(&cache->writeback_errors, 0); | |
| spin_lock_init(&cache->seg_map_lock); | |
| spin_lock_init(&cache->key_head_lock); | |
| mutex_init(&cache->cache_info_lock); | |
| mutex_init(&cache->key_tail_lock); | |
| mutex_init(&cache->dirty_tail_lock); | |
| mutex_init(&cache->writeback_lock); | |
| INIT_DELAYED_WORK(&cache->writeback_work, cache_writeback_fn); | |
| INIT_DELAYED_WORK(&cache->gc_work, pcache_cache_gc_fn); | |
| INIT_WORK(&cache->clean_work, clean_fn); | |
| return 0; | |
| free_segments: | |
| kvfree(cache->segments); | |
| err: | |
| return ret; | |
| } | |
| static void cache_exit(struct pcache_cache *cache) | |
| { | |
| kvfree(cache->seg_map); | |
| kvfree(cache->segments); | |
| } | |
| static void cache_info_init_default(struct pcache_cache *cache) | |
| { | |
| struct pcache_cache_info *cache_info = &cache->cache_info; | |
| memset(cache_info, 0, sizeof(*cache_info)); | |
| cache_info->n_segs = cache->cache_dev->seg_num; | |
| cache_info_set_gc_percent(cache_info, PCACHE_CACHE_GC_PERCENT_DEFAULT); | |
| } | |
| static int cache_tail_init(struct pcache_cache *cache) | |
| { | |
| struct dm_pcache *pcache = CACHE_TO_PCACHE(cache); | |
| bool new_cache = !(cache->cache_info.flags & PCACHE_CACHE_FLAGS_INIT_DONE); | |
| int ret; | |
| if (new_cache) { | |
| __set_bit(0, cache->seg_map); | |
| cache->key_head.cache_seg = &cache->segments[0]; | |
| cache->key_head.seg_off = 0; | |
| cache_pos_copy(&cache->key_tail, &cache->key_head); | |
| cache_pos_copy(&cache->dirty_tail, &cache->key_head); | |
| cache_encode_dirty_tail(cache); | |
| cache_encode_key_tail(cache); | |
| } else { | |
| if (cache_decode_key_tail(cache) || cache_decode_dirty_tail(cache)) { | |
| pcache_dev_err(pcache, "Corrupted key tail or dirty tail.\n"); | |
| return -EIO; | |
| } | |
| ret = cache_verify_dirty_tail(cache); | |
| if (ret) { | |
| pcache_dev_err(pcache, "dirty tail chain does not terminate (crafted cache image?)\n"); | |
| return ret; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int get_seg_id(struct pcache_cache *cache, | |
| struct pcache_cache_segment *prev_cache_seg, | |
| bool new_cache, u32 *seg_id) | |
| { | |
| struct dm_pcache *pcache = CACHE_TO_PCACHE(cache); | |
| struct pcache_cache_dev *cache_dev = cache->cache_dev; | |
| int ret; | |
| if (new_cache) { | |
| ret = cache_dev_get_empty_segment_id(cache_dev, seg_id); | |
| if (ret) { | |
| pcache_dev_err(pcache, "no available segment\n"); | |
| goto err; | |
| } | |
| if (prev_cache_seg) | |
| cache_seg_set_next_seg(prev_cache_seg, *seg_id); | |
| else | |
| cache_info_set_seg_id(cache, *seg_id); | |
| } else { | |
| if (prev_cache_seg) { | |
| struct pcache_segment_info *prev_seg_info; | |
| prev_seg_info = &prev_cache_seg->cache_seg_info; | |
| if (!segment_info_has_next(prev_seg_info)) { | |
| ret = -EFAULT; | |
| goto err; | |
| } | |
| *seg_id = prev_cache_seg->cache_seg_info.next_seg; | |
| } else { | |
| *seg_id = cache->cache_info.seg_id; | |
| } | |
| if (*seg_id >= cache_dev->seg_num) { | |
| pcache_dev_err(pcache, "invalid segment id %u from cache device (seg_num %u)\n", | |
| *seg_id, cache_dev->seg_num); | |
| ret = -EIO; | |
| goto err; | |
| } | |
| } | |
| return 0; | |
| err: | |
| return ret; | |
| } | |
| static int cache_segs_init(struct pcache_cache *cache) | |
| { | |
| struct pcache_cache_segment *prev_cache_seg = NULL; | |
| struct pcache_cache_info *cache_info = &cache->cache_info; | |
| bool new_cache = !(cache->cache_info.flags & PCACHE_CACHE_FLAGS_INIT_DONE); | |
| u32 seg_id; | |
| int ret; | |
| u32 i; | |
| if (cache_info->n_segs > cache->cache_dev->seg_num) { | |
| pcache_dev_err(CACHE_TO_PCACHE(cache), | |
| "cache_info n_segs %u exceeds cache device segments %u\n", | |
| cache_info->n_segs, cache->cache_dev->seg_num); | |
| return -EIO; | |
| } | |
| for (i = 0; i < cache_info->n_segs; i++) { | |
| ret = get_seg_id(cache, prev_cache_seg, new_cache, &seg_id); | |
| if (ret) | |
| goto err; | |
| ret = cache_seg_init(cache, seg_id, i, new_cache); | |
| if (ret) | |
| goto err; | |
| prev_cache_seg = &cache->segments[i]; | |
| } | |
| return 0; | |
| err: | |
| return ret; | |
| } | |
| static int cache_init_req_keys(struct pcache_cache *cache, u32 n_paral) | |
| { | |
| struct dm_pcache *pcache = CACHE_TO_PCACHE(cache); | |
| u32 n_subtrees; | |
| int ret; | |
| u32 i, cpu; | |
| /* Calculate number of cache trees based on the device size */ | |
| n_subtrees = DIV_ROUND_UP(cache->dev_size << SECTOR_SHIFT, PCACHE_CACHE_SUBTREE_SIZE); | |
| ret = cache_tree_init(cache, &cache->req_key_tree, n_subtrees); | |
| if (ret) | |
| goto err; | |
| cache->n_ksets = n_paral; | |
| cache->ksets = kvcalloc(cache->n_ksets, PCACHE_KSET_SIZE, GFP_KERNEL); | |
| if (!cache->ksets) { | |
| ret = -ENOMEM; | |
| goto req_tree_exit; | |
| } | |
| /* | |
| * Initialize each kset with a spinlock and delayed work for flushing. | |
| * Each kset is associated with one queue to ensure independent handling | |
| * of cache keys across multiple queues, maximizing multiqueue concurrency. | |
| */ | |
| for (i = 0; i < cache->n_ksets; i++) { | |
| struct pcache_cache_kset *kset = get_kset(cache, i); | |
| kset->cache = cache; | |
| spin_lock_init(&kset->kset_lock); | |
| INIT_DELAYED_WORK(&kset->flush_work, kset_flush_fn); | |
| } | |
| cache->data_heads = alloc_percpu(struct pcache_cache_data_head); | |
| if (!cache->data_heads) { | |
| ret = -ENOMEM; | |
| goto free_kset; | |
| } | |
| for_each_possible_cpu(cpu) { | |
| struct pcache_cache_data_head *h = | |
| per_cpu_ptr(cache->data_heads, cpu); | |
| h->head_pos.cache_seg = NULL; | |
| } | |
| /* | |
| * Replay persisted cache keys using cache_replay. | |
| * This function loads and replays cache keys from previously stored | |
| * ksets, allowing the cache to restore its state after a restart. | |
| */ | |
| ret = cache_replay(cache); | |
| if (ret) { | |
| pcache_dev_err(pcache, "failed to replay keys\n"); | |
| goto free_heads; | |
| } | |
| return 0; | |
| free_heads: | |
| free_percpu(cache->data_heads); | |
| free_kset: | |
| kvfree(cache->ksets); | |
| req_tree_exit: | |
| cache_tree_exit(&cache->req_key_tree); | |
| err: | |
| return ret; | |
| } | |
| static void cache_destroy_req_keys(struct pcache_cache *cache) | |
| { | |
| u32 i; | |
| for (i = 0; i < cache->n_ksets; i++) { | |
| struct pcache_cache_kset *kset = get_kset(cache, i); | |
| cancel_delayed_work_sync(&kset->flush_work); | |
| } | |
| free_percpu(cache->data_heads); | |
| kvfree(cache->ksets); | |
| cache_tree_exit(&cache->req_key_tree); | |
| } | |
| int pcache_cache_start(struct dm_pcache *pcache) | |
| { | |
| struct pcache_backing_dev *backing_dev = &pcache->backing_dev; | |
| struct pcache_cache *cache = &pcache->cache; | |
| struct pcache_cache_options *opts = &pcache->opts; | |
| int ret; | |
| ret = cache_init(pcache); | |
| if (ret) | |
| return ret; | |
| cache->cache_info_addr = CACHE_DEV_CACHE_INFO(cache->cache_dev); | |
| cache->cache_ctrl = CACHE_DEV_CACHE_CTRL(cache->cache_dev); | |
| backing_dev->cache = cache; | |
| cache->dev_size = backing_dev->dev_size; | |
| ret = cache_info_init(cache, opts); | |
| if (ret) | |
| goto cache_exit; | |
| ret = cache_segs_init(cache); | |
| if (ret) | |
| goto cache_exit; | |
| ret = cache_tail_init(cache); | |
| if (ret) | |
| goto cache_exit; | |
| ret = cache_init_req_keys(cache, num_online_cpus()); | |
| if (ret) | |
| goto cache_exit; | |
| ret = cache_writeback_init(cache); | |
| if (ret) | |
| goto destroy_keys; | |
| cache->cache_info.flags |= PCACHE_CACHE_FLAGS_INIT_DONE; | |
| cache_info_write(cache); | |
| queue_delayed_work(cache_get_wq(cache), &cache->gc_work, 0); | |
| return 0; | |
| destroy_keys: | |
| cache_destroy_req_keys(cache); | |
| cache_exit: | |
| cache_exit(cache); | |
| return ret; | |
| } | |
| void pcache_cache_stop(struct dm_pcache *pcache) | |
| { | |
| struct pcache_cache *cache = &pcache->cache; | |
| pcache_cache_flush(cache); | |
| cancel_delayed_work_sync(&cache->gc_work); | |
| flush_work(&cache->clean_work); | |
| cache_writeback_exit(cache); | |
| if (cache->req_key_tree.n_subtrees) | |
| cache_destroy_req_keys(cache); | |
| cache_exit(cache); | |
| } | |
| struct workqueue_struct *cache_get_wq(struct pcache_cache *cache) | |
| { | |
| struct dm_pcache *pcache = CACHE_TO_PCACHE(cache); | |
| return pcache->task_wq; | |
| } | |
| int pcache_cache_init(void) | |
| { | |
| key_cache = KMEM_CACHE(pcache_cache_key, 0); | |
| if (!key_cache) | |
| return -ENOMEM; | |
| return 0; | |
| } | |
| void pcache_cache_exit(void) | |
| { | |
| kmem_cache_destroy(key_cache); | |
| } |