From aac96602a094eede7d12ade40804533c01bbd68f Mon Sep 17 00:00:00 2001 From: Vyacheslav Cherkashin Date: Tue, 17 Apr 2018 12:33:12 +0300 Subject: [PATCH] master: add support for kernel version v4.7..v4.14 This is a workaround for fix deadlock in a kernel debugfs v4.7..v4.14 Change-Id: Ib1ad88b205f82e9503e1ca21e915a7fa2536a95b Signed-off-by: Vyacheslav Cherkashin --- modules/master/swap_debugfs.c | 106 ++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/modules/master/swap_debugfs.c b/modules/master/swap_debugfs.c index f4370162..7c237528 100644 --- a/modules/master/swap_debugfs.c +++ b/modules/master/swap_debugfs.c @@ -296,16 +296,6 @@ static ssize_t write_enable(struct file *file, const char __user *user_buf, char buf[32]; size_t buf_size; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) - /* - * Global rcu-lock(debugfs_srcu) was added to debugfs - * in commit to linux-kernel:9fd4dcece43. - * It causes deadlock in debugfs_remove_recursive(). - */ - pr_err("This kernel version does not supported\n"); - return -EPERM; -#endif - buf_size = min(count, (sizeof(buf) - 1)); if (copy_from_user(buf, user_buf, buf_size)) return -EFAULT; @@ -315,7 +305,101 @@ static ssize_t write_enable(struct file *file, const char __user *user_buf, return ret ? ret : count; } + +/* For kernel v4.7..v4.14 */ +#define WORKAROUND_DEADLOCK_IN_DEBUGFS ( \ + LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) && \ + LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) \ +) + +#if WORKAROUND_DEADLOCK_IN_DEBUGFS +/* + * Global rcu-lock(debugfs_srcu) was added to kernel debugfs + * in commit 9fd4dcece43 and removed in commit c9afbec2708. + * In case write 0 to "/sys/kernel/debug/swap/enable" + * it causes deadlock in debugfs_remove_recursive(). + * + * This workaround solves this problem! + */ + + +#include + + +DEFINE_STATIC_SRCU(g_workaround_srcu); + +static ssize_t do_proxy_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + int srcu_idx; + struct file_operations *real_fops = file->f_path.dentry->d_fsdata; + + /* use file start */ + srcu_idx = srcu_read_lock(&g_workaround_srcu); + barrier(); + + ret = real_fops->write(file, user_buf, count, ppos); + + /* use file finish */ + srcu_read_unlock(&g_workaround_srcu, srcu_idx); + + return ret; +} + +static void *g_real_proxy_write; + +static int workaround_proxy_enable_open(struct inode *inode, struct file *filp) +{ + struct file_operations *fops; + + /* remove const mode */ + fops = (struct file_operations *)filp->f_op; + + if (g_real_proxy_write) { + BUG_ON(g_real_proxy_write != fops->write); + } else { + /* save write method */ + g_real_proxy_write = fops->write; + } + + /* replace write method */ + fops->write = do_proxy_write; + + return 0; +} + +static int workaround_proxy_enable_release(struct inode *inode, + struct file *filp) +{ + struct file_operations *fops; + BUG_ON(!g_real_proxy_write); + + /* remove const mode */ + fops = (struct file_operations *)filp->f_op; + + /* restore write method */ + fops->write = g_real_proxy_write; + + return 0; +} + +static void fops_enable_sync(void) +{ + synchronize_srcu(&g_workaround_srcu); +} +#else /* WORKAROUND_DEADLOCK_IN_DEBUGFS */ +static void fops_enable_sync(void) +{ +} +#endif /* WORKAROUND_DEADLOCK_IN_DEBUGFS */ + + static const struct file_operations fops_enable = { +#if WORKAROUND_DEADLOCK_IN_DEBUGFS + .open = workaround_proxy_enable_open, + .release = workaround_proxy_enable_release, +#endif /* WORKAROUND_DEADLOCK_IN_DEBUGFS */ .owner = THIS_MODULE, .read = read_enable, .write = write_enable, @@ -351,6 +435,8 @@ static void debugfs_dir_exit(void) swap_dir = NULL; debugfs_remove_recursive(dir); + + fops_enable_sync(); } /** -- 2.34.1