|  | /* SPDX-License-Identifier: GPL-2.0 */ | 
|  | /* Copyright (c) 2023 Isovalent */ | 
|  | #ifndef __NET_TCX_H | 
|  | #define __NET_TCX_H | 
|  |  | 
|  | #include <linux/bpf.h> | 
|  | #include <linux/bpf_mprog.h> | 
|  |  | 
|  | #include <net/sch_generic.h> | 
|  |  | 
|  | struct mini_Qdisc; | 
|  |  | 
|  | struct tcx_entry { | 
|  | struct mini_Qdisc __rcu *miniq; | 
|  | struct bpf_mprog_bundle bundle; | 
|  | u32 miniq_active; | 
|  | struct rcu_head rcu; | 
|  | }; | 
|  |  | 
|  | struct tcx_link { | 
|  | struct bpf_link link; | 
|  | struct net_device *dev; | 
|  | }; | 
|  |  | 
|  | static inline void tcx_set_ingress(struct sk_buff *skb, bool ingress) | 
|  | { | 
|  | #ifdef CONFIG_NET_XGRESS | 
|  | skb->tc_at_ingress = ingress; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_NET_XGRESS | 
|  | static inline struct tcx_entry *tcx_entry(struct bpf_mprog_entry *entry) | 
|  | { | 
|  | struct bpf_mprog_bundle *bundle = entry->parent; | 
|  |  | 
|  | return container_of(bundle, struct tcx_entry, bundle); | 
|  | } | 
|  |  | 
|  | static inline struct tcx_link *tcx_link(const struct bpf_link *link) | 
|  | { | 
|  | return container_of(link, struct tcx_link, link); | 
|  | } | 
|  |  | 
|  | void tcx_inc(void); | 
|  | void tcx_dec(void); | 
|  |  | 
|  | static inline void tcx_entry_sync(void) | 
|  | { | 
|  | /* bpf_mprog_entry got a/b swapped, therefore ensure that | 
|  | * there are no inflight users on the old one anymore. | 
|  | */ | 
|  | synchronize_rcu(); | 
|  | } | 
|  |  | 
|  | static inline void | 
|  | tcx_entry_update(struct net_device *dev, struct bpf_mprog_entry *entry, | 
|  | bool ingress) | 
|  | { | 
|  | ASSERT_RTNL(); | 
|  | if (ingress) | 
|  | rcu_assign_pointer(dev->tcx_ingress, entry); | 
|  | else | 
|  | rcu_assign_pointer(dev->tcx_egress, entry); | 
|  | } | 
|  |  | 
|  | static inline struct bpf_mprog_entry * | 
|  | tcx_entry_fetch(struct net_device *dev, bool ingress) | 
|  | { | 
|  | ASSERT_RTNL(); | 
|  | if (ingress) | 
|  | return rcu_dereference_rtnl(dev->tcx_ingress); | 
|  | else | 
|  | return rcu_dereference_rtnl(dev->tcx_egress); | 
|  | } | 
|  |  | 
|  | static inline struct bpf_mprog_entry *tcx_entry_create_noprof(void) | 
|  | { | 
|  | struct tcx_entry *tcx = kzalloc_noprof(sizeof(*tcx), GFP_KERNEL); | 
|  |  | 
|  | if (tcx) { | 
|  | bpf_mprog_bundle_init(&tcx->bundle); | 
|  | return &tcx->bundle.a; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  | #define tcx_entry_create(...)	alloc_hooks(tcx_entry_create_noprof(__VA_ARGS__)) | 
|  |  | 
|  | static inline void tcx_entry_free(struct bpf_mprog_entry *entry) | 
|  | { | 
|  | kfree_rcu(tcx_entry(entry), rcu); | 
|  | } | 
|  |  | 
|  | static inline struct bpf_mprog_entry * | 
|  | tcx_entry_fetch_or_create(struct net_device *dev, bool ingress, bool *created) | 
|  | { | 
|  | struct bpf_mprog_entry *entry = tcx_entry_fetch(dev, ingress); | 
|  |  | 
|  | *created = false; | 
|  | if (!entry) { | 
|  | entry = tcx_entry_create(); | 
|  | if (!entry) | 
|  | return NULL; | 
|  | *created = true; | 
|  | } | 
|  | return entry; | 
|  | } | 
|  |  | 
|  | static inline void tcx_skeys_inc(bool ingress) | 
|  | { | 
|  | tcx_inc(); | 
|  | if (ingress) | 
|  | net_inc_ingress_queue(); | 
|  | else | 
|  | net_inc_egress_queue(); | 
|  | } | 
|  |  | 
|  | static inline void tcx_skeys_dec(bool ingress) | 
|  | { | 
|  | if (ingress) | 
|  | net_dec_ingress_queue(); | 
|  | else | 
|  | net_dec_egress_queue(); | 
|  | tcx_dec(); | 
|  | } | 
|  |  | 
|  | static inline void tcx_miniq_inc(struct bpf_mprog_entry *entry) | 
|  | { | 
|  | ASSERT_RTNL(); | 
|  | tcx_entry(entry)->miniq_active++; | 
|  | } | 
|  |  | 
|  | static inline void tcx_miniq_dec(struct bpf_mprog_entry *entry) | 
|  | { | 
|  | ASSERT_RTNL(); | 
|  | tcx_entry(entry)->miniq_active--; | 
|  | } | 
|  |  | 
|  | static inline bool tcx_entry_is_active(struct bpf_mprog_entry *entry) | 
|  | { | 
|  | ASSERT_RTNL(); | 
|  | return bpf_mprog_total(entry) || tcx_entry(entry)->miniq_active; | 
|  | } | 
|  |  | 
|  | static inline enum tcx_action_base tcx_action_code(struct sk_buff *skb, | 
|  | int code) | 
|  | { | 
|  | switch (code) { | 
|  | case TCX_PASS: | 
|  | skb->tc_index = qdisc_skb_cb(skb)->tc_classid; | 
|  | fallthrough; | 
|  | case TCX_DROP: | 
|  | case TCX_REDIRECT: | 
|  | return code; | 
|  | case TCX_NEXT: | 
|  | default: | 
|  | return TCX_NEXT; | 
|  | } | 
|  | } | 
|  | #endif /* CONFIG_NET_XGRESS */ | 
|  |  | 
|  | #if defined(CONFIG_NET_XGRESS) && defined(CONFIG_BPF_SYSCALL) | 
|  | int tcx_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog); | 
|  | int tcx_link_attach(const union bpf_attr *attr, struct bpf_prog *prog); | 
|  | int tcx_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog); | 
|  | void tcx_uninstall(struct net_device *dev, bool ingress); | 
|  |  | 
|  | int tcx_prog_query(const union bpf_attr *attr, | 
|  | union bpf_attr __user *uattr); | 
|  |  | 
|  | static inline void dev_tcx_uninstall(struct net_device *dev) | 
|  | { | 
|  | ASSERT_RTNL(); | 
|  | tcx_uninstall(dev, true); | 
|  | tcx_uninstall(dev, false); | 
|  | } | 
|  | #else | 
|  | static inline int tcx_prog_attach(const union bpf_attr *attr, | 
|  | struct bpf_prog *prog) | 
|  | { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static inline int tcx_link_attach(const union bpf_attr *attr, | 
|  | struct bpf_prog *prog) | 
|  | { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static inline int tcx_prog_detach(const union bpf_attr *attr, | 
|  | struct bpf_prog *prog) | 
|  | { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static inline int tcx_prog_query(const union bpf_attr *attr, | 
|  | union bpf_attr __user *uattr) | 
|  | { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static inline void dev_tcx_uninstall(struct net_device *dev) | 
|  | { | 
|  | } | 
|  | #endif /* CONFIG_NET_XGRESS && CONFIG_BPF_SYSCALL */ | 
|  | #endif /* __NET_TCX_H */ |