diff mbox series

[bpf-next,v4,01/13] bpf: Make btf_find_field more generic

Message ID 20220409093303.499196-2-memxor@gmail.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series Introduce typed pointer support in BPF maps | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-PR success PR summary
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 10 this patch: 10
netdev/cc_maintainers warning 6 maintainers not CCed: songliubraving@fb.com netdev@vger.kernel.org kafai@fb.com yhs@fb.com john.fastabend@gmail.com kpsingh@kernel.org
netdev/build_clang success Errors and warnings before: 9 this patch: 9
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 10 this patch: 10
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: line length of 84 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns WARNING: line length of 87 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns WARNING: line length of 91 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-1 success Logs for Kernel LATEST on ubuntu-latest + selftests
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Kernel LATEST on z15 + selftests

Commit Message

Kumar Kartikeya Dwivedi April 9, 2022, 9:32 a.m. UTC
Next commit introduces field type 'kptr' whose kind will not be struct,
but pointer, and it will not be limited to one offset, but multiple
ones. Make existing btf_find_struct_field and btf_find_datasec_var
functions amenable to use for finding kptrs in map value, by moving
spin_lock and timer specific checks into their own function.

The alignment, and name are checked before the function is called, so it
is the last point where we can skip field or return an error before the
next loop iteration happens. The name parameter is now optional, and
only checked if it is not NULL. Size of the field and type is meant to
be checked inside the function, and base type will need to be obtained
by skipping modifiers.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/btf.c | 129 +++++++++++++++++++++++++++++++++++------------
 1 file changed, 96 insertions(+), 33 deletions(-)

Comments

Joanne Koong April 11, 2022, 8:20 p.m. UTC | #1
On Sat, Apr 9, 2022 at 2:32 AM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>
> Next commit introduces field type 'kptr' whose kind will not be struct,
> but pointer, and it will not be limited to one offset, but multiple
> ones. Make existing btf_find_struct_field and btf_find_datasec_var
> functions amenable to use for finding kptrs in map value, by moving
> spin_lock and timer specific checks into their own function.
>
> The alignment, and name are checked before the function is called, so it
> is the last point where we can skip field or return an error before the
> next loop iteration happens. The name parameter is now optional, and
> only checked if it is not NULL. Size of the field and type is meant to
> be checked inside the function, and base type will need to be obtained
> by skipping modifiers.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
>  kernel/bpf/btf.c | 129 +++++++++++++++++++++++++++++++++++------------
>  1 file changed, 96 insertions(+), 33 deletions(-)
>
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 0918a39279f6..db7bf05adfc5 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -3163,71 +3163,126 @@ static void btf_struct_log(struct btf_verifier_env *env,
>         btf_verifier_log(env, "size=%u vlen=%u", t->size, btf_type_vlen(t));
>  }
>
> +enum {
> +       BTF_FIELD_SPIN_LOCK,
> +       BTF_FIELD_TIMER,
> +};
> +
> +struct btf_field_info {
> +       u32 off;
> +};
> +
> +static int btf_find_field_struct(const struct btf *btf, const struct btf_type *t,
> +                                u32 off, int sz, struct btf_field_info *info)
> +{
> +       if (!__btf_type_is_struct(t))
> +               return 0;
> +       if (t->size != sz)
> +               return 0;
Do we need these two checks? I think in the original version we did
because we were checking this before doing the name comparison, but
now that the name comparison check is first, if the struct name is a
match, then won't these two things always be true (or if not, then it
seems like we should return -EINVAL)? But maybe I'm missing something
here - I'm still getting more familiar with BTF :)

Also, as a side-note: I personally find this function name
"btf_find_field_struct" confusing since there's also the
"btf_find_struct_field" function that exists. I wonder whether we
should just keep the logic inside btf_find_struct_field instead of
putting it in this separate function?

> +       if (info->off != -ENOENT)
> +               /* only one such field is allowed */
> +               return -E2BIG;
In the future, do you plan to add support for multiple fields? I think
this would be useful for dynptrs as well, so just curious what your
plans for this are.
> +       info->off = off;
> +       return 0;
> +}
> +
>  static int btf_find_struct_field(const struct btf *btf, const struct btf_type *t,
> -                                const char *name, int sz, int align)
> +                                const char *name, int sz, int align, int field_type,

What are your thoughts on just passing in field_type in place of name,
sz, and align? As in a function signature like:

static int btf_find_struct_field(const struct btf *btf, const struct
btf_type *t, int field_type, struct btf_field_info *info);

where inside btf_find_struct_field when we do the switch statement on
field_type, we can have the name, sz, and align for each of the
different field types there? That to me seems a bit cleaner where the
descriptors for the field types are all in one place (instead of also
in btf_find_spin_lock() and btf_find_timer() functions) and the
function definition for btf_find_struct_field is more straightforward.
At that point, I don't think we'd even need btf_find_spin_lock() and
btf_find_timer() as functions since it'd be just a straightforward
"btf_find_field(btf, t, BTF_FIELD_SPIN_LOCK/BTF_FIELD_TIMER)" call
instead. Curious to hear your thoughts.

nit: should field_type be a u32 since it's an enum? Or should we be
explicit and give the enum a name and define this as something like
"enum btf_field_type type"?

> +                                struct btf_field_info *info)
>  {
>         const struct btf_member *member;
> -       u32 i, off = -ENOENT;
> +       u32 i, off;
> +       int ret;
>
>         for_each_member(i, t, member) {
>                 const struct btf_type *member_type = btf_type_by_id(btf,
>                                                                     member->type);
> -               if (!__btf_type_is_struct(member_type))
> -                       continue;
> -               if (member_type->size != sz)
> -                       continue;
> -               if (strcmp(__btf_name_by_offset(btf, member_type->name_off), name))
> -                       continue;
> -               if (off != -ENOENT)
> -                       /* only one such field is allowed */
> -                       return -E2BIG;
> +
>                 off = __btf_member_bit_offset(t, member);
nit: should this be moved to after the strcmp on the name? Since if
the name doesn't match, there's no point in doing this
__btf_member_bit_offset call
> +
> +               if (name && strcmp(__btf_name_by_offset(btf, member_type->name_off), name))
I'm confused by the if (name) part of the check. If name is NULL, then
won't this "btf_find_struct_field" function always return the offset
to the first struct? I don't think name will ever be NULL so maybe we
should just remove this? Or do something like if (name) return
-EINVAL; before doing the strcmp?

> +                       continue;
>                 if (off % 8)
>                         /* valid C code cannot generate such BTF */
>                         return -EINVAL;
>                 off /= 8;
>                 if (off % align)
>                         return -EINVAL;
> +
> +               switch (field_type) {
> +               case BTF_FIELD_SPIN_LOCK:
> +               case BTF_FIELD_TIMER:
> +                       ret = btf_find_field_struct(btf, member_type, off, sz, info);
nit: I think we can just do "return btf_find_field_struct(btf,
member_type, off, sz, info);" here and remove the "int ret;"
declaration a few lines above.

> +                       if (ret < 0)
> +                               return ret;
> +                       break;
> +               default:
> +                       return -EFAULT;
> +               }
>         }
> -       return off;
> +       return 0;
>  }
>
>  static int btf_find_datasec_var(const struct btf *btf, const struct btf_type *t,
> -                               const char *name, int sz, int align)
> +                               const char *name, int sz, int align, int field_type,
> +                               struct btf_field_info *info)
The same comments for the btf_find_struct_field function also apply to
this function
>  {
>         const struct btf_var_secinfo *vsi;
> -       u32 i, off = -ENOENT;
> +       u32 i, off;
> +       int ret;
>
>         for_each_vsi(i, t, vsi) {
>                 const struct btf_type *var = btf_type_by_id(btf, vsi->type);
>                 const struct btf_type *var_type = btf_type_by_id(btf, var->type);
>
> -               if (!__btf_type_is_struct(var_type))
> -                       continue;
> -               if (var_type->size != sz)
> +               off = vsi->offset;
> +
> +               if (name && strcmp(__btf_name_by_offset(btf, var_type->name_off), name))
>                         continue;
>                 if (vsi->size != sz)
>                         continue;
> -               if (strcmp(__btf_name_by_offset(btf, var_type->name_off), name))
> -                       continue;
> -               if (off != -ENOENT)
> -                       /* only one such field is allowed */
> -                       return -E2BIG;
> -               off = vsi->offset;
>                 if (off % align)
>                         return -EINVAL;
> +
> +               switch (field_type) {
> +               case BTF_FIELD_SPIN_LOCK:
> +               case BTF_FIELD_TIMER:
> +                       ret = btf_find_field_struct(btf, var_type, off, sz, info);
> +                       if (ret < 0)
> +                               return ret;
> +                       break;
> +               default:
> +                       return -EFAULT;
> +               }
>         }
> -       return off;
> +       return 0;
>  }
>
>  static int btf_find_field(const struct btf *btf, const struct btf_type *t,
> -                         const char *name, int sz, int align)
> +                         int field_type, struct btf_field_info *info)
>  {
> +       const char *name;
> +       int sz, align;
> +
> +       switch (field_type) {
> +       case BTF_FIELD_SPIN_LOCK:
> +               name = "bpf_spin_lock";
> +               sz = sizeof(struct bpf_spin_lock);
> +               align = __alignof__(struct bpf_spin_lock);
> +               break;
> +       case BTF_FIELD_TIMER:
> +               name = "bpf_timer";
> +               sz = sizeof(struct bpf_timer);
> +               align = __alignof__(struct bpf_timer);
> +               break;
> +       default:
> +               return -EFAULT;
> +       }
>
>         if (__btf_type_is_struct(t))
> -               return btf_find_struct_field(btf, t, name, sz, align);
> +               return btf_find_struct_field(btf, t, name, sz, align, field_type, info);
>         else if (btf_type_is_datasec(t))
> -               return btf_find_datasec_var(btf, t, name, sz, align);
> +               return btf_find_datasec_var(btf, t, name, sz, align, field_type, info);
>         return -EINVAL;
>  }
>
> @@ -3237,16 +3292,24 @@ static int btf_find_field(const struct btf *btf, const struct btf_type *t,
>   */
>  int btf_find_spin_lock(const struct btf *btf, const struct btf_type *t)
>  {
> -       return btf_find_field(btf, t, "bpf_spin_lock",
> -                             sizeof(struct bpf_spin_lock),
> -                             __alignof__(struct bpf_spin_lock));
> +       struct btf_field_info info = { .off = -ENOENT };
> +       int ret;
> +
> +       ret = btf_find_field(btf, t, BTF_FIELD_SPIN_LOCK, &info);
I'm confused about why we pass in "struct btf_field_info" as the out
parameter. Maybe I'm missing something here, but why can't
"btf_find_field" just return back the offset?
> +       if (ret < 0)
> +               return ret;
> +       return info.off;
>  }
>
>  int btf_find_timer(const struct btf *btf, const struct btf_type *t)
>  {
> -       return btf_find_field(btf, t, "bpf_timer",
> -                             sizeof(struct bpf_timer),
> -                             __alignof__(struct bpf_timer));
> +       struct btf_field_info info = { .off = -ENOENT };
> +       int ret;
> +
> +       ret = btf_find_field(btf, t, BTF_FIELD_TIMER, &info);
> +       if (ret < 0)
> +               return ret;
> +       return info.off;
>  }
>
>  static void __btf_struct_show(const struct btf *btf, const struct btf_type *t,
> --
> 2.35.1
>
Kumar Kartikeya Dwivedi April 12, 2022, 7:48 p.m. UTC | #2
On Tue, Apr 12, 2022 at 01:50:28AM IST, Joanne Koong wrote:
> On Sat, Apr 9, 2022 at 2:32 AM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
> >
> > Next commit introduces field type 'kptr' whose kind will not be struct,
> > but pointer, and it will not be limited to one offset, but multiple
> > ones. Make existing btf_find_struct_field and btf_find_datasec_var
> > functions amenable to use for finding kptrs in map value, by moving
> > spin_lock and timer specific checks into their own function.
> >
> > The alignment, and name are checked before the function is called, so it
> > is the last point where we can skip field or return an error before the
> > next loop iteration happens. The name parameter is now optional, and
> > only checked if it is not NULL. Size of the field and type is meant to
> > be checked inside the function, and base type will need to be obtained
> > by skipping modifiers.
> >
> > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> > ---
> >  kernel/bpf/btf.c | 129 +++++++++++++++++++++++++++++++++++------------
> >  1 file changed, 96 insertions(+), 33 deletions(-)
> >
> > diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> > index 0918a39279f6..db7bf05adfc5 100644
> > --- a/kernel/bpf/btf.c
> > +++ b/kernel/bpf/btf.c
> > @@ -3163,71 +3163,126 @@ static void btf_struct_log(struct btf_verifier_env *env,
> >         btf_verifier_log(env, "size=%u vlen=%u", t->size, btf_type_vlen(t));
> >  }
> >
> > +enum {
> > +       BTF_FIELD_SPIN_LOCK,
> > +       BTF_FIELD_TIMER,
> > +};
> > +
> > +struct btf_field_info {
> > +       u32 off;
> > +};
> > +
> > +static int btf_find_field_struct(const struct btf *btf, const struct btf_type *t,
> > +                                u32 off, int sz, struct btf_field_info *info)
> > +{
> > +       if (!__btf_type_is_struct(t))
> > +               return 0;
> > +       if (t->size != sz)
> > +               return 0;
> Do we need these two checks? I think in the original version we did
> because we were checking this before doing the name comparison, but
> now that the name comparison check is first, if the struct name is a
> match, then won't these two things always be true (or if not, then it
> seems like we should return -EINVAL)? But maybe I'm missing something
> here - I'm still getting more familiar with BTF :)

The name can be the same, but since this comes from map BTF, it could be a
different struct with the same name string in the map BTF, with a different
size as well. So checking both is still needed.

Returning -EINVAL now would be backwards incompatible, code this is replacing
continues when it doesn't find struct with the required size.

>
> Also, as a side-note: I personally find this function name
> "btf_find_field_struct" confusing since there's also the
> "btf_find_struct_field" function that exists. I wonder whether we
> should just keep the logic inside btf_find_struct_field instead of
> putting it in this separate function?

I'm open to renaming, how about we just call it btf_find_struct? Then in the
next patch I could rename btf_find_field_kptr to btf_find_kptr.

>
> > +       if (info->off != -ENOENT)
> > +               /* only one such field is allowed */
> > +               return -E2BIG;
> In the future, do you plan to add support for multiple fields? I think
> this would be useful for dynptrs as well, so just curious what your
> plans for this are.

In the next patch it is modified to deal with one info at once, so supporting
multiple fields is a matter of passing different info_cnt. It won't do this
info->off check to ensure it only saw one field from next patch, that will be
handled outside in the loop.

> > +       info->off = off;
> > +       return 0;
> > +}
> > +
> >  static int btf_find_struct_field(const struct btf *btf, const struct btf_type *t,
> > -                                const char *name, int sz, int align)
> > +                                const char *name, int sz, int align, int field_type,
>
> What are your thoughts on just passing in field_type in place of name,
> sz, and align? As in a function signature like:
>
> static int btf_find_struct_field(const struct btf *btf, const struct
> btf_type *t, int field_type, struct btf_field_info *info);
>
> where inside btf_find_struct_field when we do the switch statement on
> field_type, we can have the name, sz, and align for each of the
> different field types there? That to me seems a bit cleaner where the
> descriptors for the field types are all in one place (instead of also
> in btf_find_spin_lock() and btf_find_timer() functions) and the
> function definition for btf_find_struct_field is more straightforward.
> At that point, I don't think we'd even need btf_find_spin_lock() and
> btf_find_timer() as functions since it'd be just a straightforward
> "btf_find_field(btf, t, BTF_FIELD_SPIN_LOCK/BTF_FIELD_TIMER)" call
> instead. Curious to hear your thoughts.

Isn't btf_find_field doing exactly this? btf_find_timer e.g. only passed
BTF_FIELD_TIMER; name, sz, and alignment come from btf_find_field.

Also, after btf_find_field, there needs to be handling for the info that was
populated. In case of timer and spin_lock, we just return the offset, but in
case of kptrs we populate the kptr_off_tab. If we move this inside
btf_find_field, then it would be done based on field type inside the same
function (either using if/else or switch cases), not sure that is cleaner than
doing it in separate wrappers.

>
> nit: should field_type be a u32 since it's an enum? Or should we be
> explicit and give the enum a name and define this as something like
> "enum btf_field_type type"?
>

Ok, I'll do that (since I'm respinning anyway), though it doesn't really matter,
the underlying type is still int in C.

> > +                                struct btf_field_info *info)
> >  {
> >         const struct btf_member *member;
> > -       u32 i, off = -ENOENT;
> > +       u32 i, off;
> > +       int ret;
> >
> >         for_each_member(i, t, member) {
> >                 const struct btf_type *member_type = btf_type_by_id(btf,
> >                                                                     member->type);
> > -               if (!__btf_type_is_struct(member_type))
> > -                       continue;
> > -               if (member_type->size != sz)
> > -                       continue;
> > -               if (strcmp(__btf_name_by_offset(btf, member_type->name_off), name))
> > -                       continue;
> > -               if (off != -ENOENT)
> > -                       /* only one such field is allowed */
> > -                       return -E2BIG;
> > +
> >                 off = __btf_member_bit_offset(t, member);
> nit: should this be moved to after the strcmp on the name? Since if
> the name doesn't match, there's no point in doing this
> __btf_member_bit_offset call

Ack.

> > +
> > +               if (name && strcmp(__btf_name_by_offset(btf, member_type->name_off), name))
> I'm confused by the if (name) part of the check. If name is NULL, then
> won't this "btf_find_struct_field" function always return the offset
> to the first struct? I don't think name will ever be NULL so maybe we
> should just remove this? Or do something like if (name) return
> -EINVAL; before doing the strcmp?
>

I'll move it to the next patch, since you noted there that you realised this is
for kptr.

> > +                       continue;
> >                 if (off % 8)
> >                         /* valid C code cannot generate such BTF */
> >                         return -EINVAL;
> >                 off /= 8;
> >                 if (off % align)
> >                         return -EINVAL;
> > +
> > +               switch (field_type) {
> > +               case BTF_FIELD_SPIN_LOCK:
> > +               case BTF_FIELD_TIMER:
> > +                       ret = btf_find_field_struct(btf, member_type, off, sz, info);
> nit: I think we can just do "return btf_find_field_struct(btf,
> member_type, off, sz, info);" here and remove the "int ret;"
> declaration a few lines above.
>

Ack.

> > +                       if (ret < 0)
> > +                               return ret;
> > +                       break;
> > +               default:
> > +                       return -EFAULT;
> > +               }
> >         }
> > -       return off;
> > +       return 0;
> >  }
> >
> >  static int btf_find_datasec_var(const struct btf *btf, const struct btf_type *t,
> > -                               const char *name, int sz, int align)
> > +                               const char *name, int sz, int align, int field_type,
> > +                               struct btf_field_info *info)
> The same comments for the btf_find_struct_field function also apply to
> this function
> >  {
> >         const struct btf_var_secinfo *vsi;
> > -       u32 i, off = -ENOENT;
> > +       u32 i, off;
> > +       int ret;
> >
> >         for_each_vsi(i, t, vsi) {
> >                 const struct btf_type *var = btf_type_by_id(btf, vsi->type);
> >                 const struct btf_type *var_type = btf_type_by_id(btf, var->type);
> >
> > -               if (!__btf_type_is_struct(var_type))
> > -                       continue;
> > -               if (var_type->size != sz)
> > +               off = vsi->offset;
> > +
> > +               if (name && strcmp(__btf_name_by_offset(btf, var_type->name_off), name))
> >                         continue;
> >                 if (vsi->size != sz)
> >                         continue;
> > -               if (strcmp(__btf_name_by_offset(btf, var_type->name_off), name))
> > -                       continue;
> > -               if (off != -ENOENT)
> > -                       /* only one such field is allowed */
> > -                       return -E2BIG;
> > -               off = vsi->offset;
> >                 if (off % align)
> >                         return -EINVAL;
> > +
> > +               switch (field_type) {
> > +               case BTF_FIELD_SPIN_LOCK:
> > +               case BTF_FIELD_TIMER:
> > +                       ret = btf_find_field_struct(btf, var_type, off, sz, info);
> > +                       if (ret < 0)
> > +                               return ret;
> > +                       break;
> > +               default:
> > +                       return -EFAULT;
> > +               }
> >         }
> > -       return off;
> > +       return 0;
> >  }
> >
> >  static int btf_find_field(const struct btf *btf, const struct btf_type *t,
> > -                         const char *name, int sz, int align)
> > +                         int field_type, struct btf_field_info *info)
> >  {
> > +       const char *name;
> > +       int sz, align;
> > +
> > +       switch (field_type) {
> > +       case BTF_FIELD_SPIN_LOCK:
> > +               name = "bpf_spin_lock";
> > +               sz = sizeof(struct bpf_spin_lock);
> > +               align = __alignof__(struct bpf_spin_lock);
> > +               break;
> > +       case BTF_FIELD_TIMER:
> > +               name = "bpf_timer";
> > +               sz = sizeof(struct bpf_timer);
> > +               align = __alignof__(struct bpf_timer);
> > +               break;
> > +       default:
> > +               return -EFAULT;
> > +       }
> >
> >         if (__btf_type_is_struct(t))
> > -               return btf_find_struct_field(btf, t, name, sz, align);
> > +               return btf_find_struct_field(btf, t, name, sz, align, field_type, info);
> >         else if (btf_type_is_datasec(t))
> > -               return btf_find_datasec_var(btf, t, name, sz, align);
> > +               return btf_find_datasec_var(btf, t, name, sz, align, field_type, info);
> >         return -EINVAL;
> >  }
> >
> > @@ -3237,16 +3292,24 @@ static int btf_find_field(const struct btf *btf, const struct btf_type *t,
> >   */
> >  int btf_find_spin_lock(const struct btf *btf, const struct btf_type *t)
> >  {
> > -       return btf_find_field(btf, t, "bpf_spin_lock",
> > -                             sizeof(struct bpf_spin_lock),
> > -                             __alignof__(struct bpf_spin_lock));
> > +       struct btf_field_info info = { .off = -ENOENT };
> > +       int ret;
> > +
> > +       ret = btf_find_field(btf, t, BTF_FIELD_SPIN_LOCK, &info);
> I'm confused about why we pass in "struct btf_field_info" as the out
> parameter. Maybe I'm missing something here, but why can't
> "btf_find_field" just return back the offset?
> > +       if (ret < 0)
> > +               return ret;
> > +       return info.off;
> >  }
> >
> >  int btf_find_timer(const struct btf *btf, const struct btf_type *t)
> >  {
> > -       return btf_find_field(btf, t, "bpf_timer",
> > -                             sizeof(struct bpf_timer),
> > -                             __alignof__(struct bpf_timer));
> > +       struct btf_field_info info = { .off = -ENOENT };
> > +       int ret;
> > +
> > +       ret = btf_find_field(btf, t, BTF_FIELD_TIMER, &info);
> > +       if (ret < 0)
> > +               return ret;
> > +       return info.off;
> >  }
> >
> >  static void __btf_struct_show(const struct btf *btf, const struct btf_type *t,
> > --
> > 2.35.1
> >

--
Kartikeya
diff mbox series

Patch

diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 0918a39279f6..db7bf05adfc5 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -3163,71 +3163,126 @@  static void btf_struct_log(struct btf_verifier_env *env,
 	btf_verifier_log(env, "size=%u vlen=%u", t->size, btf_type_vlen(t));
 }
 
+enum {
+	BTF_FIELD_SPIN_LOCK,
+	BTF_FIELD_TIMER,
+};
+
+struct btf_field_info {
+	u32 off;
+};
+
+static int btf_find_field_struct(const struct btf *btf, const struct btf_type *t,
+				 u32 off, int sz, struct btf_field_info *info)
+{
+	if (!__btf_type_is_struct(t))
+		return 0;
+	if (t->size != sz)
+		return 0;
+	if (info->off != -ENOENT)
+		/* only one such field is allowed */
+		return -E2BIG;
+	info->off = off;
+	return 0;
+}
+
 static int btf_find_struct_field(const struct btf *btf, const struct btf_type *t,
-				 const char *name, int sz, int align)
+				 const char *name, int sz, int align, int field_type,
+				 struct btf_field_info *info)
 {
 	const struct btf_member *member;
-	u32 i, off = -ENOENT;
+	u32 i, off;
+	int ret;
 
 	for_each_member(i, t, member) {
 		const struct btf_type *member_type = btf_type_by_id(btf,
 								    member->type);
-		if (!__btf_type_is_struct(member_type))
-			continue;
-		if (member_type->size != sz)
-			continue;
-		if (strcmp(__btf_name_by_offset(btf, member_type->name_off), name))
-			continue;
-		if (off != -ENOENT)
-			/* only one such field is allowed */
-			return -E2BIG;
+
 		off = __btf_member_bit_offset(t, member);
+
+		if (name && strcmp(__btf_name_by_offset(btf, member_type->name_off), name))
+			continue;
 		if (off % 8)
 			/* valid C code cannot generate such BTF */
 			return -EINVAL;
 		off /= 8;
 		if (off % align)
 			return -EINVAL;
+
+		switch (field_type) {
+		case BTF_FIELD_SPIN_LOCK:
+		case BTF_FIELD_TIMER:
+			ret = btf_find_field_struct(btf, member_type, off, sz, info);
+			if (ret < 0)
+				return ret;
+			break;
+		default:
+			return -EFAULT;
+		}
 	}
-	return off;
+	return 0;
 }
 
 static int btf_find_datasec_var(const struct btf *btf, const struct btf_type *t,
-				const char *name, int sz, int align)
+				const char *name, int sz, int align, int field_type,
+				struct btf_field_info *info)
 {
 	const struct btf_var_secinfo *vsi;
-	u32 i, off = -ENOENT;
+	u32 i, off;
+	int ret;
 
 	for_each_vsi(i, t, vsi) {
 		const struct btf_type *var = btf_type_by_id(btf, vsi->type);
 		const struct btf_type *var_type = btf_type_by_id(btf, var->type);
 
-		if (!__btf_type_is_struct(var_type))
-			continue;
-		if (var_type->size != sz)
+		off = vsi->offset;
+
+		if (name && strcmp(__btf_name_by_offset(btf, var_type->name_off), name))
 			continue;
 		if (vsi->size != sz)
 			continue;
-		if (strcmp(__btf_name_by_offset(btf, var_type->name_off), name))
-			continue;
-		if (off != -ENOENT)
-			/* only one such field is allowed */
-			return -E2BIG;
-		off = vsi->offset;
 		if (off % align)
 			return -EINVAL;
+
+		switch (field_type) {
+		case BTF_FIELD_SPIN_LOCK:
+		case BTF_FIELD_TIMER:
+			ret = btf_find_field_struct(btf, var_type, off, sz, info);
+			if (ret < 0)
+				return ret;
+			break;
+		default:
+			return -EFAULT;
+		}
 	}
-	return off;
+	return 0;
 }
 
 static int btf_find_field(const struct btf *btf, const struct btf_type *t,
-			  const char *name, int sz, int align)
+			  int field_type, struct btf_field_info *info)
 {
+	const char *name;
+	int sz, align;
+
+	switch (field_type) {
+	case BTF_FIELD_SPIN_LOCK:
+		name = "bpf_spin_lock";
+		sz = sizeof(struct bpf_spin_lock);
+		align = __alignof__(struct bpf_spin_lock);
+		break;
+	case BTF_FIELD_TIMER:
+		name = "bpf_timer";
+		sz = sizeof(struct bpf_timer);
+		align = __alignof__(struct bpf_timer);
+		break;
+	default:
+		return -EFAULT;
+	}
 
 	if (__btf_type_is_struct(t))
-		return btf_find_struct_field(btf, t, name, sz, align);
+		return btf_find_struct_field(btf, t, name, sz, align, field_type, info);
 	else if (btf_type_is_datasec(t))
-		return btf_find_datasec_var(btf, t, name, sz, align);
+		return btf_find_datasec_var(btf, t, name, sz, align, field_type, info);
 	return -EINVAL;
 }
 
@@ -3237,16 +3292,24 @@  static int btf_find_field(const struct btf *btf, const struct btf_type *t,
  */
 int btf_find_spin_lock(const struct btf *btf, const struct btf_type *t)
 {
-	return btf_find_field(btf, t, "bpf_spin_lock",
-			      sizeof(struct bpf_spin_lock),
-			      __alignof__(struct bpf_spin_lock));
+	struct btf_field_info info = { .off = -ENOENT };
+	int ret;
+
+	ret = btf_find_field(btf, t, BTF_FIELD_SPIN_LOCK, &info);
+	if (ret < 0)
+		return ret;
+	return info.off;
 }
 
 int btf_find_timer(const struct btf *btf, const struct btf_type *t)
 {
-	return btf_find_field(btf, t, "bpf_timer",
-			      sizeof(struct bpf_timer),
-			      __alignof__(struct bpf_timer));
+	struct btf_field_info info = { .off = -ENOENT };
+	int ret;
+
+	ret = btf_find_field(btf, t, BTF_FIELD_TIMER, &info);
+	if (ret < 0)
+		return ret;
+	return info.off;
 }
 
 static void __btf_struct_show(const struct btf *btf, const struct btf_type *t,