mirror of
https://github.com/torvalds/linux.git
synced 2026-05-27 16:44:58 +02:00
Merge branch 'support-associating-bpf-programs-with-struct_ops'
Amery Hung says: ==================== Support associating BPF programs with struct_ops Hi, This patchset adds a new BPF command BPF_PROG_ASSOC_STRUCT_OPS to the bpf() syscall to allow associating a BPF program with a struct_ops. The command is introduced to address a emerging need from struct_ops users. As the number of subsystems adopting struct_ops grows, more users are building their struct_ops-based solution with some help from other BPF programs. For example, scx_layer uses a syscall program as a user space trigger to refresh layers [0]. It also uses tracing program to infer whether a task is using GPU and needs to be prioritized [1]. In these use cases, when there are multiple struct_ops instances, the struct_ops kfuncs called from different BPF programs, whether struct_ops or not needs to be able to refer to a specific one, which currently is not possible. The new BPF command will allow users to explicitly associate a BPF program with a struct_ops map. The libbpf wrapper can be called after loading programs and before attaching programs and struct_ops. Internally, it will set prog->aux->st_ops_assoc to the struct_ops map. struct_ops kfuncs can then get the associated struct_ops struct by calling bpf_prog_get_assoc_struct_ops() with prog->aux, which can be acquired from a "__prog" argument. The value of the special argument will be fixed up by the verifier during verification. The command conceptually associates the implementation of BPF programs with struct_ops map, not the attachment. A program associated with the map will take a refcount of it so that st_ops_assoc always points to a valid struct_ops struct. struct_ops implementers can use the helper, bpf_prog_get_assoc_struct_ops to get the pointer. The returned struct_ops if not NULL is guaranteed to be valid and initialized. However, it is not guaranteed that the struct_ops is attached. The struct_ops implementer still need to take steps to track and check the state of the struct_ops in kdata, if the use case demand the struct_ops to be attached. We can also consider support associating struct_ops link with BPF programs, which on one hand make struct_ops implementer's job easier, but might complicate libbpf workflow and does not apply to legacy struct_ops attachment. [0] https://github.com/sched-ext/scx/blob/main/scheds/rust/scx_layered/src/bpf/main.bpf.c#L557 [1] https://github.com/sched-ext/scx/blob/main/scheds/rust/scx_layered/src/bpf/main.bpf.c#L754 --- v7 -> v8 - Fix libbpf return (Andrii) - Follow kfunc _impl suffic naming convention in selftest (Alexei) Link: https://lore.kernel.org/bpf/20251121231352.4032020-1-ameryhung@gmail.com/ v6 -> v7 - Drop the guarantee that bpf_prog_get_assoc_struct_ops() will always return an initialized struct_ops (Martin) - Minor misc. changes in selftests Link: https://lore.kernel.org/bpf/20251114221741.317631-1-ameryhung@gmail.com/ v5 -> v6 - Drop refcnt bumping for async callbacks and add RCU annotation (Martin) - Fix libbpf bug and update comments (Andrii) - Fix refcount bug in bpf_prog_assoc_struct_ops() (AI) Link: https://lore.kernel.org/bpf/20251104172652.1746988-1-ameryhung@gmail.com/ v4 -> v5 - Simplify the API for getting associated struct_ops and dont't expose struct_ops map lifecycle management (Andrii, Alexei) Link: https://lore.kernel.org/bpf/20251024212914.1474337-1-ameryhung@gmail.com/ v3 -> v4 - Fix potential dangling pointer in timer callback. Protect st_ops_assoc with RCU. The get helper now needs to be paired with bpf_struct_ops_put() - The command should only increase refcount once for a program (Andrii) - Test a struct_ops program reused in two struct_ops maps - Test getting associated struct_ops in timer callback Link: https://lore.kernel.org/bpf/20251017215627.722338-1-ameryhung@gmail.com/ v2 -> v3 - Change the type of st_ops_assoc from void* (i.e., kdata) to bpf_map (Andrii) - Fix a bug that clears BPF_PTR_POISON when a struct_ops map is freed (Andrii) - Return NULL if the map is not fully initialized (Martin) - Move struct_ops map refcount inc/dec into internal helpers (Martin) - Add libbpf API, bpf_program__assoc_struct_ops (Andrii) Link: https://lore.kernel.org/bpf/20251016204503.3203690-1-ameryhung@gmail.com/ v1 -> v2 - Poison st_ops_assoc when reusing the program in more than one struct_ops maps and add a helper to access the pointer (Andrii) - Minor style and naming changes (Andrii) Link: https://lore.kernel.org/bpf/20251010174953.2884682-1-ameryhung@gmail.com/ --- ==================== Link: https://patch.msgid.link/20251203233748.668365-1-ameryhung@gmail.com Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
This commit is contained in:
commit
5d9fb42f05
|
|
@ -1739,6 +1739,8 @@ struct bpf_prog_aux {
|
|||
struct rcu_head rcu;
|
||||
};
|
||||
struct bpf_stream stream[2];
|
||||
struct mutex st_ops_assoc_mutex;
|
||||
struct bpf_map __rcu *st_ops_assoc;
|
||||
};
|
||||
|
||||
struct bpf_prog {
|
||||
|
|
@ -2041,6 +2043,9 @@ static inline void bpf_module_put(const void *data, struct module *owner)
|
|||
module_put(owner);
|
||||
}
|
||||
int bpf_struct_ops_link_create(union bpf_attr *attr);
|
||||
int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map);
|
||||
void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog);
|
||||
void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux);
|
||||
u32 bpf_struct_ops_id(const void *kdata);
|
||||
|
||||
#ifdef CONFIG_NET
|
||||
|
|
@ -2088,6 +2093,17 @@ static inline int bpf_struct_ops_link_create(union bpf_attr *attr)
|
|||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
static inline int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
static inline void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog)
|
||||
{
|
||||
}
|
||||
static inline void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
static inline void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -918,6 +918,16 @@ union bpf_iter_link_info {
|
|||
* Number of bytes read from the stream on success, or -1 if an
|
||||
* error occurred (in which case, *errno* is set appropriately).
|
||||
*
|
||||
* BPF_PROG_ASSOC_STRUCT_OPS
|
||||
* Description
|
||||
* Associate a BPF program with a struct_ops map. The struct_ops
|
||||
* map is identified by *map_fd* and the BPF program is
|
||||
* identified by *prog_fd*.
|
||||
*
|
||||
* Return
|
||||
* 0 on success or -1 if an error occurred (in which case,
|
||||
* *errno* is set appropriately).
|
||||
*
|
||||
* NOTES
|
||||
* eBPF objects (maps and programs) can be shared between processes.
|
||||
*
|
||||
|
|
@ -974,6 +984,7 @@ enum bpf_cmd {
|
|||
BPF_PROG_BIND_MAP,
|
||||
BPF_TOKEN_CREATE,
|
||||
BPF_PROG_STREAM_READ_BY_FD,
|
||||
BPF_PROG_ASSOC_STRUCT_OPS,
|
||||
__MAX_BPF_CMD,
|
||||
};
|
||||
|
||||
|
|
@ -1894,6 +1905,12 @@ union bpf_attr {
|
|||
__u32 prog_fd;
|
||||
} prog_stream_read;
|
||||
|
||||
struct {
|
||||
__u32 map_fd;
|
||||
__u32 prog_fd;
|
||||
__u32 flags;
|
||||
} prog_assoc_struct_ops;
|
||||
|
||||
} __attribute__((aligned(8)));
|
||||
|
||||
/* The description below is an attempt at providing documentation to eBPF
|
||||
|
|
|
|||
|
|
@ -533,6 +533,17 @@ static void bpf_struct_ops_map_put_progs(struct bpf_struct_ops_map *st_map)
|
|||
}
|
||||
}
|
||||
|
||||
static void bpf_struct_ops_map_dissoc_progs(struct bpf_struct_ops_map *st_map)
|
||||
{
|
||||
u32 i;
|
||||
|
||||
for (i = 0; i < st_map->funcs_cnt; i++) {
|
||||
if (!st_map->links[i])
|
||||
break;
|
||||
bpf_prog_disassoc_struct_ops(st_map->links[i]->prog);
|
||||
}
|
||||
}
|
||||
|
||||
static void bpf_struct_ops_map_free_image(struct bpf_struct_ops_map *st_map)
|
||||
{
|
||||
int i;
|
||||
|
|
@ -801,6 +812,9 @@ static long bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key,
|
|||
goto reset_unlock;
|
||||
}
|
||||
|
||||
/* Poison pointer on error instead of return for backward compatibility */
|
||||
bpf_prog_assoc_struct_ops(prog, &st_map->map);
|
||||
|
||||
link = kzalloc(sizeof(*link), GFP_USER);
|
||||
if (!link) {
|
||||
bpf_prog_put(prog);
|
||||
|
|
@ -980,6 +994,8 @@ static void bpf_struct_ops_map_free(struct bpf_map *map)
|
|||
if (btf_is_module(st_map->btf))
|
||||
module_put(st_map->st_ops_desc->st_ops->owner);
|
||||
|
||||
bpf_struct_ops_map_dissoc_progs(st_map);
|
||||
|
||||
bpf_struct_ops_map_del_ksyms(st_map);
|
||||
|
||||
/* The struct_ops's function may switch to another struct_ops.
|
||||
|
|
@ -1396,6 +1412,78 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
|
|||
return err;
|
||||
}
|
||||
|
||||
int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map)
|
||||
{
|
||||
struct bpf_map *st_ops_assoc;
|
||||
|
||||
guard(mutex)(&prog->aux->st_ops_assoc_mutex);
|
||||
|
||||
st_ops_assoc = rcu_dereference_protected(prog->aux->st_ops_assoc,
|
||||
lockdep_is_held(&prog->aux->st_ops_assoc_mutex));
|
||||
if (st_ops_assoc && st_ops_assoc == map)
|
||||
return 0;
|
||||
|
||||
if (st_ops_assoc) {
|
||||
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
|
||||
return -EBUSY;
|
||||
|
||||
rcu_assign_pointer(prog->aux->st_ops_assoc, BPF_PTR_POISON);
|
||||
} else {
|
||||
/*
|
||||
* struct_ops map does not track associated non-struct_ops programs.
|
||||
* Bump the refcount to make sure st_ops_assoc is always valid.
|
||||
*/
|
||||
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
|
||||
bpf_map_inc(map);
|
||||
|
||||
rcu_assign_pointer(prog->aux->st_ops_assoc, map);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog)
|
||||
{
|
||||
struct bpf_map *st_ops_assoc;
|
||||
|
||||
guard(mutex)(&prog->aux->st_ops_assoc_mutex);
|
||||
|
||||
st_ops_assoc = rcu_dereference_protected(prog->aux->st_ops_assoc,
|
||||
lockdep_is_held(&prog->aux->st_ops_assoc_mutex));
|
||||
if (!st_ops_assoc || st_ops_assoc == BPF_PTR_POISON)
|
||||
return;
|
||||
|
||||
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
|
||||
bpf_map_put(st_ops_assoc);
|
||||
|
||||
RCU_INIT_POINTER(prog->aux->st_ops_assoc, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a reference to the struct_ops struct (i.e., kdata) associated with a
|
||||
* program. Should only be called in BPF program context (e.g., in a kfunc).
|
||||
*
|
||||
* If the returned pointer is not NULL, it must points to a valid struct_ops.
|
||||
* The struct_ops map is not guaranteed to be initialized nor attached.
|
||||
* Kernel struct_ops implementers are responsible for tracking and checking
|
||||
* the state of the struct_ops if the use case requires an initialized or
|
||||
* attached struct_ops.
|
||||
*/
|
||||
void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct bpf_struct_ops_map *st_map;
|
||||
struct bpf_map *st_ops_assoc;
|
||||
|
||||
st_ops_assoc = rcu_dereference_check(aux->st_ops_assoc, bpf_rcu_lock_held());
|
||||
if (!st_ops_assoc || st_ops_assoc == BPF_PTR_POISON)
|
||||
return NULL;
|
||||
|
||||
st_map = (struct bpf_struct_ops_map *)st_ops_assoc;
|
||||
|
||||
return &st_map->kvalue.data;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bpf_prog_get_assoc_struct_ops);
|
||||
|
||||
void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map)
|
||||
{
|
||||
struct bpf_struct_ops_map *st_map = (struct bpf_struct_ops_map *)map;
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flag
|
|||
mutex_init(&fp->aux->used_maps_mutex);
|
||||
mutex_init(&fp->aux->ext_mutex);
|
||||
mutex_init(&fp->aux->dst_mutex);
|
||||
mutex_init(&fp->aux->st_ops_assoc_mutex);
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
bpf_prog_stream_init(fp);
|
||||
|
|
@ -286,6 +287,7 @@ void __bpf_prog_free(struct bpf_prog *fp)
|
|||
if (fp->aux) {
|
||||
mutex_destroy(&fp->aux->used_maps_mutex);
|
||||
mutex_destroy(&fp->aux->dst_mutex);
|
||||
mutex_destroy(&fp->aux->st_ops_assoc_mutex);
|
||||
kfree(fp->aux->poke_tab);
|
||||
kfree(fp->aux);
|
||||
}
|
||||
|
|
@ -2896,6 +2898,7 @@ static void bpf_prog_free_deferred(struct work_struct *work)
|
|||
#endif
|
||||
bpf_free_used_maps(aux);
|
||||
bpf_free_used_btfs(aux);
|
||||
bpf_prog_disassoc_struct_ops(aux->prog);
|
||||
if (bpf_prog_is_dev_bound(aux))
|
||||
bpf_prog_dev_bound_destroy(aux->prog);
|
||||
#ifdef CONFIG_PERF_EVENTS
|
||||
|
|
|
|||
|
|
@ -6122,6 +6122,49 @@ static int prog_stream_read(union bpf_attr *attr)
|
|||
return ret;
|
||||
}
|
||||
|
||||
#define BPF_PROG_ASSOC_STRUCT_OPS_LAST_FIELD prog_assoc_struct_ops.prog_fd
|
||||
|
||||
static int prog_assoc_struct_ops(union bpf_attr *attr)
|
||||
{
|
||||
struct bpf_prog *prog;
|
||||
struct bpf_map *map;
|
||||
int ret;
|
||||
|
||||
if (CHECK_ATTR(BPF_PROG_ASSOC_STRUCT_OPS))
|
||||
return -EINVAL;
|
||||
|
||||
if (attr->prog_assoc_struct_ops.flags)
|
||||
return -EINVAL;
|
||||
|
||||
prog = bpf_prog_get(attr->prog_assoc_struct_ops.prog_fd);
|
||||
if (IS_ERR(prog))
|
||||
return PTR_ERR(prog);
|
||||
|
||||
if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) {
|
||||
ret = -EINVAL;
|
||||
goto put_prog;
|
||||
}
|
||||
|
||||
map = bpf_map_get(attr->prog_assoc_struct_ops.map_fd);
|
||||
if (IS_ERR(map)) {
|
||||
ret = PTR_ERR(map);
|
||||
goto put_prog;
|
||||
}
|
||||
|
||||
if (map->map_type != BPF_MAP_TYPE_STRUCT_OPS) {
|
||||
ret = -EINVAL;
|
||||
goto put_map;
|
||||
}
|
||||
|
||||
ret = bpf_prog_assoc_struct_ops(prog, map);
|
||||
|
||||
put_map:
|
||||
bpf_map_put(map);
|
||||
put_prog:
|
||||
bpf_prog_put(prog);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __sys_bpf(enum bpf_cmd cmd, bpfptr_t uattr, unsigned int size)
|
||||
{
|
||||
union bpf_attr attr;
|
||||
|
|
@ -6261,6 +6304,9 @@ static int __sys_bpf(enum bpf_cmd cmd, bpfptr_t uattr, unsigned int size)
|
|||
case BPF_PROG_STREAM_READ_BY_FD:
|
||||
err = prog_stream_read(&attr);
|
||||
break;
|
||||
case BPF_PROG_ASSOC_STRUCT_OPS:
|
||||
err = prog_assoc_struct_ops(&attr);
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -22493,8 +22493,7 @@ static int fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
|||
|
||||
if (!bpf_jit_supports_far_kfunc_call())
|
||||
insn->imm = BPF_CALL_IMM(desc->addr);
|
||||
if (insn->off)
|
||||
return 0;
|
||||
|
||||
if (desc->func_id == special_kfunc_list[KF_bpf_obj_new_impl] ||
|
||||
desc->func_id == special_kfunc_list[KF_bpf_percpu_obj_new_impl]) {
|
||||
struct btf_struct_meta *kptr_struct_meta = env->insn_aux_data[insn_idx].kptr_struct_meta;
|
||||
|
|
|
|||
|
|
@ -918,6 +918,16 @@ union bpf_iter_link_info {
|
|||
* Number of bytes read from the stream on success, or -1 if an
|
||||
* error occurred (in which case, *errno* is set appropriately).
|
||||
*
|
||||
* BPF_PROG_ASSOC_STRUCT_OPS
|
||||
* Description
|
||||
* Associate a BPF program with a struct_ops map. The struct_ops
|
||||
* map is identified by *map_fd* and the BPF program is
|
||||
* identified by *prog_fd*.
|
||||
*
|
||||
* Return
|
||||
* 0 on success or -1 if an error occurred (in which case,
|
||||
* *errno* is set appropriately).
|
||||
*
|
||||
* NOTES
|
||||
* eBPF objects (maps and programs) can be shared between processes.
|
||||
*
|
||||
|
|
@ -974,6 +984,7 @@ enum bpf_cmd {
|
|||
BPF_PROG_BIND_MAP,
|
||||
BPF_TOKEN_CREATE,
|
||||
BPF_PROG_STREAM_READ_BY_FD,
|
||||
BPF_PROG_ASSOC_STRUCT_OPS,
|
||||
__MAX_BPF_CMD,
|
||||
};
|
||||
|
||||
|
|
@ -1894,6 +1905,12 @@ union bpf_attr {
|
|||
__u32 prog_fd;
|
||||
} prog_stream_read;
|
||||
|
||||
struct {
|
||||
__u32 map_fd;
|
||||
__u32 prog_fd;
|
||||
__u32 flags;
|
||||
} prog_assoc_struct_ops;
|
||||
|
||||
} __attribute__((aligned(8)));
|
||||
|
||||
/* The description below is an attempt at providing documentation to eBPF
|
||||
|
|
|
|||
|
|
@ -1397,3 +1397,22 @@ int bpf_prog_stream_read(int prog_fd, __u32 stream_id, void *buf, __u32 buf_len,
|
|||
err = sys_bpf(BPF_PROG_STREAM_READ_BY_FD, &attr, attr_sz);
|
||||
return libbpf_err_errno(err);
|
||||
}
|
||||
|
||||
int bpf_prog_assoc_struct_ops(int prog_fd, int map_fd,
|
||||
struct bpf_prog_assoc_struct_ops_opts *opts)
|
||||
{
|
||||
const size_t attr_sz = offsetofend(union bpf_attr, prog_assoc_struct_ops);
|
||||
union bpf_attr attr;
|
||||
int err;
|
||||
|
||||
if (!OPTS_VALID(opts, bpf_prog_assoc_struct_ops_opts))
|
||||
return libbpf_err(-EINVAL);
|
||||
|
||||
memset(&attr, 0, attr_sz);
|
||||
attr.prog_assoc_struct_ops.map_fd = map_fd;
|
||||
attr.prog_assoc_struct_ops.prog_fd = prog_fd;
|
||||
attr.prog_assoc_struct_ops.flags = OPTS_GET(opts, flags, 0);
|
||||
|
||||
err = sys_bpf(BPF_PROG_ASSOC_STRUCT_OPS, &attr, attr_sz);
|
||||
return libbpf_err_errno(err);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -733,6 +733,27 @@ struct bpf_prog_stream_read_opts {
|
|||
LIBBPF_API int bpf_prog_stream_read(int prog_fd, __u32 stream_id, void *buf, __u32 buf_len,
|
||||
struct bpf_prog_stream_read_opts *opts);
|
||||
|
||||
struct bpf_prog_assoc_struct_ops_opts {
|
||||
size_t sz;
|
||||
__u32 flags;
|
||||
size_t :0;
|
||||
};
|
||||
#define bpf_prog_assoc_struct_ops_opts__last_field flags
|
||||
|
||||
/**
|
||||
* @brief **bpf_prog_assoc_struct_ops** associates a BPF program with a
|
||||
* struct_ops map.
|
||||
*
|
||||
* @param prog_fd FD for the BPF program
|
||||
* @param map_fd FD for the struct_ops map to be associated with the BPF program
|
||||
* @param opts optional options, can be NULL
|
||||
*
|
||||
* @return 0 on success; negative error code, otherwise (errno is also set to
|
||||
* the error code)
|
||||
*/
|
||||
LIBBPF_API int bpf_prog_assoc_struct_ops(int prog_fd, int map_fd,
|
||||
struct bpf_prog_assoc_struct_ops_opts *opts);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -14133,6 +14133,37 @@ int bpf_program__set_attach_target(struct bpf_program *prog,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int bpf_program__assoc_struct_ops(struct bpf_program *prog, struct bpf_map *map,
|
||||
struct bpf_prog_assoc_struct_ops_opts *opts)
|
||||
{
|
||||
int prog_fd, map_fd;
|
||||
|
||||
prog_fd = bpf_program__fd(prog);
|
||||
if (prog_fd < 0) {
|
||||
pr_warn("prog '%s': can't associate BPF program without FD (was it loaded?)\n",
|
||||
prog->name);
|
||||
return libbpf_err(-EINVAL);
|
||||
}
|
||||
|
||||
if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) {
|
||||
pr_warn("prog '%s': can't associate struct_ops program\n", prog->name);
|
||||
return libbpf_err(-EINVAL);
|
||||
}
|
||||
|
||||
map_fd = bpf_map__fd(map);
|
||||
if (map_fd < 0) {
|
||||
pr_warn("map '%s': can't associate BPF map without FD (was it created?)\n", map->name);
|
||||
return libbpf_err(-EINVAL);
|
||||
}
|
||||
|
||||
if (!bpf_map__is_struct_ops(map)) {
|
||||
pr_warn("map '%s': can't associate non-struct_ops map\n", map->name);
|
||||
return libbpf_err(-EINVAL);
|
||||
}
|
||||
|
||||
return bpf_prog_assoc_struct_ops(prog_fd, map_fd, opts);
|
||||
}
|
||||
|
||||
int parse_cpu_mask_str(const char *s, bool **mask, int *mask_sz)
|
||||
{
|
||||
int err = 0, n, len, start, end = -1;
|
||||
|
|
|
|||
|
|
@ -1006,6 +1006,22 @@ LIBBPF_API int
|
|||
bpf_program__set_attach_target(struct bpf_program *prog, int attach_prog_fd,
|
||||
const char *attach_func_name);
|
||||
|
||||
struct bpf_prog_assoc_struct_ops_opts; /* defined in bpf.h */
|
||||
|
||||
/**
|
||||
* @brief **bpf_program__assoc_struct_ops()** associates a BPF program with a
|
||||
* struct_ops map.
|
||||
*
|
||||
* @param prog BPF program
|
||||
* @param map struct_ops map to be associated with the BPF program
|
||||
* @param opts optional options, can be NULL
|
||||
*
|
||||
* @return 0, on success; negative error code, otherwise
|
||||
*/
|
||||
LIBBPF_API int
|
||||
bpf_program__assoc_struct_ops(struct bpf_program *prog, struct bpf_map *map,
|
||||
struct bpf_prog_assoc_struct_ops_opts *opts);
|
||||
|
||||
/**
|
||||
* @brief **bpf_object__find_map_by_name()** returns BPF map of
|
||||
* the given name, if it exists within the passed BPF object
|
||||
|
|
|
|||
|
|
@ -451,4 +451,6 @@ LIBBPF_1.7.0 {
|
|||
global:
|
||||
bpf_map__set_exclusive_program;
|
||||
bpf_map__exclusive_program;
|
||||
bpf_prog_assoc_struct_ops;
|
||||
bpf_program__assoc_struct_ops;
|
||||
} LIBBPF_1.6.0;
|
||||
|
|
|
|||
191
tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c
Normal file
191
tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <test_progs.h>
|
||||
#include "struct_ops_assoc.skel.h"
|
||||
#include "struct_ops_assoc_reuse.skel.h"
|
||||
#include "struct_ops_assoc_in_timer.skel.h"
|
||||
|
||||
static void test_st_ops_assoc(void)
|
||||
{
|
||||
struct struct_ops_assoc *skel = NULL;
|
||||
int err, pid;
|
||||
|
||||
skel = struct_ops_assoc__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc__open"))
|
||||
goto out;
|
||||
|
||||
/* cannot explicitly associate struct_ops program */
|
||||
err = bpf_program__assoc_struct_ops(skel->progs.test_1_a,
|
||||
skel->maps.st_ops_map_a, NULL);
|
||||
ASSERT_ERR(err, "bpf_program__assoc_struct_ops(test_1_a, st_ops_map_a)");
|
||||
|
||||
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_a,
|
||||
skel->maps.st_ops_map_a, NULL);
|
||||
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_a, st_ops_map_a)");
|
||||
|
||||
err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_a,
|
||||
skel->maps.st_ops_map_a, NULL);
|
||||
ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_a, st_ops_map_a)");
|
||||
|
||||
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_b,
|
||||
skel->maps.st_ops_map_b, NULL);
|
||||
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_b, st_ops_map_b)");
|
||||
|
||||
err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_b,
|
||||
skel->maps.st_ops_map_b, NULL);
|
||||
ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_b, st_ops_map_b)");
|
||||
|
||||
/* sys_enter_prog_a already associated with map_a */
|
||||
err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_a,
|
||||
skel->maps.st_ops_map_b, NULL);
|
||||
ASSERT_ERR(err, "bpf_program__assoc_struct_ops(sys_enter_prog_a, st_ops_map_b)");
|
||||
|
||||
err = struct_ops_assoc__attach(skel);
|
||||
if (!ASSERT_OK(err, "struct_ops_assoc__attach"))
|
||||
goto out;
|
||||
|
||||
/* run tracing prog that calls .test_1 and checks return */
|
||||
pid = getpid();
|
||||
skel->bss->test_pid = pid;
|
||||
sys_gettid();
|
||||
skel->bss->test_pid = 0;
|
||||
|
||||
ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a");
|
||||
ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b");
|
||||
|
||||
/* run syscall_prog that calls .test_1 and checks return */
|
||||
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_a), NULL);
|
||||
ASSERT_OK(err, "bpf_prog_test_run_opts");
|
||||
|
||||
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_b), NULL);
|
||||
ASSERT_OK(err, "bpf_prog_test_run_opts");
|
||||
|
||||
ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a");
|
||||
ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b");
|
||||
|
||||
out:
|
||||
struct_ops_assoc__destroy(skel);
|
||||
}
|
||||
|
||||
static void test_st_ops_assoc_reuse(void)
|
||||
{
|
||||
struct struct_ops_assoc_reuse *skel = NULL;
|
||||
int err;
|
||||
|
||||
skel = struct_ops_assoc_reuse__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc_reuse__open"))
|
||||
goto out;
|
||||
|
||||
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_a,
|
||||
skel->maps.st_ops_map_a, NULL);
|
||||
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_a, st_ops_map_a)");
|
||||
|
||||
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog_b,
|
||||
skel->maps.st_ops_map_b, NULL);
|
||||
ASSERT_OK(err, "bpf_program__assoc_struct_ops(syscall_prog_b, st_ops_map_b)");
|
||||
|
||||
err = struct_ops_assoc_reuse__attach(skel);
|
||||
if (!ASSERT_OK(err, "struct_ops_assoc__attach"))
|
||||
goto out;
|
||||
|
||||
/* run syscall_prog that calls .test_1 and checks return */
|
||||
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_a), NULL);
|
||||
ASSERT_OK(err, "bpf_prog_test_run_opts");
|
||||
|
||||
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_b), NULL);
|
||||
ASSERT_OK(err, "bpf_prog_test_run_opts");
|
||||
|
||||
ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a");
|
||||
ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b");
|
||||
|
||||
out:
|
||||
struct_ops_assoc_reuse__destroy(skel);
|
||||
}
|
||||
|
||||
static void test_st_ops_assoc_in_timer(void)
|
||||
{
|
||||
struct struct_ops_assoc_in_timer *skel = NULL;
|
||||
int err;
|
||||
|
||||
skel = struct_ops_assoc_in_timer__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc_in_timer__open"))
|
||||
goto out;
|
||||
|
||||
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog,
|
||||
skel->maps.st_ops_map, NULL);
|
||||
ASSERT_OK(err, "bpf_program__assoc_struct_ops");
|
||||
|
||||
err = struct_ops_assoc_in_timer__attach(skel);
|
||||
if (!ASSERT_OK(err, "struct_ops_assoc__attach"))
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Run .test_1 by calling kfunc bpf_kfunc_multi_st_ops_test_1_prog_arg() and checks
|
||||
* the return value. .test_1 will also schedule timer_cb that runs .test_1 again
|
||||
* immediately.
|
||||
*/
|
||||
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog), NULL);
|
||||
ASSERT_OK(err, "bpf_prog_test_run_opts");
|
||||
|
||||
/* Check the return of the kfunc after timer_cb runs */
|
||||
while (!READ_ONCE(skel->bss->timer_cb_run))
|
||||
sched_yield();
|
||||
ASSERT_EQ(skel->bss->timer_test_1_ret, 1234, "skel->bss->timer_test_1_ret");
|
||||
ASSERT_EQ(skel->bss->test_err, 0, "skel->bss->test_err_a");
|
||||
out:
|
||||
struct_ops_assoc_in_timer__destroy(skel);
|
||||
}
|
||||
|
||||
static void test_st_ops_assoc_in_timer_no_uref(void)
|
||||
{
|
||||
struct struct_ops_assoc_in_timer *skel = NULL;
|
||||
struct bpf_link *link;
|
||||
int err;
|
||||
|
||||
skel = struct_ops_assoc_in_timer__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "struct_ops_assoc_in_timer__open"))
|
||||
goto out;
|
||||
|
||||
err = bpf_program__assoc_struct_ops(skel->progs.syscall_prog,
|
||||
skel->maps.st_ops_map, NULL);
|
||||
ASSERT_OK(err, "bpf_program__assoc_struct_ops");
|
||||
|
||||
link = bpf_map__attach_struct_ops(skel->maps.st_ops_map);
|
||||
if (!ASSERT_OK_PTR(link, "bpf_map__attach_struct_ops"))
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Run .test_1 by calling kfunc bpf_kfunc_multi_st_ops_test_1_prog_arg() and checks
|
||||
* the return value. .test_1 will also schedule timer_cb that runs .test_1 again.
|
||||
* timer_cb will run 500ms after syscall_prog runs, when the user space no longer
|
||||
* holds a reference to st_ops_map.
|
||||
*/
|
||||
skel->bss->timer_ns = 500000000;
|
||||
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog), NULL);
|
||||
ASSERT_OK(err, "bpf_prog_test_run_opts");
|
||||
|
||||
/* Detach and close struct_ops map to cause it to be freed */
|
||||
bpf_link__destroy(link);
|
||||
close(bpf_program__fd(skel->progs.syscall_prog));
|
||||
close(bpf_map__fd(skel->maps.st_ops_map));
|
||||
|
||||
/* Check the return of the kfunc after timer_cb runs */
|
||||
while (!READ_ONCE(skel->bss->timer_cb_run))
|
||||
sched_yield();
|
||||
ASSERT_EQ(skel->bss->timer_test_1_ret, -1, "skel->bss->timer_test_1_ret");
|
||||
ASSERT_EQ(skel->bss->test_err, 0, "skel->bss->test_err_a");
|
||||
out:
|
||||
struct_ops_assoc_in_timer__destroy(skel);
|
||||
}
|
||||
|
||||
void test_struct_ops_assoc(void)
|
||||
{
|
||||
if (test__start_subtest("st_ops_assoc"))
|
||||
test_st_ops_assoc();
|
||||
if (test__start_subtest("st_ops_assoc_reuse"))
|
||||
test_st_ops_assoc_reuse();
|
||||
if (test__start_subtest("st_ops_assoc_in_timer"))
|
||||
test_st_ops_assoc_in_timer();
|
||||
if (test__start_subtest("st_ops_assoc_in_timer_no_uref"))
|
||||
test_st_ops_assoc_in_timer_no_uref();
|
||||
}
|
||||
105
tools/testing/selftests/bpf/progs/struct_ops_assoc.c
Normal file
105
tools/testing/selftests/bpf/progs/struct_ops_assoc.c
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "bpf_misc.h"
|
||||
#include "../test_kmods/bpf_testmod.h"
|
||||
#include "../test_kmods/bpf_testmod_kfunc.h"
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
int test_pid;
|
||||
|
||||
/* Programs associated with st_ops_map_a */
|
||||
|
||||
#define MAP_A_MAGIC 1234
|
||||
int test_err_a;
|
||||
|
||||
SEC("struct_ops")
|
||||
int BPF_PROG(test_1_a, struct st_ops_args *args)
|
||||
{
|
||||
return MAP_A_MAGIC;
|
||||
}
|
||||
|
||||
SEC("tp_btf/sys_enter")
|
||||
int BPF_PROG(sys_enter_prog_a, struct pt_regs *regs, long id)
|
||||
{
|
||||
struct st_ops_args args = {};
|
||||
struct task_struct *task;
|
||||
int ret;
|
||||
|
||||
task = bpf_get_current_task_btf();
|
||||
if (!test_pid || task->pid != test_pid)
|
||||
return 0;
|
||||
|
||||
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
|
||||
if (ret != MAP_A_MAGIC)
|
||||
test_err_a++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int syscall_prog_a(void *ctx)
|
||||
{
|
||||
struct st_ops_args args = {};
|
||||
int ret;
|
||||
|
||||
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
|
||||
if (ret != MAP_A_MAGIC)
|
||||
test_err_a++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC(".struct_ops.link")
|
||||
struct bpf_testmod_multi_st_ops st_ops_map_a = {
|
||||
.test_1 = (void *)test_1_a,
|
||||
};
|
||||
|
||||
/* Programs associated with st_ops_map_b */
|
||||
|
||||
#define MAP_B_MAGIC 5678
|
||||
int test_err_b;
|
||||
|
||||
SEC("struct_ops")
|
||||
int BPF_PROG(test_1_b, struct st_ops_args *args)
|
||||
{
|
||||
return MAP_B_MAGIC;
|
||||
}
|
||||
|
||||
SEC("tp_btf/sys_enter")
|
||||
int BPF_PROG(sys_enter_prog_b, struct pt_regs *regs, long id)
|
||||
{
|
||||
struct st_ops_args args = {};
|
||||
struct task_struct *task;
|
||||
int ret;
|
||||
|
||||
task = bpf_get_current_task_btf();
|
||||
if (!test_pid || task->pid != test_pid)
|
||||
return 0;
|
||||
|
||||
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
|
||||
if (ret != MAP_B_MAGIC)
|
||||
test_err_b++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int syscall_prog_b(void *ctx)
|
||||
{
|
||||
struct st_ops_args args = {};
|
||||
int ret;
|
||||
|
||||
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
|
||||
if (ret != MAP_B_MAGIC)
|
||||
test_err_b++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC(".struct_ops.link")
|
||||
struct bpf_testmod_multi_st_ops st_ops_map_b = {
|
||||
.test_1 = (void *)test_1_b,
|
||||
};
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "bpf_misc.h"
|
||||
#include "../test_kmods/bpf_testmod.h"
|
||||
#include "../test_kmods/bpf_testmod_kfunc.h"
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
struct elem {
|
||||
struct bpf_timer timer;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, int);
|
||||
__type(value, struct elem);
|
||||
} array_map SEC(".maps");
|
||||
|
||||
#define MAP_MAGIC 1234
|
||||
int recur;
|
||||
int test_err;
|
||||
int timer_ns;
|
||||
int timer_test_1_ret;
|
||||
int timer_cb_run;
|
||||
|
||||
__noinline static int timer_cb(void *map, int *key, struct bpf_timer *timer)
|
||||
{
|
||||
struct st_ops_args args = {};
|
||||
|
||||
recur++;
|
||||
timer_test_1_ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
|
||||
recur--;
|
||||
|
||||
timer_cb_run++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("struct_ops")
|
||||
int BPF_PROG(test_1, struct st_ops_args *args)
|
||||
{
|
||||
struct bpf_timer *timer;
|
||||
int key = 0;
|
||||
|
||||
if (!recur) {
|
||||
timer = bpf_map_lookup_elem(&array_map, &key);
|
||||
if (!timer)
|
||||
return 0;
|
||||
|
||||
bpf_timer_init(timer, &array_map, 1);
|
||||
bpf_timer_set_callback(timer, timer_cb);
|
||||
bpf_timer_start(timer, timer_ns, 0);
|
||||
}
|
||||
|
||||
return MAP_MAGIC;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int syscall_prog(void *ctx)
|
||||
{
|
||||
struct st_ops_args args = {};
|
||||
int ret;
|
||||
|
||||
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
|
||||
if (ret != MAP_MAGIC)
|
||||
test_err++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC(".struct_ops.link")
|
||||
struct bpf_testmod_multi_st_ops st_ops_map = {
|
||||
.test_1 = (void *)test_1,
|
||||
};
|
||||
75
tools/testing/selftests/bpf/progs/struct_ops_assoc_reuse.c
Normal file
75
tools/testing/selftests/bpf/progs/struct_ops_assoc_reuse.c
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "bpf_misc.h"
|
||||
#include "../test_kmods/bpf_testmod.h"
|
||||
#include "../test_kmods/bpf_testmod_kfunc.h"
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
#define MAP_A_MAGIC 1234
|
||||
int test_err_a;
|
||||
int recur;
|
||||
|
||||
/*
|
||||
* test_1_a is reused. The kfunc should not be able to get the associated
|
||||
* struct_ops and call test_1 recursively as it is ambiguous.
|
||||
*/
|
||||
SEC("struct_ops")
|
||||
int BPF_PROG(test_1_a, struct st_ops_args *args)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!recur) {
|
||||
recur++;
|
||||
ret = bpf_kfunc_multi_st_ops_test_1_impl(args, NULL);
|
||||
if (ret != -1)
|
||||
test_err_a++;
|
||||
recur--;
|
||||
}
|
||||
|
||||
return MAP_A_MAGIC;
|
||||
}
|
||||
|
||||
/* Programs associated with st_ops_map_a */
|
||||
|
||||
SEC("syscall")
|
||||
int syscall_prog_a(void *ctx)
|
||||
{
|
||||
struct st_ops_args args = {};
|
||||
int ret;
|
||||
|
||||
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
|
||||
if (ret != MAP_A_MAGIC)
|
||||
test_err_a++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC(".struct_ops.link")
|
||||
struct bpf_testmod_multi_st_ops st_ops_map_a = {
|
||||
.test_1 = (void *)test_1_a,
|
||||
};
|
||||
|
||||
/* Programs associated with st_ops_map_b */
|
||||
|
||||
int test_err_b;
|
||||
|
||||
SEC("syscall")
|
||||
int syscall_prog_b(void *ctx)
|
||||
{
|
||||
struct st_ops_args args = {};
|
||||
int ret;
|
||||
|
||||
ret = bpf_kfunc_multi_st_ops_test_1_impl(&args, NULL);
|
||||
if (ret != MAP_A_MAGIC)
|
||||
test_err_b++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC(".struct_ops.link")
|
||||
struct bpf_testmod_multi_st_ops st_ops_map_b = {
|
||||
.test_1 = (void *)test_1_a,
|
||||
};
|
||||
|
|
@ -1134,6 +1134,7 @@ __bpf_kfunc int bpf_kfunc_st_ops_inc10(struct st_ops_args *args)
|
|||
}
|
||||
|
||||
__bpf_kfunc int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id);
|
||||
__bpf_kfunc int bpf_kfunc_multi_st_ops_test_1_impl(struct st_ops_args *args, void *aux_prog);
|
||||
|
||||
BTF_KFUNCS_START(bpf_testmod_check_kfunc_ids)
|
||||
BTF_ID_FLAGS(func, bpf_testmod_test_mod_kfunc)
|
||||
|
|
@ -1176,6 +1177,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_st_ops_test_epilogue, KF_TRUSTED_ARGS | KF_SLEEPABL
|
|||
BTF_ID_FLAGS(func, bpf_kfunc_st_ops_test_pro_epilogue, KF_TRUSTED_ARGS | KF_SLEEPABLE)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_st_ops_inc10, KF_TRUSTED_ARGS)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1, KF_TRUSTED_ARGS)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1_impl, KF_TRUSTED_ARGS)
|
||||
BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids)
|
||||
|
||||
static int bpf_testmod_ops_init(struct btf *btf)
|
||||
|
|
@ -1637,6 +1639,7 @@ static struct bpf_testmod_multi_st_ops *multi_st_ops_find_nolock(u32 id)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* Call test_1() of the struct_ops map identified by the id */
|
||||
int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id)
|
||||
{
|
||||
struct bpf_testmod_multi_st_ops *st_ops;
|
||||
|
|
@ -1652,6 +1655,20 @@ int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Call test_1() of the associated struct_ops map */
|
||||
int bpf_kfunc_multi_st_ops_test_1_impl(struct st_ops_args *args, void *aux__prog)
|
||||
{
|
||||
struct bpf_prog_aux *prog_aux = (struct bpf_prog_aux *)aux__prog;
|
||||
struct bpf_testmod_multi_st_ops *st_ops;
|
||||
int ret = -1;
|
||||
|
||||
st_ops = (struct bpf_testmod_multi_st_ops *)bpf_prog_get_assoc_struct_ops(prog_aux);
|
||||
if (st_ops)
|
||||
ret = st_ops->test_1(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int multi_st_ops_reg(void *kdata, struct bpf_link *link)
|
||||
{
|
||||
struct bpf_testmod_multi_st_ops *st_ops =
|
||||
|
|
|
|||
|
|
@ -162,5 +162,6 @@ struct task_struct *bpf_kfunc_ret_rcu_test(void) __ksym;
|
|||
int *bpf_kfunc_ret_rcu_test_nostruct(int rdonly_buf_size) __ksym;
|
||||
|
||||
int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id) __ksym;
|
||||
int bpf_kfunc_multi_st_ops_test_1_impl(struct st_ops_args *args, void *aux__prog) __ksym;
|
||||
|
||||
#endif /* _BPF_TESTMOD_KFUNC_H */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user