[PATCH] exfat: add sysfs interface

From: Yangtao Li
Date: Wed Apr 05 2023 - 04:47:37 EST


Add sysfs interface to configure exfat related parameters.

Signed-off-by: Yangtao Li <frank.li@xxxxxxxx>
---
fs/exfat/Makefile | 2 +-
fs/exfat/dir.c | 4 +-
fs/exfat/exfat_fs.h | 13 +++++
fs/exfat/super.c | 23 +++++++-
fs/exfat/sysfs.c | 138 ++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 174 insertions(+), 6 deletions(-)
create mode 100644 fs/exfat/sysfs.c

diff --git a/fs/exfat/Makefile b/fs/exfat/Makefile
index ed51926a4971..96bda5e5cca2 100644
--- a/fs/exfat/Makefile
+++ b/fs/exfat/Makefile
@@ -5,4 +5,4 @@
obj-$(CONFIG_EXFAT_FS) += exfat.o

exfat-y := inode.o namei.o dir.o super.o fatent.o cache.o nls.o misc.o \
- file.o balloc.o
+ file.o balloc.o sysfs.o
diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c
index 957574180a5e..efd2bd6e0567 100644
--- a/fs/exfat/dir.c
+++ b/fs/exfat/dir.c
@@ -711,15 +711,13 @@ static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir
return 0;
}

-#define EXFAT_MAX_RA_SIZE (128*1024)
static int exfat_dir_readahead(struct super_block *sb, sector_t sec)
{
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct buffer_head *bh;
- unsigned int max_ra_count = EXFAT_MAX_RA_SIZE >> sb->s_blocksize_bits;
unsigned int page_ra_count = PAGE_SIZE >> sb->s_blocksize_bits;
unsigned int adj_ra_count = max(sbi->sect_per_clus, page_ra_count);
- unsigned int ra_count = min(adj_ra_count, max_ra_count);
+ unsigned int ra_count = min(adj_ra_count, sbi->max_dir_ra_count);

/* Read-ahead is not required */
if (sbi->sect_per_clus == 1)
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index 729ada9e26e8..20638334e1ec 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -236,10 +236,13 @@ struct exfat_mount_options {
int time_offset; /* Offset of timestamps from UTC (in minutes) */
};

+#define EXFAT_MAX_RA_SIZE (128*1024)
+
/*
* EXFAT file system superblock in-memory data
*/
struct exfat_sb_info {
+ struct super_block *sb; /* pointer to VFS super block */
unsigned long long num_sectors; /* num of sectors in volume */
unsigned int num_clusters; /* num of clusters in volume */
unsigned int cluster_size; /* cluster size in bytes */
@@ -275,6 +278,10 @@ struct exfat_sb_info {
struct hlist_head inode_hashtable[EXFAT_HASH_SIZE];

struct rcu_head rcu;
+
+ unsigned int max_dir_ra_count;
+ struct kobject s_kobj;
+ struct completion s_kobj_unregister;
};

#define EXFAT_CACHE_VALID 0
@@ -516,6 +523,12 @@ int exfat_write_inode(struct inode *inode, struct writeback_control *wbc);
void exfat_evict_inode(struct inode *inode);
int exfat_block_truncate_page(struct inode *inode, loff_t from);

+/* In sysfs.c */
+int exfat_sysfs_register(struct super_block *sb);
+void exfat_sysfs_unregister(struct super_block *sb);
+int __init exfat_sysfs_init(void);
+void exfat_sysfs_exit(void);
+
/* exfat/nls.c */
unsigned short exfat_toupper(struct super_block *sb, unsigned short a);
int exfat_uniname_ncmp(struct super_block *sb, unsigned short *a,
diff --git a/fs/exfat/super.c b/fs/exfat/super.c
index 8c32460e031e..a29ce9561cc1 100644
--- a/fs/exfat/super.c
+++ b/fs/exfat/super.c
@@ -501,6 +501,7 @@ static int exfat_read_boot_sector(struct super_block *sb)
sbi->vol_flags_persistent = sbi->vol_flags & (VOLUME_DIRTY | MEDIA_FAILURE);
sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER;
sbi->used_clusters = EXFAT_CLUSTERS_UNTRACKED;
+ sbi->max_dir_ra_count = EXFAT_MAX_RA_SIZE >> sb->s_blocksize_bits;

/* check consistencies */
if ((u64)sbi->num_FAT_sectors << p_boot->sect_size_bits <
@@ -638,6 +639,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc)
opts->discard = 0;
}

+ sbi->sb = sb;
sb->s_flags |= SB_NODIRATIME;
sb->s_magic = EXFAT_SUPER_MAGIC;
sb->s_op = &exfat_sops;
@@ -697,6 +699,10 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc)
goto free_table;
}

+ err = exfat_sysfs_register(sb);
+ if (err)
+ goto put_inode;
+
return 0;

put_inode:
@@ -773,12 +779,18 @@ static int exfat_init_fs_context(struct fs_context *fc)
return 0;
}

+static void exfat_kill_super(struct super_block *sb)
+{
+ kill_block_super(sb);
+ exfat_sysfs_unregister(sb);
+}
+
static struct file_system_type exfat_fs_type = {
.owner = THIS_MODULE,
.name = "exfat",
.init_fs_context = exfat_init_fs_context,
.parameters = exfat_parameters,
- .kill_sb = kill_block_super,
+ .kill_sb = exfat_kill_super,
.fs_flags = FS_REQUIRES_DEV,
};

@@ -811,12 +823,18 @@ static int __init init_exfat_fs(void)
goto shutdown_cache;
}

- err = register_filesystem(&exfat_fs_type);
+ err = exfat_sysfs_init();
if (err)
goto destroy_cache;

+ err = register_filesystem(&exfat_fs_type);
+ if (err)
+ goto sysfs_exit;
+
return 0;

+sysfs_exit:
+ exfat_sysfs_exit();
destroy_cache:
kmem_cache_destroy(exfat_inode_cachep);
shutdown_cache:
@@ -833,6 +851,7 @@ static void __exit exit_exfat_fs(void)
rcu_barrier();
kmem_cache_destroy(exfat_inode_cachep);
unregister_filesystem(&exfat_fs_type);
+ exfat_sysfs_exit();
exfat_cache_shutdown();
}

diff --git a/fs/exfat/sysfs.c b/fs/exfat/sysfs.c
new file mode 100644
index 000000000000..d0a4dac3bc71
--- /dev/null
+++ b/fs/exfat/sysfs.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Vivo Communication Technology Co.,Ltd.
+ * Author: Yangtao Li <frank.li@xxxxxxxx>
+ */
+
+#include <linux/fs.h>
+#include <linux/seq_file.h>
+#include <linux/blkdev.h>
+
+#include "exfat_raw.h"
+#include "exfat_fs.h"
+
+struct exfat_sysfs_attr {
+ struct attribute attr;
+ ssize_t (*show)(struct exfat_sb_info *sbi, char *buf);
+ ssize_t (*store)(struct exfat_sb_info *sbi, const char *buf,
+ size_t count);
+};
+
+#define EXFAT_SYSFS_ATTR_RW(name) \
+static struct exfat_sysfs_attr exfat_sysfs_attr_##name = __ATTR_RW(name)
+
+#define ATTR_LIST(name) (&exfat_sysfs_attr_##name.attr)
+
+static ssize_t exfat_sysfs_attr_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct exfat_sb_info *sbi =
+ container_of(kobj, struct exfat_sb_info, s_kobj);
+ struct exfat_sysfs_attr *exfat_attr =
+ container_of(attr, struct exfat_sysfs_attr, attr);
+
+ return exfat_attr->show(sbi, buf);
+}
+
+static ssize_t exfat_sysfs_attr_store(struct kobject *kobj,
+ struct attribute *attr, const char *buf, size_t count)
+{
+ struct exfat_sb_info *sbi =
+ container_of(kobj, struct exfat_sb_info, s_kobj);
+ struct exfat_sysfs_attr *exfat_attr =
+ container_of(attr, struct exfat_sysfs_attr, attr);
+
+ return exfat_attr->store(sbi, buf, count);
+}
+
+static ssize_t max_dir_ra_count_show(struct exfat_sb_info *sbi, char *buf)
+{
+ return sysfs_emit(buf, "%u\n", sbi->max_dir_ra_count);
+}
+
+static ssize_t max_dir_ra_count_store(struct exfat_sb_info *sbi, const char *buf,
+ size_t count)
+{
+ struct super_block *sb = sbi->sb;
+ unsigned long t;
+ int ret;
+
+ ret = kstrtoul(skip_spaces(buf), 0, &t);
+ if (ret < 0)
+ return ret;
+
+ if (t > EXFAT_MAX_RA_SIZE >> sb->s_blocksize_bits)
+ return -EINVAL;
+
+ sbi->max_dir_ra_count = t;
+
+ return count;
+}
+EXFAT_SYSFS_ATTR_RW(max_dir_ra_count);
+
+static struct attribute *exfat_sysfs_attrs[] = {
+ ATTR_LIST(max_dir_ra_count),
+ NULL,
+};
+ATTRIBUTE_GROUPS(exfat_sysfs);
+
+static void exfat_sysfs_sb_release(struct kobject *kobj)
+{
+ struct exfat_sb_info *sbi =
+ container_of(kobj, struct exfat_sb_info, s_kobj);
+
+ complete(&sbi->s_kobj_unregister);
+}
+
+static const struct sysfs_ops exfat_sysfs_attr_ops = {
+ .show = exfat_sysfs_attr_show,
+ .store = exfat_sysfs_attr_store,
+};
+
+static const struct kobj_type exfat_sb_ktype = {
+ .default_groups = exfat_sysfs_groups,
+ .sysfs_ops = &exfat_sysfs_attr_ops,
+ .release = exfat_sysfs_sb_release,
+};
+
+static struct kobject *exfat_sysfs_root;
+
+int exfat_sysfs_register(struct super_block *sb)
+{
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ int ret;
+
+ init_completion(&sbi->s_kobj_unregister);
+ ret = kobject_init_and_add(&sbi->s_kobj, &exfat_sb_ktype,
+ exfat_sysfs_root, "%s", sb->s_id);
+ if (ret) {
+ kobject_put(&sbi->s_kobj);
+ wait_for_completion(&sbi->s_kobj_unregister);
+ return ret;
+ }
+
+ return 0;
+}
+
+void exfat_sysfs_unregister(struct super_block *sb)
+{
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+
+ kobject_put(&sbi->s_kobj);
+ wait_for_completion(&sbi->s_kobj_unregister);
+}
+
+int __init exfat_sysfs_init(void)
+{
+ exfat_sysfs_root = kobject_create_and_add("exfat", fs_kobj);
+ if (!exfat_sysfs_root)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void exfat_sysfs_exit(void)
+{
+ kobject_put(exfat_sysfs_root);
+ exfat_sysfs_root = NULL;
+}
--
2.35.1