root/tools/testing/selftests/bpf/progs/bpf_qdisc_fifo.c
// SPDX-License-Identifier: GPL-2.0

#include <vmlinux.h>
#include "bpf_experimental.h"
#include "bpf_qdisc_common.h"

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

struct skb_node {
        struct sk_buff __kptr * skb;
        struct bpf_list_node node;
};

private(A) struct bpf_spin_lock q_fifo_lock;
private(A) struct bpf_list_head q_fifo __contains(skb_node, node);

bool init_called;

SEC("struct_ops/bpf_fifo_enqueue")
int BPF_PROG(bpf_fifo_enqueue, struct sk_buff *skb, struct Qdisc *sch,
             struct bpf_sk_buff_ptr *to_free)
{
        struct skb_node *skbn;
        u32 pkt_len;

        if (sch->q.qlen == sch->limit)
                goto drop;

        skbn = bpf_obj_new(typeof(*skbn));
        if (!skbn)
                goto drop;

        pkt_len = qdisc_pkt_len(skb);

        sch->q.qlen++;
        skb = bpf_kptr_xchg(&skbn->skb, skb);
        if (skb)
                bpf_qdisc_skb_drop(skb, to_free);

        bpf_spin_lock(&q_fifo_lock);
        bpf_list_push_back(&q_fifo, &skbn->node);
        bpf_spin_unlock(&q_fifo_lock);

        sch->qstats.backlog += pkt_len;
        return NET_XMIT_SUCCESS;
drop:
        bpf_qdisc_skb_drop(skb, to_free);
        return NET_XMIT_DROP;
}

SEC("struct_ops/bpf_fifo_dequeue")
struct sk_buff *BPF_PROG(bpf_fifo_dequeue, struct Qdisc *sch)
{
        struct bpf_list_node *node;
        struct sk_buff *skb = NULL;
        struct skb_node *skbn;

        bpf_spin_lock(&q_fifo_lock);
        node = bpf_list_pop_front(&q_fifo);
        bpf_spin_unlock(&q_fifo_lock);
        if (!node)
                return NULL;

        skbn = container_of(node, struct skb_node, node);
        skb = bpf_kptr_xchg(&skbn->skb, skb);
        bpf_obj_drop(skbn);
        if (!skb)
                return NULL;

        sch->qstats.backlog -= qdisc_pkt_len(skb);
        bpf_qdisc_bstats_update(sch, skb);
        sch->q.qlen--;

        return skb;
}

SEC("struct_ops/bpf_fifo_init")
int BPF_PROG(bpf_fifo_init, struct Qdisc *sch, struct nlattr *opt,
             struct netlink_ext_ack *extack)
{
        sch->limit = 1000;
        init_called = true;
        return 0;
}

SEC("struct_ops/bpf_fifo_reset")
void BPF_PROG(bpf_fifo_reset, struct Qdisc *sch)
{
        struct bpf_list_node *node;
        struct skb_node *skbn;
        int i;

        bpf_for(i, 0, sch->q.qlen) {
                struct sk_buff *skb = NULL;

                bpf_spin_lock(&q_fifo_lock);
                node = bpf_list_pop_front(&q_fifo);
                bpf_spin_unlock(&q_fifo_lock);

                if (!node)
                        break;

                skbn = container_of(node, struct skb_node, node);
                skb = bpf_kptr_xchg(&skbn->skb, skb);
                if (skb)
                        bpf_kfree_skb(skb);
                bpf_obj_drop(skbn);
        }
        sch->q.qlen = 0;
}

SEC("struct_ops")
void BPF_PROG(bpf_fifo_destroy, struct Qdisc *sch)
{
}

SEC(".struct_ops")
struct Qdisc_ops fifo = {
        .enqueue   = (void *)bpf_fifo_enqueue,
        .dequeue   = (void *)bpf_fifo_dequeue,
        .init      = (void *)bpf_fifo_init,
        .reset     = (void *)bpf_fifo_reset,
        .destroy   = (void *)bpf_fifo_destroy,
        .id        = "bpf_fifo",
};