diff --git a/tools/bpf/ksnoop/Documentation/Makefile b/tools/bpf/ksnoop/Documentation/Makefile
new file mode 100644
index 0000000..7c2331b9
--- /dev/null
+++ b/tools/bpf/ksnoop/Documentation/Makefile
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0-only
+include ../../../scripts/Makefile.include
+include ../../../scripts/utilities.mak
+
+INSTALL ?= install
+RM ?= rm -f
+RMDIR ?= rmdir --ignore-fail-on-non-empty
+
+ifeq ($(V),1)
+  Q =
+else
+  Q = @
+endif
+
+prefix ?= /usr/local
+mandir ?= $(prefix)/man
+man8dir = $(mandir)/man8
+
+MAN8_RST = ksnoop.rst
+
+_DOC_MAN8 = $(patsubst %.rst,%.8,$(MAN8_RST))
+DOC_MAN8 = $(addprefix $(OUTPUT),$(_DOC_MAN8))
+
+man: man8
+man8: $(DOC_MAN8)
+
+RST2MAN_DEP := $(shell command -v rst2man 2>/dev/null)
+RST2MAN_OPTS += --verbose
+
+list_pages = $(sort $(basename $(filter-out $(1),$(MAN8_RST))))
+see_also = $(subst " ",, \
+	"\n" \
+	"SEE ALSO\n" \
+	"========\n" \
+	"\t**bpf**\ (2),\n")
+
+$(OUTPUT)%.8: %.rst
+ifndef RST2MAN_DEP
+	$(error "rst2man not found, but required to generate man pages")
+endif
+	$(QUIET_GEN)( cat $< ; printf "%b" $(call see_also,$<) ) | rst2man $(RST2MAN_OPTS) > $@
+
+clean:
+	$(call QUIET_CLEAN, Documentation)
+	$(Q)$(RM) $(DOC_MAN8)
+
+install: man
+	$(call QUIET_INSTALL, Documentation-man)
+	$(Q)$(INSTALL) -d -m 755 $(DESTDIR)$(man8dir)
+	$(Q)$(INSTALL) -m 644 $(DOC_MAN8) $(DESTDIR)$(man8dir)
+
+uninstall:
+	$(call QUIET_UNINST, Documentation-man)
+	$(Q)$(RM) $(addprefix $(DESTDIR)$(man8dir)/,$(_DOC_MAN8))
+	$(Q)$(RMDIR) $(DESTDIR)$(man8dir)
+
+.PHONY: man man8 clean install uninstall
+.DEFAULT_GOAL := man
diff --git a/tools/bpf/ksnoop/Documentation/ksnoop.rst b/tools/bpf/ksnoop/Documentation/ksnoop.rst
new file mode 100644
index 0000000..31161c4
--- /dev/null
+++ b/tools/bpf/ksnoop/Documentation/ksnoop.rst
@@ -0,0 +1,173 @@
+================
+KSNOOP
+================
+-------------------------------------------------------------------------------
+tool for tracing kernel function entry/return showing arguments/return values
+-------------------------------------------------------------------------------
+
+:Manual section: 8
+
+SYNOPSIS
+========
+
+	**ksnoop** [*OPTIONS*] { *COMMAND*  *FUNC* | **help** }
+
+	*OPTIONS* := { { **-V** | **--version** } | { **-h** | **--help** }
+	| { [**-P** | **--pages**] nr_pages} | { [**-p** | **--pid**] pid} |
+        [{ **-s** | **--stack** }] | [{ **-d** | **--debug** }] }
+
+	*COMMAND* := { **trace** | **info** }
+
+        *FUNC* := { **name** | **name**\(**arg**\[,**arg\]) }
+
+DESCRIPTION
+===========
+	*ksnoop* allows for inspection of arguments and return values
+        associated with function entry/return.
+
+        **ksnoop info** *FUNC*
+                Show function description, arguments and return value types.
+        **ksnoop trace** *FUNC* [*FUNC*]
+                Trace function entry and return, showing arguments and
+                return values.  A function name can simply be specified,
+                or a function name along with named arguments, return values.
+                **return** is used to specify the return value.
+
+        *ksnoop* requires the kernel to provide BTF for itself, and if
+        tracing of module data is required, module BTF must be present also.
+        Check /sys/kernel/btf to see if BTF is present.
+
+        **ksnoop** requires *CAP_BPF* and *CAP_TRACING* capabilities.
+
+OPTIONS
+=======
+        -h, --help
+                  Show help information
+        -V, --version
+                  Show version.
+        -d, --debug
+                  Show debug output.
+        -p, --pid
+                  Filter events by pid.
+        -P, --pages
+                  Specify number of pages used per-CPU for perf event
+                  collection.  Default is 8.
+        -s, --stack
+                  Specified set of functions are traced if and only
+                  if they are encountered in the order specified.
+
+EXAMPLES
+========
+**# ksnoop info ip_send_skb** ::
+
+  int  ip_send_skb(struct net  * net, struct sk_buff  * skb);
+
+Show function description.
+
+**# ksnoop trace ip_send_skb** ::
+
+            TIME  CPU      PID FUNCTION/ARGS
+  78101668506811    1     2813 ip_send_skb(
+                                   net = *(0xffffffffb5959840)
+                                    (struct net){
+                                     .passive = (refcount_t){
+                                      .refs = (atomic_t){
+                                       .counter = (int)0x2,
+                                      },
+                                     },
+                                     .dev_base_seq = (unsigned int)0x18,
+                                     .ifindex = (int)0xf,
+                                     .list = (struct list_head){
+                                      .next = (struct list_head *)0xffff9895440dc120,
+                                      .prev = (struct list_head *)0xffffffffb595a8d0,
+                                     },
+                                   ...
+
+  79561322965250    1     2813 ip_send_skb(
+                                   return =
+                                    (int)0x0
+                               );
+
+Show entry/return for ip_send_skb() with arguments, return values.
+
+**# ksnoop trace "ip_send_skb(skb)"** ::
+
+
+           TIME  CPU      PID FUNCTION/ARGS
+  78142420834537    1     2813 ip_send_skb(
+                                   skb = *(0xffff989750797c00)
+                                    (struct sk_buff){
+                                     (union){
+                                      .sk = (struct sock *)0xffff98966ce19200,
+                                      .ip_defrag_offset = (int)0x6ce19200,
+                                     },
+                                     (union){
+                                      (struct){
+                                       ._skb_refdst = (long unsigned int)0xffff98981dde2d80,
+                                       .destructor = (void (*)(struct sk_buff *))0xffffffffb3e1beb0,
+                                      },
+                                  ...
+
+Show entry argument **skb**.
+
+**# ksnoop trace "ip_send_skb(return)"** ::
+
+           TIME  CPU      PID FUNCTION/ARGS
+  78178228354796    1     2813 ip_send_skb(
+                                   return =
+                                    (int)0x0
+                               );
+
+Show return value from ip_send_skb().
+
+**# ksnoop trace "ip_send_skb(skb->sk)"** ::
+
+            TIME  CPU      PID FUNCTION/ARGS
+  78207649138829    2     2813 ip_send_skb(
+                                   skb->sk = *(0xffff98966ce19200)
+                                    (struct sock){
+                                     .__sk_common = (struct sock_common){
+                                      (union){
+                                       .skc_addrpair = (__addrpair)0x1701a8c017d38f8d,
+                                       (struct){
+                                        .skc_daddr = (__be32)0x17d38f8d,
+                                        .skc_rcv_saddr = (__be32)0x1701a8c0,
+                                       },
+                                      },
+                                    ...
+
+Trace meber information associated with argument.  Only one level of
+membership is supported.
+
+**# ksnoop -p 2813 "ip_rcv(dev)"** ::
+
+            TIME  CPU      PID FUNCTION/ARGS
+  78254803164920    1     2813 ip_rcv(
+                                   dev = *(0xffff9895414cb000)
+                                    (struct net_device){
+                                     .name = (char[16])[
+                                      'l',
+                                      'o',
+                                     ],
+                                     .name_node = (struct netdev_name_node *)0xffff989541515ec0,
+                                     .state = (long unsigned int)0x3,
+                                   ...
+
+Trace **dev** argument of **ip_rcv()**.  Specify process id 2813 for events
+for that process only.
+
+**# ksnoop -s tcp_sendmsg __tcp_transmit_skb  ip_output** ::
+
+           TIME  CPU      PID FUNCTION/ARGS
+  71827770952903    1     4777 __tcp_transmit_skb(
+                                   sk = *(0xffff9852460a2300)
+                                    (struct sock){
+                                     .__sk_common = (struct sock_common){
+                                      (union){
+                                       .skc_addrpair = (__addrpair)0x61b2af0a35cbfe0a,
+
+Trace entry/return of tcp_sendmsg, __tcp_transmit_skb and ip_output when
+tcp_sendmsg leads to a call to __tcp_transmit_skb and that in turn
+leads to a call to ip_output; i.e. with a call graph matching the order
+specified.  The order does not have to be direct calls, i.e. function A
+can call another function that calls function B.
diff --git a/tools/bpf/ksnoop/Makefile b/tools/bpf/ksnoop/Makefile
index 0a1420e..149e4be 100644
--- a/tools/bpf/ksnoop/Makefile
+++ b/tools/bpf/ksnoop/Makefile
@@ -42,12 +42,12 @@ endif
 
 .DELETE_ON_ERROR:
 
-.PHONY: all clean ksnoop
+.PHONY: all clean ksnoop doc doc-clean doc-install
 all: ksnoop
 
-ksnoop: $(OUTPUT)/ksnoop
+ksnoop: $(OUTPUT)/ksnoop doc
 
-clean:
+clean: doc-clean
 	$(call QUIET_CLEAN, ksnoop)
 	$(Q)$(RM) -r $(BPFOBJ_OUTPUT) $(BPFTOOL_OUTPUT)
 	$(Q)$(RM) $(OUTPUT)*.o $(OUTPUT)*.d
@@ -55,11 +55,20 @@ clean:
 	$(Q)$(RM) $(OUTPUT)ksnoop
 	$(Q)$(RM) -r .output
 
-install: ksnoop
+install: ksnoop doc-install
 	$(call QUIET_INSTALL, ksnoop)
 	$(Q)$(INSTALL) -m 0755 -d $(DESTDIR)$(prefix)/sbin
 	$(Q)$(INSTALL) $(OUTPUT)/ksnoop $(DESTDIR)$(prefix)/sbin/ksnoop
 
+doc:
+	$(call descend,Documentation)
+
+doc-clean:
+	$(call descend,Documentation,clean)
+
+doc-install:
+	$(call descend,Documentation,install)
+
 $(OUTPUT)/ksnoop: $(OUTPUT)/ksnoop.o $(BPFOBJ)
 	$(QUIET_LINK)$(CC) $(CFLAGS) $^ -lelf -lz -o $@
 
