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;
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 <linux/srcu.h>
+
+
+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,
swap_dir = NULL;
debugfs_remove_recursive(dir);
+
+ fops_enable_sync();
}
/**