They are implemented as a workqueue, which means that there are no guarantees of timing nor ordering.
Signed-off-by: Benjamin Tissoires bentiss@kernel.org
---
changes in v6: - dropped extra spinlock - implement cancel_and_free of a sleepable timer through a dedicated workqueue
no changes in v5
changes in v4: - dropped __bpf_timer_compute_key() - use a spin_lock instead of a semaphore - ensure bpf_timer_cancel_and_free is not complaining about non sleepable context and use cancel_work() instead of cancel_work_sync() - return -EINVAL if a delay is given to bpf_timer_start() with BPF_F_TIMER_SLEEPABLE
changes in v3: - extracted the implementation in bpf_timer only, without bpf_timer_set_sleepable_cb() - rely on schedule_work() only, from bpf_timer_start() - add semaphore to ensure bpf_timer_work_cb() is accessing consistent data
changes in v2 (compared to the one attaches to v1 0/9): - make use of a kfunc - add a (non-used) BPF_F_TIMER_SLEEPABLE - the callback is *not* called, it makes the kernel crashes --- include/uapi/linux/bpf.h | 13 ++++ kernel/bpf/helpers.c | 159 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 143 insertions(+), 29 deletions(-)
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 9585f5345353..f1890eed213a 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -7469,6 +7469,19 @@ enum { BPF_F_TIMER_CPU_PIN = (1ULL << 1), };
+/* Extra flags to control bpf_timer_init() behaviour, in addition to CLOCK_*. + * - BPF_F_TIMER_SLEEPABLE: Timer will run in a workqueue in a sleepable + * context, with no guarantees of ordering nor timing (consider this as + * being just offloaded immediately). + */ +enum { + /* CLOCK_* are using bits 0 to 3 */ + BPF_F_TIMER_SLEEPABLE = (1ULL << 4), + __MAX_BPF_F_TIMER_INIT, +}; + +#define MAX_BPF_F_TIMER_INIT __MAX_BPF_F_TIMER_INIT + /* BPF numbers iterator state */ struct bpf_iter_num { /* opaque iterator state; having __u64 here allows to preserve correct diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index 9234174ccb21..fd05d4358b31 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -1096,12 +1096,19 @@ const struct bpf_func_proto bpf_snprintf_proto = { * freeing the timers when inner map is replaced or deleted by user space. */ struct bpf_hrtimer { - struct hrtimer timer; + union { + struct hrtimer timer; + struct work_struct work; + }; struct bpf_map *map; struct bpf_prog *prog; void __rcu *callback_fn; void *value; - struct rcu_head rcu; + union { + struct rcu_head rcu; + struct work_struct sync_work; + }; + u64 flags; };
/* the actual struct hidden inside uapi struct bpf_timer */ @@ -1114,6 +1121,58 @@ struct bpf_timer_kern { struct bpf_spin_lock lock; } __attribute__((aligned(8)));
+static void bpf_timer_work_cb(struct work_struct *work) +{ + struct bpf_hrtimer *t = container_of(work, struct bpf_hrtimer, work); + struct bpf_tramp_run_ctx __maybe_unused run_ctx; + struct bpf_prog *prog = t->prog; + struct bpf_map *map = t->map; + bpf_callback_t callback_fn; + void *value = t->value; + unsigned long flags; + void *key; + u32 idx; + + BTF_TYPE_EMIT(struct bpf_timer); + + callback_fn = READ_ONCE(t->callback_fn); + if (!callback_fn || !prog) + return; + + if (map->map_type == BPF_MAP_TYPE_ARRAY) { + struct bpf_array *array = container_of(map, struct bpf_array, map); + + /* compute the key */ + idx = ((char *)value - array->value) / array->elem_size; + key = &idx; + } else { /* hash or lru */ + key = value - round_up(map->key_size, 8); + } + + run_ctx.bpf_cookie = 0; + + if (!__bpf_prog_enter_sleepable_recur(prog, &run_ctx)) { + /* recursion detected */ + __bpf_prog_exit_sleepable_recur(prog, 0, &run_ctx); + return; + } + + callback_fn((u64)(long)map, (u64)(long)key, (u64)(long)value, 0, 0); + /* The verifier checked that return value is zero. */ + + __bpf_prog_exit_sleepable_recur(prog, 0 /* bpf_prog_run does runtime stats */, + &run_ctx); +} + +static void bpf_timer_sync_work_cb(struct work_struct *work) +{ + struct bpf_hrtimer *t = container_of(work, struct bpf_hrtimer, sync_work); + + cancel_work_sync(&t->work); + + kfree_rcu(t, rcu); +} + static DEFINE_PER_CPU(struct bpf_hrtimer *, hrtimer_running);
static enum hrtimer_restart bpf_timer_cb(struct hrtimer *hrtimer) @@ -1155,10 +1214,14 @@ static enum hrtimer_restart bpf_timer_cb(struct hrtimer *hrtimer) return HRTIMER_NORESTART; }
+#define BPF_TIMER_CLOCK_MASK (MAX_CLOCKS - 1) /* 0xf */ +#define BPF_TIMER_FLAGS_MASK GENMASK_ULL(63, 4) + BPF_CALL_3(bpf_timer_init, struct bpf_timer_kern *, timer, struct bpf_map *, map, u64, flags) { - clockid_t clockid = flags & (MAX_CLOCKS - 1); + clockid_t clockid = flags & BPF_TIMER_CLOCK_MASK; + u64 bpf_timer_flags = flags & BPF_TIMER_FLAGS_MASK; struct bpf_hrtimer *t; int ret = 0;
@@ -1169,7 +1232,7 @@ BPF_CALL_3(bpf_timer_init, struct bpf_timer_kern *, timer, struct bpf_map *, map if (in_nmi()) return -EOPNOTSUPP;
- if (flags >= MAX_CLOCKS || + if (bpf_timer_flags & ~(BPF_F_TIMER_SLEEPABLE) || /* similar to timerfd except _ALARM variants are not supported */ (clockid != CLOCK_MONOTONIC && clockid != CLOCK_REALTIME && @@ -1190,8 +1253,14 @@ BPF_CALL_3(bpf_timer_init, struct bpf_timer_kern *, timer, struct bpf_map *, map t->value = (void *)timer - map->record->timer_off; t->map = map; t->prog = NULL; + t->flags = flags; rcu_assign_pointer(t->callback_fn, NULL); - hrtimer_init(&t->timer, clockid, HRTIMER_MODE_REL_SOFT); + if (flags & BPF_F_TIMER_SLEEPABLE) { + INIT_WORK(&t->work, bpf_timer_work_cb); + INIT_WORK(&t->sync_work, bpf_timer_sync_work_cb); + } else { + hrtimer_init(&t->timer, clockid, HRTIMER_MODE_REL_SOFT); + } t->timer.function = bpf_timer_cb; WRITE_ONCE(timer->timer, t); /* Guarantee the order between timer->timer and map->usercnt. So @@ -1292,6 +1361,11 @@ BPF_CALL_3(bpf_timer_start, struct bpf_timer_kern *, timer, u64, nsecs, u64, fla goto out; }
+ if ((t->flags & BPF_F_TIMER_SLEEPABLE) && nsecs) { + ret = -EINVAL; + goto out; + } + if (flags & BPF_F_TIMER_ABS) mode = HRTIMER_MODE_ABS_SOFT; else @@ -1300,7 +1374,10 @@ BPF_CALL_3(bpf_timer_start, struct bpf_timer_kern *, timer, u64, nsecs, u64, fla if (flags & BPF_F_TIMER_CPU_PIN) mode |= HRTIMER_MODE_PINNED;
- hrtimer_start(&t->timer, ns_to_ktime(nsecs), mode); + if (t->flags & BPF_F_TIMER_SLEEPABLE) + schedule_work(&t->work); + else + hrtimer_start(&t->timer, ns_to_ktime(nsecs), mode); out: __bpf_spin_unlock_irqrestore(&timer->lock); return ret; @@ -1329,6 +1406,7 @@ static void drop_prog_refcnt(struct bpf_hrtimer *t) BPF_CALL_1(bpf_timer_cancel, struct bpf_timer_kern *, timer) { struct bpf_hrtimer *t; + u64 flags = 0; int ret = 0;
if (in_nmi()) @@ -1340,6 +1418,7 @@ BPF_CALL_1(bpf_timer_cancel, struct bpf_timer_kern *, timer) ret = -EINVAL; goto out; } + flags = t->flags; if (this_cpu_read(hrtimer_running) == t) { /* If bpf callback_fn is trying to bpf_timer_cancel() * its own timer the hrtimer_cancel() will deadlock @@ -1351,10 +1430,20 @@ BPF_CALL_1(bpf_timer_cancel, struct bpf_timer_kern *, timer) drop_prog_refcnt(t); out: __bpf_spin_unlock_irqrestore(&timer->lock); - /* Cancel the timer and wait for associated callback to finish - * if it was running. - */ - ret = ret ?: hrtimer_cancel(&t->timer); + + if (flags & BPF_F_TIMER_SLEEPABLE) { + /* Cancel the sleepable work, but *do not* wait for + * it to finish if it was running as we might not be in a + * sleepable context + */ + ret = ret ?: cancel_work(&t->work); + } else { + /* Cancel the timer and wait for associated callback to finish + * if it was running. + */ + ret = ret ?: hrtimer_cancel(&t->timer); + } + rcu_read_unlock(); return ret; } @@ -1373,6 +1462,7 @@ void bpf_timer_cancel_and_free(void *val) { struct bpf_timer_kern *timer = val; struct bpf_hrtimer *t; + u64 flags;
/* Performance optimization: read timer->timer without lock first. */ if (!READ_ONCE(timer->timer)) @@ -1383,6 +1473,7 @@ void bpf_timer_cancel_and_free(void *val) t = timer->timer; if (!t) goto out; + flags = t->flags; drop_prog_refcnt(t); /* The subsequent bpf_timer_start/cancel() helpers won't be able to use * this timer, since it won't be initialized. @@ -1392,25 +1483,35 @@ void bpf_timer_cancel_and_free(void *val) __bpf_spin_unlock_irqrestore(&timer->lock); if (!t) return; - /* Cancel the timer and wait for callback to complete if it was running. - * If hrtimer_cancel() can be safely called it's safe to call kfree(t) - * right after for both preallocated and non-preallocated maps. - * The timer->timer = NULL was already done and no code path can - * see address 't' anymore. - * - * Check that bpf_map_delete/update_elem() wasn't called from timer - * callback_fn. In such case don't call hrtimer_cancel() (since it will - * deadlock) and don't call hrtimer_try_to_cancel() (since it will just - * return -1). Though callback_fn is still running on this cpu it's - * safe to do kfree(t) because bpf_timer_cb() read everything it needed - * from 't'. The bpf subprog callback_fn won't be able to access 't', - * since timer->timer = NULL was already done. The timer will be - * effectively cancelled because bpf_timer_cb() will return - * HRTIMER_NORESTART. - */ - if (this_cpu_read(hrtimer_running) != t) - hrtimer_cancel(&t->timer); - kfree_rcu(t, rcu); + + if (flags & BPF_F_TIMER_SLEEPABLE) { + /* Trigger cancel of the sleepable work, but *do not* wait for + * it to finish if it was running as we might not be in a + * sleepable context. + * kfree will be called once the work has finished. + */ + schedule_work(&t->sync_work); + } else { + /* Cancel the timer and wait for callback to complete if it was running. + * If hrtimer_cancel() can be safely called it's safe to call kfree(t) + * right after for both preallocated and non-preallocated maps. + * The timer->timer = NULL was already done and no code path can + * see address 't' anymore. + * + * Check that bpf_map_delete/update_elem() wasn't called from timer + * callback_fn. In such case don't call hrtimer_cancel() (since it will + * deadlock) and don't call hrtimer_try_to_cancel() (since it will just + * return -1). Though callback_fn is still running on this cpu it's + * safe to do kfree(t) because bpf_timer_cb() read everything it needed + * from 't'. The bpf subprog callback_fn won't be able to access 't', + * since timer->timer = NULL was already done. The timer will be + * effectively cancelled because bpf_timer_cb() will return + * HRTIMER_NORESTART. + */ + if (this_cpu_read(hrtimer_running) != t) + hrtimer_cancel(&t->timer); + kfree_rcu(t, rcu); + } }
BPF_CALL_2(bpf_kptr_xchg, void *, map_value, void *, ptr)