root/io_uring/bpf-ops.c
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/mutex.h>
#include <linux/bpf.h>
#include <linux/bpf_verifier.h>

#include "io_uring.h"
#include "register.h"
#include "loop.h"
#include "memmap.h"
#include "bpf-ops.h"

static DEFINE_MUTEX(io_bpf_ctrl_mutex);
static const struct btf_type *loop_params_type;

__bpf_kfunc_start_defs();

__bpf_kfunc int bpf_io_uring_submit_sqes(struct iou_ctx *loop_ctx, u32 nr)
{
        struct io_ring_ctx *ctx = io_loop_demangle_ctx(loop_ctx);

        return io_submit_sqes(ctx, nr);
}

__bpf_kfunc
__u8 *bpf_io_uring_get_region(struct iou_ctx *loop_ctx, __u32 region_id,
                              const size_t rdwr_buf_size)
{
        struct io_ring_ctx *ctx = io_loop_demangle_ctx(loop_ctx);
        struct io_mapped_region *r;

        lockdep_assert_held(&ctx->uring_lock);

        switch (region_id) {
        case IOU_REGION_MEM:
                r = &ctx->param_region;
                break;
        case IOU_REGION_CQ:
                r = &ctx->ring_region;
                break;
        case IOU_REGION_SQ:
                r = &ctx->sq_region;
                break;
        default:
                return NULL;
        }

        if (unlikely(rdwr_buf_size > io_region_size(r)))
                return NULL;
        return io_region_get_ptr(r);
}

__bpf_kfunc_end_defs();

BTF_KFUNCS_START(io_uring_kfunc_set)
BTF_ID_FLAGS(func, bpf_io_uring_submit_sqes, KF_SLEEPABLE);
BTF_ID_FLAGS(func, bpf_io_uring_get_region, KF_RET_NULL);
BTF_KFUNCS_END(io_uring_kfunc_set)

static const struct btf_kfunc_id_set bpf_io_uring_kfunc_set = {
        .owner = THIS_MODULE,
        .set = &io_uring_kfunc_set,
};

static int io_bpf_ops__loop_step(struct iou_ctx *ctx,
                                 struct iou_loop_params *lp)
{
        return IOU_LOOP_STOP;
}

static struct io_uring_bpf_ops io_bpf_ops_stubs = {
        .loop_step = io_bpf_ops__loop_step,
};

static bool bpf_io_is_valid_access(int off, int size,
                                    enum bpf_access_type type,
                                    const struct bpf_prog *prog,
                                    struct bpf_insn_access_aux *info)
{
        if (type != BPF_READ)
                return false;
        if (off < 0 || off >= sizeof(__u64) * MAX_BPF_FUNC_ARGS)
                return false;
        if (off % size != 0)
                return false;

        return btf_ctx_access(off, size, type, prog, info);
}

static int bpf_io_btf_struct_access(struct bpf_verifier_log *log,
                                    const struct bpf_reg_state *reg, int off,
                                    int size)
{
        const struct btf_type *t = btf_type_by_id(reg->btf, reg->btf_id);

        if (t == loop_params_type) {
                if (off + size <= offsetofend(struct iou_loop_params, cq_wait_idx))
                        return SCALAR_VALUE;
        }

        return -EACCES;
}

static const struct bpf_verifier_ops bpf_io_verifier_ops = {
        .get_func_proto = bpf_base_func_proto,
        .is_valid_access = bpf_io_is_valid_access,
        .btf_struct_access = bpf_io_btf_struct_access,
};

static const struct btf_type *
io_lookup_struct_type(struct btf *btf, const char *name)
{
        s32 type_id;

        type_id = btf_find_by_name_kind(btf, name, BTF_KIND_STRUCT);
        if (type_id < 0)
                return NULL;
        return btf_type_by_id(btf, type_id);
}

static int bpf_io_init(struct btf *btf)
{
        int ret;

        loop_params_type = io_lookup_struct_type(btf, "iou_loop_params");
        if (!loop_params_type) {
                pr_err("io_uring: Failed to locate iou_loop_params\n");
                return -EINVAL;
        }

        ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
                                        &bpf_io_uring_kfunc_set);
        if (ret) {
                pr_err("io_uring: Failed to register kfuncs (%d)\n", ret);
                return ret;
        }
        return 0;
}

static int bpf_io_check_member(const struct btf_type *t,
                                const struct btf_member *member,
                                const struct bpf_prog *prog)
{
        return 0;
}

static int bpf_io_init_member(const struct btf_type *t,
                               const struct btf_member *member,
                               void *kdata, const void *udata)
{
        u32 moff = __btf_member_bit_offset(t, member) / 8;
        const struct io_uring_bpf_ops *uops = udata;
        struct io_uring_bpf_ops *ops = kdata;

        switch (moff) {
        case offsetof(struct io_uring_bpf_ops, ring_fd):
                ops->ring_fd = uops->ring_fd;
                return 1;
        }
        return 0;
}

static int io_install_bpf(struct io_ring_ctx *ctx, struct io_uring_bpf_ops *ops)
{
        if (ctx->flags & (IORING_SETUP_SQPOLL | IORING_SETUP_IOPOLL))
                return -EOPNOTSUPP;
        if (!(ctx->flags & IORING_SETUP_DEFER_TASKRUN))
                return -EOPNOTSUPP;

        if (ctx->bpf_ops)
                return -EBUSY;
        if (ops->priv)
                return -EBUSY;
        if (WARN_ON_ONCE(!ops->loop_step))
                return -EINVAL;

        ops->priv = ctx;
        ctx->bpf_ops = ops;
        ctx->loop_step = ops->loop_step;
        return 0;
}

static int bpf_io_reg(void *kdata, struct bpf_link *link)
{
        struct io_uring_bpf_ops *ops = kdata;
        struct io_ring_ctx *ctx;
        struct file *file;
        int ret = -EBUSY;

        file = io_uring_ctx_get_file(ops->ring_fd, false);
        if (IS_ERR(file))
                return PTR_ERR(file);
        ctx = file->private_data;

        scoped_guard(mutex, &io_bpf_ctrl_mutex) {
                guard(mutex)(&ctx->uring_lock);
                ret = io_install_bpf(ctx, ops);
        }

        fput(file);
        return ret;
}

static void io_eject_bpf(struct io_ring_ctx *ctx)
{
        struct io_uring_bpf_ops *ops = ctx->bpf_ops;

        if (WARN_ON_ONCE(!ops))
                return;
        if (WARN_ON_ONCE(ops->priv != ctx))
                return;

        ops->priv = NULL;
        ctx->bpf_ops = NULL;
        ctx->loop_step = NULL;
}

static void bpf_io_unreg(void *kdata, struct bpf_link *link)
{
        struct io_uring_bpf_ops *ops = kdata;
        struct io_ring_ctx *ctx;

        guard(mutex)(&io_bpf_ctrl_mutex);
        ctx = ops->priv;
        if (ctx) {
                guard(mutex)(&ctx->uring_lock);
                if (WARN_ON_ONCE(ctx->bpf_ops != ops))
                        return;

                io_eject_bpf(ctx);
        }
}

void io_unregister_bpf_ops(struct io_ring_ctx *ctx)
{
        /*
         * ->bpf_ops is write protected by io_bpf_ctrl_mutex and uring_lock,
         * and read protected by either. Try to avoid taking the global lock
         * for rings that never had any bpf installed.
         */
        scoped_guard(mutex, &ctx->uring_lock) {
                if (!ctx->bpf_ops)
                        return;
        }

        guard(mutex)(&io_bpf_ctrl_mutex);
        guard(mutex)(&ctx->uring_lock);
        if (ctx->bpf_ops)
                io_eject_bpf(ctx);
}

static struct bpf_struct_ops bpf_ring_ops = {
        .verifier_ops = &bpf_io_verifier_ops,
        .reg = bpf_io_reg,
        .unreg = bpf_io_unreg,
        .check_member = bpf_io_check_member,
        .init_member = bpf_io_init_member,
        .init = bpf_io_init,
        .cfi_stubs = &io_bpf_ops_stubs,
        .name = "io_uring_bpf_ops",
        .owner = THIS_MODULE,
};

static int __init io_uring_bpf_init(void)
{
        int ret;

        ret = register_bpf_struct_ops(&bpf_ring_ops, io_uring_bpf_ops);
        if (ret) {
                pr_err("io_uring: Failed to register struct_ops (%d)\n", ret);
                return ret;
        }

        return 0;
}
__initcall(io_uring_bpf_init);