kernfs, sysfs, driver-core: implement synchronous self-removal
Subsystems (7)
Tejun Heo
maintainerPATCH v4 1/1 kernfs, sysfs, driver-core: implement synchronous self-removal
Hello,
This is v4 of kernfs self-removal patchset. Unfortunately, this series has a somewhat turbulent history. After sending out the first iteration[1], I realized that cgroup's use case would need more flexibility so I called it off and after a while posted the second version[2] which contained patches which were slightly stale by mistake, so soon after v3[3] was posted, which got applied to the driver-core-next for 3.14-rc1 inclusion; unfortunately, while working on it later, I realized that the new more flexible scheme was fundamentally broken and the first approach in v1 was actually the correct one. So, the whole series got reverted and this is the fourth trial aiming for the 3.15-rc1 window. This time, the whole cgroup conversion is completed on top and verified to work properly, so hopefully this take is the last for this series.
As described above, the approach used in the first iteration was the correct one, so this patchset is an updated version of v1. The only necessary addition was exposing breaking out of s_active protection as a separate API.
* Incorporate fixes from v3 of the patchset.
* kernfs_[un]break_active_protection() added. "break" puts the active reference while "unbreak" undoes it so that the refcnt is balanced; however, unbreaking doesn't and can't restore active reference protection itself. Once broken, active ref protection is gone for the full duration of the method invocation. This allows more complex implementation of self-removal where self-removal attempts may be rejected by shifting the responsibility of ensuring object accessbility to the kernfs user.
This API is a bit cumbersome to use but the simpler kernfs_remove_self() is still provided and built on top of the above API.
Original patch description follows.
kernfs / sysfs implement the "sever" semantic for userland accesses. When a node is removed, no further userland operations are allowed and the in-flight ones are drained before removal is finished. This makes policing post-mortem userland accesses trivial for its users; unfortunately, this comes with a drawback - a node which tries to delete oneself through one of its userland operations deadlocks. Removal wants to drain the active access that the operation itself is running on top of.
This currently is worked around in the sysfs layer using sysfs_schedule_callback() which punts the actual removal to a work item. While making the operation asynchronous kinda works, it's a bit cumbersome to use and its behavior isn't quite correct as the caller has no way of telling when or even whether the operation is actually complete. If such self-removal is followed by another operation which expects the removed name to be available, there's no way to make the second operation reliable - e.g. something like "echo 1 > asdf/delete; echo asdf > create_new_child" can't work properly.
This patchset improves kernfs removal path and implements kernfs_remove_self() which is to be called from an on-going kernfs operation and removes the self node. The function can be called concurrently and only one will return %true and all others will wait until the winner's file operation is complete (not the kernfs_remove_self() call itself but the enclosing file operation which invoked the function). This ensures that if there are multiple concurrent "echo 1 > asdf/delete", all of them would finish only after the whole store_delete() method is complete.
kernfs_remove_self() is exposed to upper layers through sysfs_remove_file_self() and device_remove_file_self(). The existing users of device_schedule_callback() are converted to use remove_self and the unused async mechanism is removed.
This patchset contains the following eleven patches.
0001-kernfs-replace-kernfs_node-u.completion-with-kernfs_.patch 0002-kernfs-restructure-removal-path-to-fix-possible-prem.patch 0003-kernfs-invoke-kernfs_unmap_bin_file-directly-from-ke.patch 0004-kernfs-remove-kernfs_addrm_cxt.patch 0005-kernfs-remove-KERNFS_ACTIVE_REF-and-add-kernfs_lockd.patch 0006-kernfs-remove-KERNFS_REMOVED.patch 0007-kernfs-sysfs-driver-core-implement-kernfs_remove_sel.patch 0008-pci-use-device_remove_file_self-instead-of-device_sc.patch 0009-scsi-use-device_remove_file_self-instead-of-device_s.patch 0010-s390-use-device_remove_file_self-instead-of-device_s.patch 0011-sysfs-driver-core-remove-unused-sysfs-device-_schedu.patch
0001 replaces kernfs_node->u.completion with a hierarchy-wide wait_queue_head. This will be used to fix concurrent removal behavior.
0002 fixes premature completion of node removal when multiple removers are competing. This shouldn't matter for the existing sysfs users.
0003-0006 clean up removal path. The size of kernfs_node is reduced by one pointer in the process.
0007 implements kernfs_remove_self() and friends.
0008-0011 convert the existing users of device_schedule_callback() to device_remove_file_self() and removes now unused async mechanism.
After the changes, kernfs_node is shrunken by a pointer and LOC goes down a bit too.
The patchset is on top of the current linus#master 54c0a4b46150 ("Merge branch 'akpm' (incoming from Andrew)"). While 3.14-rc1 hasn't been tagged yet, it's unlikely that the affected code area would receive any significant updates in the meantime. If the patchset doesn't apply cleanly on -rc1, I'll post the refreshed version.
The patchset is also available in the following git branch.
git://git.kernel.org/pub/scm/linux/kernel/git/tj/misc.git review-kernfs-suicide
diffstat follows.
arch/s390/include/asm/ccwgroup.h | 1 arch/s390/pci/pci_sysfs.c | 18 - drivers/base/core.c | 50 +--- drivers/pci/pci-sysfs.c | 17 - drivers/s390/block/dcssblk.c | 14 - drivers/s390/cio/ccwgroup.c | 26 +- drivers/scsi/scsi_sysfs.c | 15 - fs/kernfs/dir.c | 447 +++++++++++++++++++++++---------------- fs/kernfs/file.c | 6 fs/kernfs/kernfs-internal.h | 14 - fs/kernfs/symlink.c | 6 fs/sysfs/file.c | 115 ++-------- include/linux/device.h | 13 - include/linux/kernfs.h | 18 - include/linux/sysfs.h | 16 - 15 files changed, 373 insertions(+), 403 deletions(-)
Thanks.
--
tejun
[1] http://lkml.kernel.org/g/<[email protected]>
[2] http://lkml.kernel.org/g/<[email protected]>
[3] http://lkml.kernel.org/g/<[email protected]>
Tejun Heo
maintainerPATCH 1/11 kernfs: replace kernfs_node->u.completion with kernfs_root->deactivate_waitq
kernfs_node->u.completion is used to notify deactivation completion from kernfs_put_active() to kernfs_deactivate(). We now allow multiple racing removals of the same node and the current removal scheme is no longer correct - kernfs_remove() invocation may return before the node is properly deactivated if it races against another removal. The removal path will be restructured to address the issue.
To help such restructure which requires supporting multiple waiters, this patch replaces kernfs_node->u.completion with kernfs_root->deactivate_waitq. This makes deactivation event notifications share a per-root waitqueue_head; however, the wait path is quite cold and this will also allow shaving one pointer off kernfs_node.
--- fs/kernfs/dir.c | 27 +++++++++++---------------- include/linux/kernfs.h | 4 ++-- 2 files changed, 13 insertions(+), 18 deletions(-)
fs/kernfs/dir.c +11 -16
@@ -8,6 +8,7 @@* This file is released under the GPLv2. */ +#include <linux/sched.h> #include <linux/fs.h> #include <linux/namei.h> #include <linux/idr.h>
@@ -151,6 +152,7 @@ struct kernfs_node *kernfs_get_active(struct kernfs_node *kn)*/ void kernfs_put_active (struct kernfs_node *kn) { +struct kernfs_root *root =kernfs_root (kn);int v;if (unlikely (!kn))
@@ -162,11 +164,7 @@ void kernfs_put_active(struct kernfs_node *kn)if (likely (v !=KN_DEACTIVATED_BIAS ))return ; -/* -* atomic_dec_return() is a mb(), we'll always see the updated -* kn->u.completion. -*/ -complete (kn->u.completion); +wake_up_all (&root->deactivate_waitq); }/**
@@ -177,26 +175,22 @@ void kernfs_put_active(struct kernfs_node *kn)*/ static void kernfs_deactivate (struct kernfs_node *kn) { -DECLARE_COMPLETION_ONSTACK (wait); -int v; +struct kernfs_root *root =kernfs_root (kn);BUG_ON (!(kn->flags &KERNFS_REMOVED ));if (!(kernfs_type (kn) &KERNFS_ACTIVE_REF ))return ; - kn->u.completion = (void *)&wait; -rwsem_acquire (&kn->dep_map,0 ,0 , _RET_IP_); -/* atomic_add_return() is a mb(), put_active() will always see -* the updated kn->u.completion. -*/ - v =atomic_add_return (KN_DEACTIVATED_BIAS , &kn->active); -if (v !=KN_DEACTIVATED_BIAS ) { +atomic_add (KN_DEACTIVATED_BIAS , &kn->active); + +if (atomic_read (&kn->active) !=KN_DEACTIVATED_BIAS )lock_contended (&kn->dep_map, _RET_IP_); -wait_for_completion (&wait); - } + +wait_event (root->deactivate_waitq, +atomic_read (&kn->active) ==KN_DEACTIVATED_BIAS );lock_acquired (&kn->dep_map, _RET_IP_);rwsem_release (&kn->dep_map,1 , _RET_IP_);
@@ -626,6 +620,7 @@ struct kernfs_root *kernfs_create_root(struct kernfs_dir_ops *kdops, void *priv) root->dir_ops = kdops; root->kn = kn; +init_waitqueue_head (&root->deactivate_waitq);return root; }
include/linux/kernfs.h +2 -2
@@ -15,7 +15,7 @@#include <linux/lockdep.h> #include <linux/rbtree.h> #include <linux/atomic.h> -#include <linux/completion.h> +#include <linux/wait.h> struct file ;struct dentry ;
@@ -92,7 +92,6 @@ struct kernfs_node {struct rb_node rb;union { -struct completion *completion;struct kernfs_node *removed_list; } u;
@@ -133,6 +132,7 @@ struct kernfs_root {/* private fields, do not use outside kernfs proper */ struct ida ino_ida;struct kernfs_dir_ops *dir_ops; +wait_queue_head_t deactivate_waitq; };struct kernfs_open_file {
Signature · 1 line
1.8.5.3
Tejun Heo
maintainerPATCH 2/11 kernfs: restructure removal path to fix possible premature return
In reply to Tejun HeoThe recursive nature of kernfs_remove() means that, even if kernfs_remove() is not allowed to be called multiple times on the same node, there may be race conditions between removal of parent and its descendants. While we can claim that kernfs_remove() shouldn't be called on one of the descendants while the removal of an ancestor is in progress, such rule is unnecessarily restrictive and very difficult to enforce. It's better to simply allow invoking kernfs_remove() as the caller sees fit as long as the caller ensures that the node is accessible.
The current behavior in such situations is broken. Whoever enters removal path first takes the node off the hierarchy and then deactivates. Following removers either return as soon as it notices that it's not the first one or can't even find the target node as it has already been removed from the hierarchy. In both cases, the following removers may finish prematurely while the nodes which should be removed and drained are still being processed by the first one.
This patch restructures so that multiple removers, whether through recursion or direction invocation, always follow the following rules.
* When there are multiple concurrent removers, only one puts the base ref.
* Regardless of which one puts the base ref, all removers are blocked until the target node is fully deactivated and removed.
To achieve the above, removal path now first marks all descendants including self REMOVED and then deactivates and unlinks leftmost descendant one-by-one. kernfs_deactivate() is called directly from __kernfs_removal() and drops and regrabs kernfs_mutex for each descendant to drain active refs. As this means that multiple removers can enter kernfs_deactivate() for the same node, the function is updated so that it can handle multiple deactivators of the same node - only one actually deactivates but all wait till drain completion.
The restructured removal path guarantees that a removed node gets unlinked only after the node is deactivated and drained. Combined with proper multiple deactivator handling, this guarantees that any invocation of kernfs_remove() returns only after the node itself and all its descendants are deactivated, drained and removed.
v2: Draining separated into a separate loop (used to be in the same
loop as unlink) and done from __kernfs_deactivate(). This is to allow exposing deactivation as a separate interface later.
Root node removal was broken in v1 patch. Fixed.
v3: Revert most of v2 except for root node removal fix and
simplification of KERNFS_REMOVED setting loop.
--- fs/kernfs/dir.c | 121 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 64 insertions(+), 57 deletions(-)
fs/kernfs/dir.c +64 -57
@@ -106,18 +106,24 @@ static int kernfs_link_sibling(struct kernfs_node *kn)* kernfs_unlink_sibling - unlink kernfs_node from sibling rbtree * @kn: kernfs_node of interest * -* Unlink @kn from its sibling rbtree which starts from -* kn->parent->dir.children. +* Try to unlink @kn from its sibling rbtree which starts from +* kn->parent->dir.children. Returns %true if @kn was actually +* removed, %false if @kn wasn't on the rbtree. * * Locking: * mutex_lock(kernfs_mutex) */ -static void kernfs_unlink_sibling (struct kernfs_node *kn) +static bool kernfs_unlink_sibling (struct kernfs_node *kn) { +if (RB_EMPTY_NODE (&kn->rb)) +return false; +if (kernfs_type (kn) ==KERNFS_DIR ) kn->parent->dir.subdirs--;rb_erase (&kn->rb, &kn->parent->dir.children); +RB_CLEAR_NODE (&kn->rb); +return true; }/**
@@ -171,29 +177,39 @@ void kernfs_put_active(struct kernfs_node *kn)* kernfs_deactivate - deactivate kernfs_node * @kn: kernfs_node to deactivate * -* Deny new active references and drain existing ones. +* Deny new active references and drain existing ones. Mutiple +* removers may invoke this function concurrently on @kn and all will +* return after deactivation and draining are complete. */ static void kernfs_deactivate (struct kernfs_node *kn) + __releases(&kernfs_mutex) __acquires(&kernfs_mutex) {struct kernfs_root *root =kernfs_root (kn); +lockdep_assert_held (&kernfs_mutex);BUG_ON (!(kn->flags &KERNFS_REMOVED ));if (!(kernfs_type (kn) &KERNFS_ACTIVE_REF ))return ; -rwsem_acquire (&kn->dep_map,0 ,0 , _RET_IP_); +/* only the first invocation on @kn should deactivate it */ +if (atomic_read (&kn->active) >=0 ) +atomic_add (KN_DEACTIVATED_BIAS , &kn->active); -atomic_add (KN_DEACTIVATED_BIAS , &kn->active); +mutex_unlock (&kernfs_mutex); +rwsem_acquire (&kn->dep_map,0 ,0 , _RET_IP_);if (atomic_read (&kn->active) !=KN_DEACTIVATED_BIAS )lock_contended (&kn->dep_map, _RET_IP_); +/* but everyone should wait for draining */ wait_event (root->deactivate_waitq,atomic_read (&kn->active) ==KN_DEACTIVATED_BIAS );lock_acquired (&kn->dep_map, _RET_IP_);rwsem_release (&kn->dep_map,1 , _RET_IP_); + +mutex_lock (&kernfs_mutex); }/**
@@ -343,6 +359,7 @@ static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root,atomic_set (&kn->count,1 );atomic_set (&kn->active,0 ); +RB_CLEAR_NODE (&kn->rb); kn->name = name; kn->mode = mode;
@@ -450,49 +467,6 @@ int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn) }/** -* kernfs_remove_one - remove kernfs_node from parent -* @acxt: addrm context to use -* @kn: kernfs_node to be removed -* -* Mark @kn removed and drop nlink of parent inode if @kn is a -* directory. @kn is unlinked from the children list. -* -* This function should be called between calls to -* kernfs_addrm_start() and kernfs_addrm_finish() and should be -* passed the same @acxt as passed to kernfs_addrm_start(). -* -* LOCKING: -* Determined by kernfs_addrm_start(). -*/ -static void kernfs_remove_one (struct kernfs_addrm_cxt *acxt, -struct kernfs_node *kn) -{ -struct kernfs_iattrs *ps_iattr; - -/* -* Removal can be called multiple times on the same node. Only the -* first invocation is effective and puts the base ref. -*/ -if (kn->flags &KERNFS_REMOVED ) -return ; - -if (kn->parent) { -kernfs_unlink_sibling (kn); - -/* Update timestamps on the parent */ - ps_iattr = kn->parent->iattr; -if (ps_iattr) { - ps_iattr->ia_iattr.ia_ctime =CURRENT_TIME ; - ps_iattr->ia_iattr.ia_mtime =CURRENT_TIME ; - } - } - - kn->flags |=KERNFS_REMOVED ; - kn->u.removed_list = acxt->removed; - acxt->removed = kn; -} - -/** * kernfs_addrm_finish - finish up kernfs_node add/remove * @acxt: addrm context to finish up *
@@ -515,7 +489,6 @@ void kernfs_addrm_finish(struct kernfs_addrm_cxt *acxt) acxt->removed = kn->u.removed_list; -kernfs_deactivate (kn);kernfs_unmap_bin_file (kn);kernfs_put (kn); }
@@ -824,20 +797,54 @@ static struct kernfs_node *kernfs_next_descendant_post(struct kernfs_node *pos,static void __kernfs_remove (struct kernfs_addrm_cxt *acxt,struct kernfs_node *kn) { -struct kernfs_node *pos, *next; +struct kernfs_node *pos; + +lockdep_assert_held (&kernfs_mutex);if (!kn)return ;pr_debug ("kernfs %s: removing\n" , kn->name); - next =NULL ; +/* disable lookup and node creation under @kn */ + pos =NULL ; +while ((pos =kernfs_next_descendant_post (pos, kn))) + pos->flags |=KERNFS_REMOVED ; + +/* deactivate and unlink the subtree node-by-node */ do { - pos = next; - next =kernfs_next_descendant_post (pos, kn); -if (pos) -kernfs_remove_one (acxt, pos); - }while (next); + pos =kernfs_leftmost_descendant (kn); + +/* +* kernfs_deactivate() drops kernfs_mutex temporarily and +* @pos's base ref could have been put by someone else by +* the time the function returns. Make sure it doesn't go +* away underneath us. +*/ +kernfs_get (pos); + +kernfs_deactivate (pos); + +/* +* kernfs_unlink_sibling() succeeds once per node. Use it +* to decide who's responsible for cleanups. +*/ +if (!pos->parent ||kernfs_unlink_sibling (pos)) { +struct kernfs_iattrs *ps_iattr = + pos->parent ? pos->parent->iattr :NULL ; + +/* update timestamps on the parent */ +if (ps_iattr) { + ps_iattr->ia_iattr.ia_ctime =CURRENT_TIME ; + ps_iattr->ia_iattr.ia_mtime =CURRENT_TIME ; + } + + pos->u.removed_list = acxt->removed; + acxt->removed = pos; + } + +kernfs_put (pos); + }while (pos != kn); }/**
Signature · 1 line
1.8.5.3
Tejun Heo
maintainerPATCH 3/11 kernfs: invoke kernfs_unmap_bin_file() directly from kernfs_deactivate()
In reply to Tejun Heokernfs_unmap_bin_file() is supposed to unmap all memory mappings of the target file before kernfs_remove() finishes; however, it currently is being called from kernfs_addrm_finish() and has the same race problem as the original implementation of deactivation when there are multiple removers - only the remover which snatches the node to its addrm_cxt->removed list is guaranteed to wait for its completion before returning.
It can be easily fixed by moving kernfs_unmap_bin_file() invocation from kernfs_addrm_finish() to kernfs_deactivated(). The function may be called multiple times but that shouldn't do any harm.
--- fs/kernfs/dir.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
fs/kernfs/dir.c +6 -4
@@ -177,9 +177,10 @@ void kernfs_put_active(struct kernfs_node *kn)* kernfs_deactivate - deactivate kernfs_node * @kn: kernfs_node to deactivate * -* Deny new active references and drain existing ones. Mutiple -* removers may invoke this function concurrently on @kn and all will -* return after deactivation and draining are complete. +* Deny new active references, drain existing ones and nuke all +* existing mmaps. Mutiple removers may invoke this function +* concurrently on @kn and all will return after deactivation and +* draining are complete. */ static void kernfs_deactivate(struct kernfs_node *kn) __releases(&kernfs_mutex) __acquires(&kernfs_mutex)
@@ -209,6 +210,8 @@ static void kernfs_deactivate(struct kernfs_node *kn)lock_acquired (&kn->dep_map, _RET_IP_);rwsem_release (&kn->dep_map,1 , _RET_IP_); +kernfs_unmap_bin_file (kn); +mutex_lock (&kernfs_mutex); }
@@ -489,7 +492,6 @@ void kernfs_addrm_finish(struct kernfs_addrm_cxt *acxt) acxt->removed = kn->u.removed_list; -kernfs_unmap_bin_file (kn);kernfs_put (kn); } }
Signature · 1 line
1.8.5.3
Tejun Heo
maintainerPATCH 4/11 kernfs: remove kernfs_addrm_cxt
In reply to Tejun Heokernfs_addrm_cxt and the accompanying kernfs_addrm_start/finish() were added because there were operations which should be performed outside kernfs_mutex after adding and removing kernfs_nodes. The necessary operations were recorded in kernfs_addrm_cxt and performed by kernfs_addrm_finish(); however, after the recent changes which relocated deactivation and unmapping so that they're performed directly during removal, the only operation kernfs_addrm_finish() performs is kernfs_put(), which can be moved inside the removal path too.
This patch moves the kernfs_put() of the base ref to __kernfs_remove() and remove kernfs_addrm_cxt and kernfs_addrm_start/finish().
* kernfs_add_one() is updated to grab and release kernfs_mutex itself. sysfs_addrm_start/finish() invocations around it are removed from all users.
* __kernfs_remove() puts an unlinked node directly instead of chaining it to kernfs_addrm_cxt. Its callers are updated to grab and release kernfs_mutex instead of calling kernfs_addrm_start/finish() around it.
v2: Rebased on top of "kernfs: associate a new kernfs_node with its
parent on creation" which dropped @parent from kernfs_add_one().
--- fs/kernfs/dir.c | 109 ++++++++++---------------------------------- fs/kernfs/file.c | 6 +-- fs/kernfs/kernfs-internal.h | 11 +---- fs/kernfs/symlink.c | 6 +-- include/linux/kernfs.h | 4 -- 5 files changed, 28 insertions(+), 108 deletions(-)
fs/kernfs/dir.c +25 -84
@@ -392,69 +392,44 @@ struct kernfs_node *kernfs_new_node(struct kernfs_node *parent, }/** -* kernfs_addrm_start - prepare for kernfs_node add/remove -* @acxt: pointer to kernfs_addrm_cxt to be used -* -* This function is called when the caller is about to add or remove -* kernfs_node. This function acquires kernfs_mutex. @acxt is used -* to keep and pass context to other addrm functions. -* -* LOCKING: -* Kernel thread context (may sleep). kernfs_mutex is locked on -* return. -*/ -void kernfs_addrm_start (struct kernfs_addrm_cxt *acxt) -__acquires (kernfs_mutex) -{ -memset (acxt,0 ,sizeof (*acxt)); - -mutex_lock (&kernfs_mutex); -} - -/** * kernfs_add_one - add kernfs_node to parent without warning -* @acxt: addrm context to use * @kn: kernfs_node to be added * * The caller must already have initialized @kn->parent. This * function increments nlink of the parent's inode if @kn is a * directory and link into the children list of the parent. * -* This function should be called between calls to -* kernfs_addrm_start() and kernfs_addrm_finish() and should be passed -* the same @acxt as passed to kernfs_addrm_start(). -* -* LOCKING: -* Determined by kernfs_addrm_start(). -* * RETURNS: * 0 on success, -EEXIST if entry with the given name already * exists. */ -int kernfs_add_one (struct kernfs_addrm_cxt *acxt,struct kernfs_node *kn) +int kernfs_add_one (struct kernfs_node *kn) {struct kernfs_node *parent = kn->parent; -bool has_ns =kernfs_ns_enabled (parent);struct kernfs_iattrs *ps_iattr; +bool has_ns;int ret; -if (has_ns != (bool )kn->ns) { -WARN (1 ,KERN_WARNING "kernfs: ns %s in '%s' for '%s'\n" , - has_ns ?"required" :"invalid" , parent->name, kn->name); -return -EINVAL ; - } +mutex_lock (&kernfs_mutex); + + ret = -EINVAL ; + has_ns =kernfs_ns_enabled (parent); +if (WARN (has_ns != (bool )kn->ns,KERN_WARNING "kernfs: ns %s in '%s' for '%s'\n" , + has_ns ?"required" :"invalid" , parent->name, kn->name)) + goto out_unlock;if (kernfs_type (parent) !=KERNFS_DIR ) -return -EINVAL ; + goto out_unlock; + ret = -ENOENT ;if (parent->flags &KERNFS_REMOVED ) -return -ENOENT ; + goto out_unlock; kn->hash =kernfs_name_hash (kn->name, kn->ns); ret =kernfs_link_sibling (kn);if (ret) -return ret; + goto out_unlock;/* Update timestamps on the parent */ ps_iattr = parent->iattr;
@@ -465,35 +440,10 @@ int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn)/* Mark the entry added into directory tree */ kn->flags &= ~KERNFS_REMOVED ; - -return 0 ; -} - -/** -* kernfs_addrm_finish - finish up kernfs_node add/remove -* @acxt: addrm context to finish up -* -* Finish up kernfs_node add/remove. Resources acquired by -* kernfs_addrm_start() are released and removed kernfs_nodes are -* cleaned up. -* -* LOCKING: -* kernfs_mutex is released. -*/ -void kernfs_addrm_finish (struct kernfs_addrm_cxt *acxt) -__releases (kernfs_mutex) -{ -/* release resources acquired by kernfs_addrm_start() */ + ret =0 ; +out_unlock:mutex_unlock (&kernfs_mutex); - -/* kill removed kernfs_nodes */ -while (acxt->removed) { -struct kernfs_node *kn = acxt->removed; - - acxt->removed = kn->u.removed_list; - -kernfs_put (kn); - } +return ret; }/**
@@ -626,7 +576,6 @@ struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent, const char *name, umode_t mode, void *priv, const void *ns) { - struct kernfs_addrm_cxt acxt; struct kernfs_node *kn;int rc;
@@ -640,10 +589,7 @@ struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent, kn->priv = priv;/* link in */ -kernfs_addrm_start (&acxt); - rc =kernfs_add_one (&acxt, kn); -kernfs_addrm_finish (&acxt); - + rc =kernfs_add_one (kn);if (!rc)return kn;
@@ -796,8 +742,7 @@ static struct kernfs_node *kernfs_next_descendant_post(struct kernfs_node *pos,return pos->parent; } -static void __kernfs_remove (struct kernfs_addrm_cxt *acxt, -struct kernfs_node *kn) +static void __kernfs_remove (struct kernfs_node *kn) {struct kernfs_node *pos;
@@ -841,8 +786,7 @@ static void __kernfs_remove(struct kernfs_addrm_cxt *acxt, ps_iattr->ia_iattr.ia_mtime =CURRENT_TIME ; } - pos->u.removed_list = acxt->removed; - acxt->removed = pos; +kernfs_put (pos); }kernfs_put (pos);
@@ -857,11 +801,9 @@ static void __kernfs_remove(struct kernfs_addrm_cxt *acxt,*/ void kernfs_remove (struct kernfs_node *kn) { -struct kernfs_addrm_cxt acxt; - -kernfs_addrm_start (&acxt); -__kernfs_remove (&acxt, kn); -kernfs_addrm_finish (&acxt); +mutex_lock (&kernfs_mutex); +__kernfs_remove (kn); +mutex_unlock (&kernfs_mutex); }/**
@@ -876,7 +818,6 @@ void kernfs_remove(struct kernfs_node *kn)int kernfs_remove_by_name_ns (struct kernfs_node *parent,const char *name,const void *ns) { -struct kernfs_addrm_cxt acxt;struct kernfs_node *kn;if (!parent) {
@@ -885,13 +826,13 @@ int kernfs_remove_by_name_ns(struct kernfs_node *parent, const char *name,return -ENOENT ; } -kernfs_addrm_start (&acxt); +mutex_lock (&kernfs_mutex); kn =kernfs_find_ns (parent, name, ns);if (kn) -__kernfs_remove (&acxt, kn); +__kernfs_remove (kn); -kernfs_addrm_finish (&acxt); +mutex_unlock (&kernfs_mutex);if (kn)return 0 ;
fs/kernfs/file.c +1 -5
@@ -820,7 +820,6 @@ struct kernfs_node *__kernfs_create_file(struct kernfs_node *parent,bool name_is_static, struct lock_class_key *key) { - struct kernfs_addrm_cxt acxt; struct kernfs_node *kn;unsigned flags;int rc;
@@ -855,10 +854,7 @@ struct kernfs_node *__kernfs_create_file(struct kernfs_node *parent,if (ops->mmap) kn->flags |=KERNFS_HAS_MMAP ; -kernfs_addrm_start (&acxt); - rc =kernfs_add_one (&acxt, kn); -kernfs_addrm_finish (&acxt); - + rc =kernfs_add_one (kn);if (rc) {kernfs_put (kn);return ERR_PTR (rc);
fs/kernfs/kernfs-internal.h +1 -10
@@ -45,13 +45,6 @@ static inline struct kernfs_root *kernfs_root(struct kernfs_node *kn) }/* -* Context structure to be used while adding/removing nodes. -*/ -struct kernfs_addrm_cxt { -struct kernfs_node *removed; -}; - -/* * mount.c */ struct kernfs_super_info {
@@ -100,9 +93,7 @@ extern const struct inode_operations kernfs_dir_iops;struct kernfs_node *kernfs_get_active (struct kernfs_node *kn);void kernfs_put_active (struct kernfs_node *kn); -void kernfs_addrm_start (struct kernfs_addrm_cxt *acxt); -int kernfs_add_one (struct kernfs_addrm_cxt *acxt,struct kernfs_node *kn); -void kernfs_addrm_finish (struct kernfs_addrm_cxt *acxt); +int kernfs_add_one (struct kernfs_node *kn);struct kernfs_node *kernfs_new_node (struct kernfs_node *parent,const char *name,umode_t mode,unsigned flags);
fs/kernfs/symlink.c +1 -5
@@ -27,7 +27,6 @@ struct kernfs_node *kernfs_create_link(struct kernfs_node *parent, struct kernfs_node *target) {struct kernfs_node *kn; -struct kernfs_addrm_cxt acxt;int error; kn =kernfs_new_node (parent, name,S_IFLNK |S_IRWXUGO ,KERNFS_LINK );
@@ -39,10 +38,7 @@ struct kernfs_node *kernfs_create_link(struct kernfs_node *parent, kn->symlink.target_kn = target;kernfs_get (target);/* ref owned by symlink */ -kernfs_addrm_start (&acxt); - error =kernfs_add_one (&acxt, kn); -kernfs_addrm_finish (&acxt); - + error =kernfs_add_one (kn);if (!error)return kn;
include/linux/kernfs.h +0 -4
@@ -91,10 +91,6 @@ struct kernfs_node {struct rb_node rb; -union { -struct kernfs_node *removed_list; - } u; -const void *ns;/* namespace tag */ unsigned int hash;/* ns + name hash */ union {
Signature · 1 line
1.8.5.3
Tejun Heo
maintainerPATCH 5/11 kernfs: remove KERNFS_ACTIVE_REF and add kernfs_lockdep()
In reply to Tejun HeoThere currently are two mechanisms gating active ref lockdep annotations - KERNFS_LOCKDEP flag and KERNFS_ACTIVE_REF type mask. The former disables lockdep annotations in kernfs_get/put_active() while the latter disables all of kernfs_deactivate().
While KERNFS_ACTIVE_REF also behaves as an optimization to skip the deactivation step for non-file nodes, the benefit is marginal and it needlessly diverges code paths. Let's drop KERNFS_ACTIVE_REF and use KERNFS_LOCKDEP in kernfs_deactivate() too.
While at it, add a test helper kernfs_lockdep() to test KERNFS_LOCKDEP flag so that it's more convenient and the related code can be compiled out when not enabled.
--- fs/kernfs/dir.c | 30 ++++++++++++++++++++---------- include/linux/kernfs.h | 1 - 2 files changed, 20 insertions(+), 11 deletions(-)
fs/kernfs/dir.c +20 -10
@@ -22,6 +22,15 @@ DEFINE_MUTEX(kernfs_mutex);#define rb_to_kn (X ) rb_entry((X), struct kernfs_node, rb) +static bool kernfs_lockdep (struct kernfs_node *kn) +{ +#ifdef CONFIG_DEBUG_LOCK_ALLOC +return kn->flags &KERNFS_LOCKDEP ; +#else +return false; +#endif +} +/** * kernfs_name_hash * @name: Null terminated string to hash
@@ -144,7 +153,7 @@ struct kernfs_node *kernfs_get_active(struct kernfs_node *kn)if (!atomic_inc_unless_negative (&kn->active))return NULL ; -if (kn->flags &KERNFS_LOCKDEP ) +if (kernfs_lockdep (kn))rwsem_acquire_read (&kn->dep_map,0 ,1 , _RET_IP_);return kn; }
@@ -164,7 +173,7 @@ void kernfs_put_active(struct kernfs_node *kn)if (unlikely (!kn))return ; -if (kn->flags &KERNFS_LOCKDEP ) +if (kernfs_lockdep (kn))rwsem_release (&kn->dep_map,1 , _RET_IP_); v =atomic_dec_return (&kn->active);if (likely (v !=KN_DEACTIVATED_BIAS ))
@@ -190,25 +199,26 @@ static void kernfs_deactivate(struct kernfs_node *kn)lockdep_assert_held (&kernfs_mutex);BUG_ON (!(kn->flags &KERNFS_REMOVED )); -if (!(kernfs_type (kn) &KERNFS_ACTIVE_REF )) -return ; -/* only the first invocation on @kn should deactivate it */ if (atomic_read (&kn->active) >=0 )atomic_add (KN_DEACTIVATED_BIAS , &kn->active);mutex_unlock (&kernfs_mutex); -rwsem_acquire (&kn->dep_map,0 ,0 , _RET_IP_); -if (atomic_read (&kn->active) !=KN_DEACTIVATED_BIAS ) -lock_contended (&kn->dep_map, _RET_IP_); +if (kernfs_lockdep (kn)) { +rwsem_acquire (&kn->dep_map,0 ,0 , _RET_IP_); +if (atomic_read (&kn->active) !=KN_DEACTIVATED_BIAS ) +lock_contended (&kn->dep_map, _RET_IP_); + }/* but everyone should wait for draining */ wait_event (root->deactivate_waitq,atomic_read (&kn->active) ==KN_DEACTIVATED_BIAS ); -lock_acquired (&kn->dep_map, _RET_IP_); -rwsem_release (&kn->dep_map,1 , _RET_IP_); +if (kernfs_lockdep (kn)) { +lock_acquired (&kn->dep_map, _RET_IP_); +rwsem_release (&kn->dep_map,1 , _RET_IP_); + }kernfs_unmap_bin_file (kn);
include/linux/kernfs.h +0 -1
@@ -35,7 +35,6 @@ enum kernfs_node_type { };#define KERNFS_TYPE_MASK 0x000f -#define KERNFS_ACTIVE_REF KERNFS_FILE#define KERNFS_FLAG_MASK ~KERNFS_TYPE_MASKenum kernfs_node_flag {
Signature · 1 line
1.8.5.3
Tejun Heo
maintainerPATCH 6/11 kernfs: remove KERNFS_REMOVED
In reply to Tejun HeoKERNFS_REMOVED is used to mark half-initialized and dying nodes so that they don't show up in lookups and deny adding new nodes under or renaming it; however, its role overlaps that of deactivation.
It's necessary to deny addition of new children while removal is in progress; however, this role considerably intersects with deactivation
- KERNFS_REMOVED prevents new children while deactivation prevents new file operations. There's no reason to have them separate making things more complex than necessary.
This patch removes KERNFS_REMOVED.
* Instead of KERNFS_REMOVED, each node now starts its life deactivated. This means that we now use both atomic_add() and atomic_sub() on KN_DEACTIVATED_BIAS, which is INT_MIN. The compiler generates an overflow warnings when negating INT_MIN as the negation can't be represented as a positive number. Nothing is actually broken but let's bump BIAS by one to avoid the warnings for archs which negates the subtrahend..
* A new helper kernfs_active() which tests whether kn->active >= 0 is added for convenience and lockdep annotation. All KERNFS_REMOVED tests are replaced with negated kernfs_active() tests.
* __kernfs_remove() is updated to deactivate, but not drain, all nodes in the subtree instead of setting KERNFS_REMOVED. This removes deactivation from kernfs_deactivate(), which is now renamed to kernfs_drain().
* Sanity check on KERNFS_REMOVED in kernfs_put() is replaced with checks on the active ref.
* Some comment style updates in the affected area.
v2: Reordered before removal path restructuring. kernfs_active()
dropped and kernfs_get/put_active() used instead. RB_EMPTY_NODE() used in the lookup paths.
v3: Reverted most of v2 except for creating a new node with
KN_DEACTIVATED_BIAS.
--- fs/kernfs/dir.c | 66 ++++++++++++++++++++++++--------------------- fs/kernfs/kernfs-internal.h | 3 ++- include/linux/kernfs.h | 1 - 3 files changed, 37 insertions(+), 33 deletions(-)
fs/kernfs/dir.c +35 -31
@@ -22,6 +22,12 @@ DEFINE_MUTEX(kernfs_mutex);#define rb_to_kn (X ) rb_entry((X), struct kernfs_node, rb) +static bool kernfs_active (struct kernfs_node *kn) +{ +lockdep_assert_held (&kernfs_mutex); +return atomic_read (&kn->active) >=0 ; +} +static bool kernfs_lockdep (struct kernfs_node *kn) {#ifdef CONFIG_DEBUG_LOCK_ALLOC
@@ -183,25 +189,20 @@ void kernfs_put_active(struct kernfs_node *kn) }/** -* kernfs_deactivate - deactivate kernfs_node -* @kn: kernfs_node to deactivate +* kernfs_drain - drain kernfs_node +* @kn: kernfs_node to drain * -* Deny new active references, drain existing ones and nuke all -* existing mmaps. Mutiple removers may invoke this function -* concurrently on @kn and all will return after deactivation and -* draining are complete. +* Drain existing usages and nuke all existing mmaps of @kn. Mutiple +* removers may invoke this function concurrently on @kn and all will +* return after draining is complete. */ -static void kernfs_deactivate (struct kernfs_node *kn) +static void kernfs_drain (struct kernfs_node *kn) __releases(&kernfs_mutex) __acquires(&kernfs_mutex) {struct kernfs_root *root =kernfs_root (kn);lockdep_assert_held (&kernfs_mutex); -BUG_ON (!(kn->flags &KERNFS_REMOVED )); - -/* only the first invocation on @kn should deactivate it */ -if (atomic_read (&kn->active) >=0 ) -atomic_add (KN_DEACTIVATED_BIAS , &kn->active); +WARN_ON_ONCE (kernfs_active (kn));mutex_unlock (&kernfs_mutex);
@@ -253,13 +254,15 @@ void kernfs_put(struct kernfs_node *kn)return ; root =kernfs_root (kn); repeat: -/* Moving/renaming is always done while holding reference. +/* +* Moving/renaming is always done while holding reference. * kn->parent won't change beneath us. */ parent = kn->parent; -WARN (!(kn->flags &KERNFS_REMOVED ),"kernfs: free using entry: %s/%s\n" , - parent ? parent->name :"" , kn->name); +WARN_ONCE (atomic_read (&kn->active) !=KN_DEACTIVATED_BIAS , +"kernfs_put: %s/%s: released with incorrect active_ref %d\n" , + parent ? parent->name :"" , kn->name,atomic_read (&kn->active));if (kernfs_type (kn) ==KERNFS_LINK )kernfs_put (kn->symlink.target_kn);
@@ -301,8 +304,8 @@ static int kernfs_dop_revalidate(struct dentry *dentry, unsigned int flags) kn = dentry->d_fsdata;mutex_lock (&kernfs_mutex); -/* The kernfs node has been deleted */ -if (kn->flags &KERNFS_REMOVED ) +/* The kernfs node has been deactivated */ +if (!kernfs_active (kn)) goto out_bad;/* The kernfs node has been moved? */
@@ -371,12 +374,12 @@ static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root, kn->ino = ret;atomic_set (&kn->count,1 ); -atomic_set (&kn->active,0 ); +atomic_set (&kn->active,KN_DEACTIVATED_BIAS );RB_CLEAR_NODE (&kn->rb); kn->name = name; kn->mode = mode; - kn->flags = flags |KERNFS_REMOVED ; + kn->flags = flags;return kn;
@@ -432,7 +435,7 @@ int kernfs_add_one(struct kernfs_node *kn) goto out_unlock; ret = -ENOENT ; -if (parent->flags &KERNFS_REMOVED ) +if (!kernfs_active (parent)) goto out_unlock; kn->hash =kernfs_name_hash (kn->name, kn->ns);
@@ -449,7 +452,7 @@ int kernfs_add_one(struct kernfs_node *kn) }/* Mark the entry added into directory tree */ - kn->flags &= ~KERNFS_REMOVED ; +atomic_sub (KN_DEACTIVATED_BIAS , &kn->active); ret =0 ; out_unlock:mutex_unlock (&kernfs_mutex);
@@ -549,7 +552,7 @@ struct kernfs_root *kernfs_create_root(struct kernfs_dir_ops *kdops, void *priv)return ERR_PTR (-ENOMEM ); } - kn->flags &= ~KERNFS_REMOVED ; +atomic_sub (KN_DEACTIVATED_BIAS , &kn->active); kn->priv = priv; kn->dir.root = root;
@@ -763,24 +766,25 @@ static void __kernfs_remove(struct kernfs_node *kn)pr_debug ("kernfs %s: removing\n" , kn->name); -/* disable lookup and node creation under @kn */ +/* prevent any new usage under @kn by deactivating all nodes */ pos =NULL ;while ((pos =kernfs_next_descendant_post (pos, kn))) - pos->flags |=KERNFS_REMOVED ; +if (kernfs_active (pos)) +atomic_add (KN_DEACTIVATED_BIAS , &pos->active);/* deactivate and unlink the subtree node-by-node */ do { pos = kernfs_leftmost_descendant(kn); /* - * kernfs_deactivate() drops kernfs_mutex temporarily and - * @pos's base ref could have been put by someone else by - * the time the function returns. Make sure it doesn't go - * away underneath us. + * kernfs_drain() drops kernfs_mutex temporarily and @pos's + * base ref could have been put by someone else by the time + * the function returns. Make sure it doesn't go away + * underneath us. */ kernfs_get(pos); - kernfs_deactivate(pos); + kernfs_drain(pos); /* * kernfs_unlink_sibling() succeeds once per node. Use it
@@ -865,7 +869,7 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent,mutex_lock (&kernfs_mutex); error = -ENOENT ; -if ((kn->flags | new_parent->flags) &KERNFS_REMOVED ) +if (!kernfs_active (kn) || !kernfs_active (new_parent)) goto out; error =0 ;
@@ -925,7 +929,7 @@ static struct kernfs_node *kernfs_dir_pos(const void *ns, struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos) { if (pos) { - int valid = !(pos->flags & KERNFS_REMOVED) && + int valid = kernfs_active(pos) && pos->parent == parent && hash == pos->hash;kernfs_put (pos);if (!valid)
fs/kernfs/kernfs-internal.h +2 -1
@@ -26,7 +26,8 @@ struct kernfs_iattrs {struct simple_xattrs xattrs; }; -#define KN_DEACTIVATED_BIAS INT_MIN +/* +1 to avoid triggering overflow warning when negating it */ +#define KN_DEACTIVATED_BIAS (INT_MIN + 1)/* KERNFS_TYPE_MASK and types are defined in include/linux/kernfs.h */
include/linux/kernfs.h +0 -1
@@ -38,7 +38,6 @@ enum kernfs_node_type {#define KERNFS_FLAG_MASK ~KERNFS_TYPE_MASKenum kernfs_node_flag { -KERNFS_REMOVED =0x0010 ,KERNFS_NS =0x0020 ,KERNFS_HAS_SEQ_SHOW =0x0040 ,KERNFS_HAS_MMAP =0x0080 ,
Signature · 1 line
1.8.5.3
Tejun Heo
maintainerPATCH 7/11 kernfs, sysfs, driver-core: implement kernfs_remove_self() and its wrappers
In reply to Tejun HeoSometimes it's necessary to implement a node which wants to delete nodes including itself. This isn't straightforward because of kernfs active reference. While a file operation is in progress, an active reference is held and kernfs_remove() waits for all such references to drain before completing. For a self-deleting node, this is a deadlock as kernfs_remove() ends up waiting for an active reference that itself is sitting on top of.
This currently is worked around in the sysfs layer using sysfs_schedule_callback() which makes such removals asynchronous. While it works, it's rather cumbersome and inherently breaks synchronicity of the operation - the file operation which triggered the operation may complete before the removal is finished (or even started) and the removal may fail asynchronously. If a removal operation is immmediately followed by another operation which expects the specific name to be available (e.g. removal followed by rename onto the same name), there's no way to make the latter operation reliable.
The thing is there's no inherent reason for this to be asynchrnous. All that's necessary to do this synchronous is a dedicated operation which drops its own active ref and deactivates self. This patch implements kernfs_remove_self() and its wrappers in sysfs and driver core. kernfs_remove_self() is to be called from one of the file operations, drops the active ref the task is holding, removes the self node, and restores active ref to the dead node so that the ref is balanced afterwards. __kernfs_remove() is updated so that it takes an early exit if the target node is already fully removed so that the active ref restored by kernfs_remove_self() after removal doesn't confuse the deactivation path.
This makes implementing self-deleting nodes very easy. The normal removal path doesn't even need to be changed to use kernfs_remove_self() for the self-deleting node. The method can invoke kernfs_remove_self() on itself before proceeding the normal removal path. kernfs_remove() invoked on the node by the normal deletion path will simply be ignored.
This will replace sysfs_schedule_callback(). A subtle feature of sysfs_schedule_callback() is that it collapses multiple invocations - even if multiple removals are triggered, the removal callback is run only once. An equivalent effect can be achieved by testing the return value of kernfs_remove_self() - only the one which gets %true return value should proceed with actual deletion. All other instances of kernfs_remove_self() will wait till the enclosing kernfs operation which invoked the winning instance of kernfs_remove_self() finishes and then return %false. This trivially makes all users of kernfs_remove_self() automatically show correct synchronous behavior even when there are multiple concurrent operations - all "echo 1 > delete" instances will finish only after the whole operation is completed by one of the instances.
Note that manipulation of active ref is implemented in separate public functions - kernfs_[un]break_active_protection(). kernfs_remove_self() is the only user at the moment but this will be used to cater to more complex cases.
v2: For !CONFIG_SYSFS, dummy version kernfs_remove_self() was missing
and sysfs_remove_file_self() had incorrect return type. Fix it. Reported by kbuild test bot.
v3: kernfs_[un]break_active_protection() separated out from
kernfs_remove_self() and exposed as public API.
--- drivers/base/core.c | 17 ++++++ fs/kernfs/dir.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++- fs/sysfs/file.c | 23 +++++++++ include/linux/device.h | 2 + include/linux/kernfs.h | 8 +++ include/linux/sysfs.h | 7 +++ 6 files changed, 194 insertions(+), 1 deletion(-)
drivers/base/core.c +17 -0
@@ -571,6 +571,23 @@ void device_remove_file(struct device *dev,EXPORT_SYMBOL_GPL (device_remove_file);/** +* device_remove_file_self - remove sysfs attribute file from its own method. +* @dev: device. +* @attr: device attribute descriptor. +* +* See kernfs_remove_self() for details. +*/ +bool device_remove_file_self (struct device *dev, +const struct device_attribute *attr) +{ +if (dev) +return sysfs_remove_file_self (&dev->kobj, &attr->attr); +else +return false; +} +EXPORT_SYMBOL_GPL (device_remove_file_self); + +/** * device_create_bin_file - create sysfs binary attribute file for device. * @dev: device. * @attr: device binary attribute descriptor.
fs/kernfs/dir.c +137 -1
150 lines not on this page · raw message
fs/sysfs/file.c +23 -0
@@ -372,6 +372,29 @@ void sysfs_remove_file_ns(struct kobject *kobj, const struct attribute *attr, }EXPORT_SYMBOL_GPL (sysfs_remove_file_ns); +/** +* sysfs_remove_file_self - remove an object attribute from its own method +* @kobj: object we're acting for +* @attr: attribute descriptor +* +* See kernfs_remove_self() for details. +*/ +bool sysfs_remove_file_self (struct kobject *kobj,const struct attribute *attr) +{ +struct kernfs_node *parent = kobj->sd; +struct kernfs_node *kn; +bool ret; + + kn =kernfs_find_and_get (parent, attr->name); +if (WARN_ON_ONCE (!kn)) +return false; + + ret =kernfs_remove_self (kn); + +kernfs_put (kn); +return ret; +} +void sysfs_remove_files (struct kobject *kobj,const struct attribute **ptr) {int i;
include/linux/device.h +2 -0
@@ -560,6 +560,8 @@ extern int device_create_file(struct device *device, const struct device_attribute *entry);extern void device_remove_file (struct device *dev,const struct device_attribute *attr); +extern bool device_remove_file_self (struct device *dev, +const struct device_attribute *attr);extern int __must_check device_create_bin_file (struct device *dev,const struct bin_attribute *attr); extern void device_remove_bin_file(struct device *dev,
include/linux/kernfs.h +8 -0
@@ -43,6 +43,8 @@ enum kernfs_node_flag {KERNFS_HAS_MMAP =0x0080 ,KERNFS_LOCKDEP =0x0100 ,KERNFS_STATIC_NAME =0x0200 , +KERNFS_SUICIDAL =0x0400 , + KERNFS_SUICIDED = 0x0800, };/* type-specific structures for kernfs_node union members */
@@ -234,6 +236,9 @@ struct kernfs_node *kernfs_create_link(struct kernfs_node *parent,const char *name, struct kernfs_node *target);void kernfs_remove (struct kernfs_node *kn); +void kernfs_break_active_protection (struct kernfs_node *kn); +void kernfs_unbreak_active_protection (struct kernfs_node *kn); +bool kernfs_remove_self (struct kernfs_node *kn);int kernfs_remove_by_name_ns (struct kernfs_node *parent,const char *name,const void *ns); int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent,
@@ -291,6 +296,9 @@ kernfs_create_link(struct kernfs_node *parent, const char *name,static inline void kernfs_remove (struct kernfs_node *kn) { } +static inline bool kernfs_remove_self (struct kernfs_node *kn) +{return false; } +static inline int kernfs_remove_by_name_ns (struct kernfs_node *kn,const char *name,const void *ns) {return -ENOSYS ; }
include/linux/sysfs.h +7 -0
@@ -198,6 +198,7 @@ int __must_check sysfs_chmod_file(struct kobject *kobj, const struct attribute *attr, umode_t mode);void sysfs_remove_file_ns (struct kobject *kobj,const struct attribute *attr,const void *ns); +bool sysfs_remove_file_self (struct kobject *kobj,const struct attribute *attr);void sysfs_remove_files (struct kobject *kobj,const struct attribute **attr); int __must_check sysfs_create_bin_file(struct kobject *kobj,
@@ -301,6 +302,12 @@ static inline void sysfs_remove_file_ns(struct kobject *kobj, { } +static inline bool sysfs_remove_file_self (struct kobject *kobj, +const struct attribute *attr) +{ +return false; +} +static inline void sysfs_remove_files (struct kobject *kobj,const struct attribute **attr) {
Signature · 1 line
1.8.5.3
Tejun Heo
maintainerPATCH 8/11 pci: use device_remove_file_self() instead of device_schedule_callback()
In reply to Tejun Heodriver-core now supports synchrnous self-deletion of attributes and the asynchrnous removal mechanism is scheduled for removal. Use it instead of device_schedule_callback(). This makes "remove" behave synchronously.
--- drivers/pci/pci-sysfs.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-)
drivers/pci/pci-sysfs.c +3 -14
@@ -351,28 +351,17 @@ static struct device_attribute dev_rescan_attr = __ATTR(rescan, (S_IWUSR |S_IWGRP ), NULL, dev_rescan_store); -static void remove_callback (struct device *dev) -{ -pci_stop_and_remove_bus_device_locked (to_pci_dev (dev)); -} -static ssize_t -remove_store (struct device *dev,struct device_attribute *dummy, +remove_store (struct device *dev,struct device_attribute *attr,const char *buf,size_t count) { -int ret =0 ;unsigned long val;if (kstrtoul (buf,0 , &val) <0 )return -EINVAL ; -/* An attribute cannot be unregistered by one of its own methods, -* so we have to use this roundabout approach. -*/ -if (val) - ret =device_schedule_callback (dev, remove_callback); -if (ret) - count = ret; +if (val &&device_remove_file_self (dev, attr)) +pci_stop_and_remove_bus_device_locked (to_pci_dev (dev));return count; } static struct device_attribute dev_remove_attr = __ATTR(remove,
Signature · 1 line
1.8.5.3
Tejun Heo
maintainerPATCH 9/11 scsi: use device_remove_file_self() instead of device_schedule_callback()
In reply to Tejun Heodriver-core now supports synchrnous self-deletion of attributes and the asynchrnous removal mechanism is scheduled for removal. Use it instead of device_schedule_callback(). This makes "delete" behave synchronously.
--- drivers/scsi/scsi_sysfs.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-)
drivers/scsi/scsi_sysfs.c +2 -13
@@ -649,23 +649,12 @@ store_rescan_field (struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR(rescan, S_IWUSR, NULL, store_rescan_field); -static void sdev_store_delete_callback (struct device *dev) -{ -scsi_remove_device (to_scsi_device (dev)); -} -static ssize_t sdev_store_delete (struct device *dev,struct device_attribute *attr,const char *buf,size_t count) { -int rc; - -/* An attribute cannot be unregistered by one of its own methods, -* so we have to use this roundabout approach. -*/ - rc =device_schedule_callback (dev, sdev_store_delete_callback); -if (rc) - count = rc; +if (device_remove_file_self (dev, attr)) +scsi_remove_device (to_scsi_device (dev));return count; }; static DEVICE_ATTR(delete, S_IWUSR, NULL, sdev_store_delete);
Signature · 1 line
1.8.5.3
Tejun Heo
maintainerPATCH 10/11 s390: use device_remove_file_self() instead of device_schedule_callback()
In reply to Tejun Heodriver-core now supports synchrnous self-deletion of attributes and the asynchrnous removal mechanism is scheduled for removal. Use it instead of device_schedule_callback().
* Conversions in arch/s390/pci/pci_sysfs.c and drivers/s390/block/dcssblk.c are straightforward.
* drivers/s390/cio/ccwgroup.c is a bit more tricky because ccwgroup_notifier() was (ab)using device_schedule_callback() to purely obtain a process context to kick off ungroup operation which may block from a notifier callback.
Rename ccwgroup_ungroup_callback() to ccwgroup_ungroup() and make it take ccwgroup_device * instead. The new function is now called directly from ccwgroup_ungroup_store().
ccwgroup_notifier() chain is updated to explicitly bounce through ccwgroup_device->ungroup_work. This also removes possible failure from memory pressure.
Only compile-tested.
--- arch/s390/include/asm/ccwgroup.h | 1 + arch/s390/pci/pci_sysfs.c | 18 ++++++++---------- drivers/s390/block/dcssblk.c | 14 +++++++------- drivers/s390/cio/ccwgroup.c | 26 ++++++++++++++++---------- 4 files changed, 32 insertions(+), 27 deletions(-)
arch/s390/include/asm/ccwgroup.h +1 -0
@@ -23,6 +23,7 @@ struct ccwgroup_device {unsigned int count;struct device dev;struct ccw_device *cdev[0 ]; +struct work_struct ungroup_work; };/**
arch/s390/pci/pci_sysfs.c +8 -10
@@ -48,29 +48,27 @@ static ssize_t show_pfgid(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR(pfgid, S_IRUGO, show_pfgid, NULL); -static void recover_callback (struct device *dev) +static ssize_t store_recover (struct device *dev,struct device_attribute *attr, +const char *buf,size_t count) {struct pci_dev *pdev =to_pci_dev (dev);struct zpci_dev *zdev =get_zdev (pdev);int ret; +if (!device_remove_file_self (dev, attr)) +return count; +pci_stop_and_remove_bus_device (pdev); ret =zpci_disable_device (zdev);if (ret) -return ; +return ret; ret =zpci_enable_device (zdev);if (ret) -return ; +return ret;pci_rescan_bus (zdev->bus); -} - -static ssize_t store_recover (struct device *dev,struct device_attribute *attr, -const char *buf,size_t count) -{ -int rc =device_schedule_callback (dev, recover_callback); -return rc ? rc : count; +return count; } static DEVICE_ATTR(recover, S_IWUSR, NULL, store_recover);
drivers/s390/block/dcssblk.c +7 -7
@@ -304,12 +304,6 @@ dcssblk_load_segment(char *name, struct segment_info **seg_info)return rc; } -static void dcssblk_unregister_callback (struct device *dev) -{ -device_unregister (dev); -put_device (dev); -} -/* * device attribute for switching shared/nonshared (exclusive) * operation (show + store)
@@ -397,7 +391,13 @@ removeseg:blk_cleanup_queue (dev_info->dcssblk_queue); dev_info->gd->queue =NULL ;put_disk (dev_info->gd); - rc =device_schedule_callback (dev, dcssblk_unregister_callback); +up_write (&dcssblk_devices_sem); + +if (device_remove_file_self (dev, attr)) { +device_unregister (dev); +put_device (dev); + } +return rc; out:up_write (&dcssblk_devices_sem);return rc;
drivers/s390/cio/ccwgroup.c +16 -10
@@ -168,14 +168,12 @@ static ssize_t ccwgroup_online_show(struct device *dev,* Provide an 'ungroup' attribute so the user can remove group devices no * longer needed or accidentially created. Saves memory :) */ -static void ccwgroup_ungroup_callback (struct device *dev) +static void ccwgroup_ungroup (struct ccwgroup_device *gdev) { -struct ccwgroup_device *gdev =to_ccwgroupdev (dev); -mutex_lock (&gdev->reg_mutex);if (device_is_registered (&gdev->dev)) {__ccwgroup_remove_symlinks (gdev); -device_unregister (dev); +device_unregister (&gdev->dev);__ccwgroup_remove_cdev_refs (gdev); }mutex_unlock (&gdev->reg_mutex);
@@ -195,10 +193,9 @@ static ssize_t ccwgroup_ungroup_store(struct device *dev, rc = -EINVAL ; goto out; } -/* Note that we cannot unregister the device from one of its -* attribute methods, so we have to use this roundabout approach. -*/ - rc =device_schedule_callback (dev, ccwgroup_ungroup_callback); + +if (device_remove_file_self (dev, attr)) +ccwgroup_ungroup (gdev); out:if (rc) {if (rc != -EAGAIN )
@@ -224,6 +221,14 @@ static const struct attribute_group *ccwgroup_attr_groups[] = { NULL, }; +static void ccwgroup_ungroup_workfn (struct work_struct *work) +{ +struct ccwgroup_device *gdev = + container_of(work, struct ccwgroup_device, ungroup_work); + +ccwgroup_ungroup (gdev); +} +static void ccwgroup_release (struct device *dev) {kfree (to_ccwgroupdev (dev));
@@ -323,6 +328,7 @@ int ccwgroup_create_dev(struct device *parent, struct ccwgroup_driver *gdrv,atomic_set (&gdev->onoff,0 );mutex_init (&gdev->reg_mutex);mutex_lock (&gdev->reg_mutex); +INIT_WORK (&gdev->ungroup_work, ccwgroup_ungroup_workfn); gdev->count = num_devices; gdev->dev.bus = &ccwgroup_bus_type; gdev->dev.parent = parent;
@@ -404,10 +410,10 @@ EXPORT_SYMBOL(ccwgroup_create_dev);static int ccwgroup_notifier (struct notifier_block *nb,unsigned long action,void *data) { -struct device *dev = data; +struct ccwgroup_device *gdev =to_ccwgroupdev (data);if (action ==BUS_NOTIFY_UNBIND_DRIVER ) -device_schedule_callback (dev, ccwgroup_ungroup_callback); +schedule_work (&gdev->ungroup_work);return NOTIFY_OK ; }
Signature · 1 line
1.8.5.3
Tejun Heo
maintainerPATCH 11/11 sysfs, driver-core: remove unused {sysfs|device}_schedule_callback_owner()
In reply to Tejun HeoAll device_schedule_callback_owner() users are converted to use device_remove_file_self(). Remove now unused {sysfs|device}_schedule_callback_owner().
--- drivers/base/core.c | 33 ------------------ fs/sysfs/file.c | 92 -------------------------------------------------- include/linux/device.h | 11 +----- include/linux/sysfs.h | 9 ----- 4 files changed, 1 insertion(+), 144 deletions(-)
drivers/base/core.c +0 -33
@@ -615,39 +615,6 @@ void device_remove_bin_file(struct device *dev, }EXPORT_SYMBOL_GPL (device_remove_bin_file); -/** -* device_schedule_callback_owner - helper to schedule a callback for a device -* @dev: device. -* @func: callback function to invoke later. -* @owner: module owning the callback routine -* -* Attribute methods must not unregister themselves or their parent device -* (which would amount to the same thing). Attempts to do so will deadlock, -* since unregistration is mutually exclusive with driver callbacks. -* -* Instead methods can call this routine, which will attempt to allocate -* and schedule a workqueue request to call back @func with @dev as its -* argument in the workqueue's process context. @dev will be pinned until -* @func returns. -* -* This routine is usually called via the inline device_schedule_callback(), -* which automatically sets @owner to THIS_MODULE. -* -* Returns 0 if the request was submitted, -ENOMEM if storage could not -* be allocated, -ENODEV if a reference to @owner isn't available. -* -* NOTE: This routine won't work if CONFIG_SYSFS isn't set! It uses an -* underlying sysfs routine (since it is intended for use by attribute -* methods), and if sysfs isn't available you'll get nothing but -ENOSYS. -*/ -int device_schedule_callback_owner (struct device *dev, -void (*func)(struct device *),struct module *owner) -{ -return sysfs_schedule_callback (&dev->kobj, - (void (*)(void *)) func, dev, owner); -} -EXPORT_SYMBOL_GPL (device_schedule_callback_owner); -static void klist_children_get (struct klist_node *n) {struct device_private *p =to_device_private_parent (n);
fs/sysfs/file.c +0 -92
95 lines not on this page · raw message
include/linux/device.h +1 -10
@@ -566,12 +566,6 @@ extern int __must_check device_create_bin_file(struct device *dev, const struct bin_attribute *attr);extern void device_remove_bin_file (struct device *dev,const struct bin_attribute *attr); -extern int device_schedule_callback_owner (struct device *dev, -void (*func)(struct device *dev),struct module *owner); - -/* This is a macro to avoid include problems with THIS_MODULE */ -#define device_schedule_callback (dev, func) \ - device_schedule_callback_owner(dev, func, THIS_MODULE)/* device resource management */ typedef void (*dr_release_t )(struct device *dev,void *res);
@@ -931,10 +925,7 @@ extern int device_online(struct device *dev);extern struct device *__root_device_register (const char *name,struct module *owner); -/* -* This is a macro to avoid include problems with THIS_MODULE, -* just as per what is done for device_schedule_callback() above. -*/ +/* This is a macro to avoid include problems with THIS_MODULE */ #define root_device_register (name) \ __root_device_register(name, THIS_MODULE)
include/linux/sysfs.h +0 -9
@@ -178,9 +178,6 @@ struct sysfs_ops {#ifdef CONFIG_SYSFS -int sysfs_schedule_callback (struct kobject *kobj,void (*func)(void *), -void *data,struct module *owner); -int __must_check sysfs_create_dir_ns (struct kobject *kobj,const void *ns);void sysfs_remove_dir (struct kobject *kobj); int __must_check sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name,
@@ -249,12 +246,6 @@ int __must_check sysfs_init(void);#else /* CONFIG_SYSFS */ -static inline int sysfs_schedule_callback (struct kobject *kobj, -void (*func)(void *),void *data,struct module *owner) -{ -return -ENOSYS ; -} -static inline int sysfs_create_dir_ns (struct kobject *kobj,const void *ns) {return 0 ;
Signature · 1 line
1.8.5.3