Re: [PATCH] file, i915: fix file reference for mmap_singleton()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Oct 25, 2023 at 2:01 PM Christian Brauner <brauner@xxxxxxxxxx> wrote:
> Today we got a report at [1] for rcu stalls on the i915 testsuite in [2]
> due to the conversion of files to SLAB_TYPSSAFE_BY_RCU. Afaict,
> get_file_rcu() goes into an infinite loop trying to carefully verify
> that i915->gem.mmap_singleton hasn't changed - see the splat below.
>
> So I stared at this code to figure out what it actually does. It seems
> that the i915->gem.mmap_singleton pointer itself never had rcu semantics.
>
> The i915->gem.mmap_singleton is replaced in
> file->f_op->release::singleton_release():
>
>         static int singleton_release(struct inode *inode, struct file *file)
>         {
>                 struct drm_i915_private *i915 = file->private_data;
>
>                 cmpxchg(&i915->gem.mmap_singleton, file, NULL);
>                 drm_dev_put(&i915->drm);
>
>                 return 0;
>         }
>
> The cmpxchg() is ordered against a concurrent update of
> i915->gem.mmap_singleton from mmap_singleton(). IOW, when
> mmap_singleton() fails to get a reference on i915->gem.mmap_singleton
> via mmap_singleton():
>
>         rcu_read_lock();
>         file = get_file_rcu(&i915->gem.mmap_singleton);
>         rcu_read_unlock();
>
> mmap_singleton() allocates a new file via anon_inode_getfile() and does
>
>         smp_store_mb(i915->gem.mmap_singleton, file);
>
> So, afaiu, then what happens in the case of this bug is that at some
> point fput() is called and drops the file->f_count to zero but obviously
> leaving the pointer in i915->gem.mmap_singleton in tact until
> file->f_op->release::singleton_release() is called.
>
> Now, there might be possibly larger delays until
> file->f_op->release::singleton_release() is called and
> i915->gem.mmap_singleton is set to NULL?
>
> Say concurrently another task hits mmap_singleton() and does:
>
>         rcu_read_lock();
>         file = get_file_rcu(&i915->gem.mmap_singleton);
>         rcu_read_unlock();
>
> When get_file_rcu() fails to get a reference via atomic_inc_not_zero()
> it will try the reload from i915->gem.mmap_singleton assuming it has
> comparable semantics as __fget_files_rcu() that also reloads on
> atomic_inc_not_zero() failure.
>
> But since i915->gem.mmap_singleton doesn't have proper rcu semantics it
> reloads the same pointer again, trying the same atomic_inc_not_zero()
> again and doing so until file->f_op->release::singleton_release() of the
> old file has been called.
>
> So, in contrast to __fget_files_rcu() here we want to not retry when
> atomic_inc_not_zero() has failed. We only want to retry in case we
> managed to get a reference but the pointer did change on reload.
[...]
> Link: [1]: https://lore.kernel.org/intel-gfx/SJ1PR11MB6129CB39EED831784C331BAFB9DEA@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
> Link: [2]: https://intel-gfx-ci.01.org/tree/linux-next/next-20231013/bat-dg2-11/igt@i915_selftest@live@xxxxxxxxx#dmesg-warnings10963
> Signed-off-by: Christian Brauner <brauner@xxxxxxxxxx>

Patch makes sense to me. I'm not sure why you changed EAGAIN to
EINVAL, and it's obviously a bit ugly, but it looks like a valid fix
for what the SLUB_TYPESAFE_BY_RCU conversion broke.

Reviewed-by: Jann Horn <jannh@xxxxxxxxxx>


But as a side note, I am a bit confused about how the concurrency of
the existing code is supposed to work... it looks like two parallel
calls to mmap_singleton() can end up returning different pointers, and
then the singleton is not actually a singleton anymore? If that part
of the existing code is wrong even before the SLAB_TYPESAFE_BY_RCU
conversion, we might later have to open-code get_file_rcu() in
mmap_singleton(), so that we can do a cmpxchg at the end that checks
whether the i915->gem.mmap_singleton pointer is still the same?

Like:

static struct file *mmap_singleton(struct drm_i915_private *i915)
{
        struct file *file, *new_file;

        rcu_read_lock();
        file = READ_ONCE(i915->gem.mmap_singleton);
        if (file && atomic_long_inc_not_zero(&file->f_count)) {
                rcu_read_unlock();
                return file;
        }
        rcu_read_unlock();

        new_file = anon_inode_getfile("i915.gem", &singleton_fops,
i915, O_RDWR);
        if (IS_ERR(new_file))
                return new_file;

        /* Everyone shares a single global address space */
        new_file->f_mapping = i915->drm.anon_inode->i_mapping;

        if (try_cmpxchg(&i915->gem.mmap_singleton, &file, new_file)) {
                // something with drm_dev refcount ?
                return new_file;
        }

        // something with drm_dev refcount ?
        fput(new_file);

        return file;
}

It would be nice to get some i915 maintainer's comment on how the
singleton stuff is supposed to work.

> ---
>
> Jann, Linus,
>
> Since you've been quite involved, can you check that what I'm babbling
> here makes sense? If this isn't the fix then I would have to drop the
> SLAB_TYPESAFE_BY_RCU conversion patch for now since I'd like to send PRs
> by the end of the week.
>
> This is on top of
> https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git/log/?h=vfs.misc
>
> Appreciate the help, thanks!
> Christian
>
> ---
>  drivers/gpu/drm/i915/gem/i915_gem_mman.c |  2 +-
>  fs/file.c                                | 17 ++++++++++++++++-
>  include/linux/fs.h                       |  1 +
>  3 files changed, 18 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/i915/gem/i915_gem_mman.c b/drivers/gpu/drm/i915/gem/i915_gem_mman.c
> index 97bf10861cad..da97e61b18d4 100644
> --- a/drivers/gpu/drm/i915/gem/i915_gem_mman.c
> +++ b/drivers/gpu/drm/i915/gem/i915_gem_mman.c
> @@ -916,7 +916,7 @@ static struct file *mmap_singleton(struct drm_i915_private *i915)
>         struct file *file;
>
>         rcu_read_lock();
> -       file = get_file_rcu(&i915->gem.mmap_singleton);
> +       file = get_file_rcu_once(&i915->gem.mmap_singleton);
>         rcu_read_unlock();
>         if (file)
>                 return file;
> diff --git a/fs/file.c b/fs/file.c
> index 1a475d7d636e..2c64d6836f0c 100644
> --- a/fs/file.c
> +++ b/fs/file.c
> @@ -867,7 +867,7 @@ static struct file *__get_file_rcu(struct file __rcu **f)
>                 return NULL;
>
>         if (unlikely(!atomic_long_inc_not_zero(&file->f_count)))
> -               return ERR_PTR(-EAGAIN);
> +               return ERR_PTR(-EINVAL);
>
>         file_reloaded = rcu_dereference_raw(*f);
>
> @@ -927,6 +927,21 @@ struct file *get_file_rcu(struct file __rcu **f)
>  }
>  EXPORT_SYMBOL_GPL(get_file_rcu);
>
> +struct file *get_file_rcu_once(struct file __rcu **f)
> +{
> +       for (;;) {
> +               struct file __rcu *file;
> +
> +               file = __get_file_rcu(f);
> +               if (!IS_ERR(file))
> +                       return file;
> +
> +               if (PTR_ERR(file) == -EINVAL)
> +                       return NULL;
> +       }
> +}
> +EXPORT_SYMBOL_GPL(get_file_rcu_once);
> +
>  static inline struct file *__fget_files_rcu(struct files_struct *files,
>         unsigned int fd, fmode_t mask)
>  {
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index cb8bfa1f8ecb..657bbd824490 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -1044,6 +1044,7 @@ static inline struct file *get_file(struct file *f)
>  }
>
>  struct file *get_file_rcu(struct file __rcu **f);
> +struct file *get_file_rcu_once(struct file __rcu **f);
>
>  #define file_count(x)  atomic_long_read(&(x)->f_count)
>
> --
> 2.34.1
>




[Index of Archives]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux