diff --git a/include/linux/bitops.h b/include/linux/bitops.h
index 2ba557e067fe..290c2399ad18 100644
--- a/include/linux/bitops.h
+++ b/include/linux/bitops.h
@@ -19,6 +19,7 @@
 #define BITS_TO_LONGS(nr)	__KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(long))
 #define BITS_TO_U64(nr)		__KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(u64))
 #define BITS_TO_U32(nr)		__KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(u32))
+#define BITS_TO_U16(nr)		__KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(u16))
 #define BITS_TO_BYTES(nr)	__KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(char))
 
 extern unsigned int __sw_hweight8(unsigned int w);
diff --git a/include/linux/filter.h b/include/linux/filter.h
index bbce89937fde..ebcc0ac50656 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -577,6 +577,9 @@ typedef unsigned int (*bpf_dispatcher_fn)(const void *ctx,
 					  const struct bpf_insn *insnsi,
 					  unsigned int (*bpf_func)(const void *,
 								   const struct bpf_insn *));
+#ifndef CONFIG_NET_P4_TC_KFUNCS
+extern int is_p4tc_kfunc(const struct bpf_reg_state *reg);
+#endif
 
 static __always_inline u32 __bpf_prog_run(const struct bpf_prog *prog,
 					  const void *ctx,
diff --git a/include/net/p4tc.h b/include/net/p4tc.h
index 53d519149d09..34e78fd3c183 100644
--- a/include/net/p4tc.h
+++ b/include/net/p4tc.h
@@ -37,11 +37,13 @@
 
 struct p4tc_percpu_scratchpad {
 	u32 prog_cookie;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	u32 keysz;
 	u32 maskid;
 	u8 key[BITS_TO_BYTES(P4TC_MAX_KEYSZ)];
 	u8 hdrs[BITS_TO_BYTES(HEADER_MAX_LEN)];
 	u8 metadata[BITS_TO_BYTES(META_MAX_LEN)];
+#endif
 };
 
 DECLARE_PER_CPU(struct p4tc_percpu_scratchpad, p4tc_percpu_scratchpad);
@@ -105,6 +107,7 @@ struct p4tc_template_common {
 
 extern const struct p4tc_template_ops p4tc_pipeline_ops;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 struct p4tc_act_dep_edge_node {
 	struct list_head head;
 	u32 act_id;
@@ -115,24 +118,31 @@ struct p4tc_act_dep_node {
 	struct list_head head;
 	u32 act_id;
 };
+#endif
 
 struct p4tc_pipeline {
 	struct p4tc_template_common common;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct idr                  p_meta_idr;
+#endif
 	struct idr                  p_act_idr;
 	struct idr                  p_tbl_idr;
 	struct idr                  p_reg_idr;
 	struct rcu_head             rcu;
 	struct net                  *net;
 	struct p4tc_parser          *parser;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct tc_action            **preacts;
 	int                         num_preacts;
 	struct tc_action            **postacts;
 	int                         num_postacts;
 	struct list_head            act_dep_graph;
 	struct list_head            act_topological_order;
+#endif
 	u32                         max_rules;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	u32                         p_meta_offset;
+#endif
 	u32                         num_created_acts;
 	refcount_t                  p_ref;
 	refcount_t                  p_ctrl_ref;
@@ -143,8 +153,28 @@ struct p4tc_pipeline {
 	refcount_t                  p_hdrs_used;
 };
 
+#define P4TC_PIPELINE_MAX_ARRAY 32
+
+struct p4tc_table;
+
+struct p4tc_tbl_cache_key {
+	u32 pipeid;
+	u32 tblid;
+};
+
+extern const struct rhashtable_params tbl_cache_ht_params;
+
+int p4tc_tbl_cache_insert(struct net *net, u32 pipeid, struct p4tc_table *table);
+void p4tc_tbl_cache_remove(struct net *net, struct p4tc_table *table);
+struct p4tc_table *p4tc_tbl_cache_lookup(struct net *net, u32 pipeid, u32 tblid);
+
+#define P4TC_TBLS_CACHE_SIZE 32
+
 struct p4tc_pipeline_net {
-	struct idr pipeline_idr;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	struct list_head  tbls_cache[P4TC_TBLS_CACHE_SIZE];
+#endif
+	struct idr        pipeline_idr;
 };
 
 int tcf_p4_tmpl_generic_dump(struct sk_buff *skb, struct p4tc_dump_ctx *ctx,
@@ -182,6 +212,7 @@ static inline bool pipeline_sealed(struct p4tc_pipeline *pipeline)
 	return pipeline->p_state == P4TC_STATE_READY;
 }
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 void tcf_pipeline_add_dep_edge(struct p4tc_pipeline *pipeline,
 			       struct p4tc_act_dep_edge_node *edge_node,
 			       u32 vertex_id);
@@ -194,7 +225,9 @@ int determine_act_topological_order(struct p4tc_pipeline *pipeline,
 struct p4tc_act;
 void tcf_pipeline_delete_from_dep_graph(struct p4tc_pipeline *pipeline,
 					struct p4tc_act *act);
+#endif
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 struct p4tc_metadata {
 	struct p4tc_template_common common;
 	struct rcu_head             rcu;
@@ -209,6 +242,7 @@ struct p4tc_metadata {
 };
 
 extern const struct p4tc_template_ops p4tc_meta_ops;
+#endif
 
 struct p4tc_table_key {
 	struct tc_action **key_acts;
@@ -224,8 +258,24 @@ struct p4tc_table_key {
 
 #define P4TC_PERMISSIONS_UNINIT (1 << P4TC_PERM_MAX_BIT)
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+#define P4TC_MAX_PARAM_DATA_SIZE 124
+
+struct p4tc_table_entry_act_bpf {
+	u32 act_id;
+	u8 params[P4TC_MAX_PARAM_DATA_SIZE];
+} __packed;
+#endif
+
+struct p4tc_parser_buffer_act_bpf {
+	u16 hdrs[BITS_TO_U16(HEADER_MAX_LEN)];
+};
+
 struct p4tc_table_defact {
 	struct tc_action **default_acts;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	struct p4tc_table_entry_act_bpf *defact_bpf;
+#endif
 	/* Will have 2 5 bits blocks containing CRUDX (Create, read, update,
 	 * delete, execute) permissions for control plane and data plane.
 	 * The first 5 bits are for control and the next five are for data plane.
@@ -242,13 +292,18 @@ struct p4tc_table_perm {
 
 struct p4tc_table {
 	struct p4tc_template_common         common;
+	struct list_head                    tbl_cache_node;
 	struct list_head                    tbl_acts_list;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct p4tc_table_key               *tbl_key;
+#endif
 	struct idr                          tbl_masks_idr;
 	struct ida                          tbl_prio_idr;
 	struct rhltable                     tbl_entries;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct tc_action                    **tbl_preacts;
 	struct tc_action                    **tbl_postacts;
+#endif
 	struct p4tc_table_entry             *tbl_const_entry;
 	struct p4tc_table_defact __rcu      *tbl_default_hitact;
 	struct p4tc_table_defact __rcu      *tbl_default_missact;
@@ -280,7 +335,10 @@ struct p4tc_ipv4_param_value {
 	u32 mask;
 };
 
+
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 #define P4TC_ACT_PARAM_FLAGS_ISDYN BIT(0)
+#endif
 
 struct p4tc_act_param {
 	char            name[ACTPARAMNAMSIZ];
@@ -305,6 +363,7 @@ struct p4tc_act_param_ops {
 	u32 alloc_len;
 };
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 struct p4tc_label_key {
 	char *label;
 	u32 labelsz;
@@ -315,12 +374,15 @@ struct p4tc_label_node {
 	struct p4tc_label_key key;
 	int cmd_offset;
 };
+#endif
 
 struct p4tc_act {
 	struct p4tc_template_common common;
 	struct tc_action_ops        ops;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct rhashtable           *labels;
 	struct list_head            cmd_operations;
+#endif
 	struct tc_action_net        *tn;
 	struct p4tc_pipeline        *pipeline;
 	struct idr                  params_idr;
@@ -345,6 +407,13 @@ void p4tc_label_ht_destroy(void *ptr, void *arg);
 
 extern const struct rhashtable_params entry_hlt_params;
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+struct p4tc_table_entry_act_bpf_params {
+	u32 pipeid;
+	u32 tblid;
+};
+#endif
+
 struct p4tc_table_entry;
 struct p4tc_table_entry_work {
 	struct work_struct   work;
@@ -364,6 +433,9 @@ struct p4tc_table_entry_value {
 	u32                              prio;
 	int                              num_acts;
 	struct tc_action                 **acts;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	struct p4tc_table_entry_act_bpf  *act_bpf;
+#endif
 	refcount_t                       entries_ref;
 	u32                              permissions;
 	struct p4tc_table_entry_tm __rcu *tm;
@@ -395,13 +467,24 @@ static inline void *p4tc_table_entry_value(struct p4tc_table_entry *entry)
 
 extern const struct nla_policy p4tc_root_policy[P4TC_ROOT_MAX + 1];
 extern const struct nla_policy p4tc_policy[P4TC_MAX + 1];
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 struct p4tc_table_entry *p4tc_table_entry_lookup(struct sk_buff *skb,
 						 struct p4tc_table *table,
 						 u32 keysz);
+#endif
+struct p4tc_table_entry *
+p4tc_table_entry_lookup_direct(struct p4tc_table *table,
+			       struct p4tc_table_entry_key *key);
 int __tcf_table_entry_del(struct p4tc_pipeline *pipeline,
 			  struct p4tc_table *table,
 			  struct p4tc_table_entry_key *key,
 			  struct p4tc_table_entry_mask *mask, u32 prio);
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+struct p4tc_table_entry_act_bpf *
+tcf_table_entry_create_act_bpf(struct tc_action *action,
+			       struct netlink_ext_ack *extack);
+#endif
+int register_p4tc_tbl_bpf(void);
 
 struct p4tc_parser {
 	char parser_name[PARSERNAMSIZ];
@@ -439,6 +522,7 @@ struct p4tc_register {
 
 extern const struct p4tc_template_ops p4tc_register_ops;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 struct p4tc_metadata *tcf_meta_find_byid(struct p4tc_pipeline *pipeline,
 					 u32 m_id);
 void tcf_meta_fill_user_offsets(struct p4tc_pipeline *pipeline);
@@ -448,6 +532,7 @@ struct p4tc_metadata *tcf_meta_get(struct p4tc_pipeline *pipeline,
 				   struct netlink_ext_ack *extack);
 void tcf_meta_put_ref(struct p4tc_metadata *meta);
 void *tcf_meta_fetch(struct sk_buff *skb, struct p4tc_metadata *meta);
+#endif
 
 static inline int p4tc_action_init(struct net *net, struct nlattr *nla,
 				   struct tc_action *acts[], u32 pipeid,
@@ -553,12 +638,13 @@ struct p4tc_hdrfield *tcf_hdrfield_find_byany(struct p4tc_parser *parser,
 					      const char *hdrfield_name,
 					      u32 hdrfield_id,
 					      struct netlink_ext_ack *extack);
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 void *tcf_hdrfield_fetch(struct sk_buff *skb, struct p4tc_hdrfield *hdrfield);
+#endif
 struct p4tc_hdrfield *tcf_hdrfield_get(struct p4tc_parser *parser,
 				       const char *hdrfield_name,
 				       u32 hdrfield_id,
 				       struct netlink_ext_ack *extack);
-void *tcf_hdrfield_fetch(struct sk_buff *skb, struct p4tc_hdrfield *hdrfield);
 void tcf_hdrfield_put_ref(struct p4tc_hdrfield *hdrfield);
 
 int p4tc_init_net_ops(struct net *net, unsigned int id);
@@ -595,13 +681,16 @@ struct p4tc_register *tcf_register_find_byany(struct p4tc_pipeline *pipeline,
 void tcf_register_put_rcu(struct rcu_head *head);
 
 #define to_pipeline(t) ((struct p4tc_pipeline *)t)
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 #define to_meta(t) ((struct p4tc_metadata *)t)
+#endif
 #define to_hdrfield(t) ((struct p4tc_hdrfield *)t)
 #define to_act(t) ((struct p4tc_act *)t)
 #define to_table(t) ((struct p4tc_table *)t)
 #define to_register(t) ((struct p4tc_register *)t)
 
 /* P4TC COMMANDS */
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 int p4tc_cmds_parse(struct net *net, struct p4tc_act *act, struct nlattr *nla,
 		    bool ovr, struct netlink_ext_ack *extack);
 int p4tc_cmds_copy(struct p4tc_act *act, struct list_head *new_cmd_operations,
@@ -680,5 +769,6 @@ static inline int __p4tc_cmd_run(struct sk_buff *skb, struct p4tc_cmd_operate *o
 	return op->cmd->run(skb, op, cmd, res);
 }
 #endif
+#endif
 
 #endif
diff --git a/net/core/filter.c b/net/core/filter.c
index d9ce04ca22ce..1bd289c634c9 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -8756,6 +8756,11 @@ static int tc_cls_act_btf_struct_access(struct bpf_verifier_log *log,
 {
 	int ret = -EACCES;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
+	if (is_p4tc_kfunc(reg))
+		return 0;
+#endif
+
 	mutex_lock(&nf_conn_btf_access_lock);
 	if (nfct_btf_struct_access)
 		ret = nfct_btf_struct_access(log, reg, off, size);
@@ -8829,6 +8834,11 @@ static int xdp_btf_struct_access(struct bpf_verifier_log *log,
 {
 	int ret = -EACCES;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
+	if (is_p4tc_kfunc(reg))
+		return 0;
+#endif
+
 	mutex_lock(&nf_conn_btf_access_lock);
 	if (nfct_btf_struct_access)
 		ret = nfct_btf_struct_access(log, reg, off, size);
diff --git a/net/sched/Kconfig b/net/sched/Kconfig
index 43d300ef0f5a..ffd06565c606 100644
--- a/net/sched/Kconfig
+++ b/net/sched/Kconfig
@@ -696,6 +696,13 @@ config NET_P4_TC
 	  The concept of Pipelines, Tables, metadata will be enabled
           with this option.
 
+config NET_P4_TC_KFUNCS
+	bool "P4 TC support for eBPF SW data path"
+	depends on NET_P4_TC
+	select NET_CLS_ACT
+	help
+	  Say Y here if you want to use P4 with eBPF SW data path.
+
 config NET_CLS_ACT
 	bool "Actions"
 	select NET_CLS
diff --git a/net/sched/cls_p4.c b/net/sched/cls_p4.c
index 25e3f0cc7aa8..f068137336f1 100644
--- a/net/sched/cls_p4.c
+++ b/net/sched/cls_p4.c
@@ -46,8 +46,10 @@ static int p4_classify(struct sk_buff *skb, const struct tcf_proto *tp,
 	bool at_ingress = skb_at_tc_ingress(skb);
 	int rc = TC_ACT_PIPE;
 	struct p4tc_percpu_scratchpad *pad;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct tcf_result p4res = {};
 	struct p4tc_pipeline *pipeline;
+#endif
 
 	if (unlikely(!head)) {
 		pr_err("P4 classifier not found\n");
@@ -85,6 +87,7 @@ static int p4_classify(struct sk_buff *skb, const struct tcf_proto *tp,
 	if (rc != TC_ACT_PIPE)
 		goto zero_pad;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	pipeline = head->pipeline;
 	trace_p4_classify(skb, pipeline);
 
@@ -97,6 +100,7 @@ static int p4_classify(struct sk_buff *skb, const struct tcf_proto *tp,
 			     &p4res);
 	if (rc != TC_ACT_PIPE)
 		goto zero_pad;
+#endif
 
 	*res = head->res;
 
diff --git a/net/sched/p4tc/Makefile b/net/sched/p4tc/Makefile
index ac118a79cbf4..9ee62cf3f85b 100644
--- a/net/sched/p4tc/Makefile
+++ b/net/sched/p4tc/Makefile
@@ -2,6 +2,10 @@
 
 CFLAGS_trace.o := -I$(src)
 
-obj-y := p4tc_types.o p4tc_pipeline.o p4tc_tmpl_api.o p4tc_meta.o \
+obj-y := p4tc_types.o p4tc_pipeline.o p4tc_tmpl_api.o \
 	p4tc_parser_api.o p4tc_hdrfield.o p4tc_action.o p4tc_table.o \
-	p4tc_tbl_api.o p4tc_register.o p4tc_cmds.o trace.o
+	p4tc_tbl_api.o p4tc_register.o p4tc_bpf.o trace.o
+
+ifndef CONFIG_NET_P4_TC_KFUNCS
+obj-y += p4tc_meta.o p4tc_cmds.o
+endif
diff --git a/net/sched/p4tc/p4tc_action.c b/net/sched/p4tc/p4tc_action.c
index 8b69842cc3ce..8e08deb30a2f 100644
--- a/net/sched/p4tc/p4tc_action.c
+++ b/net/sched/p4tc/p4tc_action.c
@@ -29,12 +29,14 @@
 #include <net/p4tc.h>
 #include <net/sch_generic.h>
 #include <net/sock.h>
+
 #include <net/tc_act/p4tc.h>
 
 static LIST_HEAD(dynact_list);
 
 #define SEPARATOR "/"
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 static u32 label_hash_fn(const void *data, u32 len, u32 seed)
 {
 	const struct p4tc_label_key *key = data;
@@ -73,6 +75,7 @@ const struct rhashtable_params p4tc_label_ht_params = {
 	.key_offset = offsetof(struct p4tc_label_node, key),
 	.automatic_shrinking = true,
 };
+#endif
 
 static void set_param_indices(struct p4tc_act *act)
 {
@@ -131,7 +134,9 @@ static int __tcf_p4_dyna_init(struct net *net, struct nlattr *est,
 		p = to_p4act(*a);
 		p->p_id = pipeline->common.p_id;
 		p->act_id = act->a_id;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 		INIT_LIST_HEAD(&p->cmd_operations);
+#endif
 
 		ret = ACT_P_CREATED;
 	} else {
@@ -160,7 +165,7 @@ static int __tcf_p4_dyna_init_set(struct p4tc_act *act, struct tc_action **a,
 {
 	struct tcf_p4act_params *params_old;
 	struct tcf_p4act *p;
-	int err;
+	int err = 0;
 
 	p = to_p4act(*a);
 
@@ -169,6 +174,7 @@ static int __tcf_p4_dyna_init_set(struct p4tc_act *act, struct tc_action **a,
 
 	goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	err = p4tc_cmds_copy(act, &p->cmd_operations, exists, extack);
 	if (err < 0) {
 		if (exists)
@@ -176,6 +182,7 @@ static int __tcf_p4_dyna_init_set(struct p4tc_act *act, struct tc_action **a,
 
 		return err;
 	}
+#endif
 
 	params_old = rcu_replace_pointer(p->params, params, 1);
 	if (exists)
@@ -375,9 +382,13 @@ static int dev_dump_param_value(struct sk_buff *skb,
 				struct p4tc_act_param *param)
 {
 	struct nlattr *nest;
+	u32 *ifindex;
 	int ret;
 
 	nest = nla_nest_start(skb, P4TC_ACT_PARAMS_VALUE);
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	ifindex = (u32 *)param->value;
+#else
 	if (param->flags & P4TC_ACT_PARAM_FLAGS_ISDYN) {
 		struct p4tc_cmd_operand *kopnd;
 		struct nlattr *nla_opnd;
@@ -390,13 +401,16 @@ static int dev_dump_param_value(struct sk_buff *skb,
 		}
 		nla_nest_end(skb, nla_opnd);
 	} else {
-		const u32 *ifindex = param->value;
+		ifindex = (u32 *)param->value;
+#endif
 
 		if (nla_put_u32(skb, P4TC_ACT_PARAMS_VALUE_RAW, *ifindex)) {
 			ret = -EINVAL;
 			goto out_nla_cancel;
 		}
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	}
+#endif
 	nla_nest_end(skb, nest);
 
 	return 0;
@@ -408,8 +422,12 @@ static int dev_dump_param_value(struct sk_buff *skb,
 
 static void dev_free_param_value(struct p4tc_act_param *param)
 {
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	kfree(param->value);
+#else
 	if (!(param->flags & P4TC_ACT_PARAM_FLAGS_ISDYN))
 		kfree(param->value);
+#endif
 }
 
 static int generic_init_param_value(struct p4tc_act_param *nparam,
@@ -486,10 +504,15 @@ const struct p4tc_act_param_ops param_ops[P4T_MAX + 1] = {
 
 static void generic_free_param_value(struct p4tc_act_param *param)
 {
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	kfree(param->value);
+	kfree(param->mask);
+#else
 	if (!(param->flags & P4TC_ACT_PARAM_FLAGS_ISDYN)) {
 		kfree(param->value);
 		kfree(param->mask);
 	}
+#endif
 }
 
 int tcf_p4_act_init_params_list(struct tcf_p4act_params *params,
@@ -586,12 +609,15 @@ INDIRECT_CALLABLE_SCOPE int tcf_p4_dyna_act(struct sk_buff *skb,
 {
 	struct tcf_p4act *dynact = to_p4act(a);
 	int ret = 0;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	int jmp_cnt = 0;
 	struct p4tc_cmd_operate *op;
+#endif
 
 	tcf_lastuse_update(&dynact->tcf_tm);
 	tcf_action_update_bstats(&dynact->common, skb);
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	list_for_each_entry(op, &dynact->cmd_operations, cmd_operations) {
 		if (jmp_cnt-- > 0)
 			continue;
@@ -609,6 +635,7 @@ INDIRECT_CALLABLE_SCOPE int tcf_p4_dyna_act(struct sk_buff *skb,
 			break;
 		}
 	}
+#endif
 
 	if (ret == TC_ACT_SHOT)
 		tcf_action_inc_drop_qstats(&dynact->common);
@@ -636,7 +663,9 @@ static int tcf_p4_dyna_dump(struct sk_buff *skb, struct tc_action *a, int bind,
 	struct tcf_p4act_params *params;
 	struct p4tc_act_param *parm;
 	struct nlattr *nest_parms;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct nlattr *nest;
+#endif
 	struct tcf_t t;
 	int id;
 
@@ -646,10 +675,12 @@ static int tcf_p4_dyna_dump(struct sk_buff *skb, struct tc_action *a, int bind,
 	if (nla_put(skb, P4TC_ACT_OPT, sizeof(opt), &opt))
 		goto nla_put_failure;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	nest = nla_nest_start(skb, P4TC_ACT_CMDS_LIST);
 	if (p4tc_cmds_fillup(skb, &dynact->cmd_operations))
 		goto nla_put_failure;
 	nla_nest_end(skb, nest);
+#endif
 
 	if (nla_put_string(skb, P4TC_ACT_NAME, a->ops->kind))
 		goto nla_put_failure;
@@ -744,7 +775,9 @@ static void tcf_p4_dyna_cleanup(struct tc_action *a)
 	if (refcount_read(&ops->dyn_ref) > 1)
 		refcount_dec(&ops->dyn_ref);
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	p4tc_cmds_release_ope_list(NULL, &m->cmd_operations, false);
+#endif
 	if (params)
 		call_rcu(&params->rcu, tcf_p4_act_params_destroy_rcu);
 }
@@ -757,6 +790,7 @@ int generic_dump_param_value(struct sk_buff *skb, struct p4tc_type *type,
 	struct nlattr *nla_value;
 
 	nla_value = nla_nest_start(skb, P4TC_ACT_PARAMS_VALUE);
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (param->flags & P4TC_ACT_PARAM_FLAGS_ISDYN) {
 		struct p4tc_cmd_operand *kopnd;
 		struct nlattr *nla_opnd;
@@ -767,10 +801,13 @@ int generic_dump_param_value(struct sk_buff *skb, struct p4tc_type *type,
 			goto out_nlmsg_trim;
 		nla_nest_end(skb, nla_opnd);
 	} else {
+#endif
 		if (nla_put(skb, P4TC_ACT_PARAMS_VALUE_RAW, bytesz,
 			    param->value))
 			goto out_nlmsg_trim;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	}
+#endif
 	nla_nest_end(skb, nla_value);
 
 	if (param->mask &&
@@ -1343,7 +1380,9 @@ static int __tcf_act_put(struct net *net, struct p4tc_pipeline *pipeline,
 		kfree(act_param);
 	}
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	p4tc_cmds_release_ope_list(net, &act->cmd_operations, true);
+#endif
 
 	ret = tcf_unregister_dyn_action(net, &act->ops);
 	if (ret < 0) {
@@ -1353,16 +1392,20 @@ static int __tcf_act_put(struct net *net, struct p4tc_pipeline *pipeline,
 	}
 	p4tc_action_net_exit(act->tn);
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (act->labels) {
 		rhashtable_free_and_destroy(act->labels, p4tc_label_ht_destroy,
 					    NULL);
 		kfree(act->labels);
 	}
+#endif
 
 	idr_remove(&pipeline->p_act_idr, act->a_id);
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (!unconditional_purge)
 		tcf_pipeline_delete_from_dep_graph(pipeline, act);
+#endif
 
 	list_del(&act->head);
 
@@ -1378,9 +1421,12 @@ static int _tcf_act_fill_nlmsg(struct net *net, struct sk_buff *skb,
 {
 	unsigned char *b = nlmsg_get_pos(skb);
 	int i = 1;
-	struct nlattr *nest, *parms, *cmds;
+	struct nlattr *nest, *parms;
 	struct p4tc_act_param *param;
 	unsigned long param_id, tmp;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
+	struct nlattr *cmds;
+#endif
 
 	if (nla_put_u32(skb, P4TC_PATH, act->a_id))
 		goto out_nlmsg_trim;
@@ -1417,10 +1463,12 @@ static int _tcf_act_fill_nlmsg(struct net *net, struct sk_buff *skb,
 	}
 	nla_nest_end(skb, parms);
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	cmds = nla_nest_start(skb, P4TC_ACT_CMDS_LIST);
 	if (p4tc_cmds_fillup(skb, &act->cmd_operations))
 		goto out_nlmsg_trim;
 	nla_nest_end(skb, cmds);
+#endif
 
 	nla_nest_end(skb, nest);
 
@@ -1578,7 +1626,9 @@ static struct p4tc_act *tcf_act_create(struct net *net, struct nlattr **tb,
 	u32 a_id = ids[P4TC_AID_IDX];
 	int num_params = 0;
 	int ret = 0;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct p4tc_act_dep_node *dep_node;
+#endif
 	struct p4tc_act *act;
 	char *act_name;
 
@@ -1649,6 +1699,7 @@ static struct p4tc_act *tcf_act_create(struct net *net, struct nlattr **tb,
 		}
 	}
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	dep_node = kzalloc(sizeof(*dep_node), GFP_KERNEL);
 	if (!dep_node) {
 		ret = -ENOMEM;
@@ -1657,13 +1708,18 @@ static struct p4tc_act *tcf_act_create(struct net *net, struct nlattr **tb,
 	dep_node->act_id = act->a_id;
 	INIT_LIST_HEAD(&dep_node->incoming_egde_list);
 	list_add_tail(&dep_node->head, &pipeline->act_dep_graph);
+#endif
 
 	refcount_set(&act->ops.dyn_ref, 1);
 	ret = tcf_register_dyn_action(net, &act->ops);
 	if (ret < 0) {
 		NL_SET_ERR_MSG(extack,
 			       "Unable to register new action template");
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+		goto idr_rm;
+#else
 		goto free_dep_node;
+#endif
 	}
 
 	num_params = p4_act_init(act, tb[P4TC_ACT_PARMS], params, extack);
@@ -1675,6 +1731,13 @@ static struct p4tc_act *tcf_act_create(struct net *net, struct nlattr **tb,
 
 	set_param_indices(act);
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	if (tb[P4TC_ACT_CMDS_LIST]) {
+		NL_SET_ERR_MSG(extack, "Commands not supported in kfuncs mode");
+		ret = -EOPNOTSUPP;
+		goto uninit;
+	}
+#else
 	INIT_LIST_HEAD(&act->cmd_operations);
 	act->pipeline = pipeline;
 	if (tb[P4TC_ACT_CMDS_LIST]) {
@@ -1683,14 +1746,17 @@ static struct p4tc_act *tcf_act_create(struct net *net, struct nlattr **tb,
 		if (ret < 0)
 			goto uninit;
 	}
+#endif
 
 	pipeline->num_created_acts++;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	ret = determine_act_topological_order(pipeline, true);
 	if (ret < 0) {
 		pipeline->num_created_acts--;
 		goto release_cmds;
 	}
+#endif
 
 	act->common.p_id = pipeline->common.p_id;
 	snprintf(act->common.name, ACTNAMSIZ, "%s/%s", pipeline->common.name,
@@ -1703,9 +1769,11 @@ static struct p4tc_act *tcf_act_create(struct net *net, struct nlattr **tb,
 
 	return act;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 release_cmds:
 	if (tb[P4TC_ACT_CMDS_LIST])
 		p4tc_cmds_release_ope_list(net, &act->cmd_operations, false);
+#endif
 
 uninit:
 	p4_put_many_params(&act->params_idr, params, num_params);
@@ -1716,9 +1784,11 @@ static struct p4tc_act *tcf_act_create(struct net *net, struct nlattr **tb,
 	tcf_unregister_dyn_action(net, &act->ops);
 	rtnl_lock();
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 free_dep_node:
 	list_del(&dep_node->head);
 	kfree(dep_node);
+#endif
 
 idr_rm:
 	idr_remove(&pipeline->p_act_idr, act->a_id);
@@ -1785,6 +1855,13 @@ static struct p4tc_act *tcf_act_update(struct net *net, struct nlattr **tb,
 		goto params_del;
 	}
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	if (tb[P4TC_ACT_CMDS_LIST]) {
+		NL_SET_ERR_MSG(extack, "Commands not supported in kfuncs mode");
+		ret = -EOPNOTSUPP;
+		goto params_del;
+	}
+#else
 	if (tb[P4TC_ACT_CMDS_LIST]) {
 		ret = p4tc_cmds_parse(net, act, tb[P4TC_ACT_CMDS_LIST], true,
 				      extack);
@@ -1795,12 +1872,15 @@ static struct p4tc_act *tcf_act_update(struct net *net, struct nlattr **tb,
 		if (ret < 0)
 			goto release_cmds;
 	}
+#endif
 
 	p4tc_params_replace_many(&act->params_idr, params, num_params);
 	return act;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 release_cmds:
 	p4tc_cmds_release_ope_list(net, &act->cmd_operations, false);
+#endif
 
 params_del:
 	p4_put_many_params(&act->params_idr, params, num_params);
@@ -1880,7 +1960,9 @@ static int tcf_act_dump_1(struct sk_buff *skb,
 	struct nlattr *param = nla_nest_start(skb, P4TC_PARAMS);
 	unsigned char *b = nlmsg_get_pos(skb);
 	struct p4tc_act *act = to_act(common);
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct nlattr *nest;
+#endif
 
 	if (!param)
 		goto out_nlmsg_trim;
@@ -1888,10 +1970,12 @@ static int tcf_act_dump_1(struct sk_buff *skb,
 	if (nla_put_string(skb, P4TC_ACT_NAME, act->common.name))
 		goto out_nlmsg_trim;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	nest = nla_nest_start(skb, P4TC_ACT_CMDS_LIST);
 	if (p4tc_cmds_fillup(skb, &act->cmd_operations))
 		goto out_nlmsg_trim;
 	nla_nest_end(skb, nest);
+#endif
 
 	if (nla_put_u8(skb, P4TC_ACT_ACTIVE, act->active))
 		goto out_nlmsg_trim;
diff --git a/net/sched/p4tc/p4tc_bpf.c b/net/sched/p4tc/p4tc_bpf.c
index 08d26a6499c5..3e4eb1fd9d97 100644
--- a/net/sched/p4tc/p4tc_bpf.c
+++ b/net/sched/p4tc/p4tc_bpf.c
@@ -21,8 +21,74 @@
 #include <linux/filter.h>
 
 BTF_ID_LIST(btf_p4tc_ids)
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+BTF_ID(struct, p4tc_table_entry_act_bpf)
+BTF_ID(struct, p4tc_table_entry_act_bpf_params)
+#else
 BTF_ID(struct, p4tc_parser_buffer_act_bpf)
+#endif
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+#define ENTRY_KEY_OFFSET (offsetof(struct p4tc_table_entry_key, fa_key))
+
+struct p4tc_table_entry_act_bpf *
+__bpf_p4tc_tbl_lookup(struct net *caller_net,
+		      struct p4tc_table_entry_act_bpf_params *params,
+		      void *key, const u32 key__sz)
+{
+	struct p4tc_table_entry_key *entry_key = (struct p4tc_table_entry_key *)key;
+	const u32 pipeid = params->pipeid;
+	const u32 tblid = params->tblid;
+	struct p4tc_table_entry_value *value;
+	struct p4tc_table_entry *entry;
+	struct p4tc_table *table;
+
+	entry_key->keysz = (key__sz - ENTRY_KEY_OFFSET) << 3;
+
+	table = p4tc_tbl_cache_lookup(caller_net, pipeid, tblid);
+	if (!table)
+		return NULL;
+
+	entry = p4tc_table_entry_lookup_direct(table, entry_key);
+	if (!entry) {
+		struct p4tc_table_defact *defact;
+
+		defact = rcu_dereference(table->tbl_default_missact);
+		return defact ? defact->defact_bpf : NULL;
+	}
+
+	value = p4tc_table_entry_value(entry);
+
+	return value->act_bpf;
+}
+
+struct p4tc_table_entry_act_bpf *
+bpf_skb_p4tc_tbl_lookup(struct __sk_buff *skb_ctx,
+			struct p4tc_table_entry_act_bpf_params *params,
+			void *key, const u32 key__sz)
+{
+	struct sk_buff *skb = (struct sk_buff *)skb_ctx;
+	struct net *caller_net;
+
+	caller_net = skb->dev ? dev_net(skb->dev) : sock_net(skb->sk);
+
+	return __bpf_p4tc_tbl_lookup(caller_net, params, key, key__sz);
+}
+
+struct p4tc_table_entry_act_bpf *
+bpf_xdp_p4tc_tbl_lookup(struct xdp_md *xdp_ctx,
+			struct p4tc_table_entry_act_bpf_params *params,
+			void *key, const u32 key__sz)
+{
+	struct xdp_buff *ctx = (struct xdp_buff *)xdp_ctx;
+	struct net *caller_net;
+
+	caller_net = dev_net(ctx->rxq->dev);
+
+	return __bpf_p4tc_tbl_lookup(caller_net, params, key, key__sz);
+}
+
+#else
 struct p4tc_parser_buffer_act_bpf *bpf_p4tc_get_parser_buffer(void)
 {
 	struct p4tc_percpu_scratchpad *pad;
@@ -45,6 +111,7 @@ int is_p4tc_kfunc(const struct bpf_reg_state *reg)
 
 	return p4tc_parser_type == t;
 }
+#endif
 
 void bpf_p4tc_set_cookie(u32 cookie)
 {
@@ -55,7 +122,12 @@ void bpf_p4tc_set_cookie(u32 cookie)
 }
 
 BTF_SET8_START(p4tc_tbl_kfunc_set)
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+BTF_ID_FLAGS(func, bpf_skb_p4tc_tbl_lookup, KF_RET_NULL);
+BTF_ID_FLAGS(func, bpf_xdp_p4tc_tbl_lookup, KF_RET_NULL);
+#else
 BTF_ID_FLAGS(func, bpf_p4tc_get_parser_buffer, 0);
+#endif
 BTF_ID_FLAGS(func, bpf_p4tc_set_cookie, 0);
 BTF_SET8_END(p4tc_tbl_kfunc_set)
 
diff --git a/net/sched/p4tc/p4tc_hdrfield.c b/net/sched/p4tc/p4tc_hdrfield.c
index d5dd9b5c885d..c7c2fe90ec81 100644
--- a/net/sched/p4tc/p4tc_hdrfield.c
+++ b/net/sched/p4tc/p4tc_hdrfield.c
@@ -165,6 +165,7 @@ tcf_hdrfield_find_byanyattr(struct p4tc_parser *parser,
 				       extack);
 }
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 void *tcf_hdrfield_fetch(struct sk_buff *skb, struct p4tc_hdrfield *hdrfield)
 {
 	size_t hdr_offset_len = sizeof(u16);
@@ -183,6 +184,7 @@ void *tcf_hdrfield_fetch(struct sk_buff *skb, struct p4tc_hdrfield *hdrfield)
 
 	return skb_mac_header(skb) + hdr_offset;
 }
+#endif
 
 static struct p4tc_hdrfield *tcf_hdrfield_create(struct nlmsghdr *n,
 						 struct nlattr *nla,
diff --git a/net/sched/p4tc/p4tc_pipeline.c b/net/sched/p4tc/p4tc_pipeline.c
index fafb9c849b13..6d5722f6a298 100644
--- a/net/sched/p4tc/p4tc_pipeline.c
+++ b/net/sched/p4tc/p4tc_pipeline.c
@@ -37,9 +37,56 @@ static __net_init int pipeline_init_net(struct net *net)
 
 	idr_init(&pipe_net->pipeline_idr);
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	for (int i = 0; i < P4TC_TBLS_CACHE_SIZE; i++)
+		INIT_LIST_HEAD(&pipe_net->tbls_cache[i]);
+#endif
+
 	return 0;
 }
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+static inline size_t p4tc_tbl_cache_hash(u32 pipeid, u32 tblid)
+{
+	return (pipeid + tblid) % P4TC_TBLS_CACHE_SIZE;
+}
+
+struct p4tc_table *p4tc_tbl_cache_lookup(struct net *net, u32 pipeid, u32 tblid)
+{
+	size_t hash = p4tc_tbl_cache_hash(pipeid, tblid);
+	struct p4tc_pipeline_net *pipe_net;
+	struct p4tc_table *pos, *tmp;
+	struct net_generic *ng;
+
+	/* RCU read lock is already being held */
+	ng = rcu_dereference(net->gen);
+	pipe_net = ng->ptr[pipeline_net_id];
+
+	list_for_each_entry_safe(pos, tmp, &pipe_net->tbls_cache[hash],
+				 tbl_cache_node) {
+		if (pos->common.p_id == pipeid && pos->tbl_id == tblid)
+			return pos;
+	}
+
+	return NULL;
+}
+
+int p4tc_tbl_cache_insert(struct net *net, u32 pipeid, struct p4tc_table *table)
+{
+	struct p4tc_pipeline_net *pipe_net = net_generic(net, pipeline_net_id);
+	size_t hash = p4tc_tbl_cache_hash(pipeid, table->tbl_id);
+
+	list_add_tail(&table->tbl_cache_node, &pipe_net->tbls_cache[hash]);
+
+	return 0;
+}
+
+void p4tc_tbl_cache_remove(struct net *net, struct p4tc_table *table)
+{
+	list_del(&table->tbl_cache_node);
+}
+#endif
+
 static int tcf_pipeline_put(struct net *net,
 			    struct p4tc_template_common *template,
 			    bool unconditional_purgeline,
@@ -73,10 +120,13 @@ static const struct nla_policy tc_pipeline_policy[P4TC_PIPELINE_MAX + 1] = {
 	[P4TC_PIPELINE_NUMTABLES] =
 		NLA_POLICY_RANGE(NLA_U16, P4TC_MINTABLES_COUNT, P4TC_MAXTABLES_COUNT),
 	[P4TC_PIPELINE_STATE] = { .type = NLA_U8 },
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	[P4TC_PIPELINE_PREACTIONS] = { .type = NLA_NESTED },
 	[P4TC_PIPELINE_POSTACTIONS] = { .type = NLA_NESTED },
+#endif
 };
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 static void __act_dep_graph_free(struct list_head *incoming_egde_list)
 {
 	struct p4tc_act_dep_edge_node *cursor_edge, *tmp_edge;
@@ -291,11 +341,14 @@ int determine_act_topological_order(struct p4tc_pipeline *pipeline,
 
 	return 0;
 }
+#endif
 
 static void tcf_pipeline_destroy(struct p4tc_pipeline *pipeline,
 				 bool free_pipeline)
 {
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	idr_destroy(&pipeline->p_meta_idr);
+#endif
 	idr_destroy(&pipeline->p_act_idr);
 	idr_destroy(&pipeline->p_tbl_idr);
 	idr_destroy(&pipeline->p_reg_idr);
@@ -324,9 +377,17 @@ static int tcf_pipeline_put(struct net *net,
 	struct p4tc_pipeline_net *pipe_net = net_generic(net, pipeline_net_id);
 	struct p4tc_pipeline *pipeline = to_pipeline(template);
 	struct net *pipeline_net = maybe_get_net(net);
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	struct p4tc_act *act;
+	unsigned long iter_act_id;
+#else
 	struct p4tc_act_dep_node *act_node, *node_tmp;
-	unsigned long reg_id, tbl_id, m_id, tmp;
+#endif
+	unsigned long reg_id, tbl_id, tmp;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct p4tc_metadata *meta;
+	unsigned long m_id;
+#endif
 	struct p4tc_register *reg;
 	struct p4tc_table *table;
 
@@ -349,12 +410,18 @@ static int tcf_pipeline_put(struct net *net,
 	 * will use them. So there is no need to free them in the rcu
 	 * callback. We can just free them here
 	 */
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	p4tc_action_destroy(pipeline->preacts);
 	p4tc_action_destroy(pipeline->postacts);
+#endif
 
 	idr_for_each_entry_ul(&pipeline->p_tbl_idr, table, tmp, tbl_id)
 		table->common.ops->put(net, &table->common, true, extack);
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	idr_for_each_entry_ul(&pipeline->p_act_idr, act, tmp, iter_act_id)
+		act->common.ops->put(net, &act->common, true, extack);
+#else
 	act_dep_graph_free(&pipeline->act_dep_graph);
 
 	list_for_each_entry_safe(act_node, node_tmp,
@@ -366,9 +433,12 @@ static int tcf_pipeline_put(struct net *net,
 		list_del(&act_node->head);
 		kfree(act_node);
 	}
+#endif
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	idr_for_each_entry_ul(&pipeline->p_meta_idr, meta, tmp, m_id)
 		meta->common.ops->put(net, &meta->common, true, extack);
+#endif
 
 	if (pipeline->parser)
 		tcf_parser_del(net, pipeline, pipeline->parser, extack);
@@ -398,6 +468,7 @@ static inline int pipeline_try_set_state_ready(struct p4tc_pipeline *pipeline,
 		return -EINVAL;
 	}
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (!pipeline->preacts) {
 		NL_SET_ERR_MSG(extack,
 			       "Must specify pipeline preactions before sealing");
@@ -409,12 +480,15 @@ static inline int pipeline_try_set_state_ready(struct p4tc_pipeline *pipeline,
 			       "Must specify pipeline postactions before sealing");
 		return -EINVAL;
 	}
+#endif
 	ret = tcf_table_try_set_state_ready(pipeline, extack);
 	if (ret < 0)
 		return ret;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	/* Will never fail in this case */
 	determine_act_topological_order(pipeline, false);
+#endif
 
 	pipeline->p_state = P4TC_STATE_READY;
 	return true;
@@ -520,6 +594,14 @@ static struct p4tc_pipeline *tcf_pipeline_create(struct net *net,
 	else
 		pipeline->num_tables = P4TC_DEFAULT_NUM_TABLES;
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	if (tb[P4TC_PIPELINE_PREACTIONS]) {
+		NL_SET_ERR_MSG(extack,
+			       "Pipeline preactions not supported in kfuncs mode");
+		ret = -EOPNOTSUPP;
+		goto idr_rm;
+	}
+#else
 	if (tb[P4TC_PIPELINE_PREACTIONS]) {
 		pipeline->preacts = kcalloc(TCA_ACT_MAX_PRIO,
 					    sizeof(struct tc_action *),
@@ -540,7 +622,16 @@ static struct p4tc_pipeline *tcf_pipeline_create(struct net *net,
 		pipeline->preacts = NULL;
 		pipeline->num_preacts = 0;
 	}
+#endif
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	if (tb[P4TC_PIPELINE_POSTACTIONS]) {
+		NL_SET_ERR_MSG(extack,
+			       "Pipeline postactions not supported in kfuncs mode");
+		ret = -EOPNOTSUPP;
+		goto idr_rm;
+	}
+#else
 	if (tb[P4TC_PIPELINE_POSTACTIONS]) {
 		pipeline->postacts = kcalloc(TCA_ACT_MAX_PRIO,
 					     sizeof(struct tc_action *),
@@ -561,6 +652,7 @@ static struct p4tc_pipeline *tcf_pipeline_create(struct net *net,
 		pipeline->postacts = NULL;
 		pipeline->num_postacts = 0;
 	}
+#endif
 
 	pipeline->parser = NULL;
 
@@ -569,13 +661,17 @@ static struct p4tc_pipeline *tcf_pipeline_create(struct net *net,
 	idr_init(&pipeline->p_tbl_idr);
 	pipeline->curr_tables = 0;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	idr_init(&pipeline->p_meta_idr);
 	pipeline->p_meta_offset = 0;
+#endif
 
 	idr_init(&pipeline->p_reg_idr);
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	INIT_LIST_HEAD(&pipeline->act_dep_graph);
 	INIT_LIST_HEAD(&pipeline->act_topological_order);
+#endif
 	pipeline->num_created_acts = 0;
 
 	pipeline->p_state = P4TC_STATE_NOT_READY;
@@ -591,8 +687,10 @@ static struct p4tc_pipeline *tcf_pipeline_create(struct net *net,
 
 	return pipeline;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 preactions_destroy:
 	p4tc_action_destroy(pipeline->preacts);
+#endif
 
 idr_rm:
 	idr_remove(&pipe_net->pipeline_idr, pipeid);
@@ -715,7 +813,9 @@ tcf_pipeline_update(struct net *net, struct nlmsghdr *n, struct nlattr *nla,
 	int ret = 0;
 	struct nlattr *tb[P4TC_PIPELINE_MAX + 1];
 	struct p4tc_pipeline *pipeline;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	int num_preacts, num_postacts;
+#endif
 
 	ret = nla_parse_nested(tb, P4TC_PIPELINE_MAX, nla, tc_pipeline_policy,
 			       extack);
@@ -734,6 +834,14 @@ tcf_pipeline_update(struct net *net, struct nlmsghdr *n, struct nlattr *nla,
 	if (tb[P4TC_PIPELINE_MAXRULES])
 		max_rules = nla_get_u32(tb[P4TC_PIPELINE_MAXRULES]);
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	if (tb[P4TC_PIPELINE_PREACTIONS]) {
+		NL_SET_ERR_MSG(extack,
+			       "Pipeline preactions not supported in kfuncs mode");
+		ret = -EOPNOTSUPP;
+		goto out;
+	}
+#else
 	if (tb[P4TC_PIPELINE_PREACTIONS]) {
 		preacts = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *),
 				  GFP_KERNEL);
@@ -751,7 +859,16 @@ tcf_pipeline_update(struct net *net, struct nlmsghdr *n, struct nlattr *nla,
 		}
 		num_preacts = ret;
 	}
+#endif
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	if (tb[P4TC_PIPELINE_POSTACTIONS]) {
+		NL_SET_ERR_MSG(extack,
+			       "Pipeline preactions not supported in kfuncs mode");
+		ret = -EOPNOTSUPP;
+		goto preactions_destroy;
+	}
+#else
 	if (tb[P4TC_PIPELINE_POSTACTIONS]) {
 		postacts = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *),
 				   GFP_KERNEL);
@@ -769,18 +886,22 @@ tcf_pipeline_update(struct net *net, struct nlmsghdr *n, struct nlattr *nla,
 		}
 		num_postacts = ret;
 	}
+#endif
 
 	if (tb[P4TC_PIPELINE_STATE]) {
 		ret = pipeline_try_set_state_ready(pipeline, extack);
 		if (ret < 0)
 			goto postactions_destroy;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 		tcf_meta_fill_user_offsets(pipeline);
+#endif
 	}
 
 	if (max_rules)
 		pipeline->max_rules = max_rules;
 	if (num_tables)
 		pipeline->num_tables = num_tables;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (preacts) {
 		p4tc_action_destroy(pipeline->preacts);
 		pipeline->preacts = preacts;
@@ -791,6 +912,7 @@ tcf_pipeline_update(struct net *net, struct nlmsghdr *n, struct nlattr *nla,
 		pipeline->postacts = postacts;
 		pipeline->num_postacts = num_postacts;
 	}
+#endif
 
 	return pipeline;
 
@@ -835,7 +957,10 @@ static int _tcf_pipeline_fill_nlmsg(struct sk_buff *skb,
 				    const struct p4tc_pipeline *pipeline)
 {
 	unsigned char *b = nlmsg_get_pos(skb);
-	struct nlattr *nest, *preacts, *postacts;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
+	 struct nlattr *preacts, *postacts;
+#endif
+	struct nlattr *nest;
 
 	nest = nla_nest_start(skb, P4TC_PARAMS);
 	if (!nest)
@@ -848,6 +973,7 @@ static int _tcf_pipeline_fill_nlmsg(struct sk_buff *skb,
 	if (nla_put_u8(skb, P4TC_PIPELINE_STATE, pipeline->p_state))
 		goto out_nlmsg_trim;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (pipeline->preacts) {
 		preacts = nla_nest_start(skb, P4TC_PIPELINE_PREACTIONS);
 		if (tcf_action_dump(skb, pipeline->preacts, 0, 0, false) < 0)
@@ -861,6 +987,7 @@ static int _tcf_pipeline_fill_nlmsg(struct sk_buff *skb,
 			goto out_nlmsg_trim;
 		nla_nest_end(skb, postacts);
 	}
+#endif
 
 	nla_nest_end(skb, nest);
 
@@ -992,7 +1119,9 @@ static void __tcf_pipeline_init(void)
 
 	strscpy(root_pipeline->common.name, "kernel", PIPELINENAMSIZ);
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	idr_init(&root_pipeline->p_meta_idr);
+#endif
 
 	root_pipeline->common.ops =
 		(struct p4tc_template_ops *)&p4tc_pipeline_ops;
@@ -1001,7 +1130,9 @@ static void __tcf_pipeline_init(void)
 
 	root_pipeline->p_state = P4TC_STATE_READY;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	tcf_meta_init(root_pipeline);
+#endif
 }
 
 static void tcf_pipeline_init(void)
diff --git a/net/sched/p4tc/p4tc_table.c b/net/sched/p4tc/p4tc_table.c
index e5b1a56aed7d..64f994c7dffc 100644
--- a/net/sched/p4tc/p4tc_table.c
+++ b/net/sched/p4tc/p4tc_table.c
@@ -31,6 +31,7 @@
 #define P4TC_P_UNSPEC 0
 #define P4TC_P_CREATED 1
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 static int tcf_key_try_set_state_ready(struct p4tc_table_key *key,
 				       struct netlink_ext_ack *extack)
 {
@@ -42,11 +43,12 @@ static int tcf_key_try_set_state_ready(struct p4tc_table_key *key,
 
 	return 0;
 }
+#endif
 
 static int __tcf_table_try_set_state_ready(struct p4tc_table *table,
 					   struct netlink_ext_ack *extack)
 {
-	int i;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	int ret;
 
 	if (!table->tbl_postacts) {
@@ -64,6 +66,7 @@ static int __tcf_table_try_set_state_ready(struct p4tc_table *table,
 	ret = tcf_key_try_set_state_ready(table->tbl_key, extack);
 	if (ret < 0)
 		return ret;
+#endif
 
 	table->tbl_masks_array = kcalloc(table->tbl_max_masks,
 					 sizeof(*(table->tbl_masks_array)),
@@ -131,15 +134,18 @@ static const struct nla_policy p4tc_table_policy[P4TC_TABLE_MAX + 1] = {
 	[P4TC_TABLE_NAME] = { .type = NLA_STRING, .len = TABLENAMSIZ },
 	[P4TC_TABLE_INFO] = { .type = NLA_BINARY,
 			      .len = sizeof(struct p4tc_table_parm) },
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	[P4TC_TABLE_PREACTIONS] = { .type = NLA_NESTED },
 	[P4TC_TABLE_KEY] = { .type = NLA_NESTED },
 	[P4TC_TABLE_POSTACTIONS] = { .type = NLA_NESTED },
+#endif
 	[P4TC_TABLE_DEFAULT_HIT] = { .type = NLA_NESTED },
 	[P4TC_TABLE_DEFAULT_MISS] = { .type = NLA_NESTED },
 	[P4TC_TABLE_ACTS_LIST] = { .type = NLA_NESTED },
 	[P4TC_TABLE_OPT_ENTRY] = { .type = NLA_NESTED },
 };
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 static const struct nla_policy p4tc_table_key_policy[P4TC_MAXPARSE_KEYS + 1] = {
 	[P4TC_KEY_ACT] = { .type = NLA_NESTED },
 };
@@ -160,6 +166,7 @@ static int tcf_table_key_fill_nlmsg(struct sk_buff *skb,
 
 	return ret;
 }
+#endif
 
 static int _tcf_table_fill_nlmsg(struct sk_buff *skb, struct p4tc_table *table)
 {
@@ -172,11 +179,15 @@ static int _tcf_table_fill_nlmsg(struct sk_buff *skb, struct p4tc_table *table)
 	struct nlattr *default_hitact;
 	struct nlattr *nested_count;
 	struct p4tc_table_parm parm;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct nlattr *nest_key;
+#endif
 	struct nlattr *nest;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct nlattr *preacts;
 	struct nlattr *postacts;
 	int err;
+#endif
 
 	if (nla_put_u32(skb, P4TC_PATH, table->tbl_id))
 		goto out_nlmsg_trim;
@@ -196,6 +207,7 @@ static int _tcf_table_fill_nlmsg(struct sk_buff *skb, struct p4tc_table *table)
 	tbl_perm = rcu_dereference_rtnl(table->tbl_permissions);
 	parm.tbl_permissions = tbl_perm->permissions;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (table->tbl_key) {
 		nest_key = nla_nest_start(skb, P4TC_TABLE_KEY);
 		err = tcf_table_key_fill_nlmsg(skb, table->tbl_key);
@@ -217,6 +229,7 @@ static int _tcf_table_fill_nlmsg(struct sk_buff *skb, struct p4tc_table *table)
 			goto out_nlmsg_trim;
 		nla_nest_end(skb, postacts);
 	}
+#endif
 
 	if (table->tbl_default_hitact) {
 		struct p4tc_table_defact *hitact;
@@ -322,16 +335,21 @@ static int tcf_table_fill_nlmsg(struct net *net, struct sk_buff *skb,
 	return 0;
 }
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 static inline void tcf_table_key_put(struct p4tc_table_key *key)
 {
 	p4tc_action_destroy(key->key_acts);
 	kfree(key);
 }
+#endif
 
 static inline void p4tc_table_defact_destroy(struct p4tc_table_defact *defact)
 {
 	if (defact) {
 		p4tc_action_destroy(defact->default_acts);
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+		kfree(defact->defact_bpf);
+#endif
 		kfree(defact);
 	}
 }
@@ -435,16 +453,21 @@ static inline int _tcf_table_put(struct net *net, struct nlattr **tb,
 	if (default_act_del)
 		return 0;
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (table->tbl_key)
 		tcf_table_key_put(table->tbl_key);
 
 	p4tc_action_destroy(table->tbl_preacts);
 	p4tc_action_destroy(table->tbl_postacts);
+#endif
 
 	tcf_table_acts_list_destroy(&table->tbl_acts_list);
 
 	rhltable_free_and_destroy(&table->tbl_entries,
 				  tcf_table_entry_destroy_hash, table);
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	p4tc_tbl_cache_remove(net, table);
+#endif
 
 	idr_destroy(&table->tbl_masks_idr);
 	ida_destroy(&table->tbl_prio_idr);
@@ -475,6 +498,7 @@ static int tcf_table_put(struct net *net, struct p4tc_template_common *tmpl,
 			      extack);
 }
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 static inline struct p4tc_table_key *
 tcf_table_key_add(struct net *net, struct p4tc_table *table, struct nlattr *nla,
 		  struct netlink_ext_ack *extack)
@@ -520,6 +544,7 @@ tcf_table_key_add(struct net *net, struct p4tc_table *table, struct nlattr *nla,
 out:
 	return ERR_PTR(ret);
 }
+#endif
 
 struct p4tc_table *tcf_table_find_byid(struct p4tc_pipeline *pipeline,
 				       const u32 tbl_id)
@@ -623,6 +648,9 @@ static int tcf_table_init_default_act(struct net *net, struct nlattr **tb,
 	}
 
 	if (tb[P4TC_TABLE_DEFAULT_ACTION]) {
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+		struct p4tc_table_entry_act_bpf *act_bpf;
+#endif
 		struct tc_action **default_acts;
 
 		if (!p4tc_ctrl_update_ok(curr_permissions)) {
@@ -651,6 +679,17 @@ static int tcf_table_init_default_act(struct net *net, struct nlattr **tb,
 			ret = -EINVAL;
 			goto default_act_free;
 		}
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+		act_bpf = tcf_table_entry_create_act_bpf(default_acts[0],
+							 extack);
+		if (IS_ERR(act_bpf)) {
+			tcf_action_destroy(default_acts, TCA_ACT_UNBIND);
+			kfree(default_acts);
+			ret = -EINVAL;
+			goto default_act_free;
+		}
+		(*default_act)->defact_bpf = act_bpf;
+#endif
 		(*default_act)->default_acts = default_acts;
 	}
 
@@ -895,7 +934,9 @@ static struct p4tc_table *tcf_table_create(struct net *net, struct nlattr **tb,
 					   struct netlink_ext_ack *extack)
 {
 	struct rhashtable_params table_hlt_params = entry_hlt_params;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct p4tc_table_key *key = NULL;
+#endif
 	struct p4tc_table_parm *parm;
 	struct p4tc_table *table;
 	char *tblname;
@@ -1086,6 +1127,14 @@ static struct p4tc_table *tcf_table_create(struct net *net, struct nlattr **tb,
 			goto idr_rm;
 	}
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	if (tb[P4TC_TABLE_PREACTIONS]) {
+		NL_SET_ERR_MSG(extack,
+			       "Table preactions not supported in kfuncs mode");
+		ret = -EOPNOTSUPP;
+		goto table_acts_destroy;
+	}
+#else
 	if (tb[P4TC_TABLE_PREACTIONS]) {
 		table->tbl_preacts = kcalloc(TCA_ACT_MAX_PRIO,
 					     sizeof(struct tc_action *),
@@ -1106,7 +1155,16 @@ static struct p4tc_table *tcf_table_create(struct net *net, struct nlattr **tb,
 	} else {
 		table->tbl_preacts = NULL;
 	}
+#endif
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	if (tb[P4TC_TABLE_POSTACTIONS]) {
+		NL_SET_ERR_MSG(extack,
+			       "Table postactions not supported in kfuncs mode");
+		ret = -EOPNOTSUPP;
+		goto table_acts_destroy;
+	}
+#else
 	if (tb[P4TC_TABLE_POSTACTIONS]) {
 		table->tbl_postacts = kcalloc(TCA_ACT_MAX_PRIO,
 					      sizeof(struct tc_action *),
@@ -1128,7 +1186,16 @@ static struct p4tc_table *tcf_table_create(struct net *net, struct nlattr **tb,
 		table->tbl_postacts = NULL;
 		table->tbl_num_postacts = 0;
 	}
+#endif
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	if (tb[P4TC_TABLE_KEY]) {
+		NL_SET_ERR_MSG(extack,
+			       "Mustn't specify key in kfuncs mode");
+		ret = -EOPNOTSUPP;
+		goto table_acts_destroy;
+	}
+#else
 	if (tb[P4TC_TABLE_KEY]) {
 		key = tcf_table_key_add(net, table, tb[P4TC_TABLE_KEY], extack);
 		if (IS_ERR(key)) {
@@ -1136,13 +1203,18 @@ static struct p4tc_table *tcf_table_create(struct net *net, struct nlattr **tb,
 			goto postacts_destroy;
 		}
 	}
+#endif
 
 	ret = tcf_table_init_default_acts(net, tb, table,
 					  &table->tbl_default_hitact,
 					  &table->tbl_default_missact,
 					  &table->tbl_acts_list, extack);
 	if (ret < 0)
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+		goto table_acts_destroy;
+#else
 		goto key_put;
+#endif
 
 	table->tbl_curr_used_entries = 0;
 	table->tbl_curr_count = 0;
@@ -1164,7 +1236,15 @@ static struct p4tc_table *tcf_table_create(struct net *net, struct nlattr **tb,
 		goto defaultacts_destroy;
 	}
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	ret = p4tc_tbl_cache_insert(net, pipeline->common.p_id, table);
+	if (ret < 0)
+		goto entries_hashtable_destroy;
+#endif
+
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	table->tbl_key = key;
+#endif
 
 	pipeline->curr_tables += 1;
 
@@ -1172,8 +1252,14 @@ static struct p4tc_table *tcf_table_create(struct net *net, struct nlattr **tb,
 
 	return table;
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+entries_hashtable_destroy:
+	rhltable_destroy(&table->tbl_entries);
+#endif
+
 defaultacts_destroy:
 	p4tc_table_defact_destroy(table->tbl_default_missact);
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	p4tc_table_defact_destroy(table->tbl_default_hitact);
 
 key_put:
@@ -1185,6 +1271,7 @@ static struct p4tc_table *tcf_table_create(struct net *net, struct nlattr **tb,
 
 preactions_destroy:
 	p4tc_action_destroy(table->tbl_preacts);
+#endif
 
 idr_rm:
 	idr_remove(&pipeline->p_tbl_idr, table->tbl_id);
@@ -1208,15 +1295,19 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 					   u32 flags,
 					   struct netlink_ext_ack *extack)
 {
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct p4tc_table_key *key = NULL;
 	int num_postacts = 0, num_preacts = 0;
+#endif
 	struct p4tc_table_defact *default_hitact = NULL;
 	struct p4tc_table_defact *default_missact = NULL;
 	struct list_head *tbl_acts_list = NULL;
 	struct p4tc_table_perm *perm = NULL;
 	struct p4tc_table_parm *parm = NULL;
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	struct tc_action **postacts = NULL;
 	struct tc_action **preacts = NULL;
+#endif
 	int ret = 0;
 	struct p4tc_table *table;
 
@@ -1238,6 +1329,7 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 			goto table_acts_destroy;
 	}
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (tb[P4TC_TABLE_PREACTIONS]) {
 		preacts = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *),
 				  GFP_KERNEL);
@@ -1271,6 +1363,7 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 		}
 		num_postacts = ret;
 	}
+#endif
 
 	if (tbl_acts_list)
 		ret = tcf_table_init_default_acts(net, tb, table,
@@ -1284,8 +1377,20 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 						  &table->tbl_acts_list,
 						  extack);
 	if (ret < 0)
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+		goto table_acts_destroy;
+#else
 		goto postactions_destroy;
+#endif
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+	if (tb[P4TC_TABLE_KEY]) {
+		NL_SET_ERR_MSG(extack,
+			       "Mustn't specify key in kfuncs mode");
+		ret = -EOPNOTSUPP;
+		goto defaultacts_destroy;
+	}
+#else
 	if (tb[P4TC_TABLE_KEY]) {
 		key = tcf_table_key_add(net, table, tb[P4TC_TABLE_KEY], extack);
 		if (IS_ERR(key)) {
@@ -1293,6 +1398,7 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 			goto defaultacts_destroy;
 		}
 	}
+#endif
 
 	if (tb[P4TC_TABLE_INFO]) {
 		parm = nla_data(tb[P4TC_TABLE_INFO]);
@@ -1301,13 +1407,21 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 				NL_SET_ERR_MSG(extack,
 					       "Table keysz cannot be zero");
 				ret = -EINVAL;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+				goto defaultacts_destroy;
+#else
 				goto key_destroy;
+#endif
 			}
 			if (parm->tbl_keysz > P4TC_MAX_KEYSZ) {
 				NL_SET_ERR_MSG(extack,
 					       "Table keysz exceeds maximum keysz");
 				ret = -EINVAL;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+				goto defaultacts_destroy;
+#else
 				goto key_destroy;
+#endif
 			}
 			table->tbl_keysz = parm->tbl_keysz;
 		}
@@ -1317,13 +1431,21 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 				NL_SET_ERR_MSG(extack,
 					       "Table max_entries cannot be zero");
 				ret = -EINVAL;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+				goto defaultacts_destroy;
+#else
 				goto key_destroy;
+#endif
 			}
 			if (parm->tbl_max_entries > P4TC_MAX_TENTRIES) {
 				NL_SET_ERR_MSG(extack,
 					       "Table max_entries exceeds maximum value");
 				ret = -EINVAL;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+				goto defaultacts_destroy;
+#else
 				goto key_destroy;
+#endif
 			}
 			table->tbl_max_entries = parm->tbl_max_entries;
 		}
@@ -1333,13 +1455,21 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 				NL_SET_ERR_MSG(extack,
 					       "Table max_masks cannot be zero");
 				ret = -EINVAL;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+				goto defaultacts_destroy;
+#else
 				goto key_destroy;
+#endif
 			}
 			if (parm->tbl_max_masks > P4TC_MAX_TMASKS) {
 				NL_SET_ERR_MSG(extack,
 					       "Table max_masks exceeds maximum value");
 				ret = -EINVAL;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+				goto defaultacts_destroy;
+#else
 				goto key_destroy;
+#endif
 			}
 			table->tbl_max_masks = parm->tbl_max_masks;
 		}
@@ -1348,25 +1478,41 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 				NL_SET_ERR_MSG(extack,
 					       "Permission may only have 10 bits turned on");
 				ret = -EINVAL;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+				goto defaultacts_destroy;
+#else
 				goto key_destroy;
+#endif
 			}
 			if (!p4tc_data_exec_ok(parm->tbl_permissions)) {
 				NL_SET_ERR_MSG(extack,
 					       "Table must have execute permissions");
 				ret = -EINVAL;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+				goto defaultacts_destroy;
+#else
 				goto key_destroy;
+#endif
 			}
 			if (!p4tc_data_read_ok(parm->tbl_permissions)) {
 				NL_SET_ERR_MSG(extack,
 					       "Data path read permissions must be set");
 				ret = -EINVAL;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+				goto defaultacts_destroy;
+#else
 				goto key_destroy;
+#endif
 			}
 
 			perm = kzalloc(sizeof(*perm), GFP_KERNEL);
 			if (!perm) {
 				ret = -ENOMEM;
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+				goto defaultacts_destroy;
+#else
 				goto key_destroy;
+#endif
 			}
 			perm->permissions = parm->tbl_permissions;
 		}
@@ -1395,6 +1541,7 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 		table->tbl_const_entry = entry;
 	}
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (preacts) {
 		p4tc_action_destroy(table->tbl_preacts);
 		table->tbl_preacts = preacts;
@@ -1406,6 +1553,7 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 		table->tbl_postacts = postacts;
 		table->tbl_num_postacts = num_postacts;
 	}
+#endif
 
 	if (default_hitact) {
 		struct p4tc_table_defact *hitact;
@@ -1429,11 +1577,13 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 		}
 	}
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	if (key) {
 		if (table->tbl_key)
 			tcf_table_key_put(table->tbl_key);
 		table->tbl_key = key;
 	}
+#endif
 
 	if (perm) {
 		perm = rcu_replace_pointer_rtnl(table->tbl_permissions, perm);
@@ -1445,19 +1595,23 @@ static struct p4tc_table *tcf_table_update(struct net *net, struct nlattr **tb,
 free_perm:
 	kfree(perm);
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 key_destroy:
 	if (key)
 		tcf_table_key_put(key);
+#endif
 
 defaultacts_destroy:
 	p4tc_table_defact_destroy(default_missact);
 	p4tc_table_defact_destroy(default_hitact);
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 postactions_destroy:
 	p4tc_action_destroy(postacts);
 
 preactions_destroy:
 	p4tc_action_destroy(preacts);
+#endif
 
 table_acts_destroy:
 	if (tbl_acts_list) {
diff --git a/net/sched/p4tc/p4tc_tbl_api.c b/net/sched/p4tc/p4tc_tbl_api.c
index 21784b84864f..fb0a17cab3a7 100644
--- a/net/sched/p4tc/p4tc_tbl_api.c
+++ b/net/sched/p4tc/p4tc_tbl_api.c
@@ -142,23 +142,15 @@ static void mask_key(const struct p4tc_table_entry_mask *mask, u8 *masked_key,
 		masked_key[i] = skb_key[i] & mask->fa_value[i];
 }
 
-struct p4tc_table_entry *p4tc_table_entry_lookup(struct sk_buff *skb,
-						 struct p4tc_table *table,
-						 u32 keysz)
+struct p4tc_table_entry *
+p4tc_table_entry_lookup_direct(struct p4tc_table *table,
+			       struct p4tc_table_entry_key *key)
 {
-	const struct p4tc_table_entry_mask **masks_array;
-	u32 smallest_prio = U32_MAX;
 	struct p4tc_table_entry *entry = NULL;
-	struct p4tc_percpu_scratchpad *pad;
-	struct p4tc_table_entry_key *key;
+	u32 smallest_prio = U32_MAX;
+	const struct p4tc_table_entry_mask **masks_array;
 	int i;
 
-	pad = this_cpu_ptr(&p4tc_percpu_scratchpad);
-
-	key = (struct p4tc_table_entry_key *)&pad->keysz;
-	key->keysz = keysz;
-	key->maskid = 0;
-
 	if (table->tbl_type == P4TC_TABLE_TYPE_EXACT)
 		return __p4tc_entry_lookup_fast(table, key);
 
@@ -194,6 +186,24 @@ struct p4tc_table_entry *p4tc_table_entry_lookup(struct sk_buff *skb,
 	return entry;
 }
 
+#ifndef CONFIG_NET_P4_TC_KFUNCS
+struct p4tc_table_entry *p4tc_table_entry_lookup(struct sk_buff *skb,
+						 struct p4tc_table *table,
+						 u32 keysz)
+{
+	struct p4tc_percpu_scratchpad *pad;
+	struct p4tc_table_entry_key *key;
+
+	pad = this_cpu_ptr(&p4tc_percpu_scratchpad);
+
+	key = (void *)&pad->keysz;
+	key->keysz = keysz;
+	key->maskid = 0;
+
+	return p4tc_table_entry_lookup_direct(table, key);
+}
+#endif
+
 #define tcf_table_entry_mask_find_byid(table, id) \
 	(idr_find(&(table)->tbl_masks_idr, id))
 
@@ -596,6 +606,10 @@ static void tcf_table_entry_put(struct p4tc_table_entry *entry)
 		struct p4tc_pipeline *pipeline = entry_work->pipeline;
 		struct net *net;
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+		kfree(value->act_bpf);
+#endif
+
 		if (entry_work->defer_deletion) {
 			net = get_net(pipeline->net);
 			refcount_inc(&entry_work->pipeline->p_entry_deferal_ref);
@@ -1454,7 +1468,9 @@ static struct p4tc_table_entry *__tcf_table_entry_cu(struct net *net, u32 flags,
 	}
 
 	if (tb[P4TC_ENTRY_ACT]) {
-
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+		struct p4tc_table_entry_act_bpf *act_bpf;
+#endif
 		value->acts = kcalloc(TCA_ACT_MAX_PRIO,
 				      sizeof(struct tc_action *), GFP_KERNEL);
 		if (!value->acts) {
@@ -1480,6 +1496,16 @@ static struct p4tc_table_entry *__tcf_table_entry_cu(struct net *net, u32 flags,
 				       "Action is not allowed as entry action");
 			goto free_acts;
 		}
+
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+		act_bpf = tcf_table_entry_create_act_bpf(value->acts[0],
+							 extack);
+		if (IS_ERR(act_bpf)) {
+			ret = PTR_ERR(act_bpf);
+			goto free_acts;
+		}
+		value->act_bpf = act_bpf;
+#endif
 	}
 
 	rcu_read_lock();
@@ -1491,12 +1517,21 @@ static struct p4tc_table_entry *__tcf_table_entry_cu(struct net *net, u32 flags,
 					       whodunnit, true);
 	if (ret < 0) {
 		rcu_read_unlock();
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+		goto free_act_bpf;
+#else
 		goto free_acts;
+#endif
 	}
 	rcu_read_unlock();
 
 	return entry;
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+free_act_bpf:
+	kfree(value->act_bpf);
+#endif
+
 free_acts:
 	p4tc_action_destroy(value->acts);
 
@@ -1510,6 +1545,58 @@ static struct p4tc_table_entry *__tcf_table_entry_cu(struct net *net, u32 flags,
 	return ERR_PTR(ret);
 }
 
+#ifdef CONFIG_NET_P4_TC_KFUNCS
+struct p4tc_table_entry_act_bpf *
+tcf_table_entry_create_act_bpf(struct tc_action *action,
+			       struct netlink_ext_ack *extack)
+{
+	size_t tot_params_sz = 0;
+	int num_params = 0;
+	struct p4tc_act_param *params[P4TC_MSGBATCH_SIZE];
+	struct p4tc_table_entry_act_bpf *act_bpf;
+	struct p4tc_act_param *param;
+	unsigned long param_id, tmp;
+	struct tcf_p4act *p4act;
+	struct tcf_p4act_params *act_params;
+	u8 *params_cursor;
+	int i;
+
+	p4act = to_p4act(action);
+
+	act_params = rcu_dereference(p4act->params);
+
+	idr_for_each_entry_ul(&act_params->params_idr, param, tmp, param_id) {
+		const struct p4tc_type *type = param->type;
+
+		if (tot_params_sz > P4TC_MAX_PARAM_DATA_SIZE) {
+			NL_SET_ERR_MSG(extack, "Maximum parameter byte size reached");
+			return ERR_PTR(-EINVAL);
+		}
+
+		tot_params_sz += BITS_TO_BYTES(type->container_bitsz);
+		params[num_params] = param;
+		num_params++;
+	}
+
+	act_bpf = kzalloc(sizeof(*act_bpf), GFP_KERNEL);
+	if (!act_bpf)
+		return ERR_PTR(-ENOMEM);
+
+	act_bpf->act_id = p4act->act_id;
+	params_cursor = (u8 *)act_bpf + sizeof(act_bpf->act_id);
+	for (i = 0; i < num_params; i++) {
+		const struct p4tc_act_param *param = params[i];
+		const struct p4tc_type *type = param->type;
+		const u32 type_bytesz = BITS_TO_BYTES(type->container_bitsz);
+
+		memcpy(params_cursor, param->value, type_bytesz);
+		params_cursor += type_bytesz;
+	}
+
+	return act_bpf;
+}
+#endif
+
 static int tcf_table_entry_cu(struct sk_buff *skb, struct net *net, u32 flags,
 			      struct nlattr *arg, u32 *ids,
 			      struct p4tc_nl_pname *nl_pname,
diff --git a/net/sched/p4tc/p4tc_tmpl_api.c b/net/sched/p4tc/p4tc_tmpl_api.c
index e8f2ad250256..8e9d3cda44a3 100644
--- a/net/sched/p4tc/p4tc_tmpl_api.c
+++ b/net/sched/p4tc/p4tc_tmpl_api.c
@@ -42,7 +42,9 @@ static bool obj_is_valid(u32 obj)
 {
 	switch (obj) {
 	case P4TC_OBJ_PIPELINE:
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	case P4TC_OBJ_META:
+#endif
 	case P4TC_OBJ_HDR_FIELD:
 	case P4TC_OBJ_ACT:
 	case P4TC_OBJ_TABLE:
@@ -55,7 +57,9 @@ static bool obj_is_valid(u32 obj)
 
 static const struct p4tc_template_ops *p4tc_ops[P4TC_OBJ_MAX] = {
 	[P4TC_OBJ_PIPELINE] = &p4tc_pipeline_ops,
+#ifndef CONFIG_NET_P4_TC_KFUNCS
 	[P4TC_OBJ_META] = &p4tc_meta_ops,
+#endif
 	[P4TC_OBJ_HDR_FIELD] = &p4tc_hdrfield_ops,
 	[P4TC_OBJ_ACT] = &p4tc_act_ops,
 	[P4TC_OBJ_TABLE] = &p4tc_table_ops,
@@ -576,6 +580,9 @@ static int __init p4tc_template_init(void)
 	for (obj = P4TC_OBJ_PIPELINE; obj < P4TC_OBJ_MAX; obj++) {
 		const struct p4tc_template_ops *op = p4tc_ops[obj];
 
+		if (!op)
+			continue;
+
 		if (!obj_is_valid(obj))
 			continue;
 
@@ -583,6 +590,8 @@ static int __init p4tc_template_init(void)
 			op->init();
 	}
 
+	register_p4tc_tbl_bpf();
+
 	return 0;
 }
 
