| /* |
| * f2fs sysfs interface |
| * |
| * Copyright (c) 2012 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com/ |
| * Copyright (c) 2017 Chao Yu <chao@kernel.org> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| #include <linux/proc_fs.h> |
| #include <linux/f2fs_fs.h> |
| |
| #include "f2fs.h" |
| #include "segment.h" |
| #include "gc.h" |
| |
| static struct proc_dir_entry *f2fs_proc_root; |
| static struct kset *f2fs_kset; |
| |
| /* Sysfs support for f2fs */ |
| enum { |
| GC_THREAD, /* struct f2fs_gc_thread */ |
| SM_INFO, /* struct f2fs_sm_info */ |
| DCC_INFO, /* struct discard_cmd_control */ |
| NM_INFO, /* struct f2fs_nm_info */ |
| F2FS_SBI, /* struct f2fs_sb_info */ |
| #ifdef CONFIG_F2FS_FAULT_INJECTION |
| FAULT_INFO_RATE, /* struct f2fs_fault_info */ |
| FAULT_INFO_TYPE, /* struct f2fs_fault_info */ |
| #endif |
| }; |
| |
| struct f2fs_attr { |
| struct attribute attr; |
| ssize_t (*show)(struct f2fs_attr *, struct f2fs_sb_info *, char *); |
| ssize_t (*store)(struct f2fs_attr *, struct f2fs_sb_info *, |
| const char *, size_t); |
| int struct_type; |
| int offset; |
| }; |
| |
| static unsigned char *__struct_ptr(struct f2fs_sb_info *sbi, int struct_type) |
| { |
| if (struct_type == GC_THREAD) |
| return (unsigned char *)sbi->gc_thread; |
| else if (struct_type == SM_INFO) |
| return (unsigned char *)SM_I(sbi); |
| else if (struct_type == DCC_INFO) |
| return (unsigned char *)SM_I(sbi)->dcc_info; |
| else if (struct_type == NM_INFO) |
| return (unsigned char *)NM_I(sbi); |
| else if (struct_type == F2FS_SBI) |
| return (unsigned char *)sbi; |
| #ifdef CONFIG_F2FS_FAULT_INJECTION |
| else if (struct_type == FAULT_INFO_RATE || |
| struct_type == FAULT_INFO_TYPE) |
| return (unsigned char *)&sbi->fault_info; |
| #endif |
| return NULL; |
| } |
| |
| static ssize_t lifetime_write_kbytes_show(struct f2fs_attr *a, |
| struct f2fs_sb_info *sbi, char *buf) |
| { |
| struct super_block *sb = sbi->sb; |
| |
| if (!sb->s_bdev->bd_part) |
| return snprintf(buf, PAGE_SIZE, "0\n"); |
| |
| return snprintf(buf, PAGE_SIZE, "%llu\n", |
| (unsigned long long)(sbi->kbytes_written + |
| BD_PART_WRITTEN(sbi))); |
| } |
| |
| static ssize_t f2fs_sbi_show(struct f2fs_attr *a, |
| struct f2fs_sb_info *sbi, char *buf) |
| { |
| unsigned char *ptr = NULL; |
| unsigned int *ui; |
| |
| ptr = __struct_ptr(sbi, a->struct_type); |
| if (!ptr) |
| return -EINVAL; |
| |
| ui = (unsigned int *)(ptr + a->offset); |
| |
| return snprintf(buf, PAGE_SIZE, "%u\n", *ui); |
| } |
| |
| static ssize_t f2fs_sbi_store(struct f2fs_attr *a, |
| struct f2fs_sb_info *sbi, |
| const char *buf, size_t count) |
| { |
| unsigned char *ptr; |
| unsigned long t; |
| unsigned int *ui; |
| ssize_t ret; |
| |
| ptr = __struct_ptr(sbi, a->struct_type); |
| if (!ptr) |
| return -EINVAL; |
| |
| ui = (unsigned int *)(ptr + a->offset); |
| |
| ret = kstrtoul(skip_spaces(buf), 0, &t); |
| if (ret < 0) |
| return ret; |
| #ifdef CONFIG_F2FS_FAULT_INJECTION |
| if (a->struct_type == FAULT_INFO_TYPE && t >= (1 << FAULT_MAX)) |
| return -EINVAL; |
| #endif |
| *ui = t; |
| return count; |
| } |
| |
| static ssize_t f2fs_attr_show(struct kobject *kobj, |
| struct attribute *attr, char *buf) |
| { |
| struct f2fs_sb_info *sbi = container_of(kobj, struct f2fs_sb_info, |
| s_kobj); |
| struct f2fs_attr *a = container_of(attr, struct f2fs_attr, attr); |
| |
| return a->show ? a->show(a, sbi, buf) : 0; |
| } |
| |
| static ssize_t f2fs_attr_store(struct kobject *kobj, struct attribute *attr, |
| const char *buf, size_t len) |
| { |
| struct f2fs_sb_info *sbi = container_of(kobj, struct f2fs_sb_info, |
| s_kobj); |
| struct f2fs_attr *a = container_of(attr, struct f2fs_attr, attr); |
| |
| return a->store ? a->store(a, sbi, buf, len) : 0; |
| } |
| |
| static void f2fs_sb_release(struct kobject *kobj) |
| { |
| struct f2fs_sb_info *sbi = container_of(kobj, struct f2fs_sb_info, |
| s_kobj); |
| complete(&sbi->s_kobj_unregister); |
| } |
| |
| #define F2FS_ATTR_OFFSET(_struct_type, _name, _mode, _show, _store, _offset) \ |
| static struct f2fs_attr f2fs_attr_##_name = { \ |
| .attr = {.name = __stringify(_name), .mode = _mode }, \ |
| .show = _show, \ |
| .store = _store, \ |
| .struct_type = _struct_type, \ |
| .offset = _offset \ |
| } |
| |
| #define F2FS_RW_ATTR(struct_type, struct_name, name, elname) \ |
| F2FS_ATTR_OFFSET(struct_type, name, 0644, \ |
| f2fs_sbi_show, f2fs_sbi_store, \ |
| offsetof(struct struct_name, elname)) |
| |
| #define F2FS_GENERAL_RO_ATTR(name) \ |
| static struct f2fs_attr f2fs_attr_##name = __ATTR(name, 0444, name##_show, NULL) |
| |
| F2FS_RW_ATTR(GC_THREAD, f2fs_gc_kthread, gc_min_sleep_time, min_sleep_time); |
| F2FS_RW_ATTR(GC_THREAD, f2fs_gc_kthread, gc_max_sleep_time, max_sleep_time); |
| F2FS_RW_ATTR(GC_THREAD, f2fs_gc_kthread, gc_no_gc_sleep_time, no_gc_sleep_time); |
| F2FS_RW_ATTR(GC_THREAD, f2fs_gc_kthread, gc_idle, gc_idle); |
| F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, reclaim_segments, rec_prefree_segments); |
| F2FS_RW_ATTR(DCC_INFO, discard_cmd_control, max_small_discards, max_discards); |
| F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, batched_trim_sections, trim_sections); |
| F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, ipu_policy, ipu_policy); |
| F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, min_ipu_util, min_ipu_util); |
| F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, min_fsync_blocks, min_fsync_blocks); |
| F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, min_hot_blocks, min_hot_blocks); |
| F2FS_RW_ATTR(NM_INFO, f2fs_nm_info, ram_thresh, ram_thresh); |
| F2FS_RW_ATTR(NM_INFO, f2fs_nm_info, ra_nid_pages, ra_nid_pages); |
| F2FS_RW_ATTR(NM_INFO, f2fs_nm_info, dirty_nats_ratio, dirty_nats_ratio); |
| F2FS_RW_ATTR(F2FS_SBI, f2fs_sb_info, max_victim_search, max_victim_search); |
| F2FS_RW_ATTR(F2FS_SBI, f2fs_sb_info, dir_level, dir_level); |
| F2FS_RW_ATTR(F2FS_SBI, f2fs_sb_info, cp_interval, interval_time[CP_TIME]); |
| F2FS_RW_ATTR(F2FS_SBI, f2fs_sb_info, idle_interval, interval_time[REQ_TIME]); |
| #ifdef CONFIG_F2FS_FAULT_INJECTION |
| F2FS_RW_ATTR(FAULT_INFO_RATE, f2fs_fault_info, inject_rate, inject_rate); |
| F2FS_RW_ATTR(FAULT_INFO_TYPE, f2fs_fault_info, inject_type, inject_type); |
| #endif |
| F2FS_GENERAL_RO_ATTR(lifetime_write_kbytes); |
| |
| #define ATTR_LIST(name) (&f2fs_attr_##name.attr) |
| static struct attribute *f2fs_attrs[] = { |
| ATTR_LIST(gc_min_sleep_time), |
| ATTR_LIST(gc_max_sleep_time), |
| ATTR_LIST(gc_no_gc_sleep_time), |
| ATTR_LIST(gc_idle), |
| ATTR_LIST(reclaim_segments), |
| ATTR_LIST(max_small_discards), |
| ATTR_LIST(batched_trim_sections), |
| ATTR_LIST(ipu_policy), |
| ATTR_LIST(min_ipu_util), |
| ATTR_LIST(min_fsync_blocks), |
| ATTR_LIST(min_hot_blocks), |
| ATTR_LIST(max_victim_search), |
| ATTR_LIST(dir_level), |
| ATTR_LIST(ram_thresh), |
| ATTR_LIST(ra_nid_pages), |
| ATTR_LIST(dirty_nats_ratio), |
| ATTR_LIST(cp_interval), |
| ATTR_LIST(idle_interval), |
| #ifdef CONFIG_F2FS_FAULT_INJECTION |
| ATTR_LIST(inject_rate), |
| ATTR_LIST(inject_type), |
| #endif |
| ATTR_LIST(lifetime_write_kbytes), |
| NULL, |
| }; |
| |
| static const struct sysfs_ops f2fs_attr_ops = { |
| .show = f2fs_attr_show, |
| .store = f2fs_attr_store, |
| }; |
| |
| static struct kobj_type f2fs_ktype = { |
| .default_attrs = f2fs_attrs, |
| .sysfs_ops = &f2fs_attr_ops, |
| .release = f2fs_sb_release, |
| }; |
| |
| static int segment_info_seq_show(struct seq_file *seq, void *offset) |
| { |
| struct super_block *sb = seq->private; |
| struct f2fs_sb_info *sbi = F2FS_SB(sb); |
| unsigned int total_segs = |
| le32_to_cpu(sbi->raw_super->segment_count_main); |
| int i; |
| |
| seq_puts(seq, "format: segment_type|valid_blocks\n" |
| "segment_type(0:HD, 1:WD, 2:CD, 3:HN, 4:WN, 5:CN)\n"); |
| |
| for (i = 0; i < total_segs; i++) { |
| struct seg_entry *se = get_seg_entry(sbi, i); |
| |
| if ((i % 10) == 0) |
| seq_printf(seq, "%-10d", i); |
| seq_printf(seq, "%d|%-3u", se->type, |
| get_valid_blocks(sbi, i, false)); |
| if ((i % 10) == 9 || i == (total_segs - 1)) |
| seq_putc(seq, '\n'); |
| else |
| seq_putc(seq, ' '); |
| } |
| |
| return 0; |
| } |
| |
| static int segment_bits_seq_show(struct seq_file *seq, void *offset) |
| { |
| struct super_block *sb = seq->private; |
| struct f2fs_sb_info *sbi = F2FS_SB(sb); |
| unsigned int total_segs = |
| le32_to_cpu(sbi->raw_super->segment_count_main); |
| int i, j; |
| |
| seq_puts(seq, "format: segment_type|valid_blocks|bitmaps\n" |
| "segment_type(0:HD, 1:WD, 2:CD, 3:HN, 4:WN, 5:CN)\n"); |
| |
| for (i = 0; i < total_segs; i++) { |
| struct seg_entry *se = get_seg_entry(sbi, i); |
| |
| seq_printf(seq, "%-10d", i); |
| seq_printf(seq, "%d|%-3u|", se->type, |
| get_valid_blocks(sbi, i, false)); |
| for (j = 0; j < SIT_VBLOCK_MAP_SIZE; j++) |
| seq_printf(seq, " %.2x", se->cur_valid_map[j]); |
| seq_putc(seq, '\n'); |
| } |
| return 0; |
| } |
| |
| #define F2FS_PROC_FILE_DEF(_name) \ |
| static int _name##_open_fs(struct inode *inode, struct file *file) \ |
| { \ |
| return single_open(file, _name##_seq_show, PDE_DATA(inode)); \ |
| } \ |
| \ |
| static const struct file_operations f2fs_seq_##_name##_fops = { \ |
| .open = _name##_open_fs, \ |
| .read = seq_read, \ |
| .llseek = seq_lseek, \ |
| .release = single_release, \ |
| }; |
| |
| F2FS_PROC_FILE_DEF(segment_info); |
| F2FS_PROC_FILE_DEF(segment_bits); |
| |
| int __init f2fs_register_sysfs(void) |
| { |
| f2fs_proc_root = proc_mkdir("fs/f2fs", NULL); |
| |
| f2fs_kset = kset_create_and_add("f2fs", NULL, fs_kobj); |
| if (!f2fs_kset) |
| return -ENOMEM; |
| return 0; |
| } |
| |
| void f2fs_unregister_sysfs(void) |
| { |
| kset_unregister(f2fs_kset); |
| remove_proc_entry("fs/f2fs", NULL); |
| } |
| |
| int f2fs_init_sysfs(struct f2fs_sb_info *sbi) |
| { |
| struct super_block *sb = sbi->sb; |
| int err; |
| |
| if (f2fs_proc_root) |
| sbi->s_proc = proc_mkdir(sb->s_id, f2fs_proc_root); |
| |
| if (sbi->s_proc) { |
| proc_create_data("segment_info", S_IRUGO, sbi->s_proc, |
| &f2fs_seq_segment_info_fops, sb); |
| proc_create_data("segment_bits", S_IRUGO, sbi->s_proc, |
| &f2fs_seq_segment_bits_fops, sb); |
| } |
| |
| sbi->s_kobj.kset = f2fs_kset; |
| init_completion(&sbi->s_kobj_unregister); |
| err = kobject_init_and_add(&sbi->s_kobj, &f2fs_ktype, NULL, |
| "%s", sb->s_id); |
| if (err) |
| goto err_out; |
| return 0; |
| err_out: |
| if (sbi->s_proc) { |
| remove_proc_entry("segment_info", sbi->s_proc); |
| remove_proc_entry("segment_bits", sbi->s_proc); |
| remove_proc_entry(sb->s_id, f2fs_proc_root); |
| } |
| return err; |
| } |
| |
| void f2fs_exit_sysfs(struct f2fs_sb_info *sbi) |
| { |
| kobject_del(&sbi->s_kobj); |
| kobject_put(&sbi->s_kobj); |
| wait_for_completion(&sbi->s_kobj_unregister); |
| |
| if (sbi->s_proc) { |
| remove_proc_entry("segment_info", sbi->s_proc); |
| remove_proc_entry("segment_bits", sbi->s_proc); |
| remove_proc_entry(sbi->sb->s_id, f2fs_proc_root); |
| } |
| } |