samples: net: zperf: Convert to use the new shell
Use new shell instead of the legacy one. This commit also fixes the configuration file mess and allows the program to be run on more hardware. Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
parent
1d81fd8211
commit
7cd17d58ca
20 changed files with 1073 additions and 1079 deletions
|
@ -1,48 +1,30 @@
|
|||
cmake_minimum_required(VERSION 3.8.2)
|
||||
|
||||
macro(set_conf_file)
|
||||
if(PROFILER)
|
||||
set(CONF_FILE prj_${BOARD}_prof.conf)
|
||||
if(EXISTS ${APPLICATION_SOURCE_DIR}/prj_${BOARD}.conf)
|
||||
set(CONF_FILE "${APPLICATION_SOURCE_DIR}/prj_${BOARD}.conf")
|
||||
elseif(EXISTS ${APPLICATION_SOURCE_DIR}/boards/${BOARD}.conf)
|
||||
set(CONF_FILE
|
||||
"prj.conf ${APPLICATION_SOURCE_DIR}/boards/${BOARD}.conf")
|
||||
else()
|
||||
set(CONF_FILE prj_${BOARD}.conf)
|
||||
set(CONF_FILE "prj.conf")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
|
||||
project(NONE)
|
||||
|
||||
target_sources(
|
||||
app PRIVATE
|
||||
target_sources(app PRIVATE
|
||||
src/shell_utils.c
|
||||
src/zperf_session.c
|
||||
src/zperf_shell.c
|
||||
)
|
||||
target_sources_ifdef(
|
||||
CONFIG_NET_UDP
|
||||
app PRIVATE
|
||||
src/zperf_udp_receiver.c
|
||||
src/zperf_udp_uploader.c
|
||||
)
|
||||
target_sources_ifdef(
|
||||
CONFIG_NET_TCP
|
||||
app PRIVATE
|
||||
src/zperf_tcp_receiver.c
|
||||
src/zperf_tcp_uploader.c
|
||||
)
|
||||
|
||||
target_compile_definitions_ifdef(
|
||||
PROFILER
|
||||
app PRIVATE
|
||||
PROFILER
|
||||
)
|
||||
|
||||
target_include_directories(app PRIVATE
|
||||
$ENV{ZEPHYR_BASE}/subsys/net/ip
|
||||
$ENV{ZEPHYR_BASE}/samples/task_profiler/profiler/src
|
||||
)
|
||||
|
||||
if(PROFILER)
|
||||
assert(0 "PROFILER is not supported yet")
|
||||
# KBuild did this, but this did not work, not sure why.
|
||||
# export PROFILER_NO_SHELL_REGISTER=1
|
||||
# obj-y += ../../../task_profiler/profiler/
|
||||
endif()
|
||||
|
|
|
@ -14,7 +14,6 @@ Features
|
|||
|
||||
- Compatible with iPerf_2.0.5.
|
||||
- Client or server mode allowed without need to modify the source code.
|
||||
- Working with task profiler (PROFILER=1 to be set when building zperf)
|
||||
|
||||
Supported Boards
|
||||
****************
|
||||
|
@ -58,14 +57,14 @@ In the Zephyr console, zperf can be executed as follows:
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
zperf> udp.upload 2001:db8::2 5001 10 1K 1M
|
||||
zperf> udp upload 2001:db8::2 5001 10 1K 1M
|
||||
|
||||
|
||||
For TCP the zperf command would look like this:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
zperf> tcp.upload 2001:db8::2 5001 10 1K 1M
|
||||
zperf> tcp upload 2001:db8::2 5001 10 1K 1M
|
||||
|
||||
|
||||
If the IP addresses of Zephyr and the host machine are specified in the
|
||||
|
@ -73,28 +72,28 @@ config file, zperf can be started as follows:
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
zperf> udp.upload2 v6 10 1K 1M
|
||||
zperf> udp upload2 v6 10 1K 1M
|
||||
|
||||
|
||||
or like this if you want to test TCP:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
zperf> tcp.upload2 v6 10 1K 1M
|
||||
zperf> tcp upload2 v6 10 1K 1M
|
||||
|
||||
|
||||
If Zephyr is acting as a server, set the download mode as follows for UDP:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
zperf> udp.download 5001
|
||||
zperf> udp download 5001
|
||||
|
||||
|
||||
or like this for TCP:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
zperf> tcp.download 5001
|
||||
zperf> tcp download 5001
|
||||
|
||||
|
||||
and in the host side, iPerf must be executed with the following
|
||||
|
|
9
samples/net/zperf/boards/quark_se_c1000_devboard.conf
Normal file
9
samples/net/zperf/boards/quark_se_c1000_devboard.conf
Normal file
|
@ -0,0 +1,9 @@
|
|||
CONFIG_NET_L2_IEEE802154=y
|
||||
CONFIG_IEEE802154_CC2520=y
|
||||
CONFIG_NET_6LO_CONTEXT=y
|
||||
|
||||
CONFIG_NET_PKT_RX_COUNT=24
|
||||
CONFIG_NET_PKT_TX_COUNT=24
|
||||
CONFIG_NET_BUF_RX_COUNT=48
|
||||
CONFIG_NET_BUF_TX_COUNT=48
|
||||
CONFIG_NET_BUF_DATA_SIZE=128
|
2
samples/net/zperf/overlay-bt.conf
Normal file
2
samples/net/zperf/overlay-bt.conf
Normal file
|
@ -0,0 +1,2 @@
|
|||
CONFIG_NET_L2_BT=y
|
||||
CONFIG_NET_L2_BT_SHELL=y
|
6
samples/net/zperf/overlay-netusb.conf
Normal file
6
samples/net/zperf/overlay-netusb.conf
Normal file
|
@ -0,0 +1,6 @@
|
|||
# USB Device Settings
|
||||
CONFIG_USB=y
|
||||
CONFIG_USB_DEVICE_STACK=y
|
||||
|
||||
# Select USB Configurations
|
||||
CONFIG_USB_DEVICE_NETWORK_ECM=y
|
|
@ -4,15 +4,14 @@ CONFIG_NET_IPV6=y
|
|||
CONFIG_NET_IPV4=y
|
||||
CONFIG_NET_DHCPV4=n
|
||||
CONFIG_NET_UDP=y
|
||||
CONFIG_NET_TCP=n
|
||||
CONFIG_NET_TCP=y
|
||||
CONFIG_NET_STATISTICS=y
|
||||
|
||||
CONFIG_NET_PKT_RX_COUNT=14
|
||||
CONFIG_NET_PKT_TX_COUNT=14
|
||||
CONFIG_NET_BUF_RX_COUNT=28
|
||||
CONFIG_NET_BUF_TX_COUNT=28
|
||||
CONFIG_NET_BUF_DATA_SIZE=512
|
||||
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=5
|
||||
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=4
|
||||
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=5
|
||||
CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=1
|
||||
CONFIG_NET_MAX_CONTEXTS=3
|
||||
|
@ -24,12 +23,10 @@ CONFIG_TEST_RANDOM_GENERATOR=y
|
|||
CONFIG_NET_L2_ETHERNET=y
|
||||
CONFIG_NET_SHELL=y
|
||||
|
||||
CONFIG_CONSOLE_HANDLER=y
|
||||
CONFIG_CONSOLE_SHELL=y
|
||||
CONFIG_PRINTK=y
|
||||
|
||||
CONFIG_NET_CONFIG_SETTINGS=y
|
||||
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
|
||||
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
|
||||
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
|
||||
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"
|
||||
|
||||
CONFIG_LOG=y
|
|
@ -1,32 +0,0 @@
|
|||
CONFIG_NETWORKING=y
|
||||
CONFIG_NET_LOG=y
|
||||
CONFIG_NET_IPV6=y
|
||||
CONFIG_NET_UDP=y
|
||||
CONFIG_NET_TCP=n
|
||||
CONFIG_NET_STATISTICS=y
|
||||
|
||||
CONFIG_NET_PKT_RX_COUNT=14
|
||||
CONFIG_NET_PKT_TX_COUNT=14
|
||||
CONFIG_NET_BUF_RX_COUNT=28
|
||||
CONFIG_NET_BUF_TX_COUNT=28
|
||||
CONFIG_NET_BUF_DATA_SIZE=512
|
||||
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=5
|
||||
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=5
|
||||
CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=1
|
||||
CONFIG_NET_MAX_CONTEXTS=3
|
||||
CONFIG_NET_CONTEXT_SYNC_RECV=y
|
||||
|
||||
CONFIG_INIT_STACKS=y
|
||||
CONFIG_TEST_RANDOM_GENERATOR=y
|
||||
|
||||
CONFIG_NET_L2_BT=y
|
||||
CONFIG_NET_SHELL=y
|
||||
CONFIG_NET_L2_BT_SHELL=y
|
||||
|
||||
CONFIG_CONSOLE_HANDLER=y
|
||||
CONFIG_CONSOLE_SHELL=y
|
||||
CONFIG_PRINTK=y
|
||||
|
||||
CONFIG_NET_CONFIG_SETTINGS=y
|
||||
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
|
||||
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
|
|
@ -1,36 +0,0 @@
|
|||
CONFIG_NETWORKING=y
|
||||
CONFIG_NET_LOG=y
|
||||
CONFIG_NET_IPV6=y
|
||||
CONFIG_NET_IPV4=y
|
||||
CONFIG_NET_DHCPV4=n
|
||||
CONFIG_NET_UDP=y
|
||||
CONFIG_NET_TCP=y
|
||||
CONFIG_NET_STATISTICS=y
|
||||
|
||||
CONFIG_NET_PKT_RX_COUNT=100
|
||||
CONFIG_NET_PKT_TX_COUNT=100
|
||||
CONFIG_NET_BUF_RX_COUNT=100
|
||||
CONFIG_NET_BUF_TX_COUNT=100
|
||||
CONFIG_NET_BUF_DATA_SIZE=128
|
||||
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3
|
||||
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2
|
||||
CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=2
|
||||
CONFIG_NET_MAX_CONTEXTS=10
|
||||
CONFIG_NET_CONTEXT_SYNC_RECV=y
|
||||
|
||||
CONFIG_INIT_STACKS=y
|
||||
CONFIG_TEST_RANDOM_GENERATOR=y
|
||||
CONFIG_PRINTK=y
|
||||
|
||||
CONFIG_NET_L2_ETHERNET=y
|
||||
|
||||
CONFIG_NET_SHELL=y
|
||||
CONFIG_CONSOLE_HANDLER=y
|
||||
CONFIG_CONSOLE_SHELL=y
|
||||
CONFIG_PRINTK=y
|
||||
|
||||
CONFIG_NET_CONFIG_SETTINGS=y
|
||||
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
|
||||
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
|
||||
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
|
||||
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"
|
|
@ -1,39 +0,0 @@
|
|||
CONFIG_NETWORKING=y
|
||||
CONFIG_NET_LOG=y
|
||||
CONFIG_NET_IPV6=y
|
||||
CONFIG_NET_IPV4=n
|
||||
CONFIG_NET_UDP=y
|
||||
CONFIG_NET_TCP=y
|
||||
CONFIG_NET_STATISTICS=y
|
||||
|
||||
CONFIG_NET_PKT_RX_COUNT=50
|
||||
CONFIG_NET_PKT_TX_COUNT=50
|
||||
CONFIG_NET_BUF_RX_COUNT=50
|
||||
CONFIG_NET_BUF_TX_COUNT=50
|
||||
|
||||
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3
|
||||
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2
|
||||
CONFIG_NET_MAX_CONTEXTS=8
|
||||
CONFIG_NET_CONTEXT_SYNC_RECV=y
|
||||
|
||||
CONFIG_INIT_STACKS=y
|
||||
CONFIG_TEST_RANDOM_GENERATOR=y
|
||||
CONFIG_PRINTK=y
|
||||
|
||||
CONFIG_NET_6LO_CONTEXT=y
|
||||
|
||||
CONFIG_NET_SHELL=y
|
||||
CONFIG_CONSOLE_HANDLER=y
|
||||
CONFIG_CONSOLE_SHELL=y
|
||||
CONFIG_PRINTK=y
|
||||
|
||||
# USB Device Settings
|
||||
CONFIG_USB=y
|
||||
CONFIG_USB_DEVICE_STACK=y
|
||||
|
||||
# Select USB Configurations
|
||||
CONFIG_USB_DEVICE_NETWORK_ECM=y
|
||||
|
||||
CONFIG_NET_CONFIG_SETTINGS=y
|
||||
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
|
||||
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
|
|
@ -1,35 +0,0 @@
|
|||
CONFIG_NETWORKING=y
|
||||
CONFIG_NET_LOG=y
|
||||
CONFIG_NET_IPV6=y
|
||||
CONFIG_NET_IPV4=n
|
||||
CONFIG_NET_UDP=y
|
||||
CONFIG_NET_TCP=n
|
||||
CONFIG_NET_STATISTICS=y
|
||||
|
||||
CONFIG_NET_PKT_RX_COUNT=96
|
||||
CONFIG_NET_PKT_TX_COUNT=96
|
||||
CONFIG_NET_BUF_RX_COUNT=48
|
||||
CONFIG_NET_BUF_TX_COUNT=48
|
||||
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3
|
||||
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2
|
||||
CONFIG_NET_MAX_CONTEXTS=10
|
||||
CONFIG_NET_CONTEXT_SYNC_RECV=y
|
||||
|
||||
CONFIG_INIT_STACKS=y
|
||||
CONFIG_TEST_RANDOM_GENERATOR=y
|
||||
CONFIG_PRINTK=y
|
||||
|
||||
CONFIG_NET_L2_IEEE802154=y
|
||||
|
||||
CONFIG_IEEE802154_CC2520=y
|
||||
|
||||
CONFIG_NET_6LO_CONTEXT=y
|
||||
|
||||
CONFIG_NET_SHELL=y
|
||||
CONFIG_CONSOLE_HANDLER=y
|
||||
CONFIG_CONSOLE_SHELL=y
|
||||
CONFIG_PRINTK=y
|
||||
|
||||
CONFIG_NET_CONFIG_SETTINGS=y
|
||||
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
|
||||
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
|
|
@ -1,36 +1,35 @@
|
|||
common:
|
||||
harness: net
|
||||
tags: net zperf samples
|
||||
# TODO: Does not work properly with native_posix, need more TLC
|
||||
platform_exclude: native_posix
|
||||
sample:
|
||||
description: TBD
|
||||
name: TBD
|
||||
description: Network performance measurement tool for Zephyr
|
||||
name: zperf
|
||||
tests:
|
||||
test:
|
||||
harness: net
|
||||
platform_whitelist: qemu_x86
|
||||
tags: samples net
|
||||
test_netusb_ecm:
|
||||
harness: net
|
||||
extra_args: CONF_FILE="prj_netusb.conf"
|
||||
extra_args: OVERLAY_CONFIG="overlay-netusb.conf"
|
||||
extra_configs:
|
||||
- CONFIG_NET_PKT_RX_COUNT=10
|
||||
- CONFIG_NET_PKT_TX_COUNT=10
|
||||
- CONFIG_NET_TCP=n
|
||||
platform_whitelist: quark_se_c1000_devboard 96b_carbon
|
||||
tags: samples usb net
|
||||
tags: samples usb net zperf
|
||||
test_netusb_eem:
|
||||
harness: net
|
||||
extra_args: CONF_FILE="prj_netusb.conf"
|
||||
extra_args: OVERLAY_CONFIG="overlay-netusb.conf"
|
||||
extra_configs:
|
||||
- CONFIG_USB_DEVICE_NETWORK_ECM=n
|
||||
- CONFIG_USB_DEVICE_NETWORK_EEM=y
|
||||
- CONFIG_NET_PKT_RX_COUNT=10
|
||||
- CONFIG_NET_PKT_TX_COUNT=10
|
||||
- CONFIG_NET_TCP=n
|
||||
platform_whitelist: quark_se_c1000_devboard 96b_carbon
|
||||
tags: samples usb net
|
||||
tags: samples usb net zperf
|
||||
test_netusb_rndis:
|
||||
harness: net
|
||||
extra_args: CONF_FILE="prj_netusb.conf"
|
||||
extra_args: OVERLAY_CONFIG="overlay-netusb.conf"
|
||||
extra_configs:
|
||||
- CONFIG_USB_DEVICE_NETWORK_ECM=n
|
||||
- CONFIG_USB_DEVICE_NETWORK_RNDIS=y
|
||||
- CONFIG_NET_PKT_RX_COUNT=10
|
||||
- CONFIG_NET_PKT_TX_COUNT=10
|
||||
- CONFIG_NET_PKT_RX_COUNT=32
|
||||
- CONFIG_NET_PKT_TX_COUNT=32
|
||||
- CONFIG_NET_TCP=n
|
||||
platform_whitelist: quark_se_c1000_devboard 96b_carbon
|
||||
tags: samples usb net
|
||||
tags: samples usb net zperf
|
||||
|
|
|
@ -24,8 +24,8 @@ const char *KBPS_UNIT[] = { "Mbps", "Kbps" };
|
|||
const u32_t K[] = { 1024 * 1024, 1024, 0 };
|
||||
const char *K_UNIT[] = { "M", "K", "" };
|
||||
|
||||
void print_number(u32_t value, const u32_t *divisor,
|
||||
const char **units)
|
||||
void print_number(const struct shell *shell, u32_t value,
|
||||
const u32_t *divisor, const char **units)
|
||||
{
|
||||
const char **unit;
|
||||
const u32_t *div;
|
||||
|
@ -42,9 +42,10 @@ void print_number(u32_t value, const u32_t *divisor,
|
|||
if (*div != 0) {
|
||||
radix = value / *div;
|
||||
dec = (value % *div) * 100 / *div;
|
||||
printk("%u.%s%u %s", radix, (dec < 10) ? "0" : "", dec, *unit);
|
||||
shell_fprintf(shell, SHELL_NORMAL, "%u.%s%u %s", radix,
|
||||
(dec < 10) ? "0" : "", dec, *unit);
|
||||
} else {
|
||||
printk("%u %s", value, *unit);
|
||||
shell_fprintf(shell, SHELL_NORMAL, "%u %s", value, *unit);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#ifndef __SHELL_UTILS_H
|
||||
#define __SHELL_UTILS_H
|
||||
|
||||
#include <shell/shell.h>
|
||||
|
||||
#define IPV4_STR_LEN_MAX 15
|
||||
#define IPV4_STR_LEN_MIN 7
|
||||
|
||||
|
@ -16,8 +18,8 @@ extern const char *KBPS_UNIT[];
|
|||
extern const u32_t K[];
|
||||
extern const char *K_UNIT[];
|
||||
|
||||
extern void print_number(u32_t value, const u32_t *divisor,
|
||||
const char **units);
|
||||
extern void print_number(const struct shell *shell, u32_t value,
|
||||
const u32_t *divisor, const char **units);
|
||||
extern long parse_number(const char *string, const u32_t *divisor,
|
||||
const char **units);
|
||||
const char **units);
|
||||
#endif /* __SHELL_UTILS_H */
|
||||
|
|
|
@ -6,18 +6,7 @@
|
|||
#ifndef __ZPERF_H
|
||||
#define __ZPERF_H
|
||||
|
||||
#define VERSION "1.0"
|
||||
|
||||
/* commands strings */
|
||||
#define CMD_STR_SETIP "setip"
|
||||
#define CMD_STR_CONNECTAP "connectap"
|
||||
#define CMD_STR_VERSION "version"
|
||||
#define CMD_STR_UDP_UPLOAD "udp.upload"
|
||||
#define CMD_STR_UDP_UPLOAD2 "udp.upload2"
|
||||
#define CMD_STR_UDP_DOWNLOAD "udp.download"
|
||||
#define CMD_STR_TCP_UPLOAD "tcp.upload"
|
||||
#define CMD_STR_TCP_UPLOAD2 "tcp.upload2"
|
||||
#define CMD_STR_TCP_DOWNLOAD "tcp.download"
|
||||
#define VERSION "1.1"
|
||||
|
||||
struct zperf_results {
|
||||
u32_t nb_packets_sent;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <limits.h>
|
||||
#include <net/net_ip.h>
|
||||
#include <shell/shell.h>
|
||||
|
||||
#define IP6PREFIX_STR2(s) #s
|
||||
#define IP6PREFIX_STR(p) IP6PREFIX_STR2(p)
|
||||
|
@ -19,11 +20,19 @@
|
|||
#if defined(CONFIG_NET_IPV6) && defined(CONFIG_NET_CONFIG_SETTINGS)
|
||||
#define MY_IP6ADDR CONFIG_NET_CONFIG_MY_IPV6_ADDR
|
||||
#define DST_IP6ADDR CONFIG_NET_CONFIG_PEER_IPV6_ADDR
|
||||
#define MY_IP6ADDR_SET
|
||||
#else
|
||||
#define MY_IP6ADDR NULL
|
||||
#define DST_IP6ADDR NULL
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_CONFIG_SETTINGS)
|
||||
#define MY_IP4ADDR CONFIG_NET_CONFIG_MY_IPV4_ADDR
|
||||
#define DST_IP4ADDR CONFIG_NET_CONFIG_PEER_IPV4_ADDR
|
||||
#define MY_IP4ADDR_SET
|
||||
#else
|
||||
#define MY_IP4ADDR NULL
|
||||
#define DST_IP4ADDR NULL
|
||||
#endif
|
||||
|
||||
#define PACKET_SIZE_MAX 1024
|
||||
|
@ -78,33 +87,30 @@ static inline u32_t time_delta(u32_t ts, u32_t t)
|
|||
return (t >= ts) ? (t - ts) : (ULONG_MAX - ts + t);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
int zperf_get_ipv6_addr(char *host, char *prefix_str, struct in6_addr *addr,
|
||||
const char *str);
|
||||
int zperf_get_ipv6_addr(const struct shell *shell, char *host,
|
||||
char *prefix_str, struct in6_addr *addr);
|
||||
struct sockaddr_in6 *zperf_get_sin6(void);
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
int zperf_get_ipv4_addr(char *host, struct in_addr *addr, const char *str);
|
||||
int zperf_get_ipv4_addr(const struct shell *shell, char *host,
|
||||
struct in_addr *addr);
|
||||
struct sockaddr_in *zperf_get_sin(void);
|
||||
#endif
|
||||
|
||||
extern void zperf_udp_upload(struct net_context *net_context,
|
||||
extern void zperf_udp_upload(const struct shell *shell,
|
||||
struct net_context *context,
|
||||
unsigned int duration_in_ms,
|
||||
unsigned int packet_size,
|
||||
unsigned int rate_in_kbps,
|
||||
struct zperf_results *results);
|
||||
|
||||
extern void zperf_receiver_init(int port);
|
||||
extern void zperf_receiver_init(const struct shell *shell, int port);
|
||||
|
||||
#if defined(CONFIG_NET_TCP)
|
||||
extern void zperf_tcp_receiver_init(int port);
|
||||
extern void zperf_tcp_receiver_init(const struct shell *shell, int port);
|
||||
extern void zperf_tcp_uploader_init(struct k_fifo *tx_queue);
|
||||
extern void zperf_tcp_upload(struct net_context *net_context,
|
||||
extern void zperf_tcp_upload(const struct shell *shell,
|
||||
struct net_context *net_context,
|
||||
unsigned int duration_in_ms,
|
||||
unsigned int packet_size,
|
||||
struct zperf_results *results);
|
||||
#endif
|
||||
|
||||
extern void connect_ap(char *ssid);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,8 +27,6 @@
|
|||
#define NET_LOG_ENABLED 1
|
||||
#include "net_private.h"
|
||||
|
||||
#define TAG CMD_STR_TCP_DOWNLOAD" "
|
||||
|
||||
#define TCP_RX_FIBER_STACK_SIZE 1024
|
||||
|
||||
static K_THREAD_STACK_DEFINE(zperf_tcp_rx_stack, TCP_RX_FIBER_STACK_SIZE);
|
||||
|
@ -46,6 +44,7 @@ static void tcp_received(struct net_context *context,
|
|||
int status,
|
||||
void *user_data)
|
||||
{
|
||||
const struct shell *shell = user_data;
|
||||
struct session *session;
|
||||
u32_t time;
|
||||
|
||||
|
@ -57,14 +56,14 @@ static void tcp_received(struct net_context *context,
|
|||
|
||||
session = get_session(pkt, SESSION_TCP);
|
||||
if (!session) {
|
||||
printk(TAG "ERROR! cannot get a session!\n");
|
||||
shell_fprintf(shell, SHELL_WARNING, "Cannot get a session!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (session->state) {
|
||||
case STATE_NULL:
|
||||
case STATE_COMPLETED:
|
||||
printk(TAG "New session started\n");
|
||||
shell_fprintf(shell, SHELL_NORMAL, "New session started\n");
|
||||
zperf_reset_session_stats(session);
|
||||
session->start_time = k_cycle_get_32();
|
||||
session->state = STATE_ONGOING;
|
||||
|
@ -91,21 +90,23 @@ static void tcp_received(struct net_context *context,
|
|||
rate_in_kbps = 0;
|
||||
}
|
||||
|
||||
printk(TAG "TCP session ended\n");
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
"TCP session ended\n");
|
||||
|
||||
printk(TAG " duration:\t\t");
|
||||
print_number(duration, TIME_US, TIME_US_UNIT);
|
||||
printk("\n");
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
" Duration:\t\t");
|
||||
print_number(shell, duration, TIME_US, TIME_US_UNIT);
|
||||
shell_fprintf(shell, SHELL_NORMAL, "\n");
|
||||
|
||||
printk(TAG " rate:\t\t\t");
|
||||
print_number(rate_in_kbps, KBPS, KBPS_UNIT);
|
||||
printk("\n");
|
||||
shell_fprintf(shell, SHELL_NORMAL, " rate:\t\t\t");
|
||||
print_number(shell, rate_in_kbps, KBPS, KBPS_UNIT);
|
||||
shell_fprintf(shell, SHELL_NORMAL, "\n");
|
||||
}
|
||||
break;
|
||||
case STATE_LAST_PACKET_RECEIVED:
|
||||
break;
|
||||
default:
|
||||
printk(TAG "Error! Unsupported case\n");
|
||||
shell_fprintf(shell, SHELL_WARNING, "Unsupported case\n");
|
||||
}
|
||||
|
||||
net_pkt_unref(pkt);
|
||||
|
@ -117,113 +118,124 @@ static void tcp_accepted(struct net_context *context,
|
|||
int error,
|
||||
void *user_data)
|
||||
{
|
||||
const struct shell *shell = user_data;
|
||||
int ret;
|
||||
|
||||
ret = net_context_recv(context, tcp_received, K_NO_WAIT, user_data);
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot receive TCP packet (family %d)",
|
||||
net_context_get_family(context));
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot receive TCP packet (family %d)",
|
||||
net_context_get_family(context));
|
||||
}
|
||||
}
|
||||
|
||||
static void zperf_tcp_rx_thread(int port)
|
||||
static void zperf_tcp_rx_thread(const struct shell *shell, int port)
|
||||
{
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
struct net_context *context4 = NULL;
|
||||
#endif
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
struct net_context *context6 = NULL;
|
||||
#endif
|
||||
int ret, fail = 0;
|
||||
|
||||
#if defined(CONFIG_NET_IPV4) && defined(MY_IP4ADDR)
|
||||
ret = net_context_get(AF_INET, SOCK_STREAM, IPPROTO_TCP, &context4);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Cannot get IPv4 TCP network context.\n");
|
||||
return;
|
||||
if (IS_ENABLED(CONFIG_NET_IPV4) && MY_IP4ADDR) {
|
||||
ret = net_context_get(AF_INET, SOCK_STREAM, IPPROTO_TCP,
|
||||
&context4);
|
||||
if (ret < 0) {
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot get IPv4 TCP network context.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = zperf_get_ipv4_addr(shell, MY_IP4ADDR,
|
||||
&in4_addr_my->sin_addr);
|
||||
if (ret < 0) {
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Unable to set IPv4\n");
|
||||
return;
|
||||
}
|
||||
|
||||
shell_fprintf(shell, SHELL_NORMAL, "Binding to %s\n",
|
||||
net_sprint_ipv4_addr(&in4_addr_my->sin_addr));
|
||||
|
||||
in4_addr_my->sin_port = htons(port);
|
||||
}
|
||||
|
||||
ret = zperf_get_ipv4_addr(MY_IP4ADDR, &in4_addr_my->sin_addr, TAG);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Unable to set IPv4\n");
|
||||
return;
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6) && MY_IP6ADDR) {
|
||||
ret = net_context_get(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
|
||||
&context6);
|
||||
if (ret < 0) {
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot get IPv6 TCP network context.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = zperf_get_ipv6_addr(shell, MY_IP6ADDR, MY_PREFIX_LEN_STR,
|
||||
&in6_addr_my->sin6_addr);
|
||||
if (ret < 0) {
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Unable to set IPv6\n");
|
||||
return;
|
||||
}
|
||||
|
||||
shell_fprintf(shell, SHELL_NORMAL, "Binding to %s\n",
|
||||
net_sprint_ipv6_addr(&in6_addr_my->sin6_addr));
|
||||
|
||||
in6_addr_my->sin6_port = htons(port);
|
||||
}
|
||||
|
||||
printk(TAG "Binding to %s\n",
|
||||
net_sprint_ipv4_addr(&in4_addr_my->sin_addr));
|
||||
|
||||
in4_addr_my->sin_port = htons(port);
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_IPV6) && defined(MY_IP6ADDR)
|
||||
ret = net_context_get(AF_INET6, SOCK_STREAM, IPPROTO_TCP, &context6);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Cannot get IPv6 TCP network context.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = zperf_get_ipv6_addr(MY_IP6ADDR, MY_PREFIX_LEN_STR,
|
||||
&in6_addr_my->sin6_addr, TAG);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Unable to set IPv6\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printk(TAG "Binding to %s\n",
|
||||
net_sprint_ipv6_addr(&in6_addr_my->sin6_addr));
|
||||
|
||||
in6_addr_my->sin6_port = htons(port);
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
if (context6) {
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6) && context6) {
|
||||
ret = net_context_bind(context6,
|
||||
(struct sockaddr *)in6_addr_my,
|
||||
sizeof(struct sockaddr_in6));
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot bind IPv6 TCP port %d (%d)\n",
|
||||
ntohs(in6_addr_my->sin6_port), ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot bind IPv6 TCP port %d (%d)\n",
|
||||
ntohs(in6_addr_my->sin6_port), ret);
|
||||
fail++;
|
||||
}
|
||||
|
||||
ret = net_context_listen(context6, 0);
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot listen IPv6 TCP (%d)", ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot listen IPv6 TCP (%d)", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = net_context_accept(context6, tcp_accepted, K_NO_WAIT, NULL);
|
||||
ret = net_context_accept(context6, tcp_accepted, K_NO_WAIT,
|
||||
NULL);
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot receive IPv6 TCP packets (%d)", ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot receive IPv6 TCP packets (%d)",
|
||||
ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
if (context4) {
|
||||
if (IS_ENABLED(CONFIG_NET_IPV4) && context4) {
|
||||
ret = net_context_bind(context4,
|
||||
(struct sockaddr *)in4_addr_my,
|
||||
sizeof(struct sockaddr_in));
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot bind IPv4 TCP port %d (%d)\n",
|
||||
ntohs(in4_addr_my->sin_port), ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot bind IPv4 TCP port %d (%d)\n",
|
||||
ntohs(in4_addr_my->sin_port), ret);
|
||||
fail++;
|
||||
}
|
||||
|
||||
ret = net_context_listen(context4, 0);
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot listen IPv4 TCP (%d)", ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot listen IPv4 TCP (%d)", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = net_context_accept(context4, tcp_accepted, K_NO_WAIT, NULL);
|
||||
ret = net_context_accept(context4, tcp_accepted, K_NO_WAIT,
|
||||
(void *)shell);
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot receive IPv4 TCP packets (%d)", ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot receive IPv4 TCP packets (%d)",
|
||||
ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (fail > 1) {
|
||||
return;
|
||||
|
@ -232,18 +244,19 @@ static void zperf_tcp_rx_thread(int port)
|
|||
k_sleep(K_FOREVER);
|
||||
}
|
||||
|
||||
void zperf_tcp_receiver_init(int port)
|
||||
void zperf_tcp_receiver_init(const struct shell *shell, int port)
|
||||
{
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
in6_addr_my = zperf_get_sin6();
|
||||
#endif
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
in4_addr_my = zperf_get_sin();
|
||||
#endif
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6)) {
|
||||
in6_addr_my = zperf_get_sin6();
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV4)) {
|
||||
in4_addr_my = zperf_get_sin();
|
||||
}
|
||||
|
||||
k_thread_create(&zperf_tcp_rx_thread_data, zperf_tcp_rx_stack,
|
||||
K_THREAD_STACK_SIZEOF(zperf_tcp_rx_stack),
|
||||
(k_thread_entry_t)zperf_tcp_rx_thread,
|
||||
INT_TO_POINTER(port), 0, 0,
|
||||
(void *)shell, INT_TO_POINTER(port), 0,
|
||||
K_PRIO_COOP(7), 0, K_NO_WAIT);
|
||||
}
|
||||
|
|
|
@ -19,11 +19,10 @@
|
|||
#include "zperf.h"
|
||||
#include "zperf_internal.h"
|
||||
|
||||
#define TAG CMD_STR_TCP_UPLOAD" "
|
||||
|
||||
static char sample_packet[PACKET_SIZE_MAX];
|
||||
|
||||
void zperf_tcp_upload(struct net_context *ctx,
|
||||
void zperf_tcp_upload(const struct shell *shell,
|
||||
struct net_context *ctx,
|
||||
unsigned int duration_in_ms,
|
||||
unsigned int packet_size,
|
||||
struct zperf_results *results)
|
||||
|
@ -34,8 +33,9 @@ void zperf_tcp_upload(struct net_context *ctx,
|
|||
u8_t time_elapsed = 0, finished = 0;
|
||||
|
||||
if (packet_size > PACKET_SIZE_MAX) {
|
||||
printk(TAG "WARNING! packet size too large! max size: %u\n",
|
||||
PACKET_SIZE_MAX);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Packet size too large! max size: %u\n",
|
||||
PACKET_SIZE_MAX);
|
||||
packet_size = PACKET_SIZE_MAX;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,9 @@ void zperf_tcp_upload(struct net_context *ctx,
|
|||
start_time = k_cycle_get_32();
|
||||
last_print_time = start_time;
|
||||
last_loop_time = start_time;
|
||||
printk(TAG "New session started\n");
|
||||
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
"New session started\n");
|
||||
|
||||
(void)memset(sample_packet, 'z', sizeof(sample_packet));
|
||||
|
||||
|
@ -59,14 +61,16 @@ void zperf_tcp_upload(struct net_context *ctx,
|
|||
|
||||
pkt = net_pkt_get_tx(ctx, K_FOREVER);
|
||||
if (!pkt) {
|
||||
printk(TAG "ERROR! Failed to retrieve a packet\n");
|
||||
shell_fprintf(shell, SHELL_ERROR,
|
||||
"Failed to retrieve a packet\n");
|
||||
break;
|
||||
}
|
||||
|
||||
frag = net_pkt_get_data(ctx, K_FOREVER);
|
||||
if (!frag) {
|
||||
net_pkt_unref(pkt);
|
||||
printk(TAG "ERROR! Failed to retrieve a fragment\n");
|
||||
shell_fprintf(shell, SHELL_ERROR,
|
||||
"Failed to retrieve a fragment\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -79,8 +83,9 @@ void zperf_tcp_upload(struct net_context *ctx,
|
|||
/* Send the packet */
|
||||
ret = net_context_send(pkt, NULL, K_NO_WAIT, NULL, NULL);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Failed to send the packet (%d)\n",
|
||||
ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Failed to send the packet (%d)\n",
|
||||
ret);
|
||||
|
||||
net_pkt_unref(pkt);
|
||||
nb_errors++;
|
||||
|
|
|
@ -26,8 +26,6 @@
|
|||
#define NET_LOG_ENABLED 1
|
||||
#include "net_private.h"
|
||||
|
||||
#define TAG CMD_STR_UDP_DOWNLOAD" "
|
||||
|
||||
#define RX_THREAD_STACK_SIZE 1024
|
||||
#define MY_SRC_PORT 50001
|
||||
|
||||
|
@ -35,16 +33,13 @@
|
|||
static K_THREAD_STACK_DEFINE(zperf_rx_stack, RX_THREAD_STACK_SIZE);
|
||||
static struct k_thread zperf_rx_thread_data;
|
||||
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
static struct sockaddr_in6 *in6_addr_my;
|
||||
#endif
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
static struct sockaddr_in *in4_addr_my;
|
||||
#endif
|
||||
|
||||
#define MAX_DBG_PRINT 64
|
||||
|
||||
static inline void set_dst_addr(sa_family_t family,
|
||||
static inline void set_dst_addr(const struct shell *shell,
|
||||
sa_family_t family,
|
||||
struct net_pkt *pkt,
|
||||
struct sockaddr *dst_addr)
|
||||
{
|
||||
|
@ -52,30 +47,28 @@ static inline void set_dst_addr(sa_family_t family,
|
|||
|
||||
udp_hdr = net_udp_get_hdr(pkt, &hdr);
|
||||
if (!udp_hdr) {
|
||||
printk(TAG "Invalid UDP data\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Invalid UDP data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
if (family == AF_INET6) {
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) {
|
||||
net_ipaddr_copy(&net_sin6(dst_addr)->sin6_addr,
|
||||
&NET_IPV6_HDR(pkt)->src);
|
||||
net_sin6(dst_addr)->sin6_family = AF_INET6;
|
||||
net_sin6(dst_addr)->sin6_port = udp_hdr->src_port;
|
||||
}
|
||||
#endif /* CONFIG_NET_IPV6 */
|
||||
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
if (family == AF_INET) {
|
||||
if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) {
|
||||
net_ipaddr_copy(&net_sin(dst_addr)->sin_addr,
|
||||
&NET_IPV4_HDR(pkt)->src);
|
||||
net_sin(dst_addr)->sin_family = AF_INET;
|
||||
net_sin(dst_addr)->sin_port = udp_hdr->src_port;
|
||||
}
|
||||
#endif /* CONFIG_NET_IPV4 */
|
||||
}
|
||||
|
||||
static inline struct net_pkt *build_reply_pkt(struct net_context *context,
|
||||
static inline struct net_pkt *build_reply_pkt(const struct shell *shell,
|
||||
struct net_context *context,
|
||||
struct net_pkt *pkt,
|
||||
struct zperf_udp_datagram *hdr,
|
||||
struct zperf_server_hdr *stat)
|
||||
|
@ -83,7 +76,8 @@ static inline struct net_pkt *build_reply_pkt(struct net_context *context,
|
|||
struct net_pkt *reply_pkt;
|
||||
struct net_buf *frag;
|
||||
|
||||
printk(TAG "received %d bytes\n", net_pkt_appdatalen(pkt));
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
"Received %d bytes\n", net_pkt_appdatalen(pkt));
|
||||
|
||||
reply_pkt = net_pkt_get_tx(context, K_FOREVER);
|
||||
frag = net_pkt_get_data(context, K_FOREVER);
|
||||
|
@ -109,7 +103,8 @@ static inline struct net_pkt *build_reply_pkt(struct net_context *context,
|
|||
}
|
||||
|
||||
/* Send statistics to the remote client */
|
||||
static int zperf_receiver_send_stat(struct net_context *context,
|
||||
static int zperf_receiver_send_stat(const struct shell *shell,
|
||||
struct net_context *context,
|
||||
struct net_pkt *pkt,
|
||||
struct zperf_udp_datagram *hdr,
|
||||
struct zperf_server_hdr *stat)
|
||||
|
@ -118,9 +113,9 @@ static int zperf_receiver_send_stat(struct net_context *context,
|
|||
struct sockaddr dst_addr;
|
||||
int ret;
|
||||
|
||||
set_dst_addr(net_pkt_family(pkt), pkt, &dst_addr);
|
||||
set_dst_addr(shell, net_pkt_family(pkt), pkt, &dst_addr);
|
||||
|
||||
reply_pkt = build_reply_pkt(context, pkt, hdr, stat);
|
||||
reply_pkt = build_reply_pkt(shell, context, pkt, hdr, stat);
|
||||
|
||||
net_pkt_unref(pkt);
|
||||
|
||||
|
@ -130,7 +125,8 @@ static int zperf_receiver_send_stat(struct net_context *context,
|
|||
sizeof(struct sockaddr_in),
|
||||
NULL, 0, NULL, NULL);
|
||||
if (ret < 0) {
|
||||
printk(TAG " Cannot send data to peer (%d)", ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
" Cannot send data to peer (%d)", ret);
|
||||
net_pkt_unref(reply_pkt);
|
||||
}
|
||||
|
||||
|
@ -142,6 +138,7 @@ static void udp_received(struct net_context *context,
|
|||
int status,
|
||||
void *user_data)
|
||||
{
|
||||
const struct shell *shell = user_data;
|
||||
struct zperf_udp_datagram hdr;
|
||||
struct session *session;
|
||||
struct net_buf *frag;
|
||||
|
@ -157,7 +154,8 @@ static void udp_received(struct net_context *context,
|
|||
frag = pkt->frags;
|
||||
|
||||
if (net_pkt_appdatalen(pkt) < sizeof(struct zperf_udp_datagram)) {
|
||||
printk(TAG "ERROR! short iperf packet!\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Short iperf packet!\n");
|
||||
net_pkt_unref(pkt);
|
||||
return;
|
||||
}
|
||||
|
@ -166,7 +164,8 @@ static void udp_received(struct net_context *context,
|
|||
|
||||
session = get_session(pkt, SESSION_UDP);
|
||||
if (!session) {
|
||||
printk(TAG "ERROR! cannot get a session!\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot get a session!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -180,16 +179,16 @@ static void udp_received(struct net_context *context,
|
|||
|
||||
/* Check last packet flags */
|
||||
if (id < 0) {
|
||||
printk(TAG "End of session!\n");
|
||||
shell_fprintf(shell, SHELL_NORMAL, "End of session!\n");
|
||||
|
||||
if (session->state == STATE_COMPLETED) {
|
||||
/* Session is already completed: Resend the stat packet
|
||||
* and continue
|
||||
*/
|
||||
if (zperf_receiver_send_stat(context, pkt, &hdr,
|
||||
if (zperf_receiver_send_stat(shell, context, pkt, &hdr,
|
||||
&session->stat) < 0) {
|
||||
printk(TAG "ERROR! Failed to send the "
|
||||
"packet\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Failed to send the packet\n");
|
||||
|
||||
net_pkt_unref(pkt);
|
||||
}
|
||||
|
@ -200,7 +199,8 @@ static void udp_received(struct net_context *context,
|
|||
|
||||
} else if (session->state != STATE_ONGOING) {
|
||||
/* Start a new session! */
|
||||
printk(TAG "New session started.\n");
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
"New session started.\n");
|
||||
|
||||
zperf_reset_session_stats(session);
|
||||
session->state = STATE_ONGOING;
|
||||
|
@ -270,32 +270,39 @@ static void udp_received(struct net_context *context,
|
|||
session->stat.jitter1 = 0;
|
||||
session->stat.jitter2 = session->jitter;
|
||||
|
||||
if (zperf_receiver_send_stat(context, pkt, &hdr,
|
||||
if (zperf_receiver_send_stat(shell, context, pkt, &hdr,
|
||||
&session->stat) < 0) {
|
||||
printk(TAG "ERROR! Failed to send the "
|
||||
"packet\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Failed to send the packet\n");
|
||||
|
||||
net_pkt_unref(pkt);
|
||||
}
|
||||
|
||||
printk(TAG " duration:\t\t");
|
||||
print_number(duration, TIME_US, TIME_US_UNIT);
|
||||
printk("\n");
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
" duration:\t\t");
|
||||
print_number(shell, duration, TIME_US, TIME_US_UNIT);
|
||||
shell_fprintf(shell, SHELL_NORMAL, "\n");
|
||||
|
||||
printk(TAG " received packets:\t%u\n",
|
||||
session->counter);
|
||||
printk(TAG " nb packets lost:\t%u\n",
|
||||
session->outorder);
|
||||
printk(TAG " nb packets outorder:\t%u\n",
|
||||
session->error);
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
" received packets:\t%u\n",
|
||||
session->counter);
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
" nb packets lost:\t%u\n",
|
||||
session->outorder);
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
" nb packets outorder:\t%u\n",
|
||||
session->error);
|
||||
|
||||
printk(TAG " jitter:\t\t\t");
|
||||
print_number(session->jitter, TIME_US, TIME_US_UNIT);
|
||||
printk("\n");
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
" jitter:\t\t\t");
|
||||
print_number(shell, session->jitter, TIME_US,
|
||||
TIME_US_UNIT);
|
||||
shell_fprintf(shell, SHELL_NORMAL, "\n");
|
||||
|
||||
printk(TAG " rate:\t\t\t");
|
||||
print_number(rate_in_kbps, KBPS, KBPS_UNIT);
|
||||
printk("\n");
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
" rate:\t\t\t");
|
||||
print_number(shell, rate_in_kbps, KBPS, KBPS_UNIT);
|
||||
shell_fprintf(shell, SHELL_NORMAL, "\n");
|
||||
}
|
||||
} else {
|
||||
net_pkt_unref(pkt);
|
||||
|
@ -303,108 +310,123 @@ static void udp_received(struct net_context *context,
|
|||
}
|
||||
|
||||
/* RX thread entry point */
|
||||
static void zperf_rx_thread(int port)
|
||||
static void zperf_rx_thread(const struct shell *shell, int port)
|
||||
{
|
||||
#if defined(CONFIG_NET_IPV4) && defined(MY_IP4ADDR)
|
||||
struct net_context *context4 = NULL;
|
||||
#endif
|
||||
#if defined(CONFIG_NET_IPV6) && defined(MY_IP6ADDR)
|
||||
struct net_context *context6 = NULL;
|
||||
#endif
|
||||
int ret;
|
||||
|
||||
#if defined(CONFIG_NET_IPV4) && defined(MY_IP4ADDR)
|
||||
ret = net_context_get(AF_INET, SOCK_DGRAM, IPPROTO_UDP, &context4);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Cannot get IPv4 network context.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = zperf_get_ipv4_addr(MY_IP4ADDR, &in4_addr_my->sin_addr, TAG);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Unable to set IPv4\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printk(TAG "Binding to %s\n",
|
||||
net_sprint_ipv4_addr(&in4_addr_my->sin_addr));
|
||||
|
||||
in4_addr_my->sin_port = htons(port);
|
||||
|
||||
if (context4) {
|
||||
ret = net_context_bind(context4,
|
||||
(struct sockaddr *)in4_addr_my,
|
||||
sizeof(struct sockaddr_in));
|
||||
if (IS_ENABLED(CONFIG_NET_IPV4) && MY_IP4ADDR) {
|
||||
ret = net_context_get(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
|
||||
&context4);
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot bind IPv4 UDP port %d (%d)\n",
|
||||
ntohs(in4_addr_my->sin_port), ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot get IPv4 network context.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = zperf_get_ipv4_addr(shell, MY_IP4ADDR,
|
||||
&in4_addr_my->sin_addr);
|
||||
if (ret < 0) {
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Unable to set IPv4\n");
|
||||
return;
|
||||
}
|
||||
|
||||
shell_fprintf(shell, SHELL_NORMAL, "Binding to %s\n",
|
||||
net_sprint_ipv4_addr(&in4_addr_my->sin_addr));
|
||||
|
||||
in4_addr_my->sin_port = htons(port);
|
||||
|
||||
if (context4) {
|
||||
ret = net_context_bind(context4,
|
||||
(struct sockaddr *)in4_addr_my,
|
||||
sizeof(struct sockaddr_in));
|
||||
if (ret < 0) {
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot bind IPv4 UDP port %d (%d)\n",
|
||||
ntohs(in4_addr_my->sin_port),
|
||||
ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6) && MY_IP6ADDR) {
|
||||
ret = net_context_get(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
|
||||
&context6);
|
||||
if (ret < 0) {
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot get IPv6 network context.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = zperf_get_ipv6_addr(shell, MY_IP6ADDR, MY_PREFIX_LEN_STR,
|
||||
&in6_addr_my->sin6_addr);
|
||||
if (ret < 0) {
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Unable to set IPv6\n");
|
||||
return;
|
||||
}
|
||||
|
||||
shell_fprintf(shell, SHELL_NORMAL, "Binding to %s\n",
|
||||
net_sprint_ipv6_addr(&in6_addr_my->sin6_addr));
|
||||
|
||||
in6_addr_my->sin6_port = htons(port);
|
||||
|
||||
if (context6) {
|
||||
ret = net_context_bind(context6,
|
||||
(struct sockaddr *)in6_addr_my,
|
||||
sizeof(struct sockaddr_in6));
|
||||
if (ret < 0) {
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot bind IPv6 UDP port %d (%d)\n",
|
||||
ntohs(in6_addr_my->sin6_port),
|
||||
ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6)) {
|
||||
ret = net_context_recv(context6, udp_received, K_NO_WAIT,
|
||||
(void *)shell);
|
||||
if (ret < 0) {
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot receive IPv6 UDP packets\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_IPV6) && defined(MY_IP6ADDR)
|
||||
ret = net_context_get(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, &context6);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Cannot get IPv6 network context.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = zperf_get_ipv6_addr(MY_IP6ADDR, MY_PREFIX_LEN_STR,
|
||||
&in6_addr_my->sin6_addr, TAG);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Unable to set IPv6\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printk(TAG "Binding to %s\n",
|
||||
net_sprint_ipv6_addr(&in6_addr_my->sin6_addr));
|
||||
|
||||
in6_addr_my->sin6_port = htons(port);
|
||||
|
||||
if (context6) {
|
||||
ret = net_context_bind(context6,
|
||||
(struct sockaddr *)in6_addr_my,
|
||||
sizeof(struct sockaddr_in6));
|
||||
if (IS_ENABLED(CONFIG_NET_IPV4)) {
|
||||
ret = net_context_recv(context4, udp_received, K_NO_WAIT,
|
||||
(void *)shell);
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot bind IPv6 UDP port %d (%d)\n",
|
||||
ntohs(in6_addr_my->sin6_port), ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot receive IPv4 UDP packets\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
ret = net_context_recv(context6, udp_received, K_NO_WAIT, NULL);
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot receive IPv6 UDP packets\n");
|
||||
}
|
||||
#endif /* CONFIG_NET_IPV6 */
|
||||
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
ret = net_context_recv(context4, udp_received, K_NO_WAIT, NULL);
|
||||
if (ret < 0) {
|
||||
printk(TAG "Cannot receive IPv4 UDP packets\n");
|
||||
}
|
||||
#endif /* CONFIG_NET_IPV4 */
|
||||
|
||||
printk(TAG "Listening on port %d\n", port);
|
||||
shell_fprintf(shell, SHELL_NORMAL,
|
||||
"Listening on port %d\n", port);
|
||||
|
||||
k_sleep(K_FOREVER);
|
||||
}
|
||||
|
||||
void zperf_receiver_init(int port)
|
||||
void zperf_receiver_init(const struct shell *shell, int port)
|
||||
{
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
in6_addr_my = zperf_get_sin6();
|
||||
#endif
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
in4_addr_my = zperf_get_sin();
|
||||
#endif
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6)) {
|
||||
in6_addr_my = zperf_get_sin6();
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV4)) {
|
||||
in4_addr_my = zperf_get_sin();
|
||||
}
|
||||
|
||||
k_thread_create(&zperf_rx_thread_data, zperf_rx_stack,
|
||||
K_THREAD_STACK_SIZEOF(zperf_rx_stack),
|
||||
(k_thread_entry_t)zperf_rx_thread,
|
||||
INT_TO_POINTER(port), 0, 0,
|
||||
(void *)shell, INT_TO_POINTER(port), 0,
|
||||
K_PRIO_COOP(7), 0, K_NO_WAIT);
|
||||
}
|
||||
|
|
|
@ -18,11 +18,10 @@
|
|||
#include "zperf.h"
|
||||
#include "zperf_internal.h"
|
||||
|
||||
#define TAG CMD_STR_UDP_UPLOAD" "
|
||||
|
||||
static u8_t sample_packet[PACKET_SIZE_MAX];
|
||||
|
||||
static inline void zperf_upload_decode_stat(struct net_pkt *pkt,
|
||||
static inline void zperf_upload_decode_stat(const struct shell *shell,
|
||||
struct net_pkt *pkt,
|
||||
struct zperf_results *results)
|
||||
{
|
||||
struct net_buf *frag;
|
||||
|
@ -31,7 +30,8 @@ static inline void zperf_upload_decode_stat(struct net_pkt *pkt,
|
|||
u16_t pos;
|
||||
|
||||
if (!pkt) {
|
||||
printk(TAG "ERROR! Failed to receive statistic\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Failed to receive statistics\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,8 @@ static inline void zperf_upload_decode_stat(struct net_pkt *pkt,
|
|||
sizeof(struct zperf_udp_datagram),
|
||||
&offset);
|
||||
if (!frag) {
|
||||
printk(TAG "ERROR! Network packet too short\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Network packet too short\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -50,7 +51,8 @@ static inline void zperf_upload_decode_stat(struct net_pkt *pkt,
|
|||
if (net_pkt_appdatalen(pkt) <
|
||||
(sizeof(struct zperf_server_hdr) +
|
||||
sizeof(struct zperf_udp_datagram))) {
|
||||
printk(TAG "ERROR! Statistics too small\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Statistics too small\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -84,7 +86,8 @@ static void stat_received(struct net_context *context,
|
|||
*stat = pkt;
|
||||
}
|
||||
|
||||
static inline void zperf_upload_fin(struct net_context *context,
|
||||
static inline void zperf_upload_fin(const struct shell *shell,
|
||||
struct net_context *context,
|
||||
u32_t nb_packets,
|
||||
u32_t end_time,
|
||||
u32_t packet_size,
|
||||
|
@ -102,13 +105,15 @@ static inline void zperf_upload_fin(struct net_context *context,
|
|||
|
||||
pkt = net_pkt_get_tx(context, K_FOREVER);
|
||||
if (!pkt) {
|
||||
printk(TAG "ERROR! Failed to retrieve a packet\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Failed to retrieve a packet\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
frag = net_pkt_get_data(context, K_FOREVER);
|
||||
if (!frag) {
|
||||
printk(TAG "ERROR! Failed to retrieve a fragment\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Failed to retrieve a fragment\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -118,12 +123,13 @@ static inline void zperf_upload_fin(struct net_context *context,
|
|||
datagram.id = htonl(-nb_packets);
|
||||
datagram.tv_sec = htonl(HW_CYCLES_TO_SEC(end_time));
|
||||
datagram.tv_usec = htonl(HW_CYCLES_TO_USEC(end_time) %
|
||||
USEC_PER_SEC);
|
||||
USEC_PER_SEC);
|
||||
|
||||
status = net_pkt_append_all(pkt, sizeof(datagram),
|
||||
(u8_t *)&datagram, K_FOREVER);
|
||||
(u8_t *)&datagram, K_FOREVER);
|
||||
if (!status) {
|
||||
printk(TAG "ERROR! Cannot append datagram data\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot append datagram data\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -143,8 +149,9 @@ static inline void zperf_upload_fin(struct net_context *context,
|
|||
/* Send the packet */
|
||||
ret = net_context_send(pkt, NULL, K_NO_WAIT, NULL, NULL);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Failed to send the packet (%d)\n",
|
||||
ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Failed to send the packet (%d)\n",
|
||||
ret);
|
||||
net_pkt_unref(pkt);
|
||||
continue;
|
||||
}
|
||||
|
@ -153,7 +160,7 @@ static inline void zperf_upload_fin(struct net_context *context,
|
|||
stat = NULL;
|
||||
|
||||
ret = net_context_recv(context, stat_received,
|
||||
2 * MSEC_PER_SEC, &stat);
|
||||
K_SECONDS(2), &stat);
|
||||
if (ret == -ETIMEDOUT) {
|
||||
break;
|
||||
}
|
||||
|
@ -161,7 +168,7 @@ static inline void zperf_upload_fin(struct net_context *context,
|
|||
|
||||
/* Decode statistics */
|
||||
if (stat) {
|
||||
zperf_upload_decode_stat(stat, results);
|
||||
zperf_upload_decode_stat(shell, stat, results);
|
||||
|
||||
net_pkt_unref(stat);
|
||||
}
|
||||
|
@ -177,14 +184,16 @@ static inline void zperf_upload_fin(struct net_context *context,
|
|||
}
|
||||
|
||||
if (stat) {
|
||||
printk(TAG "Drain one spurious stat packet!\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Drain one spurious stat packet!\n");
|
||||
|
||||
net_pkt_unref(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void zperf_udp_upload(struct net_context *context,
|
||||
void zperf_udp_upload(const struct shell *shell,
|
||||
struct net_context *context,
|
||||
unsigned int duration_in_ms,
|
||||
unsigned int packet_size,
|
||||
unsigned int rate_in_kbps,
|
||||
|
@ -200,12 +209,14 @@ void zperf_udp_upload(struct net_context *context,
|
|||
u32_t start_time, last_print_time, last_loop_time, end_time;
|
||||
|
||||
if (packet_size > PACKET_SIZE_MAX) {
|
||||
printk(TAG "WARNING! packet size too large! max size: %u\n",
|
||||
PACKET_SIZE_MAX);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Packet size too large! max size: %u\n",
|
||||
PACKET_SIZE_MAX);
|
||||
packet_size = PACKET_SIZE_MAX;
|
||||
} else if (packet_size < sizeof(struct zperf_udp_datagram)) {
|
||||
printk(TAG "WARNING! packet size set to the min size: %zu\n",
|
||||
sizeof(struct zperf_udp_datagram));
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Packet size set to the min size: %zu\n",
|
||||
sizeof(struct zperf_udp_datagram));
|
||||
packet_size = sizeof(struct zperf_udp_datagram);
|
||||
}
|
||||
|
||||
|
@ -248,13 +259,15 @@ void zperf_udp_upload(struct net_context *context,
|
|||
|
||||
pkt = net_pkt_get_tx(context, K_FOREVER);
|
||||
if (!pkt) {
|
||||
printk(TAG "ERROR! Failed to retrieve a packet\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Failed to retrieve a packet\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
frag = net_pkt_get_data(context, K_FOREVER);
|
||||
if (!frag) {
|
||||
printk(TAG "ERROR! Failed to retrieve a frag\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Failed to retrieve a frag\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -269,7 +282,8 @@ void zperf_udp_upload(struct net_context *context,
|
|||
status = net_pkt_append_all(pkt, sizeof(datagram),
|
||||
(u8_t *)&datagram, K_FOREVER);
|
||||
if (!status) {
|
||||
printk(TAG "ERROR! Cannot append datagram data\n");
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Cannot append datagram data\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -288,8 +302,9 @@ void zperf_udp_upload(struct net_context *context,
|
|||
/* Send the packet */
|
||||
ret = net_context_send(pkt, NULL, K_NO_WAIT, NULL, NULL);
|
||||
if (ret < 0) {
|
||||
printk(TAG "ERROR! Failed to send the packet (%d)\n",
|
||||
ret);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"Failed to send the packet (%d)\n",
|
||||
ret);
|
||||
|
||||
net_pkt_unref(pkt);
|
||||
break;
|
||||
|
@ -299,8 +314,9 @@ void zperf_udp_upload(struct net_context *context,
|
|||
|
||||
/* Print log every seconds */
|
||||
if (time_delta(last_print_time, loop_time) > print_interval) {
|
||||
printk(TAG "nb_packets=%u\tdelay=%u\tadjust=%d\n",
|
||||
nb_packets, delay, adjust);
|
||||
shell_fprintf(shell, SHELL_WARNING,
|
||||
"nb_packets=%u\tdelay=%u\tadjust=%d\n",
|
||||
nb_packets, delay, adjust);
|
||||
last_print_time = loop_time;
|
||||
}
|
||||
|
||||
|
@ -313,7 +329,8 @@ void zperf_udp_upload(struct net_context *context,
|
|||
|
||||
end_time = k_cycle_get_32();
|
||||
|
||||
zperf_upload_fin(context, nb_packets, end_time, packet_size, results);
|
||||
zperf_upload_fin(shell, context, nb_packets, end_time, packet_size,
|
||||
results);
|
||||
|
||||
/* Add result coming from the client */
|
||||
results->nb_packets_sent = nb_packets;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue