* [PATCH bpf-next v2 1/6] bpf: pass bpf_struct_ops_link to callbacks in bpf_struct_ops.
2024-05-07 5:55 [PATCH bpf-next v2 0/6] Notify user space when a struct_ops object is detached/unregistered Kui-Feng Lee
@ 2024-05-07 5:55 ` Kui-Feng Lee
2024-05-07 5:55 ` [PATCH bpf-next v2 2/6] bpf: enable detaching links of struct_ops objects Kui-Feng Lee
` (4 subsequent siblings)
5 siblings, 0 replies; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-07 5:55 UTC (permalink / raw)
To: bpf, ast, martin.lau, song, kernel-team, andrii
Cc: sinquersw, kuifeng, Kui-Feng Lee
Pass an additional pointer of bpf_struct_ops_link to callback function reg,
unreg, and update provided by subsystems defined in bpf_struct_ops. A
bpf_struct_ops_map can be registered for multiple links. Passing a pointer
of bpf_struct_ops_link helps subsystems to distinguish them.
This pointer will be used in the later patches to let the subsystem
initiate a detachment on a link that was registered to it previously.
Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
include/linux/bpf.h | 6 +++---
kernel/bpf/bpf_struct_ops.c | 10 +++++-----
net/bpf/bpf_dummy_struct_ops.c | 4 ++--
net/ipv4/bpf_tcp_ca.c | 6 +++---
.../selftests/bpf/bpf_test_no_cfi/bpf_test_no_cfi.c | 4 ++--
tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c | 6 +++---
6 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 90094400cc63..b600767ebe02 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1730,9 +1730,9 @@ struct bpf_struct_ops {
int (*init_member)(const struct btf_type *t,
const struct btf_member *member,
void *kdata, const void *udata);
- int (*reg)(void *kdata);
- void (*unreg)(void *kdata);
- int (*update)(void *kdata, void *old_kdata);
+ int (*reg)(void *kdata, struct bpf_link *link);
+ void (*unreg)(void *kdata, struct bpf_link *link);
+ int (*update)(void *kdata, void *old_kdata, struct bpf_link *link);
int (*validate)(void *kdata);
void *cfi_stubs;
struct module *owner;
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index 86c7884abaf8..390f8c155135 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -757,7 +757,7 @@ static long bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key,
goto unlock;
}
- err = st_ops->reg(kdata);
+ err = st_ops->reg(kdata, NULL);
if (likely(!err)) {
/* This refcnt increment on the map here after
* 'st_ops->reg()' is secure since the state of the
@@ -805,7 +805,7 @@ static long bpf_struct_ops_map_delete_elem(struct bpf_map *map, void *key)
BPF_STRUCT_OPS_STATE_TOBEFREE);
switch (prev_state) {
case BPF_STRUCT_OPS_STATE_INUSE:
- st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data);
+ st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, NULL);
bpf_map_put(map);
return 0;
case BPF_STRUCT_OPS_STATE_TOBEFREE:
@@ -1060,7 +1060,7 @@ static void bpf_struct_ops_map_link_dealloc(struct bpf_link *link)
/* st_link->map can be NULL if
* bpf_struct_ops_link_create() fails to register.
*/
- st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data);
+ st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, st_link);
bpf_map_put(&st_map->map);
}
kfree(st_link);
@@ -1125,7 +1125,7 @@ static int bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
goto err_out;
}
- err = st_map->st_ops_desc->st_ops->update(st_map->kvalue.data, old_st_map->kvalue.data);
+ err = st_map->st_ops_desc->st_ops->update(st_map->kvalue.data, old_st_map->kvalue.data, link);
if (err)
goto err_out;
@@ -1176,7 +1176,7 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
if (err)
goto err_out;
- err = st_map->st_ops_desc->st_ops->reg(st_map->kvalue.data);
+ err = st_map->st_ops_desc->st_ops->reg(st_map->kvalue.data, &link->link);
if (err) {
bpf_link_cleanup(&link_primer);
link = NULL;
diff --git a/net/bpf/bpf_dummy_struct_ops.c b/net/bpf/bpf_dummy_struct_ops.c
index 891cdf61c65a..3ea52b05adfb 100644
--- a/net/bpf/bpf_dummy_struct_ops.c
+++ b/net/bpf/bpf_dummy_struct_ops.c
@@ -272,12 +272,12 @@ static int bpf_dummy_init_member(const struct btf_type *t,
return -EOPNOTSUPP;
}
-static int bpf_dummy_reg(void *kdata)
+static int bpf_dummy_reg(void *kdata, struct bpf_link *link)
{
return -EOPNOTSUPP;
}
-static void bpf_dummy_unreg(void *kdata)
+static void bpf_dummy_unreg(void *kdata, struct bpf_link *link)
{
}
diff --git a/net/ipv4/bpf_tcp_ca.c b/net/ipv4/bpf_tcp_ca.c
index 7f518ea5f4ac..dd97f7ebcd29 100644
--- a/net/ipv4/bpf_tcp_ca.c
+++ b/net/ipv4/bpf_tcp_ca.c
@@ -257,17 +257,17 @@ static int bpf_tcp_ca_check_member(const struct btf_type *t,
return 0;
}
-static int bpf_tcp_ca_reg(void *kdata)
+static int bpf_tcp_ca_reg(void *kdata, struct bpf_link *link)
{
return tcp_register_congestion_control(kdata);
}
-static void bpf_tcp_ca_unreg(void *kdata)
+static void bpf_tcp_ca_unreg(void *kdata, struct bpf_link *link)
{
tcp_unregister_congestion_control(kdata);
}
-static int bpf_tcp_ca_update(void *kdata, void *old_kdata)
+static int bpf_tcp_ca_update(void *kdata, void *old_kdata, struct bpf_link *link)
{
return tcp_update_congestion_control(kdata, old_kdata);
}
diff --git a/tools/testing/selftests/bpf/bpf_test_no_cfi/bpf_test_no_cfi.c b/tools/testing/selftests/bpf/bpf_test_no_cfi/bpf_test_no_cfi.c
index b1dd889d5d7d..948eb3962732 100644
--- a/tools/testing/selftests/bpf/bpf_test_no_cfi/bpf_test_no_cfi.c
+++ b/tools/testing/selftests/bpf/bpf_test_no_cfi/bpf_test_no_cfi.c
@@ -22,12 +22,12 @@ static int dummy_init_member(const struct btf_type *t,
return 0;
}
-static int dummy_reg(void *kdata)
+static int dummy_reg(void *kdata, struct bpf_link *link)
{
return 0;
}
-static void dummy_unreg(void *kdata)
+static void dummy_unreg(void *kdata, struct bpf_link *link)
{
}
diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
index eb2b78552ca2..e24a18bfee14 100644
--- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
@@ -565,7 +565,7 @@ static const struct bpf_verifier_ops bpf_testmod_verifier_ops = {
.is_valid_access = bpf_testmod_ops_is_valid_access,
};
-static int bpf_dummy_reg(void *kdata)
+static int bpf_dummy_reg(void *kdata, struct bpf_link *link)
{
struct bpf_testmod_ops *ops = kdata;
@@ -580,7 +580,7 @@ static int bpf_dummy_reg(void *kdata)
return 0;
}
-static void bpf_dummy_unreg(void *kdata)
+static void bpf_dummy_unreg(void *kdata, struct bpf_link *link)
{
}
@@ -616,7 +616,7 @@ struct bpf_struct_ops bpf_bpf_testmod_ops = {
.owner = THIS_MODULE,
};
-static int bpf_dummy_reg2(void *kdata)
+static int bpf_dummy_reg2(void *kdata, struct bpf_link *link)
{
struct bpf_testmod_ops2 *ops = kdata;
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH bpf-next v2 2/6] bpf: enable detaching links of struct_ops objects.
2024-05-07 5:55 [PATCH bpf-next v2 0/6] Notify user space when a struct_ops object is detached/unregistered Kui-Feng Lee
2024-05-07 5:55 ` [PATCH bpf-next v2 1/6] bpf: pass bpf_struct_ops_link to callbacks in bpf_struct_ops Kui-Feng Lee
@ 2024-05-07 5:55 ` Kui-Feng Lee
2024-05-08 23:22 ` Martin KaFai Lau
2024-05-07 5:55 ` [PATCH bpf-next v2 3/6] bpf: support epoll from bpf struct_ops links Kui-Feng Lee
` (3 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-07 5:55 UTC (permalink / raw)
To: bpf, ast, martin.lau, song, kernel-team, andrii
Cc: sinquersw, kuifeng, Kui-Feng Lee
Implement the detach callback in bpf_link_ops for struct_ops. The
subsystems that struct_ops objects are registered to can use this callback
to detach the links being passed to them.
Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
kernel/bpf/bpf_struct_ops.c | 50 ++++++++++++++++++++++++++++++++-----
1 file changed, 44 insertions(+), 6 deletions(-)
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index 390f8c155135..bd2602982e4d 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -1057,9 +1057,6 @@ static void bpf_struct_ops_map_link_dealloc(struct bpf_link *link)
st_map = (struct bpf_struct_ops_map *)
rcu_dereference_protected(st_link->map, true);
if (st_map) {
- /* st_link->map can be NULL if
- * bpf_struct_ops_link_create() fails to register.
- */
st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, st_link);
bpf_map_put(&st_map->map);
}
@@ -1075,7 +1072,8 @@ static void bpf_struct_ops_map_link_show_fdinfo(const struct bpf_link *link,
st_link = container_of(link, struct bpf_struct_ops_link, link);
rcu_read_lock();
map = rcu_dereference(st_link->map);
- seq_printf(seq, "map_id:\t%d\n", map->id);
+ if (map)
+ seq_printf(seq, "map_id:\t%d\n", map->id);
rcu_read_unlock();
}
@@ -1088,7 +1086,8 @@ static int bpf_struct_ops_map_link_fill_link_info(const struct bpf_link *link,
st_link = container_of(link, struct bpf_struct_ops_link, link);
rcu_read_lock();
map = rcu_dereference(st_link->map);
- info->struct_ops.map_id = map->id;
+ if (map)
+ info->struct_ops.map_id = map->id;
rcu_read_unlock();
return 0;
}
@@ -1113,6 +1112,10 @@ static int bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
mutex_lock(&update_mutex);
old_map = rcu_dereference_protected(st_link->map, lockdep_is_held(&update_mutex));
+ if (!old_map) {
+ err = -EINVAL;
+ goto err_out;
+ }
if (expected_old_map && old_map != expected_old_map) {
err = -EPERM;
goto err_out;
@@ -1139,8 +1142,37 @@ static int bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
return err;
}
+static int bpf_struct_ops_map_link_detach(struct bpf_link *link)
+{
+ struct bpf_struct_ops_link *st_link = container_of(link, struct bpf_struct_ops_link, link);
+ struct bpf_struct_ops_map *st_map;
+ struct bpf_map *map;
+
+ mutex_lock(&update_mutex);
+
+ map = rcu_dereference_protected(st_link->map, true);
+ if (!map) {
+ mutex_unlock(&update_mutex);
+ return -EINVAL;
+ }
+ st_map = container_of(map, struct bpf_struct_ops_map, map);
+
+ st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, link);
+
+ rcu_assign_pointer(st_link->map, NULL);
+ /* Pair with bpf_map_get() in bpf_struct_ops_link_create() or
+ * bpf_map_inc() in bpf_struct_ops_map_link_update().
+ */
+ bpf_map_put(&st_map->map);
+
+ mutex_unlock(&update_mutex);
+
+ return 0;
+}
+
static const struct bpf_link_ops bpf_struct_ops_map_lops = {
.dealloc = bpf_struct_ops_map_link_dealloc,
+ .detach = bpf_struct_ops_map_link_detach,
.show_fdinfo = bpf_struct_ops_map_link_show_fdinfo,
.fill_link_info = bpf_struct_ops_map_link_fill_link_info,
.update_map = bpf_struct_ops_map_link_update,
@@ -1176,13 +1208,19 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
if (err)
goto err_out;
+ /* Init link->map before calling reg() in case being detached
+ * immediately.
+ */
+ RCU_INIT_POINTER(link->map, map);
+
err = st_map->st_ops_desc->st_ops->reg(st_map->kvalue.data, &link->link);
if (err) {
+ rcu_assign_pointer(link->map, NULL);
bpf_link_cleanup(&link_primer);
+ /* The link has been free by bpf_link_cleanup() */
link = NULL;
goto err_out;
}
- RCU_INIT_POINTER(link->map, map);
return bpf_link_settle(&link_primer);
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v2 2/6] bpf: enable detaching links of struct_ops objects.
2024-05-07 5:55 ` [PATCH bpf-next v2 2/6] bpf: enable detaching links of struct_ops objects Kui-Feng Lee
@ 2024-05-08 23:22 ` Martin KaFai Lau
2024-05-09 0:14 ` Kui-Feng Lee
0 siblings, 1 reply; 18+ messages in thread
From: Martin KaFai Lau @ 2024-05-08 23:22 UTC (permalink / raw)
To: Kui-Feng Lee; +Cc: sinquersw, kuifeng, bpf, ast, song, kernel-team, andrii
On 5/6/24 10:55 PM, Kui-Feng Lee wrote:
> Implement the detach callback in bpf_link_ops for struct_ops. The
> subsystems that struct_ops objects are registered to can use this callback
> to detach the links being passed to them.
The user space can also use the detach. The subsystem is merely reusing the
similar detach callback if it stores the link during reg().
>
> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
> ---
> kernel/bpf/bpf_struct_ops.c | 50 ++++++++++++++++++++++++++++++++-----
> 1 file changed, 44 insertions(+), 6 deletions(-)
>
> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
> index 390f8c155135..bd2602982e4d 100644
> --- a/kernel/bpf/bpf_struct_ops.c
> +++ b/kernel/bpf/bpf_struct_ops.c
> @@ -1057,9 +1057,6 @@ static void bpf_struct_ops_map_link_dealloc(struct bpf_link *link)
> st_map = (struct bpf_struct_ops_map *)
> rcu_dereference_protected(st_link->map, true);
> if (st_map) {
> - /* st_link->map can be NULL if
> - * bpf_struct_ops_link_create() fails to register.
> - */
> st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, st_link);
> bpf_map_put(&st_map->map);
> }
> @@ -1075,7 +1072,8 @@ static void bpf_struct_ops_map_link_show_fdinfo(const struct bpf_link *link,
> st_link = container_of(link, struct bpf_struct_ops_link, link);
> rcu_read_lock();
> map = rcu_dereference(st_link->map);
> - seq_printf(seq, "map_id:\t%d\n", map->id);
> + if (map)
> + seq_printf(seq, "map_id:\t%d\n", map->id);
> rcu_read_unlock();
> }
>
> @@ -1088,7 +1086,8 @@ static int bpf_struct_ops_map_link_fill_link_info(const struct bpf_link *link,
> st_link = container_of(link, struct bpf_struct_ops_link, link);
> rcu_read_lock();
> map = rcu_dereference(st_link->map);
> - info->struct_ops.map_id = map->id;
> + if (map)
> + info->struct_ops.map_id = map->id;
> rcu_read_unlock();
> return 0;
> }
> @@ -1113,6 +1112,10 @@ static int bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
> mutex_lock(&update_mutex);
>
> old_map = rcu_dereference_protected(st_link->map, lockdep_is_held(&update_mutex));
> + if (!old_map) {
> + err = -EINVAL;
> + goto err_out;
> + }
> if (expected_old_map && old_map != expected_old_map) {
> err = -EPERM;
> goto err_out;
> @@ -1139,8 +1142,37 @@ static int bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
> return err;
> }
>
> +static int bpf_struct_ops_map_link_detach(struct bpf_link *link)
> +{
> + struct bpf_struct_ops_link *st_link = container_of(link, struct bpf_struct_ops_link, link);
> + struct bpf_struct_ops_map *st_map;
> + struct bpf_map *map;
> +
> + mutex_lock(&update_mutex);
> +
> + map = rcu_dereference_protected(st_link->map, true);
nit. s/true/lockdep_is_held(&update_mutex)/
> + if (!map) {
> + mutex_unlock(&update_mutex);
> + return -EINVAL;
> + }
> + st_map = container_of(map, struct bpf_struct_ops_map, map);
> +
> + st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, link);
> +
> + rcu_assign_pointer(st_link->map, NULL);
> + /* Pair with bpf_map_get() in bpf_struct_ops_link_create() or
> + * bpf_map_inc() in bpf_struct_ops_map_link_update().
> + */
> + bpf_map_put(&st_map->map);
> +
> + mutex_unlock(&update_mutex);
> +
> + return 0;
> +}
> +
> static const struct bpf_link_ops bpf_struct_ops_map_lops = {
> .dealloc = bpf_struct_ops_map_link_dealloc,
> + .detach = bpf_struct_ops_map_link_detach,
> .show_fdinfo = bpf_struct_ops_map_link_show_fdinfo,
> .fill_link_info = bpf_struct_ops_map_link_fill_link_info,
> .update_map = bpf_struct_ops_map_link_update,
> @@ -1176,13 +1208,19 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
> if (err)
> goto err_out;
>
> + /* Init link->map before calling reg() in case being detached
> + * immediately.
> + */
> + RCU_INIT_POINTER(link->map, map);
> +
> err = st_map->st_ops_desc->st_ops->reg(st_map->kvalue.data, &link->link);
> if (err) {
> + rcu_assign_pointer(link->map, NULL);
nit. RCU_INIT_POINTER(link->map, NULL) is fine.
There is a merge conflict with patch 4 also.
pw-bot: cr
> bpf_link_cleanup(&link_primer);
> + /* The link has been free by bpf_link_cleanup() */
> link = NULL;
> goto err_out;
> }
> - RCU_INIT_POINTER(link->map, map);
>
> return bpf_link_settle(&link_primer);
>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v2 2/6] bpf: enable detaching links of struct_ops objects.
2024-05-08 23:22 ` Martin KaFai Lau
@ 2024-05-09 0:14 ` Kui-Feng Lee
2024-05-09 0:36 ` Martin KaFai Lau
2024-05-09 0:46 ` Kui-Feng Lee
0 siblings, 2 replies; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-09 0:14 UTC (permalink / raw)
To: Martin KaFai Lau, Kui-Feng Lee
Cc: kuifeng, bpf, ast, song, kernel-team, andrii
On 5/8/24 16:22, Martin KaFai Lau wrote:
> On 5/6/24 10:55 PM, Kui-Feng Lee wrote:
>> Implement the detach callback in bpf_link_ops for struct_ops. The
>> subsystems that struct_ops objects are registered to can use this
>> callback
>> to detach the links being passed to them.
>
> The user space can also use the detach. The subsystem is merely reusing
> the similar detach callback if it stores the link during reg().
Sure!
>
>>
>> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
>> ---
>> kernel/bpf/bpf_struct_ops.c | 50 ++++++++++++++++++++++++++++++++-----
>> 1 file changed, 44 insertions(+), 6 deletions(-)
>>
>> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
>> index 390f8c155135..bd2602982e4d 100644
>> --- a/kernel/bpf/bpf_struct_ops.c
>> +++ b/kernel/bpf/bpf_struct_ops.c
>> @@ -1057,9 +1057,6 @@ static void
>> bpf_struct_ops_map_link_dealloc(struct bpf_link *link)
>> st_map = (struct bpf_struct_ops_map *)
>> rcu_dereference_protected(st_link->map, true);
>> if (st_map) {
>> - /* st_link->map can be NULL if
>> - * bpf_struct_ops_link_create() fails to register.
>> - */
>> st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data,
>> st_link);
>> bpf_map_put(&st_map->map);
>> }
>> @@ -1075,7 +1072,8 @@ static void
>> bpf_struct_ops_map_link_show_fdinfo(const struct bpf_link *link,
>> st_link = container_of(link, struct bpf_struct_ops_link, link);
>> rcu_read_lock();
>> map = rcu_dereference(st_link->map);
>> - seq_printf(seq, "map_id:\t%d\n", map->id);
>> + if (map)
>> + seq_printf(seq, "map_id:\t%d\n", map->id);
>> rcu_read_unlock();
>> }
>> @@ -1088,7 +1086,8 @@ static int
>> bpf_struct_ops_map_link_fill_link_info(const struct bpf_link *link,
>> st_link = container_of(link, struct bpf_struct_ops_link, link);
>> rcu_read_lock();
>> map = rcu_dereference(st_link->map);
>> - info->struct_ops.map_id = map->id;
>> + if (map)
>> + info->struct_ops.map_id = map->id;
>> rcu_read_unlock();
>> return 0;
>> }
>> @@ -1113,6 +1112,10 @@ static int
>> bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
>> mutex_lock(&update_mutex);
>> old_map = rcu_dereference_protected(st_link->map,
>> lockdep_is_held(&update_mutex));
>> + if (!old_map) {
>> + err = -EINVAL;
>> + goto err_out;
>> + }
>> if (expected_old_map && old_map != expected_old_map) {
>> err = -EPERM;
>> goto err_out;
>> @@ -1139,8 +1142,37 @@ static int
>> bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
>> return err;
>> }
>> +static int bpf_struct_ops_map_link_detach(struct bpf_link *link)
>> +{
>> + struct bpf_struct_ops_link *st_link = container_of(link, struct
>> bpf_struct_ops_link, link);
>> + struct bpf_struct_ops_map *st_map;
>> + struct bpf_map *map;
>> +
>> + mutex_lock(&update_mutex);
>> +
>> + map = rcu_dereference_protected(st_link->map, true);
>
> nit. s/true/lockdep_is_held(&update_mutex)/
I thought it is protected by the refcount holding by the caller.
WDYT?
>
>> + if (!map) {
>> + mutex_unlock(&update_mutex);
>> + return -EINVAL;
>> + }
>> + st_map = container_of(map, struct bpf_struct_ops_map, map);
>> +
>> + st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, link);
>> +
>> + rcu_assign_pointer(st_link->map, NULL);
>> + /* Pair with bpf_map_get() in bpf_struct_ops_link_create() or
>> + * bpf_map_inc() in bpf_struct_ops_map_link_update().
>> + */
>> + bpf_map_put(&st_map->map);
>> +
>> + mutex_unlock(&update_mutex);
>> +
>> + return 0;
>> +}
>> +
>> static const struct bpf_link_ops bpf_struct_ops_map_lops = {
>> .dealloc = bpf_struct_ops_map_link_dealloc,
>> + .detach = bpf_struct_ops_map_link_detach,
>> .show_fdinfo = bpf_struct_ops_map_link_show_fdinfo,
>> .fill_link_info = bpf_struct_ops_map_link_fill_link_info,
>> .update_map = bpf_struct_ops_map_link_update,
>> @@ -1176,13 +1208,19 @@ int bpf_struct_ops_link_create(union bpf_attr
>> *attr)
>> if (err)
>> goto err_out;
>> + /* Init link->map before calling reg() in case being detached
>> + * immediately.
>> + */
>> + RCU_INIT_POINTER(link->map, map);
>> +
>> err = st_map->st_ops_desc->st_ops->reg(st_map->kvalue.data,
>> &link->link);
>> if (err) {
>> + rcu_assign_pointer(link->map, NULL);
>
> nit. RCU_INIT_POINTER(link->map, NULL) is fine.
Got it!
>
> There is a merge conflict with patch 4 also.
What do you mean here? Do you mean the patch 4 can not be applied on top
of the patch 2?
>
> pw-bot: cr
>
>> bpf_link_cleanup(&link_primer);
>> + /* The link has been free by bpf_link_cleanup() */
>> link = NULL;
>> goto err_out;
>> }
>> - RCU_INIT_POINTER(link->map, map);
>> return bpf_link_settle(&link_primer);
>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v2 2/6] bpf: enable detaching links of struct_ops objects.
2024-05-09 0:14 ` Kui-Feng Lee
@ 2024-05-09 0:36 ` Martin KaFai Lau
2024-05-09 16:59 ` Kui-Feng Lee
2024-05-09 0:46 ` Kui-Feng Lee
1 sibling, 1 reply; 18+ messages in thread
From: Martin KaFai Lau @ 2024-05-09 0:36 UTC (permalink / raw)
To: Kui-Feng Lee, Kui-Feng Lee; +Cc: kuifeng, bpf, ast, song, kernel-team, andrii
On 5/8/24 5:14 PM, Kui-Feng Lee wrote:
>
>
> On 5/8/24 16:22, Martin KaFai Lau wrote:
>> On 5/6/24 10:55 PM, Kui-Feng Lee wrote:
>>> Implement the detach callback in bpf_link_ops for struct_ops. The
>>> subsystems that struct_ops objects are registered to can use this callback
>>> to detach the links being passed to them.
>>
>> The user space can also use the detach. The subsystem is merely reusing the
>> similar detach callback if it stores the link during reg().
>
> Sure!
>
>>
>>>
>>> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
>>> ---
>>> kernel/bpf/bpf_struct_ops.c | 50 ++++++++++++++++++++++++++++++++-----
>>> 1 file changed, 44 insertions(+), 6 deletions(-)
>>>
>>> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
>>> index 390f8c155135..bd2602982e4d 100644
>>> --- a/kernel/bpf/bpf_struct_ops.c
>>> +++ b/kernel/bpf/bpf_struct_ops.c
>>> @@ -1057,9 +1057,6 @@ static void bpf_struct_ops_map_link_dealloc(struct
>>> bpf_link *link)
>>> st_map = (struct bpf_struct_ops_map *)
>>> rcu_dereference_protected(st_link->map, true);
>>> if (st_map) {
>>> - /* st_link->map can be NULL if
>>> - * bpf_struct_ops_link_create() fails to register.
>>> - */
>>> st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, st_link);
>>> bpf_map_put(&st_map->map);
>>> }
>>> @@ -1075,7 +1072,8 @@ static void bpf_struct_ops_map_link_show_fdinfo(const
>>> struct bpf_link *link,
>>> st_link = container_of(link, struct bpf_struct_ops_link, link);
>>> rcu_read_lock();
>>> map = rcu_dereference(st_link->map);
>>> - seq_printf(seq, "map_id:\t%d\n", map->id);
>>> + if (map)
>>> + seq_printf(seq, "map_id:\t%d\n", map->id);
>>> rcu_read_unlock();
>>> }
>>> @@ -1088,7 +1086,8 @@ static int bpf_struct_ops_map_link_fill_link_info(const
>>> struct bpf_link *link,
>>> st_link = container_of(link, struct bpf_struct_ops_link, link);
>>> rcu_read_lock();
>>> map = rcu_dereference(st_link->map);
>>> - info->struct_ops.map_id = map->id;
>>> + if (map)
>>> + info->struct_ops.map_id = map->id;
>>> rcu_read_unlock();
>>> return 0;
>>> }
>>> @@ -1113,6 +1112,10 @@ static int bpf_struct_ops_map_link_update(struct
>>> bpf_link *link, struct bpf_map
>>> mutex_lock(&update_mutex);
>>> old_map = rcu_dereference_protected(st_link->map,
>>> lockdep_is_held(&update_mutex));
>>> + if (!old_map) {
>>> + err = -EINVAL;
>>> + goto err_out;
>>> + }
>>> if (expected_old_map && old_map != expected_old_map) {
>>> err = -EPERM;
>>> goto err_out;
>>> @@ -1139,8 +1142,37 @@ static int bpf_struct_ops_map_link_update(struct
>>> bpf_link *link, struct bpf_map
>>> return err;
>>> }
>>> +static int bpf_struct_ops_map_link_detach(struct bpf_link *link)
>>> +{
>>> + struct bpf_struct_ops_link *st_link = container_of(link, struct
>>> bpf_struct_ops_link, link);
>>> + struct bpf_struct_ops_map *st_map;
>>> + struct bpf_map *map;
>>> +
>>> + mutex_lock(&update_mutex);
>>> +
>>> + map = rcu_dereference_protected(st_link->map, true);
>>
>> nit. s/true/lockdep_is_held(&update_mutex)/
>
>
> I thought it is protected by the refcount holding by the caller.
> WDYT?
st_link->map is the one with __rcu tag and "!map" is tested next. I don't see
how these imply the map pointer is protected by refcount. Can you explain?
>
>
>>
>>> + if (!map) {
>>> + mutex_unlock(&update_mutex);
>>> + return -EINVAL;
>>> + }
>>> + st_map = container_of(map, struct bpf_struct_ops_map, map);
>>> +
>>> + st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, link);
>>> +
>>> + rcu_assign_pointer(st_link->map, NULL);
>>> + /* Pair with bpf_map_get() in bpf_struct_ops_link_create() or
>>> + * bpf_map_inc() in bpf_struct_ops_map_link_update().
>>> + */
>>> + bpf_map_put(&st_map->map);
>>> +
>>> + mutex_unlock(&update_mutex);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> static const struct bpf_link_ops bpf_struct_ops_map_lops = {
>>> .dealloc = bpf_struct_ops_map_link_dealloc,
>>> + .detach = bpf_struct_ops_map_link_detach,
>>> .show_fdinfo = bpf_struct_ops_map_link_show_fdinfo,
>>> .fill_link_info = bpf_struct_ops_map_link_fill_link_info,
>>> .update_map = bpf_struct_ops_map_link_update,
>>> @@ -1176,13 +1208,19 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
>>> if (err)
>>> goto err_out;
>>> + /* Init link->map before calling reg() in case being detached
>>> + * immediately.
>>> + */
>>> + RCU_INIT_POINTER(link->map, map);
>>> +
>>> err = st_map->st_ops_desc->st_ops->reg(st_map->kvalue.data, &link->link);
>>> if (err) {
>>> + rcu_assign_pointer(link->map, NULL);
>>
>> nit. RCU_INIT_POINTER(link->map, NULL) is fine.
>
> Got it!
>
>>
>> There is a merge conflict with patch 4 also.
>
> What do you mean here? Do you mean the patch 4 can not be applied on top
> of the patch 2?
Please monitor the bpf CI report.
bpf CI complains:
https://patchwork.kernel.org/project/netdevbpf/patch/20240507055600.2382627-2-thinker.li@gmail.com/
snippet of the error:
Applying: bpf: enable detaching links of struct_ops objects.
Applying: bpf: support epoll from bpf struct_ops links.
Applying: selftests/bpf: test struct_ops with epoll
Patch failed at 0004 selftests/bpf: test struct_ops with epoll
>
>>
>> pw-bot: cr
>>
>>> bpf_link_cleanup(&link_primer);
>>> + /* The link has been free by bpf_link_cleanup() */
>>> link = NULL;
>>> goto err_out;
>>> }
>>> - RCU_INIT_POINTER(link->map, map);
>>> return bpf_link_settle(&link_primer);
>>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v2 2/6] bpf: enable detaching links of struct_ops objects.
2024-05-09 0:36 ` Martin KaFai Lau
@ 2024-05-09 16:59 ` Kui-Feng Lee
0 siblings, 0 replies; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-09 16:59 UTC (permalink / raw)
To: Martin KaFai Lau, Kui-Feng Lee
Cc: kuifeng, bpf, ast, song, kernel-team, andrii
On 5/8/24 17:36, Martin KaFai Lau wrote:
> On 5/8/24 5:14 PM, Kui-Feng Lee wrote:
>>
>>
>> On 5/8/24 16:22, Martin KaFai Lau wrote:
>>> On 5/6/24 10:55 PM, Kui-Feng Lee wrote:
>>>> Implement the detach callback in bpf_link_ops for struct_ops. The
>>>> subsystems that struct_ops objects are registered to can use this
>>>> callback
>>>> to detach the links being passed to them.
>>>
>>> The user space can also use the detach. The subsystem is merely
>>> reusing the similar detach callback if it stores the link during reg().
>>
>> Sure!
>>
>>>
>>>>
>>>> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
>>>> ---
>>>> kernel/bpf/bpf_struct_ops.c | 50
>>>> ++++++++++++++++++++++++++++++++-----
>>>> 1 file changed, 44 insertions(+), 6 deletions(-)
>>>>
>>>> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
>>>> index 390f8c155135..bd2602982e4d 100644
>>>> --- a/kernel/bpf/bpf_struct_ops.c
>>>> +++ b/kernel/bpf/bpf_struct_ops.c
>>>> @@ -1057,9 +1057,6 @@ static void
>>>> bpf_struct_ops_map_link_dealloc(struct bpf_link *link)
>>>> st_map = (struct bpf_struct_ops_map *)
>>>> rcu_dereference_protected(st_link->map, true);
>>>> if (st_map) {
>>>> - /* st_link->map can be NULL if
>>>> - * bpf_struct_ops_link_create() fails to register.
>>>> - */
>>>> st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data,
>>>> st_link);
>>>> bpf_map_put(&st_map->map);
>>>> }
>>>> @@ -1075,7 +1072,8 @@ static void
>>>> bpf_struct_ops_map_link_show_fdinfo(const struct bpf_link *link,
>>>> st_link = container_of(link, struct bpf_struct_ops_link, link);
>>>> rcu_read_lock();
>>>> map = rcu_dereference(st_link->map);
>>>> - seq_printf(seq, "map_id:\t%d\n", map->id);
>>>> + if (map)
>>>> + seq_printf(seq, "map_id:\t%d\n", map->id);
>>>> rcu_read_unlock();
>>>> }
>>>> @@ -1088,7 +1086,8 @@ static int
>>>> bpf_struct_ops_map_link_fill_link_info(const struct bpf_link *link,
>>>> st_link = container_of(link, struct bpf_struct_ops_link, link);
>>>> rcu_read_lock();
>>>> map = rcu_dereference(st_link->map);
>>>> - info->struct_ops.map_id = map->id;
>>>> + if (map)
>>>> + info->struct_ops.map_id = map->id;
>>>> rcu_read_unlock();
>>>> return 0;
>>>> }
>>>> @@ -1113,6 +1112,10 @@ static int
>>>> bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
>>>> mutex_lock(&update_mutex);
>>>> old_map = rcu_dereference_protected(st_link->map,
>>>> lockdep_is_held(&update_mutex));
>>>> + if (!old_map) {
>>>> + err = -EINVAL;
>>>> + goto err_out;
>>>> + }
>>>> if (expected_old_map && old_map != expected_old_map) {
>>>> err = -EPERM;
>>>> goto err_out;
>>>> @@ -1139,8 +1142,37 @@ static int
>>>> bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
>>>> return err;
>>>> }
>>>> +static int bpf_struct_ops_map_link_detach(struct bpf_link *link)
>>>> +{
>>>> + struct bpf_struct_ops_link *st_link = container_of(link, struct
>>>> bpf_struct_ops_link, link);
>>>> + struct bpf_struct_ops_map *st_map;
>>>> + struct bpf_map *map;
>>>> +
>>>> + mutex_lock(&update_mutex);
>>>> +
>>>> + map = rcu_dereference_protected(st_link->map, true);
>>>
>>> nit. s/true/lockdep_is_held(&update_mutex)/
>>
>>
>> I thought it is protected by the refcount holding by the caller.
>> WDYT?
>
> st_link->map is the one with __rcu tag and "!map" is tested next. I
> don't see how these imply the map pointer is protected by refcount. Can
> you explain?
>
Ok! You are right. I confused links with maps.
>>
>>
>>>
>>>> + if (!map) {
>>>> + mutex_unlock(&update_mutex);
>>>> + return -EINVAL;
>>>> + }
>>>> + st_map = container_of(map, struct bpf_struct_ops_map, map);
>>>> +
>>>> + st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, link);
>>>> +
>>>> + rcu_assign_pointer(st_link->map, NULL);
>>>> + /* Pair with bpf_map_get() in bpf_struct_ops_link_create() or
>>>> + * bpf_map_inc() in bpf_struct_ops_map_link_update().
>>>> + */
>>>> + bpf_map_put(&st_map->map);
>>>> +
>>>> + mutex_unlock(&update_mutex);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> static const struct bpf_link_ops bpf_struct_ops_map_lops = {
>>>> .dealloc = bpf_struct_ops_map_link_dealloc,
>>>> + .detach = bpf_struct_ops_map_link_detach,
>>>> .show_fdinfo = bpf_struct_ops_map_link_show_fdinfo,
>>>> .fill_link_info = bpf_struct_ops_map_link_fill_link_info,
>>>> .update_map = bpf_struct_ops_map_link_update,
>>>> @@ -1176,13 +1208,19 @@ int bpf_struct_ops_link_create(union
>>>> bpf_attr *attr)
>>>> if (err)
>>>> goto err_out;
>>>> + /* Init link->map before calling reg() in case being detached
>>>> + * immediately.
>>>> + */
>>>> + RCU_INIT_POINTER(link->map, map);
>>>> +
>>>> err = st_map->st_ops_desc->st_ops->reg(st_map->kvalue.data,
>>>> &link->link);
>>>> if (err) {
>>>> + rcu_assign_pointer(link->map, NULL);
>>>
>>> nit. RCU_INIT_POINTER(link->map, NULL) is fine.
>>
>> Got it!
>>
>>>
>>> There is a merge conflict with patch 4 also.
>>
>> What do you mean here? Do you mean the patch 4 can not be applied on top
>> of the patch 2?
>
> Please monitor the bpf CI report.
>
> bpf CI complains:
> https://patchwork.kernel.org/project/netdevbpf/patch/20240507055600.2382627-2-thinker.li@gmail.com/
>
> snippet of the error:
>
> Applying: bpf: enable detaching links of struct_ops objects.
> Applying: bpf: support epoll from bpf struct_ops links.
> Applying: selftests/bpf: test struct_ops with epoll
> Patch failed at 0004 selftests/bpf: test struct_ops with epoll
Yes! I found it when I rebased local repository.
>
>>
>>>
>>> pw-bot: cr
>>>
>>>> bpf_link_cleanup(&link_primer);
>>>> + /* The link has been free by bpf_link_cleanup() */
>>>> link = NULL;
>>>> goto err_out;
>>>> }
>>>> - RCU_INIT_POINTER(link->map, map);
>>>> return bpf_link_settle(&link_primer);
>>>
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH bpf-next v2 2/6] bpf: enable detaching links of struct_ops objects.
2024-05-09 0:14 ` Kui-Feng Lee
2024-05-09 0:36 ` Martin KaFai Lau
@ 2024-05-09 0:46 ` Kui-Feng Lee
1 sibling, 0 replies; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-09 0:46 UTC (permalink / raw)
To: Martin KaFai Lau, Kui-Feng Lee
Cc: kuifeng, bpf, ast, song, kernel-team, andrii
On 5/8/24 17:14, Kui-Feng Lee wrote:
>
>
> On 5/8/24 16:22, Martin KaFai Lau wrote:
>> On 5/6/24 10:55 PM, Kui-Feng Lee wrote:
>>> Implement the detach callback in bpf_link_ops for struct_ops. The
>>> subsystems that struct_ops objects are registered to can use this
>>> callback
>>> to detach the links being passed to them.
>>
>> The user space can also use the detach. The subsystem is merely
>> reusing the similar detach callback if it stores the link during reg().
>
> Sure!
>
>>
>>>
>>> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
>>> ---
>>> kernel/bpf/bpf_struct_ops.c | 50 ++++++++++++++++++++++++++++++++-----
>>> 1 file changed, 44 insertions(+), 6 deletions(-)
>>>
>>> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
>>> index 390f8c155135..bd2602982e4d 100644
>>> --- a/kernel/bpf/bpf_struct_ops.c
>>> +++ b/kernel/bpf/bpf_struct_ops.c
>>> @@ -1057,9 +1057,6 @@ static void
>>> bpf_struct_ops_map_link_dealloc(struct bpf_link *link)
>>> st_map = (struct bpf_struct_ops_map *)
>>> rcu_dereference_protected(st_link->map, true);
>>> if (st_map) {
>>> - /* st_link->map can be NULL if
>>> - * bpf_struct_ops_link_create() fails to register.
>>> - */
>>> st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data,
>>> st_link);
>>> bpf_map_put(&st_map->map);
>>> }
>>> @@ -1075,7 +1072,8 @@ static void
>>> bpf_struct_ops_map_link_show_fdinfo(const struct bpf_link *link,
>>> st_link = container_of(link, struct bpf_struct_ops_link, link);
>>> rcu_read_lock();
>>> map = rcu_dereference(st_link->map);
>>> - seq_printf(seq, "map_id:\t%d\n", map->id);
>>> + if (map)
>>> + seq_printf(seq, "map_id:\t%d\n", map->id);
>>> rcu_read_unlock();
>>> }
>>> @@ -1088,7 +1086,8 @@ static int
>>> bpf_struct_ops_map_link_fill_link_info(const struct bpf_link *link,
>>> st_link = container_of(link, struct bpf_struct_ops_link, link);
>>> rcu_read_lock();
>>> map = rcu_dereference(st_link->map);
>>> - info->struct_ops.map_id = map->id;
>>> + if (map)
>>> + info->struct_ops.map_id = map->id;
>>> rcu_read_unlock();
>>> return 0;
>>> }
>>> @@ -1113,6 +1112,10 @@ static int
>>> bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
>>> mutex_lock(&update_mutex);
>>> old_map = rcu_dereference_protected(st_link->map,
>>> lockdep_is_held(&update_mutex));
>>> + if (!old_map) {
>>> + err = -EINVAL;
>>> + goto err_out;
>>> + }
>>> if (expected_old_map && old_map != expected_old_map) {
>>> err = -EPERM;
>>> goto err_out;
>>> @@ -1139,8 +1142,37 @@ static int
>>> bpf_struct_ops_map_link_update(struct bpf_link *link, struct bpf_map
>>> return err;
>>> }
>>> +static int bpf_struct_ops_map_link_detach(struct bpf_link *link)
>>> +{
>>> + struct bpf_struct_ops_link *st_link = container_of(link, struct
>>> bpf_struct_ops_link, link);
>>> + struct bpf_struct_ops_map *st_map;
>>> + struct bpf_map *map;
>>> +
>>> + mutex_lock(&update_mutex);
>>> +
>>> + map = rcu_dereference_protected(st_link->map, true);
>>
>> nit. s/true/lockdep_is_held(&update_mutex)/
>
>
> I thought it is protected by the refcount holding by the caller.
> WDYT?
>
>
>>
>>> + if (!map) {
>>> + mutex_unlock(&update_mutex);
>>> + return -EINVAL;
>>> + }
>>> + st_map = container_of(map, struct bpf_struct_ops_map, map);
>>> +
>>> + st_map->st_ops_desc->st_ops->unreg(&st_map->kvalue.data, link);
>>> +
>>> + rcu_assign_pointer(st_link->map, NULL);
>>> + /* Pair with bpf_map_get() in bpf_struct_ops_link_create() or
>>> + * bpf_map_inc() in bpf_struct_ops_map_link_update().
>>> + */
>>> + bpf_map_put(&st_map->map);
>>> +
>>> + mutex_unlock(&update_mutex);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> static const struct bpf_link_ops bpf_struct_ops_map_lops = {
>>> .dealloc = bpf_struct_ops_map_link_dealloc,
>>> + .detach = bpf_struct_ops_map_link_detach,
>>> .show_fdinfo = bpf_struct_ops_map_link_show_fdinfo,
>>> .fill_link_info = bpf_struct_ops_map_link_fill_link_info,
>>> .update_map = bpf_struct_ops_map_link_update,
>>> @@ -1176,13 +1208,19 @@ int bpf_struct_ops_link_create(union bpf_attr
>>> *attr)
>>> if (err)
>>> goto err_out;
>>> + /* Init link->map before calling reg() in case being detached
>>> + * immediately.
>>> + */
>>> + RCU_INIT_POINTER(link->map, map);
>>> +
>>> err = st_map->st_ops_desc->st_ops->reg(st_map->kvalue.data,
>>> &link->link);
>>> if (err) {
>>> + rcu_assign_pointer(link->map, NULL);
>>
>> nit. RCU_INIT_POINTER(link->map, NULL) is fine.
>
> Got it!
>
>>
>> There is a merge conflict with patch 4 also.
>
> What do you mean here? Do you mean the patch 4 can not be applied on top
> of the patch 2?
I have seen it after rebasing my local repository.
>
>>
>> pw-bot: cr
>>
>>> bpf_link_cleanup(&link_primer);
>>> + /* The link has been free by bpf_link_cleanup() */
>>> link = NULL;
>>> goto err_out;
>>> }
>>> - RCU_INIT_POINTER(link->map, map);
>>> return bpf_link_settle(&link_primer);
>>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH bpf-next v2 3/6] bpf: support epoll from bpf struct_ops links.
2024-05-07 5:55 [PATCH bpf-next v2 0/6] Notify user space when a struct_ops object is detached/unregistered Kui-Feng Lee
2024-05-07 5:55 ` [PATCH bpf-next v2 1/6] bpf: pass bpf_struct_ops_link to callbacks in bpf_struct_ops Kui-Feng Lee
2024-05-07 5:55 ` [PATCH bpf-next v2 2/6] bpf: enable detaching links of struct_ops objects Kui-Feng Lee
@ 2024-05-07 5:55 ` Kui-Feng Lee
2024-05-07 5:55 ` [PATCH bpf-next v2 4/6] selftests/bpf: test struct_ops with epoll Kui-Feng Lee
` (2 subsequent siblings)
5 siblings, 0 replies; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-07 5:55 UTC (permalink / raw)
To: bpf, ast, martin.lau, song, kernel-team, andrii
Cc: sinquersw, kuifeng, Kui-Feng Lee
Add epoll support to bpf struct_ops links to trigger EPOLLHUP event upon
detachment.
Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
include/linux/bpf.h | 1 +
kernel/bpf/bpf_struct_ops.c | 17 +++++++++++++++++
kernel/bpf/syscall.c | 11 +++++++++++
3 files changed, 29 insertions(+)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index b600767ebe02..5f7496ef8b7c 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1612,6 +1612,7 @@ struct bpf_link_ops {
struct bpf_link_info *info);
int (*update_map)(struct bpf_link *link, struct bpf_map *new_map,
struct bpf_map *old_map);
+ __poll_t (*poll)(struct file *file, struct poll_table_struct *pts);
};
struct bpf_tramp_link {
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index bd2602982e4d..f37844b1a94c 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -12,6 +12,7 @@
#include <linux/mutex.h>
#include <linux/btf_ids.h>
#include <linux/rcupdate_wait.h>
+#include <linux/poll.h>
struct bpf_struct_ops_value {
struct bpf_struct_ops_common_value common;
@@ -56,6 +57,7 @@ struct bpf_struct_ops_map {
struct bpf_struct_ops_link {
struct bpf_link link;
struct bpf_map __rcu *map;
+ wait_queue_head_t wait_hup;
};
static DEFINE_MUTEX(update_mutex);
@@ -1167,15 +1169,28 @@ static int bpf_struct_ops_map_link_detach(struct bpf_link *link)
mutex_unlock(&update_mutex);
+ wake_up_interruptible_poll(&st_link->wait_hup, EPOLLHUP);
+
return 0;
}
+static __poll_t bpf_struct_ops_map_link_poll(struct file *file,
+ struct poll_table_struct *pts)
+{
+ struct bpf_struct_ops_link *st_link = file->private_data;
+
+ poll_wait(file, &st_link->wait_hup, pts);
+
+ return (st_link->map) ? 0 : EPOLLHUP;
+}
+
static const struct bpf_link_ops bpf_struct_ops_map_lops = {
.dealloc = bpf_struct_ops_map_link_dealloc,
.detach = bpf_struct_ops_map_link_detach,
.show_fdinfo = bpf_struct_ops_map_link_show_fdinfo,
.fill_link_info = bpf_struct_ops_map_link_fill_link_info,
.update_map = bpf_struct_ops_map_link_update,
+ .poll = bpf_struct_ops_map_link_poll,
};
int bpf_struct_ops_link_create(union bpf_attr *attr)
@@ -1213,6 +1228,8 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
*/
RCU_INIT_POINTER(link->map, map);
+ init_waitqueue_head(&link->wait_hup);
+
err = st_map->st_ops_desc->st_ops->reg(st_map->kvalue.data, &link->link);
if (err) {
rcu_assign_pointer(link->map, NULL);
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 13ad74ecf2cd..ad4f81ed27f0 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3150,6 +3150,16 @@ static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
}
#endif
+static __poll_t bpf_link_poll(struct file *file, struct poll_table_struct *pts)
+{
+ struct bpf_link *link = file->private_data;
+
+ if (link->ops->poll)
+ return link->ops->poll(file, pts);
+
+ return 0;
+}
+
static const struct file_operations bpf_link_fops = {
#ifdef CONFIG_PROC_FS
.show_fdinfo = bpf_link_show_fdinfo,
@@ -3157,6 +3167,7 @@ static const struct file_operations bpf_link_fops = {
.release = bpf_link_release,
.read = bpf_dummy_read,
.write = bpf_dummy_write,
+ .poll = bpf_link_poll,
};
static int bpf_link_alloc_id(struct bpf_link *link)
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH bpf-next v2 4/6] selftests/bpf: test struct_ops with epoll
2024-05-07 5:55 [PATCH bpf-next v2 0/6] Notify user space when a struct_ops object is detached/unregistered Kui-Feng Lee
` (2 preceding siblings ...)
2024-05-07 5:55 ` [PATCH bpf-next v2 3/6] bpf: support epoll from bpf struct_ops links Kui-Feng Lee
@ 2024-05-07 5:55 ` Kui-Feng Lee
2024-05-08 23:34 ` Martin KaFai Lau
2024-05-07 5:55 ` [PATCH bpf-next v2 5/6] selftests/bpf: detach a struct_ops link from the subsystem managing it Kui-Feng Lee
2024-05-07 5:56 ` [PATCH bpf-next v2 6/6] selftests/bpf: make sure bpf_testmod handling racing link destroying well Kui-Feng Lee
5 siblings, 1 reply; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-07 5:55 UTC (permalink / raw)
To: bpf, ast, martin.lau, song, kernel-team, andrii
Cc: sinquersw, kuifeng, Kui-Feng Lee
Verify whether a user space program is informed through epoll with EPOLLHUP
when a struct_ops object is detached.
Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
.../selftests/bpf/bpf_testmod/bpf_testmod.c | 13 +++++
.../bpf/bpf_testmod/bpf_testmod_kfunc.h | 1 +
.../bpf/prog_tests/test_struct_ops_module.c | 57 +++++++++++++++++++
.../selftests/bpf/progs/struct_ops_detach.c | 31 ++++++++++
4 files changed, 102 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_detach.c
diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
index e24a18bfee14..c89a6414c69f 100644
--- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
@@ -10,6 +10,7 @@
#include <linux/percpu-defs.h>
#include <linux/sysfs.h>
#include <linux/tracepoint.h>
+#include <linux/workqueue.h>
#include "bpf_testmod.h"
#include "bpf_testmod_kfunc.h"
@@ -498,6 +499,9 @@ __bpf_kfunc void bpf_kfunc_call_test_sleepable(void)
{
}
+static DEFINE_MUTEX(detach_mutex);
+static struct bpf_link *link_to_detach;
+
BTF_KFUNCS_START(bpf_testmod_check_kfunc_ids)
BTF_ID_FLAGS(func, bpf_testmod_test_mod_kfunc)
BTF_ID_FLAGS(func, bpf_kfunc_call_test1)
@@ -577,11 +581,20 @@ static int bpf_dummy_reg(void *kdata, struct bpf_link *link)
if (ops->test_2)
ops->test_2(4, ops->data);
+ mutex_lock(&detach_mutex);
+ if (!link_to_detach)
+ link_to_detach = link;
+ mutex_unlock(&detach_mutex);
+
return 0;
}
static void bpf_dummy_unreg(void *kdata, struct bpf_link *link)
{
+ mutex_lock(&detach_mutex);
+ if (link == link_to_detach)
+ link_to_detach = NULL;
+ mutex_unlock(&detach_mutex);
}
static int bpf_testmod_test_1(void)
diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h
index ce5cd763561c..9f9b60880fd3 100644
--- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h
@@ -105,6 +105,7 @@ void bpf_kfunc_call_test_fail1(struct prog_test_fail1 *p);
void bpf_kfunc_call_test_fail2(struct prog_test_fail2 *p);
void bpf_kfunc_call_test_fail3(struct prog_test_fail3 *p);
void bpf_kfunc_call_test_mem_len_fail1(void *mem, int len);
+int bpf_dummy_do_link_detach(void) __ksym;
void bpf_kfunc_common_test(void) __ksym;
#endif /* _BPF_TESTMOD_KFUNC_H */
diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
index bd39586abd5a..f39455b81664 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
@@ -2,8 +2,12 @@
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#include <time.h>
+#include <network_helpers.h>
+
+#include <sys/epoll.h>
#include "struct_ops_module.skel.h"
+#include "struct_ops_detach.skel.h"
static void check_map_info(struct bpf_map_info *info)
{
@@ -174,6 +178,57 @@ static void test_struct_ops_incompatible(void)
struct_ops_module__destroy(skel);
}
+/* Detach a link from a user space program */
+static void test_detach_link(void)
+{
+ struct epoll_event ev, events[2];
+ struct struct_ops_detach *skel;
+ struct bpf_link *link = NULL;
+ int fd, epollfd = -1, nfds;
+ int err;
+
+ skel = struct_ops_detach__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "struct_ops_detach__open_and_load"))
+ return;
+
+ link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
+ if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
+ goto cleanup;
+
+ fd = bpf_link__fd(link);
+ if (!ASSERT_GE(fd, 0, "link_fd"))
+ goto cleanup;
+
+ epollfd = epoll_create1(0);
+ if (!ASSERT_GE(epollfd, 0, "epoll_create1"))
+ goto cleanup;
+
+ ev.events = EPOLLHUP;
+ ev.data.fd = fd;
+ err = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
+ if (!ASSERT_OK(err, "epoll_ctl"))
+ goto cleanup;
+
+ err = bpf_link__detach(link);
+ if (!ASSERT_OK(err, "detach_link"))
+ goto cleanup;
+
+ /* Wait for EPOLLHUP */
+ nfds = epoll_wait(epollfd, events, 2, 500);
+ if (!ASSERT_EQ(nfds, 1, "epoll_wait"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(events[0].data.fd, fd, "epoll_wait_fd"))
+ goto cleanup;
+ if (!ASSERT_TRUE(events[0].events & EPOLLHUP, "events[0].events"))
+ goto cleanup;
+
+cleanup:
+ close(epollfd);
+ bpf_link__destroy(link);
+ struct_ops_detach__destroy(skel);
+}
+
void serial_test_struct_ops_module(void)
{
if (test__start_subtest("test_struct_ops_load"))
@@ -182,5 +237,7 @@ void serial_test_struct_ops_module(void)
test_struct_ops_not_zeroed();
if (test__start_subtest("test_struct_ops_incompatible"))
test_struct_ops_incompatible();
+ if (test__start_subtest("test_detach_link"))
+ test_detach_link();
}
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_detach.c b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
new file mode 100644
index 000000000000..aeb355b3bea3
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "../bpf_testmod/bpf_testmod.h"
+#include "../bpf_testmod/bpf_testmod_kfunc.h"
+
+char _license[] SEC("license") = "GPL";
+
+int test_1_result = 0;
+int test_2_result = 0;
+
+SEC("struct_ops/test_1")
+int BPF_PROG(test_1)
+{
+ test_1_result = 0xdeadbeef;
+ return 0;
+}
+
+SEC("struct_ops/test_2")
+void BPF_PROG(test_2, int a, int b)
+{
+ test_2_result = a + b;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops testmod_do_detach = {
+ .test_1 = (void *)test_1,
+ .test_2 = (void *)test_2,
+};
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v2 4/6] selftests/bpf: test struct_ops with epoll
2024-05-07 5:55 ` [PATCH bpf-next v2 4/6] selftests/bpf: test struct_ops with epoll Kui-Feng Lee
@ 2024-05-08 23:34 ` Martin KaFai Lau
2024-05-09 0:22 ` Kui-Feng Lee
0 siblings, 1 reply; 18+ messages in thread
From: Martin KaFai Lau @ 2024-05-08 23:34 UTC (permalink / raw)
To: Kui-Feng Lee; +Cc: bpf, ast, song, kernel-team, andrii, sinquersw, kuifeng
On 5/6/24 10:55 PM, Kui-Feng Lee wrote:
> Verify whether a user space program is informed through epoll with EPOLLHUP
> when a struct_ops object is detached.
>
> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
> ---
> .../selftests/bpf/bpf_testmod/bpf_testmod.c | 13 +++++
> .../bpf/bpf_testmod/bpf_testmod_kfunc.h | 1 +
> .../bpf/prog_tests/test_struct_ops_module.c | 57 +++++++++++++++++++
> .../selftests/bpf/progs/struct_ops_detach.c | 31 ++++++++++
> 4 files changed, 102 insertions(+)
> create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_detach.c
>
> diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> index e24a18bfee14..c89a6414c69f 100644
> --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> @@ -10,6 +10,7 @@
> #include <linux/percpu-defs.h>
> #include <linux/sysfs.h>
> #include <linux/tracepoint.h>
> +#include <linux/workqueue.h>
> #include "bpf_testmod.h"
> #include "bpf_testmod_kfunc.h"
>
> @@ -498,6 +499,9 @@ __bpf_kfunc void bpf_kfunc_call_test_sleepable(void)
> {
> }
>
> +static DEFINE_MUTEX(detach_mutex);
> +static struct bpf_link *link_to_detach;
> +
> BTF_KFUNCS_START(bpf_testmod_check_kfunc_ids)
> BTF_ID_FLAGS(func, bpf_testmod_test_mod_kfunc)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test1)
> @@ -577,11 +581,20 @@ static int bpf_dummy_reg(void *kdata, struct bpf_link *link)
> if (ops->test_2)
> ops->test_2(4, ops->data);
>
> + mutex_lock(&detach_mutex);
> + if (!link_to_detach)
> + link_to_detach = link;
> + mutex_unlock(&detach_mutex);
> +
> return 0;
> }
>
> static void bpf_dummy_unreg(void *kdata, struct bpf_link *link)
> {
> + mutex_lock(&detach_mutex);
> + if (link == link_to_detach)
> + link_to_detach = NULL;
> + mutex_unlock(&detach_mutex);
The reg/unreg changes should belong to the next patch.
> }
>
> static int bpf_testmod_test_1(void)
> diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h
> index ce5cd763561c..9f9b60880fd3 100644
> --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h
> +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h
> @@ -105,6 +105,7 @@ void bpf_kfunc_call_test_fail1(struct prog_test_fail1 *p);
> void bpf_kfunc_call_test_fail2(struct prog_test_fail2 *p);
> void bpf_kfunc_call_test_fail3(struct prog_test_fail3 *p);
> void bpf_kfunc_call_test_mem_len_fail1(void *mem, int len);
> +int bpf_dummy_do_link_detach(void) __ksym;
The kfunc is not added in this patch either.
>
> void bpf_kfunc_common_test(void) __ksym;
> #endif /* _BPF_TESTMOD_KFUNC_H */
> diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
> index bd39586abd5a..f39455b81664 100644
> --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
> +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
> @@ -2,8 +2,12 @@
> /* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
> #include <test_progs.h>
> #include <time.h>
> +#include <network_helpers.h>
What is needed from network_herlpers.h?
> +
> +#include <sys/epoll.h>
>
> #include "struct_ops_module.skel.h"
> +#include "struct_ops_detach.skel.h"
>
> static void check_map_info(struct bpf_map_info *info)
> {
> @@ -174,6 +178,57 @@ static void test_struct_ops_incompatible(void)
> struct_ops_module__destroy(skel);
> }
>
> +/* Detach a link from a user space program */
> +static void test_detach_link(void)
> +{
> + struct epoll_event ev, events[2];
> + struct struct_ops_detach *skel;
> + struct bpf_link *link = NULL;
> + int fd, epollfd = -1, nfds;
> + int err;
> +
> + skel = struct_ops_detach__open_and_load();
> + if (!ASSERT_OK_PTR(skel, "struct_ops_detach__open_and_load"))
> + return;
> +
> + link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
> + if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
> + goto cleanup;
> +
> + fd = bpf_link__fd(link);
> + if (!ASSERT_GE(fd, 0, "link_fd"))
> + goto cleanup;
> +
> + epollfd = epoll_create1(0);
> + if (!ASSERT_GE(epollfd, 0, "epoll_create1"))
> + goto cleanup;
> +
> + ev.events = EPOLLHUP;
> + ev.data.fd = fd;
> + err = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
> + if (!ASSERT_OK(err, "epoll_ctl"))
> + goto cleanup;
> +
> + err = bpf_link__detach(link);
> + if (!ASSERT_OK(err, "detach_link"))
> + goto cleanup;
> +
> + /* Wait for EPOLLHUP */
> + nfds = epoll_wait(epollfd, events, 2, 500);
> + if (!ASSERT_EQ(nfds, 1, "epoll_wait"))
> + goto cleanup;
> +
> + if (!ASSERT_EQ(events[0].data.fd, fd, "epoll_wait_fd"))
> + goto cleanup;
> + if (!ASSERT_TRUE(events[0].events & EPOLLHUP, "events[0].events"))
> + goto cleanup;
> +
> +cleanup:
> + close(epollfd);
Better check epollfd since it is init to -1. There are cases that epollfd is -1
here.
> + bpf_link__destroy(link);
> + struct_ops_detach__destroy(skel);
> +}
> +
> void serial_test_struct_ops_module(void)
> {
> if (test__start_subtest("test_struct_ops_load"))
> @@ -182,5 +237,7 @@ void serial_test_struct_ops_module(void)
> test_struct_ops_not_zeroed();
> if (test__start_subtest("test_struct_ops_incompatible"))
> test_struct_ops_incompatible();
> + if (test__start_subtest("test_detach_link"))
> + test_detach_link();
> }
>
> diff --git a/tools/testing/selftests/bpf/progs/struct_ops_detach.c b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
> new file mode 100644
> index 000000000000..aeb355b3bea3
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
> @@ -0,0 +1,31 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
> +#include <vmlinux.h>
> +#include <bpf/bpf_helpers.h>
> +#include <bpf/bpf_tracing.h>
> +#include "../bpf_testmod/bpf_testmod.h"
> +#include "../bpf_testmod/bpf_testmod_kfunc.h"
The _kfunc.h should not be needed in this patch either.
> +
> +char _license[] SEC("license") = "GPL";
> +
> +int test_1_result = 0;
> +int test_2_result = 0;
Are these global vars tested? If not, can the test_1 and test_2 programs be
removed? or some of them is not optional?
> +
> +SEC("struct_ops/test_1")
> +int BPF_PROG(test_1)
> +{
> + test_1_result = 0xdeadbeef;
> + return 0;
> +}
> +
> +SEC("struct_ops/test_2")
> +void BPF_PROG(test_2, int a, int b)
> +{
> + test_2_result = a + b;
> +}
> +
> +SEC(".struct_ops.link")
> +struct bpf_testmod_ops testmod_do_detach = {
> + .test_1 = (void *)test_1,
> + .test_2 = (void *)test_2,
> +};
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v2 4/6] selftests/bpf: test struct_ops with epoll
2024-05-08 23:34 ` Martin KaFai Lau
@ 2024-05-09 0:22 ` Kui-Feng Lee
0 siblings, 0 replies; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-09 0:22 UTC (permalink / raw)
To: Martin KaFai Lau, Kui-Feng Lee
Cc: bpf, ast, song, kernel-team, andrii, kuifeng
On 5/8/24 16:34, Martin KaFai Lau wrote:
> On 5/6/24 10:55 PM, Kui-Feng Lee wrote:
>> Verify whether a user space program is informed through epoll with
>> EPOLLHUP
>> when a struct_ops object is detached.
>>
>> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
>> ---
>> .../selftests/bpf/bpf_testmod/bpf_testmod.c | 13 +++++
>> .../bpf/bpf_testmod/bpf_testmod_kfunc.h | 1 +
>> .../bpf/prog_tests/test_struct_ops_module.c | 57 +++++++++++++++++++
>> .../selftests/bpf/progs/struct_ops_detach.c | 31 ++++++++++
>> 4 files changed, 102 insertions(+)
>> create mode 100644
>> tools/testing/selftests/bpf/progs/struct_ops_detach.c
>>
>> diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
>> b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
>> index e24a18bfee14..c89a6414c69f 100644
>> --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
>> +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
>> @@ -10,6 +10,7 @@
>> #include <linux/percpu-defs.h>
>> #include <linux/sysfs.h>
>> #include <linux/tracepoint.h>
>> +#include <linux/workqueue.h>
>> #include "bpf_testmod.h"
>> #include "bpf_testmod_kfunc.h"
>> @@ -498,6 +499,9 @@ __bpf_kfunc void bpf_kfunc_call_test_sleepable(void)
>> {
>> }
>> +static DEFINE_MUTEX(detach_mutex);
>> +static struct bpf_link *link_to_detach;
>> +
>> BTF_KFUNCS_START(bpf_testmod_check_kfunc_ids)
>> BTF_ID_FLAGS(func, bpf_testmod_test_mod_kfunc)
>> BTF_ID_FLAGS(func, bpf_kfunc_call_test1)
>> @@ -577,11 +581,20 @@ static int bpf_dummy_reg(void *kdata, struct
>> bpf_link *link)
>> if (ops->test_2)
>> ops->test_2(4, ops->data);
>> + mutex_lock(&detach_mutex);
>> + if (!link_to_detach)
>> + link_to_detach = link;
>> + mutex_unlock(&detach_mutex);
>> +
>> return 0;
>> }
>> static void bpf_dummy_unreg(void *kdata, struct bpf_link *link)
>> {
>> + mutex_lock(&detach_mutex);
>> + if (link == link_to_detach)
>> + link_to_detach = NULL;
>> + mutex_unlock(&detach_mutex);
>
> The reg/unreg changes should belong to the next patch.
Sure!
>
>> }
>> static int bpf_testmod_test_1(void)
>> diff --git
>> a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h
>> b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h
>> index ce5cd763561c..9f9b60880fd3 100644
>> --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h
>> +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod_kfunc.h
>> @@ -105,6 +105,7 @@ void bpf_kfunc_call_test_fail1(struct
>> prog_test_fail1 *p);
>> void bpf_kfunc_call_test_fail2(struct prog_test_fail2 *p);
>> void bpf_kfunc_call_test_fail3(struct prog_test_fail3 *p);
>> void bpf_kfunc_call_test_mem_len_fail1(void *mem, int len);
>> +int bpf_dummy_do_link_detach(void) __ksym;
>
> The kfunc is not added in this patch either.
Sure!
>
>> void bpf_kfunc_common_test(void) __ksym;
>> #endif /* _BPF_TESTMOD_KFUNC_H */
>> diff --git
>> a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> index bd39586abd5a..f39455b81664 100644
>> --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> @@ -2,8 +2,12 @@
>> /* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
>> #include <test_progs.h>
>> #include <time.h>
>> +#include <network_helpers.h>
>
> What is needed from network_herlpers.h?
>
>> +
>> +#include <sys/epoll.h>
>> #include "struct_ops_module.skel.h"
>> +#include "struct_ops_detach.skel.h"
>> static void check_map_info(struct bpf_map_info *info)
>> {
>> @@ -174,6 +178,57 @@ static void test_struct_ops_incompatible(void)
>> struct_ops_module__destroy(skel);
>> }
>> +/* Detach a link from a user space program */
>> +static void test_detach_link(void)
>> +{
>> + struct epoll_event ev, events[2];
>> + struct struct_ops_detach *skel;
>> + struct bpf_link *link = NULL;
>> + int fd, epollfd = -1, nfds;
>> + int err;
>> +
>> + skel = struct_ops_detach__open_and_load();
>> + if (!ASSERT_OK_PTR(skel, "struct_ops_detach__open_and_load"))
>> + return;
>> +
>> + link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
>> + if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
>> + goto cleanup;
>> +
>> + fd = bpf_link__fd(link);
>> + if (!ASSERT_GE(fd, 0, "link_fd"))
>> + goto cleanup;
>> +
>> + epollfd = epoll_create1(0);
>> + if (!ASSERT_GE(epollfd, 0, "epoll_create1"))
>> + goto cleanup;
>> +
>> + ev.events = EPOLLHUP;
>> + ev.data.fd = fd;
>> + err = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
>> + if (!ASSERT_OK(err, "epoll_ctl"))
>> + goto cleanup;
>> +
>> + err = bpf_link__detach(link);
>> + if (!ASSERT_OK(err, "detach_link"))
>> + goto cleanup;
>> +
>> + /* Wait for EPOLLHUP */
>> + nfds = epoll_wait(epollfd, events, 2, 500);
>> + if (!ASSERT_EQ(nfds, 1, "epoll_wait"))
>> + goto cleanup;
>> +
>> + if (!ASSERT_EQ(events[0].data.fd, fd, "epoll_wait_fd"))
>> + goto cleanup;
>> + if (!ASSERT_TRUE(events[0].events & EPOLLHUP, "events[0].events"))
>> + goto cleanup;
>> +
>> +cleanup:
>> + close(epollfd);
>
> Better check epollfd since it is init to -1. There are cases that
> epollfd is -1 here.
Ok! Although close(-1) doesn't cause any issue, it makes sense doing
check before calling it.
>
>> + bpf_link__destroy(link);
>> + struct_ops_detach__destroy(skel);
>> +}
>> +
>> void serial_test_struct_ops_module(void)
>> {
>> if (test__start_subtest("test_struct_ops_load"))
>> @@ -182,5 +237,7 @@ void serial_test_struct_ops_module(void)
>> test_struct_ops_not_zeroed();
>> if (test__start_subtest("test_struct_ops_incompatible"))
>> test_struct_ops_incompatible();
>> + if (test__start_subtest("test_detach_link"))
>> + test_detach_link();
>> }
>> diff --git a/tools/testing/selftests/bpf/progs/struct_ops_detach.c
>> b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
>> new file mode 100644
>> index 000000000000..aeb355b3bea3
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
>> @@ -0,0 +1,31 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
>> +#include <vmlinux.h>
>> +#include <bpf/bpf_helpers.h>
>> +#include <bpf/bpf_tracing.h>
>> +#include "../bpf_testmod/bpf_testmod.h"
>> +#include "../bpf_testmod/bpf_testmod_kfunc.h"
>
> The _kfunc.h should not be needed in this patch either.
Sure!
>
>> +
>> +char _license[] SEC("license") = "GPL";
>> +
>> +int test_1_result = 0;
>> +int test_2_result = 0;
>
> Are these global vars tested? If not, can the test_1 and test_2 programs
> be removed? or some of them is not optional?
Yes, they are optional. I will remove these functions.
>
>> +
>> +SEC("struct_ops/test_1")
>> +int BPF_PROG(test_1)
>> +{
>> + test_1_result = 0xdeadbeef;
>> + return 0;
>> +}
>> +
>> +SEC("struct_ops/test_2")
>> +void BPF_PROG(test_2, int a, int b)
>> +{
>> + test_2_result = a + b;
>> +}
>> +
>> +SEC(".struct_ops.link")
>> +struct bpf_testmod_ops testmod_do_detach = {
>> + .test_1 = (void *)test_1,
>> + .test_2 = (void *)test_2,
>> +};
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH bpf-next v2 5/6] selftests/bpf: detach a struct_ops link from the subsystem managing it.
2024-05-07 5:55 [PATCH bpf-next v2 0/6] Notify user space when a struct_ops object is detached/unregistered Kui-Feng Lee
` (3 preceding siblings ...)
2024-05-07 5:55 ` [PATCH bpf-next v2 4/6] selftests/bpf: test struct_ops with epoll Kui-Feng Lee
@ 2024-05-07 5:55 ` Kui-Feng Lee
2024-05-08 23:50 ` Martin KaFai Lau
2024-05-07 5:56 ` [PATCH bpf-next v2 6/6] selftests/bpf: make sure bpf_testmod handling racing link destroying well Kui-Feng Lee
5 siblings, 1 reply; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-07 5:55 UTC (permalink / raw)
To: bpf, ast, martin.lau, song, kernel-team, andrii
Cc: sinquersw, kuifeng, Kui-Feng Lee
Not only a user space program can detach a struct_ops link, the subsystem
managing a link can also detach the link. This patch add a kfunc to
simulate detaching a link by the subsystem managing it.
Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
.../selftests/bpf/bpf_testmod/bpf_testmod.c | 21 ++++++
.../bpf/prog_tests/test_struct_ops_module.c | 65 +++++++++++++++++++
.../selftests/bpf/progs/struct_ops_detach.c | 6 ++
3 files changed, 92 insertions(+)
diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
index c89a6414c69f..0bf1acc1767a 100644
--- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
@@ -502,6 +502,26 @@ __bpf_kfunc void bpf_kfunc_call_test_sleepable(void)
static DEFINE_MUTEX(detach_mutex);
static struct bpf_link *link_to_detach;
+__bpf_kfunc int bpf_dummy_do_link_detach(void)
+{
+ struct bpf_link *link;
+ int ret = -ENOENT;
+
+ mutex_lock(&detach_mutex);
+ link = link_to_detach;
+ /* Make sure the link is still valid by increasing its refcnt */
+ if (link && !atomic64_inc_not_zero(&link->refcnt))
+ link = NULL;
+ mutex_unlock(&detach_mutex);
+
+ if (link) {
+ ret = link->ops->detach(link);
+ bpf_link_put(link);
+ }
+
+ return ret;
+}
+
BTF_KFUNCS_START(bpf_testmod_check_kfunc_ids)
BTF_ID_FLAGS(func, bpf_testmod_test_mod_kfunc)
BTF_ID_FLAGS(func, bpf_kfunc_call_test1)
@@ -529,6 +549,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test_destructive, KF_DESTRUCTIVE)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_static_unused_arg)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_offset)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_sleepable, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_dummy_do_link_detach)
BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids)
static int bpf_testmod_ops_init(struct btf *btf)
diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
index f39455b81664..9f6657b53a93 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
@@ -229,6 +229,69 @@ static void test_detach_link(void)
struct_ops_detach__destroy(skel);
}
+/* Detach a link from the subsystem that the link was registered to */
+static void test_subsystem_detach(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts,
+ .data_in = &pkt_v4,
+ .data_size_in = sizeof(pkt_v4));
+ struct epoll_event ev, events[2];
+ struct struct_ops_detach *skel;
+ struct bpf_link *link = NULL;
+ int fd, epollfd = -1, nfds;
+ int prog_fd;
+ int err;
+
+ skel = struct_ops_detach__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "struct_ops_detach_open_and_load"))
+ return;
+
+ link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
+ if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
+ goto cleanup;
+
+ fd = bpf_link__fd(link);
+ if (!ASSERT_GE(fd, 0, "link_fd"))
+ goto cleanup;
+
+ prog_fd = bpf_program__fd(skel->progs.start_detach);
+ if (!ASSERT_GE(prog_fd, 0, "start_detach_fd"))
+ goto cleanup;
+
+ /* Do detachment from the registered subsystem */
+ err = bpf_prog_test_run_opts(prog_fd, &topts);
+ if (!ASSERT_OK(err, "start_detach_run"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(topts.retval, 0, "start_detach_run retval"))
+ goto cleanup;
+
+ epollfd = epoll_create1(0);
+ if (!ASSERT_GE(epollfd, 0, "epoll_create1"))
+ goto cleanup;
+
+ ev.events = EPOLLHUP;
+ ev.data.fd = fd;
+ err = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
+ if (!ASSERT_OK(err, "epoll_ctl"))
+ goto cleanup;
+
+ /* Wait for EPOLLHUP */
+ nfds = epoll_wait(epollfd, events, 2, 5000);
+ if (!ASSERT_EQ(nfds, 1, "epoll_wait"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(events[0].data.fd, fd, "epoll_wait_fd"))
+ goto cleanup;
+ if (!ASSERT_TRUE(events[0].events & EPOLLHUP, "events[0].events"))
+ goto cleanup;
+
+cleanup:
+ close(epollfd);
+ bpf_link__destroy(link);
+ struct_ops_detach__destroy(skel);
+}
+
void serial_test_struct_ops_module(void)
{
if (test__start_subtest("test_struct_ops_load"))
@@ -239,5 +302,7 @@ void serial_test_struct_ops_module(void)
test_struct_ops_incompatible();
if (test__start_subtest("test_detach_link"))
test_detach_link();
+ if (test__start_subtest("test_subsystem_detach"))
+ test_subsystem_detach();
}
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_detach.c b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
index aeb355b3bea3..139f9a5c5601 100644
--- a/tools/testing/selftests/bpf/progs/struct_ops_detach.c
+++ b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
@@ -29,3 +29,9 @@ struct bpf_testmod_ops testmod_do_detach = {
.test_1 = (void *)test_1,
.test_2 = (void *)test_2,
};
+
+SEC("tc")
+int start_detach(void *skb)
+{
+ return bpf_dummy_do_link_detach();
+}
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v2 5/6] selftests/bpf: detach a struct_ops link from the subsystem managing it.
2024-05-07 5:55 ` [PATCH bpf-next v2 5/6] selftests/bpf: detach a struct_ops link from the subsystem managing it Kui-Feng Lee
@ 2024-05-08 23:50 ` Martin KaFai Lau
2024-05-09 5:50 ` Kui-Feng Lee
0 siblings, 1 reply; 18+ messages in thread
From: Martin KaFai Lau @ 2024-05-08 23:50 UTC (permalink / raw)
To: Kui-Feng Lee; +Cc: sinquersw, kuifeng, bpf, ast, song, kernel-team, andrii
On 5/6/24 10:55 PM, Kui-Feng Lee wrote:
> Not only a user space program can detach a struct_ops link, the subsystem
> managing a link can also detach the link. This patch add a kfunc to
> simulate detaching a link by the subsystem managing it.
>
> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
> ---
> .../selftests/bpf/bpf_testmod/bpf_testmod.c | 21 ++++++
> .../bpf/prog_tests/test_struct_ops_module.c | 65 +++++++++++++++++++
> .../selftests/bpf/progs/struct_ops_detach.c | 6 ++
> 3 files changed, 92 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> index c89a6414c69f..0bf1acc1767a 100644
> --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
> @@ -502,6 +502,26 @@ __bpf_kfunc void bpf_kfunc_call_test_sleepable(void)
> static DEFINE_MUTEX(detach_mutex);
> static struct bpf_link *link_to_detach;
>
> +__bpf_kfunc int bpf_dummy_do_link_detach(void)
> +{
> + struct bpf_link *link;
> + int ret = -ENOENT;
> +
> + mutex_lock(&detach_mutex);
> + link = link_to_detach;
> + /* Make sure the link is still valid by increasing its refcnt */
> + if (link && !atomic64_inc_not_zero(&link->refcnt))
It is better to reuse the bpf_link_inc_not_zero().
> + link = NULL;
> + mutex_unlock(&detach_mutex);
> +
> + if (link) {
> + ret = link->ops->detach(link);
> + bpf_link_put(link);
> + }
> +
> + return ret;
> +}
> +
> BTF_KFUNCS_START(bpf_testmod_check_kfunc_ids)
> BTF_ID_FLAGS(func, bpf_testmod_test_mod_kfunc)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test1)
> @@ -529,6 +549,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test_destructive, KF_DESTRUCTIVE)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test_static_unused_arg)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test_offset)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test_sleepable, KF_SLEEPABLE)
> +BTF_ID_FLAGS(func, bpf_dummy_do_link_detach)
It should need KF_SLEEPABLE. mutex is used.
> BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids)
>
> static int bpf_testmod_ops_init(struct btf *btf)
> diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
> index f39455b81664..9f6657b53a93 100644
> --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
> +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
> @@ -229,6 +229,69 @@ static void test_detach_link(void)
> struct_ops_detach__destroy(skel);
> }
>
> +/* Detach a link from the subsystem that the link was registered to */
> +static void test_subsystem_detach(void)
> +{
> + LIBBPF_OPTS(bpf_test_run_opts, topts,
> + .data_in = &pkt_v4,
> + .data_size_in = sizeof(pkt_v4));
> + struct epoll_event ev, events[2];
> + struct struct_ops_detach *skel;
> + struct bpf_link *link = NULL;
> + int fd, epollfd = -1, nfds;
> + int prog_fd;
> + int err;
> +
> + skel = struct_ops_detach__open_and_load();
> + if (!ASSERT_OK_PTR(skel, "struct_ops_detach_open_and_load"))
> + return;
> +
> + link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
> + if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
> + goto cleanup;
> +
> + fd = bpf_link__fd(link);
> + if (!ASSERT_GE(fd, 0, "link_fd"))
> + goto cleanup;
> +
> + prog_fd = bpf_program__fd(skel->progs.start_detach);
> + if (!ASSERT_GE(prog_fd, 0, "start_detach_fd"))
> + goto cleanup;
> +
> + /* Do detachment from the registered subsystem */
> + err = bpf_prog_test_run_opts(prog_fd, &topts);
> + if (!ASSERT_OK(err, "start_detach_run"))
> + goto cleanup;
> +
> + if (!ASSERT_EQ(topts.retval, 0, "start_detach_run retval"))
> + goto cleanup;
> +
> + epollfd = epoll_create1(0);
> + if (!ASSERT_GE(epollfd, 0, "epoll_create1"))
> + goto cleanup;
> +
> + ev.events = EPOLLHUP;
> + ev.data.fd = fd;
> + err = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
> + if (!ASSERT_OK(err, "epoll_ctl"))
> + goto cleanup;
> +
> + /* Wait for EPOLLHUP */
> + nfds = epoll_wait(epollfd, events, 2, 5000);
> + if (!ASSERT_EQ(nfds, 1, "epoll_wait"))
> + goto cleanup;
> +
> + if (!ASSERT_EQ(events[0].data.fd, fd, "epoll_wait_fd"))
> + goto cleanup;
> + if (!ASSERT_TRUE(events[0].events & EPOLLHUP, "events[0].events"))
> + goto cleanup;
> +
> +cleanup:
> + close(epollfd);
> + bpf_link__destroy(link);
> + struct_ops_detach__destroy(skel);
> +}
> +
> void serial_test_struct_ops_module(void)
> {
> if (test__start_subtest("test_struct_ops_load"))
> @@ -239,5 +302,7 @@ void serial_test_struct_ops_module(void)
> test_struct_ops_incompatible();
> if (test__start_subtest("test_detach_link"))
> test_detach_link();
> + if (test__start_subtest("test_subsystem_detach"))
> + test_subsystem_detach();
> }
>
> diff --git a/tools/testing/selftests/bpf/progs/struct_ops_detach.c b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
> index aeb355b3bea3..139f9a5c5601 100644
> --- a/tools/testing/selftests/bpf/progs/struct_ops_detach.c
> +++ b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
> @@ -29,3 +29,9 @@ struct bpf_testmod_ops testmod_do_detach = {
> .test_1 = (void *)test_1,
> .test_2 = (void *)test_2,
> };
> +
> +SEC("tc")
The bpf_dummy_do_link_detach() uses a mutex. There is no lockdep splat in the test?
> +int start_detach(void *skb)
> +{
> + return bpf_dummy_do_link_detach();
> +}
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v2 5/6] selftests/bpf: detach a struct_ops link from the subsystem managing it.
2024-05-08 23:50 ` Martin KaFai Lau
@ 2024-05-09 5:50 ` Kui-Feng Lee
0 siblings, 0 replies; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-09 5:50 UTC (permalink / raw)
To: Martin KaFai Lau, Kui-Feng Lee
Cc: kuifeng, bpf, ast, song, kernel-team, andrii
On 5/8/24 16:50, Martin KaFai Lau wrote:
> On 5/6/24 10:55 PM, Kui-Feng Lee wrote:
>> Not only a user space program can detach a struct_ops link, the subsystem
>> managing a link can also detach the link. This patch add a kfunc to
>> simulate detaching a link by the subsystem managing it.
>>
>> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
>> ---
>> .../selftests/bpf/bpf_testmod/bpf_testmod.c | 21 ++++++
>> .../bpf/prog_tests/test_struct_ops_module.c | 65 +++++++++++++++++++
>> .../selftests/bpf/progs/struct_ops_detach.c | 6 ++
>> 3 files changed, 92 insertions(+)
>>
>> diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
>> b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
>> index c89a6414c69f..0bf1acc1767a 100644
>> --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
>> +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c
>> @@ -502,6 +502,26 @@ __bpf_kfunc void bpf_kfunc_call_test_sleepable(void)
>> static DEFINE_MUTEX(detach_mutex);
>> static struct bpf_link *link_to_detach;
>> +__bpf_kfunc int bpf_dummy_do_link_detach(void)
>> +{
>> + struct bpf_link *link;
>> + int ret = -ENOENT;
>> +
>> + mutex_lock(&detach_mutex);
>> + link = link_to_detach;
>> + /* Make sure the link is still valid by increasing its refcnt */
>> + if (link && !atomic64_inc_not_zero(&link->refcnt))
>
> It is better to reuse the bpf_link_inc_not_zero().
I will export this function to be used by modules.
>
>> + link = NULL;
>> + mutex_unlock(&detach_mutex);
>> +
>> + if (link) {
>> + ret = link->ops->detach(link);
>> + bpf_link_put(link);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> BTF_KFUNCS_START(bpf_testmod_check_kfunc_ids)
>> BTF_ID_FLAGS(func, bpf_testmod_test_mod_kfunc)
>> BTF_ID_FLAGS(func, bpf_kfunc_call_test1)
>> @@ -529,6 +549,7 @@ BTF_ID_FLAGS(func,
>> bpf_kfunc_call_test_destructive, KF_DESTRUCTIVE)
>> BTF_ID_FLAGS(func, bpf_kfunc_call_test_static_unused_arg)
>> BTF_ID_FLAGS(func, bpf_kfunc_call_test_offset)
>> BTF_ID_FLAGS(func, bpf_kfunc_call_test_sleepable, KF_SLEEPABLE)
>> +BTF_ID_FLAGS(func, bpf_dummy_do_link_detach)
>
> It should need KF_SLEEPABLE. mutex is used.
To simplify it, spinlock will be used instead.
>
>> BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids)
>> static int bpf_testmod_ops_init(struct btf *btf)
>> diff --git
>> a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> index f39455b81664..9f6657b53a93 100644
>> --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> @@ -229,6 +229,69 @@ static void test_detach_link(void)
>> struct_ops_detach__destroy(skel);
>> }
>> +/* Detach a link from the subsystem that the link was registered to */
>> +static void test_subsystem_detach(void)
>> +{
>> + LIBBPF_OPTS(bpf_test_run_opts, topts,
>> + .data_in = &pkt_v4,
>> + .data_size_in = sizeof(pkt_v4));
>> + struct epoll_event ev, events[2];
>> + struct struct_ops_detach *skel;
>> + struct bpf_link *link = NULL;
>> + int fd, epollfd = -1, nfds;
>> + int prog_fd;
>> + int err;
>> +
>> + skel = struct_ops_detach__open_and_load();
>> + if (!ASSERT_OK_PTR(skel, "struct_ops_detach_open_and_load"))
>> + return;
>> +
>> + link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
>> + if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
>> + goto cleanup;
>> +
>> + fd = bpf_link__fd(link);
>> + if (!ASSERT_GE(fd, 0, "link_fd"))
>> + goto cleanup;
>> +
>> + prog_fd = bpf_program__fd(skel->progs.start_detach);
>> + if (!ASSERT_GE(prog_fd, 0, "start_detach_fd"))
>> + goto cleanup;
>> +
>> + /* Do detachment from the registered subsystem */
>> + err = bpf_prog_test_run_opts(prog_fd, &topts);
>> + if (!ASSERT_OK(err, "start_detach_run"))
>> + goto cleanup;
>> +
>> + if (!ASSERT_EQ(topts.retval, 0, "start_detach_run retval"))
>> + goto cleanup;
>> +
>> + epollfd = epoll_create1(0);
>> + if (!ASSERT_GE(epollfd, 0, "epoll_create1"))
>> + goto cleanup;
>> +
>> + ev.events = EPOLLHUP;
>> + ev.data.fd = fd;
>> + err = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
>> + if (!ASSERT_OK(err, "epoll_ctl"))
>> + goto cleanup;
>> +
>> + /* Wait for EPOLLHUP */
>> + nfds = epoll_wait(epollfd, events, 2, 5000);
>> + if (!ASSERT_EQ(nfds, 1, "epoll_wait"))
>> + goto cleanup;
>> +
>> + if (!ASSERT_EQ(events[0].data.fd, fd, "epoll_wait_fd"))
>> + goto cleanup;
>> + if (!ASSERT_TRUE(events[0].events & EPOLLHUP, "events[0].events"))
>> + goto cleanup;
>> +
>> +cleanup:
>> + close(epollfd);
>> + bpf_link__destroy(link);
>> + struct_ops_detach__destroy(skel);
>> +}
>> +
>> void serial_test_struct_ops_module(void)
>> {
>> if (test__start_subtest("test_struct_ops_load"))
>> @@ -239,5 +302,7 @@ void serial_test_struct_ops_module(void)
>> test_struct_ops_incompatible();
>> if (test__start_subtest("test_detach_link"))
>> test_detach_link();
>> + if (test__start_subtest("test_subsystem_detach"))
>> + test_subsystem_detach();
>> }
>> diff --git a/tools/testing/selftests/bpf/progs/struct_ops_detach.c
>> b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
>> index aeb355b3bea3..139f9a5c5601 100644
>> --- a/tools/testing/selftests/bpf/progs/struct_ops_detach.c
>> +++ b/tools/testing/selftests/bpf/progs/struct_ops_detach.c
>> @@ -29,3 +29,9 @@ struct bpf_testmod_ops testmod_do_detach = {
>> .test_1 = (void *)test_1,
>> .test_2 = (void *)test_2,
>> };
>> +
>> +SEC("tc")
>
> The bpf_dummy_do_link_detach() uses a mutex. There is no lockdep splat
> in the test?
>
>> +int start_detach(void *skb)
>> +{
>> + return bpf_dummy_do_link_detach();
>> +}
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH bpf-next v2 6/6] selftests/bpf: make sure bpf_testmod handling racing link destroying well.
2024-05-07 5:55 [PATCH bpf-next v2 0/6] Notify user space when a struct_ops object is detached/unregistered Kui-Feng Lee
` (4 preceding siblings ...)
2024-05-07 5:55 ` [PATCH bpf-next v2 5/6] selftests/bpf: detach a struct_ops link from the subsystem managing it Kui-Feng Lee
@ 2024-05-07 5:56 ` Kui-Feng Lee
2024-05-09 0:04 ` Martin KaFai Lau
5 siblings, 1 reply; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-07 5:56 UTC (permalink / raw)
To: bpf, ast, martin.lau, song, kernel-team, andrii
Cc: sinquersw, kuifeng, Kui-Feng Lee
Subsystems that manage struct_ops objects may attempt to detach a link when
the link has been released or is about to be released. The test in
this patch demonstrate to developers the correct way to handle this
situation using a locking mechanism and atomic64_inc_not_zero().
A subsystem must ensure that a link is valid when detaching the link. In
order to achieve that, the subsystem may need to obtain a lock to safeguard
a table that holds the pointer to the link being detached. However, the
subsystem cannot invoke link->ops->detach() while holding the lock because
other tasks may be in the process of unregistering, which could lead to a
deadlock. This is why atomic64_inc_not_zero() is used to maintain the
link's validity. (Refer to bpf_dummy_do_link_detach() in the previous patch
for more details.)
This test make sure the pattern mentioned above work correctly.
Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
.../bpf/prog_tests/test_struct_ops_module.c | 44 +++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
index 9f6657b53a93..1e37037cfd8a 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
@@ -292,6 +292,48 @@ static void test_subsystem_detach(void)
struct_ops_detach__destroy(skel);
}
+/* A subsystem detachs a link while the link is going to be free. */
+static void test_subsystem_detach_free(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts,
+ .data_in = &pkt_v4,
+ .data_size_in = sizeof(pkt_v4));
+ struct struct_ops_detach *skel;
+ struct bpf_link *link = NULL;
+ int prog_fd;
+ int err;
+
+ skel = struct_ops_detach__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "struct_ops_detach_open_and_load"))
+ return;
+
+ link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
+ if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
+ goto cleanup;
+
+ bpf_link__destroy(link);
+
+ prog_fd = bpf_program__fd(skel->progs.start_detach);
+ if (!ASSERT_GE(prog_fd, 0, "start_detach_fd"))
+ goto cleanup;
+
+ /* Do detachment from the registered subsystem */
+ err = bpf_prog_test_run_opts(prog_fd, &topts);
+ if (!ASSERT_OK(err, "start_detach_run"))
+ goto cleanup;
+
+ /* The link may have zero refcount value and may have been
+ * unregistered, so the detachment from the subsystem should fail.
+ */
+ ASSERT_EQ(topts.retval, (u32)-ENOENT, "start_detach_run retval");
+
+ /* Sync RCU to make sure the link is freed without any crash */
+ ASSERT_OK(kern_sync_rcu(), "sync rcu");
+
+cleanup:
+ struct_ops_detach__destroy(skel);
+}
+
void serial_test_struct_ops_module(void)
{
if (test__start_subtest("test_struct_ops_load"))
@@ -304,5 +346,7 @@ void serial_test_struct_ops_module(void)
test_detach_link();
if (test__start_subtest("test_subsystem_detach"))
test_subsystem_detach();
+ if (test__start_subtest("test_subsystem_detach_free"))
+ test_subsystem_detach_free();
}
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v2 6/6] selftests/bpf: make sure bpf_testmod handling racing link destroying well.
2024-05-07 5:56 ` [PATCH bpf-next v2 6/6] selftests/bpf: make sure bpf_testmod handling racing link destroying well Kui-Feng Lee
@ 2024-05-09 0:04 ` Martin KaFai Lau
2024-05-09 17:02 ` Kui-Feng Lee
0 siblings, 1 reply; 18+ messages in thread
From: Martin KaFai Lau @ 2024-05-09 0:04 UTC (permalink / raw)
To: Kui-Feng Lee; +Cc: sinquersw, kuifeng, bpf, ast, song, kernel-team, andrii
On 5/6/24 10:56 PM, Kui-Feng Lee wrote:
> Subsystems that manage struct_ops objects may attempt to detach a link when
> the link has been released or is about to be released. The test in
> this patch demonstrate to developers the correct way to handle this
> situation using a locking mechanism and atomic64_inc_not_zero().
>
> A subsystem must ensure that a link is valid when detaching the link. In
> order to achieve that, the subsystem may need to obtain a lock to safeguard
> a table that holds the pointer to the link being detached. However, the
> subsystem cannot invoke link->ops->detach() while holding the lock because
> other tasks may be in the process of unregistering, which could lead to a
> deadlock. This is why atomic64_inc_not_zero() is used to maintain the
Other tasks un-registering in parallel is not the reason for deadlock. The
deadlock is because the link detach will call unreg() which usually will acquire
the same lock (the detach_mutex here) and there is lock ordering with the
update_mutex also. Hence, the link detach must be done after releasing the
detach_mutex. After releasing the detach_mutex, the link is protected by its refcnt.
I think the above should be put as comments in bpf_dummy_do_link_detach for the
subsystem to reference later.
> link's validity. (Refer to bpf_dummy_do_link_detach() in the previous patch
> for more details.)
>
> This test make sure the pattern mentioned above work correctly.
>
> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
> ---
> .../bpf/prog_tests/test_struct_ops_module.c | 44 +++++++++++++++++++
> 1 file changed, 44 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
> index 9f6657b53a93..1e37037cfd8a 100644
> --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
> +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
> @@ -292,6 +292,48 @@ static void test_subsystem_detach(void)
> struct_ops_detach__destroy(skel);
> }
>
> +/* A subsystem detachs a link while the link is going to be free. */
> +static void test_subsystem_detach_free(void)
> +{
> + LIBBPF_OPTS(bpf_test_run_opts, topts,
> + .data_in = &pkt_v4,
> + .data_size_in = sizeof(pkt_v4));
> + struct struct_ops_detach *skel;
> + struct bpf_link *link = NULL;
> + int prog_fd;
> + int err;
> +
> + skel = struct_ops_detach__open_and_load();
> + if (!ASSERT_OK_PTR(skel, "struct_ops_detach_open_and_load"))
> + return;
> +
> + link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
> + if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
> + goto cleanup;
> +
> + bpf_link__destroy(link);
> +
> + prog_fd = bpf_program__fd(skel->progs.start_detach);
> + if (!ASSERT_GE(prog_fd, 0, "start_detach_fd"))
> + goto cleanup;
> +
> + /* Do detachment from the registered subsystem */
> + err = bpf_prog_test_run_opts(prog_fd, &topts);
> + if (!ASSERT_OK(err, "start_detach_run"))
> + goto cleanup;
> +
> + /* The link may have zero refcount value and may have been
> + * unregistered, so the detachment from the subsystem should fail.
> + */
> + ASSERT_EQ(topts.retval, (u32)-ENOENT, "start_detach_run retval");
> +
> + /* Sync RCU to make sure the link is freed without any crash */
> + ASSERT_OK(kern_sync_rcu(), "sync rcu");
> +
> +cleanup:
> + struct_ops_detach__destroy(skel);
> +}
> +
> void serial_test_struct_ops_module(void)
> {
> if (test__start_subtest("test_struct_ops_load"))
> @@ -304,5 +346,7 @@ void serial_test_struct_ops_module(void)
> test_detach_link();
> if (test__start_subtest("test_subsystem_detach"))
> test_subsystem_detach();
> + if (test__start_subtest("test_subsystem_detach_free"))
> + test_subsystem_detach_free();
> }
>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH bpf-next v2 6/6] selftests/bpf: make sure bpf_testmod handling racing link destroying well.
2024-05-09 0:04 ` Martin KaFai Lau
@ 2024-05-09 17:02 ` Kui-Feng Lee
0 siblings, 0 replies; 18+ messages in thread
From: Kui-Feng Lee @ 2024-05-09 17:02 UTC (permalink / raw)
To: Martin KaFai Lau, Kui-Feng Lee
Cc: kuifeng, bpf, ast, song, kernel-team, andrii
On 5/8/24 17:04, Martin KaFai Lau wrote:
> On 5/6/24 10:56 PM, Kui-Feng Lee wrote:
>> Subsystems that manage struct_ops objects may attempt to detach a link
>> when
>> the link has been released or is about to be released. The test in
>> this patch demonstrate to developers the correct way to handle this
>> situation using a locking mechanism and atomic64_inc_not_zero().
>>
>> A subsystem must ensure that a link is valid when detaching the link. In
>> order to achieve that, the subsystem may need to obtain a lock to
>> safeguard
>> a table that holds the pointer to the link being detached. However, the
>> subsystem cannot invoke link->ops->detach() while holding the lock
>> because
>> other tasks may be in the process of unregistering, which could lead to a
>> deadlock. This is why atomic64_inc_not_zero() is used to maintain the
>
> Other tasks un-registering in parallel is not the reason for deadlock.
> The deadlock is because the link detach will call unreg() which usually
> will acquire the same lock (the detach_mutex here) and there is lock
> ordering with the update_mutex also. Hence, the link detach must be done
> after releasing the detach_mutex. After releasing the detach_mutex, the
> link is protected by its refcnt.
It is what I mean in the commit log. I will rephrase it to emphasize
holding the same lock.
>
> I think the above should be put as comments in bpf_dummy_do_link_detach
> for the subsystem to reference later.
ok!
>
>> link's validity. (Refer to bpf_dummy_do_link_detach() in the previous
>> patch
>> for more details.)
>>
>> This test make sure the pattern mentioned above work correctly.
>>
>> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
>> ---
>> .../bpf/prog_tests/test_struct_ops_module.c | 44 +++++++++++++++++++
>> 1 file changed, 44 insertions(+)
>>
>> diff --git
>> a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> index 9f6657b53a93..1e37037cfd8a 100644
>> --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
>> @@ -292,6 +292,48 @@ static void test_subsystem_detach(void)
>> struct_ops_detach__destroy(skel);
>> }
>> +/* A subsystem detachs a link while the link is going to be free. */
>> +static void test_subsystem_detach_free(void)
>> +{
>> + LIBBPF_OPTS(bpf_test_run_opts, topts,
>> + .data_in = &pkt_v4,
>> + .data_size_in = sizeof(pkt_v4));
>> + struct struct_ops_detach *skel;
>> + struct bpf_link *link = NULL;
>> + int prog_fd;
>> + int err;
>> +
>> + skel = struct_ops_detach__open_and_load();
>> + if (!ASSERT_OK_PTR(skel, "struct_ops_detach_open_and_load"))
>> + return;
>> +
>> + link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
>> + if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
>> + goto cleanup;
>> +
>> + bpf_link__destroy(link);
>> +
>> + prog_fd = bpf_program__fd(skel->progs.start_detach);
>> + if (!ASSERT_GE(prog_fd, 0, "start_detach_fd"))
>> + goto cleanup;
>> +
>> + /* Do detachment from the registered subsystem */
>> + err = bpf_prog_test_run_opts(prog_fd, &topts);
>> + if (!ASSERT_OK(err, "start_detach_run"))
>> + goto cleanup;
>> +
>> + /* The link may have zero refcount value and may have been
>> + * unregistered, so the detachment from the subsystem should fail.
>> + */
>> + ASSERT_EQ(topts.retval, (u32)-ENOENT, "start_detach_run retval");
>> +
>> + /* Sync RCU to make sure the link is freed without any crash */
>> + ASSERT_OK(kern_sync_rcu(), "sync rcu");
>> +
>> +cleanup:
>> + struct_ops_detach__destroy(skel);
>> +}
>> +
>> void serial_test_struct_ops_module(void)
>> {
>> if (test__start_subtest("test_struct_ops_load"))
>> @@ -304,5 +346,7 @@ void serial_test_struct_ops_module(void)
>> test_detach_link();
>> if (test__start_subtest("test_subsystem_detach"))
>> test_subsystem_detach();
>> + if (test__start_subtest("test_subsystem_detach_free"))
>> + test_subsystem_detach_free();
>> }
>
^ permalink raw reply [flat|nested] 18+ messages in thread