root/tools/testing/selftests/bpf/progs/linked_list.c
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include "bpf_experimental.h"
#include "bpf_misc.h"

#include "linked_list.h"

struct head_nested_inner {
        struct bpf_spin_lock lock;
        struct bpf_list_head head __contains(foo, node2);
};

struct head_nested {
        int dummy;
        struct head_nested_inner inner;
};

private(C) struct bpf_spin_lock glock_c;
private(C) struct bpf_list_head ghead_array[2] __contains(foo, node2);
private(C) struct bpf_list_head ghead_array_one[1] __contains(foo, node2);

private(D) struct head_nested ghead_nested;

static __always_inline
int list_push_pop(struct bpf_spin_lock *lock, struct bpf_list_head *head, bool leave_in_map)
{
        struct bpf_list_node *n;
        struct foo *f;

        f = bpf_obj_new(typeof(*f));
        if (!f)
                return 2;

        bpf_spin_lock(lock);
        n = bpf_list_pop_front(head);
        bpf_spin_unlock(lock);
        if (n) {
                bpf_obj_drop(container_of(n, struct foo, node2));
                bpf_obj_drop(f);
                return 3;
        }

        bpf_spin_lock(lock);
        n = bpf_list_pop_back(head);
        bpf_spin_unlock(lock);
        if (n) {
                bpf_obj_drop(container_of(n, struct foo, node2));
                bpf_obj_drop(f);
                return 4;
        }


        bpf_spin_lock(lock);
        f->data = 42;
        bpf_list_push_front(head, &f->node2);
        bpf_spin_unlock(lock);
        if (leave_in_map)
                return 0;
        bpf_spin_lock(lock);
        n = bpf_list_pop_back(head);
        bpf_spin_unlock(lock);
        if (!n)
                return 5;
        f = container_of(n, struct foo, node2);
        if (f->data != 42) {
                bpf_obj_drop(f);
                return 6;
        }

        bpf_spin_lock(lock);
        f->data = 13;
        bpf_list_push_front(head, &f->node2);
        bpf_spin_unlock(lock);
        bpf_spin_lock(lock);
        n = bpf_list_pop_front(head);
        bpf_spin_unlock(lock);
        if (!n)
                return 7;
        f = container_of(n, struct foo, node2);
        if (f->data != 13) {
                bpf_obj_drop(f);
                return 8;
        }
        bpf_obj_drop(f);

        bpf_spin_lock(lock);
        n = bpf_list_pop_front(head);
        bpf_spin_unlock(lock);
        if (n) {
                bpf_obj_drop(container_of(n, struct foo, node2));
                return 9;
        }

        bpf_spin_lock(lock);
        n = bpf_list_pop_back(head);
        bpf_spin_unlock(lock);
        if (n) {
                bpf_obj_drop(container_of(n, struct foo, node2));
                return 10;
        }
        return 0;
}


static __always_inline
int list_push_pop_multiple(struct bpf_spin_lock *lock, struct bpf_list_head *head, bool leave_in_map)
{
        struct bpf_list_node *n;
        struct foo *f[200], *pf;
        int i;

        /* Loop following this check adds nodes 2-at-a-time in order to
         * validate multiple release_on_unlock release logic
         */
        if (ARRAY_SIZE(f) % 2)
                return 10;

        for (i = 0; i < ARRAY_SIZE(f); i += 2) {
                f[i] = bpf_obj_new(typeof(**f));
                if (!f[i])
                        return 2;
                f[i]->data = i;

                f[i + 1] = bpf_obj_new(typeof(**f));
                if (!f[i + 1]) {
                        bpf_obj_drop(f[i]);
                        return 9;
                }
                f[i + 1]->data = i + 1;

                bpf_spin_lock(lock);
                bpf_list_push_front(head, &f[i]->node2);
                bpf_list_push_front(head, &f[i + 1]->node2);
                bpf_spin_unlock(lock);
        }

        for (i = 0; i < ARRAY_SIZE(f); i++) {
                bpf_spin_lock(lock);
                n = bpf_list_pop_front(head);
                bpf_spin_unlock(lock);
                if (!n)
                        return 3;
                pf = container_of(n, struct foo, node2);
                if (pf->data != (ARRAY_SIZE(f) - i - 1)) {
                        bpf_obj_drop(pf);
                        return 4;
                }
                bpf_spin_lock(lock);
                bpf_list_push_back(head, &pf->node2);
                bpf_spin_unlock(lock);
        }

        if (leave_in_map)
                return 0;

        for (i = 0; i < ARRAY_SIZE(f); i++) {
                bpf_spin_lock(lock);
                n = bpf_list_pop_back(head);
                bpf_spin_unlock(lock);
                if (!n)
                        return 5;
                pf = container_of(n, struct foo, node2);
                if (pf->data != i) {
                        bpf_obj_drop(pf);
                        return 6;
                }
                bpf_obj_drop(pf);
        }
        bpf_spin_lock(lock);
        n = bpf_list_pop_back(head);
        bpf_spin_unlock(lock);
        if (n) {
                bpf_obj_drop(container_of(n, struct foo, node2));
                return 7;
        }

        bpf_spin_lock(lock);
        n = bpf_list_pop_front(head);
        bpf_spin_unlock(lock);
        if (n) {
                bpf_obj_drop(container_of(n, struct foo, node2));
                return 8;
        }
        return 0;
}

static __always_inline
int list_in_list(struct bpf_spin_lock *lock, struct bpf_list_head *head, bool leave_in_map)
{
        struct bpf_list_node *n;
        struct bar *ba[8], *b;
        struct foo *f;
        int i;

        f = bpf_obj_new(typeof(*f));
        if (!f)
                return 2;
        for (i = 0; i < ARRAY_SIZE(ba); i++) {
                b = bpf_obj_new(typeof(*b));
                if (!b) {
                        bpf_obj_drop(f);
                        return 3;
                }
                b->data = i;
                bpf_spin_lock(&f->lock);
                bpf_list_push_back(&f->head, &b->node);
                bpf_spin_unlock(&f->lock);
        }

        bpf_spin_lock(lock);
        f->data = 42;
        bpf_list_push_front(head, &f->node2);
        bpf_spin_unlock(lock);

        if (leave_in_map)
                return 0;

        bpf_spin_lock(lock);
        n = bpf_list_pop_front(head);
        bpf_spin_unlock(lock);
        if (!n)
                return 4;
        f = container_of(n, struct foo, node2);
        if (f->data != 42) {
                bpf_obj_drop(f);
                return 5;
        }

        for (i = 0; i < ARRAY_SIZE(ba); i++) {
                bpf_spin_lock(&f->lock);
                n = bpf_list_pop_front(&f->head);
                bpf_spin_unlock(&f->lock);
                if (!n) {
                        bpf_obj_drop(f);
                        return 6;
                }
                b = container_of(n, struct bar, node);
                if (b->data != i) {
                        bpf_obj_drop(f);
                        bpf_obj_drop(b);
                        return 7;
                }
                bpf_obj_drop(b);
        }
        bpf_spin_lock(&f->lock);
        n = bpf_list_pop_front(&f->head);
        bpf_spin_unlock(&f->lock);
        if (n) {
                bpf_obj_drop(f);
                bpf_obj_drop(container_of(n, struct bar, node));
                return 8;
        }
        bpf_obj_drop(f);
        return 0;
}

static __always_inline
int test_list_push_pop(struct bpf_spin_lock *lock, struct bpf_list_head *head)
{
        int ret;

        ret = list_push_pop(lock, head, false);
        if (ret)
                return ret;
        return list_push_pop(lock, head, true);
}

static __always_inline
int test_list_push_pop_multiple(struct bpf_spin_lock *lock, struct bpf_list_head *head)
{
        int ret;

        ret = list_push_pop_multiple(lock, head, false);
        if (ret)
                return ret;
        return list_push_pop_multiple(lock, head, true);
}

static __always_inline
int test_list_in_list(struct bpf_spin_lock *lock, struct bpf_list_head *head)
{
        int ret;

        ret = list_in_list(lock, head, false);
        if (ret)
                return ret;
        return list_in_list(lock, head, true);
}

SEC("tc")
int map_list_push_pop(void *ctx)
{
        struct map_value *v;

        v = bpf_map_lookup_elem(&array_map, &(int){0});
        if (!v)
                return 1;
        return test_list_push_pop(&v->lock, &v->head);
}

SEC("tc")
int inner_map_list_push_pop(void *ctx)
{
        struct map_value *v;
        void *map;

        map = bpf_map_lookup_elem(&map_of_maps, &(int){0});
        if (!map)
                return 1;
        v = bpf_map_lookup_elem(map, &(int){0});
        if (!v)
                return 1;
        return test_list_push_pop(&v->lock, &v->head);
}

SEC("tc")
int global_list_push_pop(void *ctx)
{
        return test_list_push_pop(&glock, &ghead);
}

SEC("tc")
int global_list_push_pop_nested(void *ctx)
{
        return test_list_push_pop(&ghead_nested.inner.lock, &ghead_nested.inner.head);
}

SEC("tc")
int global_list_array_push_pop(void *ctx)
{
        int r;

        r = test_list_push_pop(&glock_c, &ghead_array[0]);
        if (r)
                return r;

        r = test_list_push_pop(&glock_c, &ghead_array[1]);
        if (r)
                return r;

        /* Arrays with only one element is a special case, being treated
         * just like a bpf_list_head variable by the verifier, not an
         * array.
         */
        return test_list_push_pop(&glock_c, &ghead_array_one[0]);
}

SEC("tc")
int map_list_push_pop_multiple(void *ctx)
{
        struct map_value *v;

        v = bpf_map_lookup_elem(&array_map, &(int){0});
        if (!v)
                return 1;
        return test_list_push_pop_multiple(&v->lock, &v->head);
}

SEC("tc")
int inner_map_list_push_pop_multiple(void *ctx)
{
        struct map_value *v;
        void *map;

        map = bpf_map_lookup_elem(&map_of_maps, &(int){0});
        if (!map)
                return 1;
        v = bpf_map_lookup_elem(map, &(int){0});
        if (!v)
                return 1;
        return test_list_push_pop_multiple(&v->lock, &v->head);
}

SEC("tc")
int global_list_push_pop_multiple(void *ctx)
{
        int ret;

        ret = list_push_pop_multiple(&glock, &ghead, false);
        if (ret)
                return ret;
        return list_push_pop_multiple(&glock, &ghead, true);
}

SEC("tc")
int map_list_in_list(void *ctx)
{
        struct map_value *v;

        v = bpf_map_lookup_elem(&array_map, &(int){0});
        if (!v)
                return 1;
        return test_list_in_list(&v->lock, &v->head);
}

SEC("tc")
int inner_map_list_in_list(void *ctx)
{
        struct map_value *v;
        void *map;

        map = bpf_map_lookup_elem(&map_of_maps, &(int){0});
        if (!map)
                return 1;
        v = bpf_map_lookup_elem(map, &(int){0});
        if (!v)
                return 1;
        return test_list_in_list(&v->lock, &v->head);
}

SEC("tc")
int global_list_in_list(void *ctx)
{
        return test_list_in_list(&glock, &ghead);
}

char _license[] SEC("license") = "GPL";