public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Benjamin Berg <benjamin@sipsolutions.net>
To: Johannes Berg <johannes@sipsolutions.net>,
	linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: linux-fsdevel@vger.kernel.org,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	"Rafael J. Wysocki" <rafael@kernel.org>,
	Nicolai Stange <nicstange@gmail.com>,
	Ben Greear <greearb@candelatech.com>,
	Johannes Berg <johannes.berg@intel.com>
Subject: Re: [RFC PATCH 3/6] debugfs: add API to allow debugfs operations cancellation
Date: Fri, 10 Nov 2023 10:35:42 +0100	[thread overview]
Message-ID: <8e4c423645ecd4c2f1790b2ec01fd96ddfcf6cbb.camel@sipsolutions.net> (raw)
In-Reply-To: <20231109222251.e6ade2190ef4.If54cd017d5734024e7bee5e4a237e17244050480@changeid>

Hi,

I thought about this a bit more, and I think we need to change it
slightly to hold a lock around the cancellation call. After all, it is
an important guarantee that the callback has completed before
debugfs_leave_cancellation() returns.

thread 1                        thread 2
read()/write()
                                __debugfs_file_removed()
                                wait_for_completion()
debugfs_enter_cancellation()
complete()
                                cancel_callback starts
debugfs_leave_cancellation()
 -> stack data goes out of scope
debugfs_put_file()
                                cancel_callback returns


Benjamin

On Thu, 2023-11-09 at 22:22 +0100, Johannes Berg wrote:
> From: Johannes Berg <johannes.berg@intel.com>
> 
> In some cases there might be longer-running hardware accesses
> in debugfs files, or attempts to acquire locks, and we want
> to still be able to quickly remove the files.
> 
> Introduce a cancellations API to use inside the debugfs handler
> functions to be able to cancel such operations on a per-file
> basis.
> 
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> ---
>  fs/debugfs/file.c       | 82
> +++++++++++++++++++++++++++++++++++++++++
>  fs/debugfs/inode.c      | 23 +++++++++++-
>  fs/debugfs/internal.h   |  5 +++
>  include/linux/debugfs.h | 19 ++++++++++
>  4 files changed, 128 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
> index e499d38b1077..f6993c068322 100644
> --- a/fs/debugfs/file.c
> +++ b/fs/debugfs/file.c
> @@ -109,6 +109,8 @@ int debugfs_file_get(struct dentry *dentry)
>                 lockdep_init_map(&fsd->lockdep_map, fsd->lock_name ?:
> "debugfs",
>                                  &fsd->key, 0);
>  #endif
> +               INIT_LIST_HEAD(&fsd->cancellations);
> +               spin_lock_init(&fsd->cancellations_lock);
>         }
>  
>         /*
> @@ -151,6 +153,86 @@ void debugfs_file_put(struct dentry *dentry)
>  }
>  EXPORT_SYMBOL_GPL(debugfs_file_put);
>  
> +/**
> + * debugfs_enter_cancellation - enter a debugfs cancellation
> + * @file: the file being accessed
> + * @cancellation: the cancellation object, the cancel callback
> + *     inside of it must be initialized
> + *
> + * When a debugfs file is removed it needs to wait for all active
> + * operations to complete. However, the operation itself may need
> + * to wait for hardware or completion of some asynchronous process
> + * or similar. As such, it may need to be cancelled to avoid long
> + * waits or even deadlocks.
> + *
> + * This function can be used inside a debugfs handler that may
> + * need to be cancelled. As soon as this function is called, the
> + * cancellation's 'cancel' callback may be called, at which point
> + * the caller should proceed to call debugfs_leave_cancellation()
> + * and leave the debugfs handler function as soon as possible.
> + * Note that the 'cancel' callback is only ever called in the
> + * context of some kind of debugfs_remove().
> + *
> + * This function must be paired with debugfs_leave_cancellation().
> + */
> +void debugfs_enter_cancellation(struct file *file,
> +                               struct debugfs_cancellation
> *cancellation)
> +{
> +       struct debugfs_fsdata *fsd;
> +       struct dentry *dentry = F_DENTRY(file);
> +
> +       INIT_LIST_HEAD(&cancellation->list);
> +
> +       if (WARN_ON(!d_is_reg(dentry)))
> +               return;
> +
> +       if (WARN_ON(!cancellation->cancel))
> +               return;
> +
> +       fsd = READ_ONCE(dentry->d_fsdata);
> +       if (WARN_ON(!fsd ||
> +                   ((unsigned long)fsd &
> DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)))
> +               return;
> +
> +       spin_lock(&fsd->cancellations_lock);
> +       list_add(&cancellation->list, &fsd->cancellations);
> +       spin_unlock(&fsd->cancellations_lock);
> +
> +       /* if we're already removing wake it up to cancel */
> +       if (d_unlinked(dentry))
> +               complete(&fsd->active_users_drained);
> +}
> +EXPORT_SYMBOL_GPL(debugfs_enter_cancellation);
> +
> +/**
> + * debugfs_leave_cancellation - leave cancellation section
> + * @file: the file being accessed
> + * @cancellation: the cancellation previously registered with
> + *     debugfs_enter_cancellation()
> + *
> + * See the documentation of debugfs_enter_cancellation().
> + */
> +void debugfs_leave_cancellation(struct file *file,
> +                               struct debugfs_cancellation
> *cancellation)
> +{
> +       struct debugfs_fsdata *fsd;
> +       struct dentry *dentry = F_DENTRY(file);
> +
> +       if (WARN_ON(!d_is_reg(dentry)))
> +               return;
> +
> +       fsd = READ_ONCE(dentry->d_fsdata);
> +       if (WARN_ON(!fsd ||
> +                   ((unsigned long)fsd &
> DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)))
> +               return;
> +
> +       spin_lock(&fsd->cancellations_lock);
> +       if (!list_empty(&cancellation->list))
> +               list_del(&cancellation->list);
> +       spin_unlock(&fsd->cancellations_lock);
> +}
> +EXPORT_SYMBOL_GPL(debugfs_leave_cancellation);
> +
>  /*
>   * Only permit access to world-readable files when the kernel is
> locked down.
>   * We also need to exclude any file that has ways to write or alter
> it as root
> diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c
> index a4c77aafb77b..2cbcc49a8826 100644
> --- a/fs/debugfs/inode.c
> +++ b/fs/debugfs/inode.c
> @@ -247,6 +247,7 @@ static void debugfs_release_dentry(struct dentry
> *dentry)
>                 lockdep_unregister_key(&fsd->key);
>                 kfree(fsd->lock_name);
>  #endif
> +               WARN_ON(!list_empty(&fsd->cancellations));
>         }
>  
>         kfree(fsd);
> @@ -754,8 +755,28 @@ static void __debugfs_file_removed(struct dentry
> *dentry)
>         lock_map_acquire(&fsd->lockdep_map);
>         lock_map_release(&fsd->lockdep_map);
>  
> -       if (!refcount_dec_and_test(&fsd->active_users))
> +       /* if we hit zero, just wait for all to finish */
> +       if (!refcount_dec_and_test(&fsd->active_users)) {
>                 wait_for_completion(&fsd->active_users_drained);
> +               return;
> +       }
> +
> +       /* if we didn't hit zero, try to cancel any we can */
> +       while (refcount_read(&fsd->active_users)) {
> +               struct debugfs_cancellation *c;
> +
> +               spin_lock(&fsd->cancellations_lock);
> +               while ((c = list_first_entry_or_null(&fsd-
> >cancellations,
> +                                                    typeof(*c),
> list))) {
> +                       list_del_init(&c->list);
> +                       spin_unlock(&fsd->cancellations_lock);
> +                       c->cancel(dentry, c->cancel_data);
> +                       spin_lock(&fsd->cancellations_lock);
> +               }
> +               spin_unlock(&fsd->cancellations_lock);
> +
> +               wait_for_completion(&fsd->active_users_drained);
> +       }
>  }
>  
>  static void remove_one(struct dentry *victim)
> diff --git a/fs/debugfs/internal.h b/fs/debugfs/internal.h
> index c7d61cfc97d2..5f279abd9351 100644
> --- a/fs/debugfs/internal.h
> +++ b/fs/debugfs/internal.h
> @@ -8,6 +8,7 @@
>  #ifndef _DEBUGFS_INTERNAL_H_
>  #define _DEBUGFS_INTERNAL_H_
>  #include <linux/lockdep.h>
> +#include <linux/list.h>
>  
>  struct file_operations;
>  
> @@ -29,6 +30,10 @@ struct debugfs_fsdata {
>                         struct lock_class_key key;
>                         char *lock_name;
>  #endif
> +
> +                       /* lock for the cancellations list */
> +                       spinlock_t cancellations_lock;
> +                       struct list_head cancellations;
>                 };
>         };
>  };
> diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h
> index ea2d919fd9c7..c9c65b132c0f 100644
> --- a/include/linux/debugfs.h
> +++ b/include/linux/debugfs.h
> @@ -171,6 +171,25 @@ ssize_t debugfs_write_file_bool(struct file
> *file, const char __user *user_buf,
>  ssize_t debugfs_read_file_str(struct file *file, char __user
> *user_buf,
>                               size_t count, loff_t *ppos);
>  
> +/**
> + * struct debugfs_cancellation - cancellation data
> + * @list: internal, for keeping track
> + * @cancel: callback to call
> + * @cancel_data: extra data for the callback to call
> + */
> +struct debugfs_cancellation {
> +       struct list_head list;
> +       void (*cancel)(struct dentry *, void *);
> +       void *cancel_data;
> +};
> +
> +void __acquires(cancellation)
> +debugfs_enter_cancellation(struct file *file,
> +                          struct debugfs_cancellation
> *cancellation);
> +void __releases(cancellation)
> +debugfs_leave_cancellation(struct file *file,
> +                          struct debugfs_cancellation
> *cancellation);
> +
>  #else
>  
>  #include <linux/err.h>



  reply	other threads:[~2023-11-10 18:23 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-09 21:22 [RFC PATCH 0/6] debugfs/wifi: locking fixes Johannes Berg
2023-11-09 21:22 ` [RFC PATCH 1/6] debugfs: fix automount d_fsdata usage Johannes Berg
2023-11-09 21:22 ` [RFC PATCH 2/6] debugfs: annotate debugfs handlers vs. removal with lockdep Johannes Berg
2023-12-02  6:37   ` Sergey Senozhatsky
2023-12-02 10:40     ` Johannes Berg
2023-11-09 21:22 ` [RFC PATCH 3/6] debugfs: add API to allow debugfs operations cancellation Johannes Berg
2023-11-10  9:35   ` Benjamin Berg [this message]
2023-11-09 21:22 ` [RFC PATCH 4/6] wifi: cfg80211: add locked debugfs wrappers Johannes Berg
2023-11-09 21:22 ` [RFC PATCH 5/6] wifi: mac80211: use wiphy locked debugfs helpers for agg_status Johannes Berg
2023-11-09 21:22 ` [RFC PATCH 6/6] wifi: mac80211: use wiphy locked debugfs for sdata/link Johannes Berg

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=8e4c423645ecd4c2f1790b2ec01fd96ddfcf6cbb.camel@sipsolutions.net \
    --to=benjamin@sipsolutions.net \
    --cc=greearb@candelatech.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=johannes.berg@intel.com \
    --cc=johannes@sipsolutions.net \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-wireless@vger.kernel.org \
    --cc=nicstange@gmail.com \
    --cc=rafael@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox