From: Stanislav Fomichev <stfomichev@gmail.com>
To: Joe Damato <jdamato@fastly.com>
Cc: netdev@vger.kernel.org, corbet@lwn.net, hdanton@sina.com,
bagasdotme@gmail.com, pabeni@redhat.com, namangulati@google.com,
edumazet@google.com, amritha.nambiar@intel.com,
sridhar.samudrala@intel.com, sdf@fomichev.me, peter@typeblog.net,
m2shafiei@uwaterloo.ca, bjorn@rivosinc.com, hch@infradead.org,
willy@infradead.org, willemdebruijn.kernel@gmail.com,
skhawaja@google.com, kuba@kernel.org,
Martin Karsten <mkarsten@uwaterloo.ca>,
"David S. Miller" <davem@davemloft.net>,
Simon Horman <horms@kernel.org>, Shuah Khan <shuah@kernel.org>,
open list <linux-kernel@vger.kernel.org>,
"open list:KERNEL SELFTEST FRAMEWORK"
<linux-kselftest@vger.kernel.org>
Subject: Re: [PATCH net-next v6 6/7] selftests: net: Add busy_poll_test
Date: Mon, 4 Nov 2024 19:50:21 -0800 [thread overview]
Message-ID: <ZymV_dgbVKW49595@mini-arch> (raw)
In-Reply-To: <20241104215542.215919-7-jdamato@fastly.com>
On 11/04, Joe Damato wrote:
> Add an epoll busy poll test using netdevsim.
>
> This test is comprised of:
> - busy_poller (via busy_poller.c)
> - busy_poll_test.sh which loads netdevsim, sets up network namespaces,
> and runs busy_poller to receive data and socat to send data.
>
> The selftest tests two different scenarios:
> - busy poll (the pre-existing version in the kernel)
> - busy poll with suspend enabled (what this series adds)
>
> The data transmit is a 1MiB temporary file generated from /dev/urandom
> and the test is considered passing if the md5sum of the input file to
> socat matches the md5sum of the output file from busy_poller.
>
> netdevsim was chosen instead of veth due to netdevsim's support for
> netdev-genl.
>
> For now, this test uses the functionality that netdevsim provides. In the
> future, perhaps netdevsim can be extended to emulate device IRQs to more
> thoroughly test all pre-existing kernel options (like defer_hard_irqs)
> and suspend.
>
> Signed-off-by: Joe Damato <jdamato@fastly.com>
> Co-developed-by: Martin Karsten <mkarsten@uwaterloo.ca>
> Signed-off-by: Martin Karsten <mkarsten@uwaterloo.ca>
> ---
> v5:
> - Updated commit message to replace netcat with socat and fixed
> misspelling of netdevsim. No functional/code changes.
>
> v4:
> - Updated busy_poll_test.sh:
> - use socat instead of nc
> - drop cli.py usage from the script
> - removed check_ynl
> - Updated busy_poller.c:
> - use netlink to configure napi parameters
>
> v3:
> - New in v3
>
> tools/testing/selftests/net/.gitignore | 1 +
> tools/testing/selftests/net/Makefile | 3 +-
> tools/testing/selftests/net/busy_poll_test.sh | 164 +++++++++
> tools/testing/selftests/net/busy_poller.c | 328 ++++++++++++++++++
> 4 files changed, 495 insertions(+), 1 deletion(-)
> create mode 100755 tools/testing/selftests/net/busy_poll_test.sh
> create mode 100644 tools/testing/selftests/net/busy_poller.c
>
> diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
> index 217d8b7a7365..85b0c4a2179f 100644
> --- a/tools/testing/selftests/net/.gitignore
> +++ b/tools/testing/selftests/net/.gitignore
> @@ -2,6 +2,7 @@
> bind_bhash
> bind_timewait
> bind_wildcard
> +busy_poller
> cmsg_sender
> diag_uid
> epoll_busy_poll
> diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
> index 26a4883a65c9..3ccfe454db1a 100644
> --- a/tools/testing/selftests/net/Makefile
> +++ b/tools/testing/selftests/net/Makefile
> @@ -96,9 +96,10 @@ TEST_PROGS += fdb_flush.sh
> TEST_PROGS += fq_band_pktlimit.sh
> TEST_PROGS += vlan_hw_filter.sh
> TEST_PROGS += bpf_offload.py
> +TEST_PROGS += busy_poll_test.sh
>
> # YNL files, must be before "include ..lib.mk"
> -YNL_GEN_FILES := ncdevmem
> +YNL_GEN_FILES := ncdevmem busy_poller
> TEST_GEN_FILES += $(YNL_GEN_FILES)
>
> TEST_FILES := settings
> diff --git a/tools/testing/selftests/net/busy_poll_test.sh b/tools/testing/selftests/net/busy_poll_test.sh
> new file mode 100755
> index 000000000000..ffc74bc62e5a
> --- /dev/null
> +++ b/tools/testing/selftests/net/busy_poll_test.sh
> @@ -0,0 +1,164 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0-only
> +source net_helper.sh
> +
> +NSIM_DEV_1_ID=$((256 + RANDOM % 256))
> +NSIM_DEV_1_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_DEV_1_ID
> +NSIM_DEV_2_ID=$((512 + RANDOM % 256))
> +NSIM_DEV_2_SYS=/sys/bus/netdevsim/devices/netdevsim$NSIM_DEV_2_ID
> +
> +NSIM_DEV_SYS_NEW=/sys/bus/netdevsim/new_device
> +NSIM_DEV_SYS_DEL=/sys/bus/netdevsim/del_device
> +NSIM_DEV_SYS_LINK=/sys/bus/netdevsim/link_device
> +NSIM_DEV_SYS_UNLINK=/sys/bus/netdevsim/unlink_device
> +
> +setup_ns()
> +{
> + set -e
> + ip netns add nssv
> + ip netns add nscl
> +
> + NSIM_DEV_1_NAME=$(find $NSIM_DEV_1_SYS/net -maxdepth 1 -type d ! \
> + -path $NSIM_DEV_1_SYS/net -exec basename {} \;)
> + NSIM_DEV_2_NAME=$(find $NSIM_DEV_2_SYS/net -maxdepth 1 -type d ! \
> + -path $NSIM_DEV_2_SYS/net -exec basename {} \;)
> +
> + # ensure the server has 1 queue
> + ethtool -L $NSIM_DEV_1_NAME combined 1 2>/dev/null
> +
> + ip link set $NSIM_DEV_1_NAME netns nssv
> + ip link set $NSIM_DEV_2_NAME netns nscl
> +
> + ip netns exec nssv ip addr add '192.168.1.1/24' dev $NSIM_DEV_1_NAME
> + ip netns exec nscl ip addr add '192.168.1.2/24' dev $NSIM_DEV_2_NAME
> +
> + ip netns exec nssv ip link set dev $NSIM_DEV_1_NAME up
> + ip netns exec nscl ip link set dev $NSIM_DEV_2_NAME up
> +
> + set +e
> +}
> +
> +cleanup_ns()
> +{
> + ip netns del nscl
> + ip netns del nssv
> +}
> +
> +test_busypoll()
> +{
> + tmp_file=$(mktemp)
> + out_file=$(mktemp)
> +
> + # fill a test file with random data
> + dd if=/dev/urandom of=${tmp_file} bs=1M count=1 2> /dev/null
> +
> + timeout -k 1s 30s ip netns exec nssv ./busy_poller -p48675 -b192.168.1.1 -m8 -u0 -P1 -g16 -i${NSIM_DEV_1_IFIDX} -o${out_file}&
> +
> + wait_local_port_listen nssv 48675 tcp
> +
> + ip netns exec nscl socat -u $tmp_file TCP:192.168.1.1:48675
> +
> + wait
> +
> + tmp_file_md5sum=$(md5sum $tmp_file | cut -f1 -d' ')
> + out_file_md5sum=$(md5sum $out_file | cut -f1 -d' ')
> +
> + if [ "$tmp_file_md5sum" = "$out_file_md5sum" ]; then
> + res=0
> + else
> + echo "md5sum mismatch"
> + echo "input file md5sum: ${tmp_file_md5sum}";
> + echo "output file md5sum: ${out_file_md5sum}";
> + res=1
> + fi
> +
> + rm $out_file $tmp_file
> +
> + return $res
> +}
> +
> +test_busypoll_with_suspend()
> +{
> + tmp_file=$(mktemp)
> + out_file=$(mktemp)
> +
> + # fill a test file with random data
> + dd if=/dev/urandom of=${tmp_file} bs=1M count=1 2> /dev/null
> +
> + timeout -k 1s 30s ip netns exec nssv ./busy_poller -p48675 -b192.168.1.1 -m8 -u0 -P1 -g16 -d100 -r50000 -s20000000 -i${NSIM_DEV_1_IFIDX} -o${out_file}&
> +
> + wait_local_port_listen nssv 48675 tcp
> +
> + ip netns exec nscl socat -u $tmp_file TCP:192.168.1.1:48675
> +
> + wait
> +
> + tmp_file_md5sum=$(md5sum $tmp_file | cut -f1 -d' ')
> + out_file_md5sum=$(md5sum $out_file | cut -f1 -d' ')
> +
> + if [ "$tmp_file_md5sum" = "$out_file_md5sum" ]; then
> + res=0
> + else
> + echo "md5sum mismatch"
> + echo "input file md5sum: ${tmp_file_md5sum}";
> + echo "output file md5sum: ${out_file_md5sum}";
> + res=1
> + fi
> +
> + rm $out_file $tmp_file
> +
> + return $res
> +}
> +
> +###
> +### Code start
> +###
> +
> +modprobe netdevsim
> +
> +# linking
> +
> +echo $NSIM_DEV_1_ID > $NSIM_DEV_SYS_NEW
> +echo $NSIM_DEV_2_ID > $NSIM_DEV_SYS_NEW
> +udevadm settle
> +
> +setup_ns
> +
> +NSIM_DEV_1_FD=$((256 + RANDOM % 256))
> +exec {NSIM_DEV_1_FD}</var/run/netns/nssv
> +NSIM_DEV_1_IFIDX=$(ip netns exec nssv cat /sys/class/net/$NSIM_DEV_1_NAME/ifindex)
> +
> +NSIM_DEV_2_FD=$((256 + RANDOM % 256))
> +exec {NSIM_DEV_2_FD}</var/run/netns/nscl
> +NSIM_DEV_2_IFIDX=$(ip netns exec nscl cat /sys/class/net/$NSIM_DEV_2_NAME/ifindex)
> +
> +echo "$NSIM_DEV_1_FD:$NSIM_DEV_1_IFIDX $NSIM_DEV_2_FD:$NSIM_DEV_2_IFIDX" > $NSIM_DEV_SYS_LINK
> +if [ $? -ne 0 ]; then
> + echo "linking netdevsim1 with netdevsim2 should succeed"
> + cleanup_ns
> + exit 1
> +fi
> +
> +test_busypoll
> +if [ $? -ne 0 ]; then
> + echo "test_busypoll failed"
> + cleanup_ns
> + exit 1
> +fi
> +
> +test_busypoll_with_suspend
> +if [ $? -ne 0 ]; then
> + echo "test_busypoll_with_suspend failed"
> + cleanup_ns
> + exit 1
> +fi
> +
> +echo "$NSIM_DEV_1_FD:$NSIM_DEV_1_IFIDX" > $NSIM_DEV_SYS_UNLINK
> +
> +echo $NSIM_DEV_2_ID > $NSIM_DEV_SYS_DEL
> +
> +cleanup_ns
> +
> +modprobe -r netdevsim
> +
> +exit 0
> diff --git a/tools/testing/selftests/net/busy_poller.c b/tools/testing/selftests/net/busy_poller.c
> new file mode 100644
> index 000000000000..8d8aa9e5939a
> --- /dev/null
> +++ b/tools/testing/selftests/net/busy_poller.c
> @@ -0,0 +1,328 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <assert.h>
> +#include <errno.h>
> +#include <error.h>
> +#include <fcntl.h>
> +#include <inttypes.h>
> +#include <limits.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include <arpa/inet.h>
> +#include <netinet/in.h>
> +
> +#include <sys/ioctl.h>
> +#include <sys/epoll.h>
> +#include <sys/socket.h>
> +#include <sys/types.h>
> +
> +#include <linux/netlink.h>
> +#include <linux/genetlink.h>
> +#include "netdev-user.h"
> +#include <ynl.h>
> +
> +/* if the headers haven't been updated, we need to define some things */
> +#if !defined(EPOLL_IOC_TYPE)
> +struct epoll_params {
> + uint32_t busy_poll_usecs;
> + uint16_t busy_poll_budget;
> + uint8_t prefer_busy_poll;
> +
> + /* pad the struct to a multiple of 64bits */
> + uint8_t __pad;
> +};
> +
> +#define EPOLL_IOC_TYPE 0x8A
> +#define EPIOCSPARAMS _IOW(EPOLL_IOC_TYPE, 0x01, struct epoll_params)
> +#define EPIOCGPARAMS _IOR(EPOLL_IOC_TYPE, 0x02, struct epoll_params)
> +#endif
> +
> +static uint32_t cfg_port = 8000;
> +static struct in_addr cfg_bind_addr = { .s_addr = INADDR_ANY };
> +static char *cfg_outfile;
> +static int cfg_max_events = 8;
> +static int cfg_ifindex;
> +
> +/* busy poll params */
> +static uint32_t cfg_busy_poll_usecs;
> +static uint16_t cfg_busy_poll_budget;
> +static uint8_t cfg_prefer_busy_poll;
> +
> +/* IRQ params */
> +static uint32_t cfg_defer_hard_irqs;
> +static uint64_t cfg_gro_flush_timeout;
> +static uint64_t cfg_irq_suspend_timeout;
> +
> +static void usage(const char *filepath)
> +{
> + error(1, 0,
> + "Usage: %s -p<port> -b<addr> -m<max_events> -u<busy_poll_usecs> -P<prefer_busy_poll> -g<busy_poll_budget> -o<outfile> -d<defer_hard_irqs> -r<gro_flush_timeout> -s<irq_suspend_timeout> -i<ifindex>",
> + filepath);
> +}
> +
> +static void parse_opts(int argc, char **argv)
> +{
> + int ret;
> + int c;
> +
> + if (argc <= 1)
> + usage(argv[0]);
> +
> + while ((c = getopt(argc, argv, "p:m:b:u:P:g:o:d:r:s:i:")) != -1) {
> + switch (c) {
> + case 'u':
> + cfg_busy_poll_usecs = strtoul(optarg, NULL, 0);
> + if (cfg_busy_poll_usecs == ULONG_MAX ||
> + cfg_busy_poll_usecs > UINT32_MAX)
> + error(1, ERANGE, "busy_poll_usecs too large");
> + break;
> + case 'P':
> + cfg_prefer_busy_poll = strtoul(optarg, NULL, 0);
> + if (cfg_prefer_busy_poll == ULONG_MAX ||
> + cfg_prefer_busy_poll > 1)
> + error(1, ERANGE,
> + "prefer busy poll should be 0 or 1");
> + break;
> + case 'g':
> + cfg_busy_poll_budget = strtoul(optarg, NULL, 0);
> + if (cfg_busy_poll_budget == ULONG_MAX ||
> + cfg_busy_poll_budget > UINT16_MAX)
> + error(1, ERANGE,
> + "busy poll budget must be [0, UINT16_MAX]");
> + break;
> + case 'p':
> + cfg_port = strtoul(optarg, NULL, 0);
> + if (cfg_port > UINT16_MAX)
> + error(1, ERANGE, "port must be <= 65535");
> + break;
> + case 'b':
> + ret = inet_aton(optarg, &cfg_bind_addr);
> + if (ret == 0)
> + error(1, errno,
> + "bind address %s invalid", optarg);
> + break;
> + case 'o':
> + cfg_outfile = strdup(optarg);
> + if (!cfg_outfile)
> + error(1, 0, "outfile invalid");
> + break;
> + case 'm':
> + cfg_max_events = strtol(optarg, NULL, 0);
> +
> + if (cfg_max_events == LONG_MIN ||
> + cfg_max_events == LONG_MAX ||
> + cfg_max_events <= 0)
> + error(1, ERANGE,
> + "max events must be > 0 and < LONG_MAX");
> + break;
> + case 'd':
> + cfg_defer_hard_irqs = strtoul(optarg, NULL, 0);
> +
> + if (cfg_defer_hard_irqs == ULONG_MAX ||
> + cfg_defer_hard_irqs > INT32_MAX)
> + error(1, ERANGE,
> + "defer_hard_irqs must be <= INT32_MAX");
> + break;
> + case 'r':
> + cfg_gro_flush_timeout = strtoull(optarg, NULL, 0);
> +
> + if (cfg_gro_flush_timeout == ULLONG_MAX)
> + error(1, ERANGE,
> + "gro_flush_timeout must be < ULLONG_MAX");
> + break;
> + case 's':
> + cfg_irq_suspend_timeout = strtoull(optarg, NULL, 0);
> +
> + if (cfg_irq_suspend_timeout == ULLONG_MAX)
> + error(1, ERANGE,
> + "irq_suspend_timeout must be < ULLONG_MAX");
> + break;
> + case 'i':
> + cfg_ifindex = strtoul(optarg, NULL, 0);
> + if (cfg_ifindex == ULONG_MAX)
> + error(1, ERANGE,
> + "ifindex must be < ULONG_MAX");
> + break;
> + }
> + }
> +
> + if (!cfg_ifindex)
> + usage(argv[0]);
> +
> + if (optind != argc)
> + usage(argv[0]);
> +}
> +
> +static void epoll_ctl_add(int epfd, int fd, uint32_t events)
> +{
> + struct epoll_event ev;
> +
> + ev.events = events;
> + ev.data.fd = fd;
> + if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1)
> + error(1, errno, "epoll_ctl add fd: %d", fd);
> +}
> +
> +static void setnonblock(int sockfd)
> +{
> + int flags;
> +
> + flags = fcntl(sockfd, F_GETFL, 0);
> +
> + if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1)
> + error(1, errno, "unable to set socket to nonblocking mode");
> +}
> +
> +static void write_chunk(int fd, char *buf, ssize_t buflen)
> +{
> + ssize_t remaining = buflen;
> + char *buf_offset = buf;
> + ssize_t writelen = 0;
> + ssize_t write_result;
> +
> + while (writelen < buflen) {
> + write_result = write(fd, buf_offset, remaining);
> + if (write_result == -1)
> + error(1, errno, "unable to write data to outfile");
> +
> + writelen += write_result;
> + remaining -= write_result;
> + buf_offset += write_result;
> + }
> +}
> +
> +static void setup_queue(void)
> +{
> + struct netdev_napi_get_list *napi_list = NULL;
> + struct netdev_napi_get_req_dump *req = NULL;
> + struct netdev_napi_set_req *set_req = NULL;
> + struct ynl_sock *ys;
> + struct ynl_error yerr;
> + uint32_t napi_id;
> +
> + ys = ynl_sock_create(&ynl_netdev_family, &yerr);
> + if (!ys)
> + error(1, 0, "YNL: %s", yerr.msg);
> +
> + req = netdev_napi_get_req_dump_alloc();
> + netdev_napi_get_req_dump_set_ifindex(req, cfg_ifindex);
> + napi_list = netdev_napi_get_dump(ys, req);
> +
> + /* assume there is 1 NAPI configured and take the first */
> + if (napi_list->obj._present.id)
> + napi_id = napi_list->obj.id;
> + else
> + error(1, 0, "napi ID not present?");
> +
> + set_req = netdev_napi_set_req_alloc();
> + netdev_napi_set_req_set_id(set_req, napi_id);
> + netdev_napi_set_req_set_defer_hard_irqs(set_req, cfg_defer_hard_irqs);
> + netdev_napi_set_req_set_gro_flush_timeout(set_req,
> + cfg_gro_flush_timeout);
> + netdev_napi_set_req_set_irq_suspend_timeout(set_req,
> + cfg_irq_suspend_timeout);
> +
> + if (netdev_napi_set(ys, set_req))
> + error(1, 0, "can't set NAPI params: %s\n", yerr.msg);
> +
> + netdev_napi_get_list_free(napi_list);
> + netdev_napi_get_req_dump_free(req);
> + netdev_napi_set_req_free(set_req);
> + ynl_sock_destroy(ys);
> +}
> +
> +static void run_poller(void)
> +{
> + struct epoll_event events[cfg_max_events];
> + struct epoll_params epoll_params = {0};
> + struct sockaddr_in server_addr;
> + int i, epfd, nfds;
> + ssize_t readlen;
> + int outfile_fd;
> + char buf[1024];
> + int sockfd;
> + int conn;
> + int val;
[..]
> + outfile_fd = open(cfg_outfile, O_WRONLY | O_CREAT, 0644);
> + if (outfile_fd == -1)
> + error(1, errno, "unable to open outfile: %s", cfg_outfile);
Any reason you're not printing to stdout? And then redirect it to a file
in the shell script if needed. Lets you save some code on open/close
and flag parsing :-p But I guess can keep it since you already have it
all working.
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
next prev parent reply other threads:[~2024-11-05 3:50 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-11-04 21:55 [PATCH net-next v6 0/7] Suspend IRQs during application busy periods Joe Damato
2024-11-04 21:55 ` [PATCH net-next v6 1/7] net: Add napi_struct parameter irq_suspend_timeout Joe Damato
2024-11-04 21:55 ` [PATCH net-next v6 2/7] net: Suspend softirq when prefer_busy_poll is set Joe Damato
2024-11-06 5:03 ` Jakub Kicinski
2024-11-06 16:52 ` Joe Damato
2024-11-06 23:31 ` Jakub Kicinski
2024-11-07 3:24 ` Joe Damato
2024-11-07 21:01 ` Joe Damato
2024-11-08 3:52 ` Jakub Kicinski
2024-11-04 21:55 ` [PATCH net-next v6 3/7] net: Add control functions for irq suspension Joe Damato
2024-11-04 21:55 ` [PATCH net-next v6 4/7] eventpoll: Trigger napi_busy_loop, if prefer_busy_poll is set Joe Damato
2024-11-04 21:55 ` [PATCH net-next v6 5/7] eventpoll: Control irq suspension for prefer_busy_poll Joe Damato
2024-11-04 21:55 ` [PATCH net-next v6 6/7] selftests: net: Add busy_poll_test Joe Damato
2024-11-05 3:50 ` Stanislav Fomichev [this message]
2024-11-05 17:49 ` Joe Damato
2024-11-04 21:55 ` [PATCH net-next v6 7/7] docs: networking: Describe irq suspension Joe Damato
2024-11-05 1:12 ` Bagas Sanjaya
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=ZymV_dgbVKW49595@mini-arch \
--to=stfomichev@gmail.com \
--cc=amritha.nambiar@intel.com \
--cc=bagasdotme@gmail.com \
--cc=bjorn@rivosinc.com \
--cc=corbet@lwn.net \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=hch@infradead.org \
--cc=hdanton@sina.com \
--cc=horms@kernel.org \
--cc=jdamato@fastly.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=m2shafiei@uwaterloo.ca \
--cc=mkarsten@uwaterloo.ca \
--cc=namangulati@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=peter@typeblog.net \
--cc=sdf@fomichev.me \
--cc=shuah@kernel.org \
--cc=skhawaja@google.com \
--cc=sridhar.samudrala@intel.com \
--cc=willemdebruijn.kernel@gmail.com \
--cc=willy@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.