Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/aminet-survey/own-tools.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ offset lvo aminet_callers our_tool_mentions
-48 accept 245 40
-54 connect 600 21
-60 sendto 217 1
-66 send 758 83
-66 send 758 84
-72 recvfrom 198 2
-78 recv 696 17
-84 shutdown 198 8
Expand Down
11 changes: 11 additions & 0 deletions docs/user/AmiNetXDuo.guide
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,17 @@
ring overflowed 42 times in ten seconds and the card
received 2.8 Mbit/s.

@{b}TCPACKMAX@{ub}
Maximum received TCP bytes between requested ACKs on
connections using this interface, 1 to 65535. Smaller
values send more ACKs and may improve Wi-Fi receive speed
at a cost in transmit work. Leave it out for the device
default: 11680 for anxwifipi.device and wifipi.device,
50176 for other devices. An explicit value wins on this
interface only; editing the file takes effect when the
interface is next attached. This is a TCP stack setting,
not a SANA-II driver command.

Interface files written for Roadshow usually carry other keywords:
COPYMODE, ALIAS, METRIC, LEASE, DHCPUNICAST, LINKSTATUSCOMMAND,
MULTICAST, POINTTOPOINT, DESTINATION and BROADCASTADDRESS among
Expand Down
4 changes: 4 additions & 0 deletions include/aminetxduo/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ extern "C" {
#define AMINETXDUO_TX_SLOTS 32
#endif
#define AMI_CFG_WRITEREQUESTS_MAX AMINETXDUO_TX_SLOTS
#define AMI_CFG_TCP_ACK_MAX 65535UL
#define AMI_CFG_PATH_LEN 128

/* The default domain gets its own cap: SetDefaultDomainName()'s autodoc says
Expand Down Expand Up @@ -199,6 +200,9 @@ typedef struct AmiIfConfig {
* hardware is known gets a default by name. A number here wins.
*/
ULONG rx_buffer;
/* TCPACKMAX: maximum unacknowledged receive bytes before an ACK is
requested on connections using this interface. 0 = device default. */
ULONG tcp_ack_max;
BYTE priority; /* PRIORITY=, -128..127, 0 unset: the
highest wins a route two
interfaces could carry */
Expand Down
1 change: 1 addition & 0 deletions include/aminetxduo/config_advice.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@
#define AMI_CFG_ADVICE_RXBUFFER_IS_THE 49
#define AMI_CFG_ADVICE_PRIORITY_IS_A_SMALL 50
#define AMI_CFG_ADVICE_FILTER_IS_LOCAL_IPANDARP 51
#define AMI_CFG_ADVICE_TCPACKMAX_IS_ACK_BYTES 52

#endif /* AMINETXDUO_CONFIG_ADVICE_H */
5 changes: 5 additions & 0 deletions include/aminetxduo/sana2.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ UWORD ami_sana2_retained_count(VOID);
refuses such a pair with AMI_NET_ERR_RETAINED before OpenDevice(). */
ULONG ami_sana2_retained_holds(const char *device, ULONG unit);

/* TCP ACK ceiling when TCPACKMAX is absent: WiFiPi needs the measured
one-GRO-run cadence, other devices use the NetX port default. Zero means
use that port default. Names are compared by basename, ignoring case. */
ULONG ami_sana2_default_tcp_ack_max(const char *device);

/*
* Collect whatever the devices have given back since, without waiting, and
* CloseDevice() and free every interface nothing holds any more. Under
Expand Down
2 changes: 2 additions & 0 deletions install/Install-AmiNetXDuo
Original file line number Diff line number Diff line change
Expand Up @@ -2238,6 +2238,8 @@
(append "; keyword is absent. The responder costs\n")
(append "; real time on a slow machine, even when\n")
(append "; nothing asks it anything.\n")
(append "; TCPACKMAX= caps received TCP bytes between requested ACKs.\n")
(append "; Leave it out for this device's default.\n")
(append ";\n")
(append "; An IPv6 build also understands CONFIGURE6 (OFF, LINKLOCAL,\n")
(append "; AUTO, DHCP or STATIC), ADDRESS6 and GATEWAY6. A build\n")
Expand Down
3 changes: 3 additions & 0 deletions src/config/config_advice.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ static const char *const ami_cfg_advice_text[] =
"FILTER is LOCAL, IPANDARP or EVERYTHING. The first two are wha"
"t happens anyway. EVERYTHING opens the driver promiscuous, for"
" a card whose multicast filter lets nothing through.",
"TCPACKMAX is the maximum number of received TCP bytes to wait "
"before requesting an ACK, 1 to 65535. Leave it out for the "
"device default; smaller values send more ACKs.",
};

const char *ami_cfg_advice(UWORD code)
Expand Down
17 changes: 17 additions & 0 deletions src/config/config_parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ typedef enum
IF_KEY_ARPREQUESTS,
IF_KEY_WRITEREQUESTS,
IF_KEY_RXBUFFER,
IF_KEY_TCPACKMAX,
IF_KEY_PRIORITY
} IfKey;

Expand Down Expand Up @@ -76,6 +77,7 @@ ami_if_keywords[] =
{ "arprequests", IF_KEY_ARPREQUESTS },
{ "writerequests", IF_KEY_WRITEREQUESTS },
{ "rxbuffer", IF_KEY_RXBUFFER },
{ "tcpackmax", IF_KEY_TCPACKMAX },
{ "priority", IF_KEY_PRIORITY }, /* Roadshow's, and PRI */
{ "pri", IF_KEY_PRIORITY },

Expand Down Expand Up @@ -763,6 +765,21 @@ LONG ami_cfg_parse_interface(const char *name, char *buf, AmiIfConfig *out)
}
break;

case IF_KEY_TCPACKMAX:
if (ami_cfg_parse_ulong(value, &n) &&
n != 0 && n <= AMI_CFG_TCP_ACK_MAX)
{
out->tcp_ack_max = n;
}
else
{
AMI_WARN("config: %s: bad TCPACKMAX '%s'", out->name, value);
report_bad_value(lineno, AMI_CFG_PROBLEM_WARN,
"TCPACKMAX", value,
AMI_CFG_ADVICE_TCPACKMAX_IS_ACK_BYTES);
}
break;

case IF_KEY_IPREQUESTS:
case IF_KEY_ARPREQUESTS:
case IF_KEY_WRITEREQUESTS:
Expand Down
34 changes: 34 additions & 0 deletions src/config/test/test_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,39 @@ static void test_request_counts_have_ceilings(void)
CHECK(seen[1].line == 4);
CHECK(seen[1].severity == AMI_CFG_PROBLEM_WARN);
}
static void test_interface_tcp_ack_max(void)
{
AmiIfConfig iface;
char *buf;

printf("interface: TCPACKMAX is optional and bounded per interface\n");

buf = dup_text("device = anxwifipi.device\nconfigure = dhcp\n"
"tcpackmax = 11680\n");
CHECK(ami_cfg_parse_interface("wifipi", buf, &iface) == AMI_CFG_OK);
CHECK(iface.tcp_ack_max == 11680UL);
free(buf);

seen_count = 0;
ami_config_set_reporter(collect, NULL);
ami_cfg_problem_file("DEVS:NetInterfaces/wifipi");
buf = dup_text("device = anxwifipi.device\nconfigure = dhcp\n"
"tcpackmax = 0\n");
CHECK(ami_cfg_parse_interface("wifipi", buf, &iface) == AMI_CFG_OK);
CHECK(iface.tcp_ack_max == 0);
CHECK(seen_count == 1 && seen[0].line == 3);
free(buf);

seen_count = 0;
buf = dup_text("device = anxwifipi.device\nconfigure = dhcp\n"
"tcpackmax = 65536\n");
CHECK(ami_cfg_parse_interface("wifipi", buf, &iface) == AMI_CFG_OK);
CHECK(iface.tcp_ack_max == 0);
CHECK(seen_count == 1 && seen[0].line == 3);
free(buf);
ami_config_set_reporter(NULL, NULL);
}

static void test_interface_priority(void)
{
AmiIfConfig iface;
Expand Down Expand Up @@ -2934,6 +2967,7 @@ int main(int argc, char **argv)
test_problem_reporter();
test_inert_keywords_are_notes();
test_request_counts_have_ceilings();
test_interface_tcp_ack_max();
test_interface_priority();
test_interface_ipv6_only();
#ifdef AMINETXDUO_IPV6
Expand Down
16 changes: 13 additions & 3 deletions src/netstack/netstack.c
Original file line number Diff line number Diff line change
Expand Up @@ -532,10 +532,20 @@ static VOID ami_ns_park_unaddressed(AmiNetStack *ns, UWORD index)
* keep attach order, which without this line is the whole rule -- a 3c589 in
* slot 0 carried a subnet an anxgenet.device in slot 1 shares.
*/
static VOID ami_ns_apply_priority(AmiNetStack *ns, UWORD index)
static VOID ami_ns_apply_interface_policy(AmiNetStack *ns, UWORD index)
{
ns->ns_Ip.nx_ip_interface[index].nx_interface_priority =
(INT)ns->ns_Config.interfaces[index].priority;

/* A TCP connection keeps its NX_INTERFACE for its lifetime. Publish the
ACK policy before bringing this interface online; zero leaves NetX at
the port default (50176), while WiFiPi defaults to the measured 11680.
An explicit TCPACKMAX always wins for this interface alone. */
ns->ns_Ip.nx_ip_interface[index].nx_interface_tcp_ack_threshold_max =
(ns->ns_Config.interfaces[index].tcp_ack_max != 0)
? ns->ns_Config.interfaces[index].tcp_ack_max
: ami_sana2_default_tcp_ack_max(
ns->ns_Config.interfaces[index].device);
}

/*
Expand Down Expand Up @@ -809,7 +819,7 @@ static LONG ami_ns_create_ip(AmiNetStack *ns)
for (i = 0; i < ns->ns_IfaceCount; i++)
{
ami_ns_park_unaddressed(ns, i);
ami_ns_apply_priority(ns, i);
ami_ns_apply_interface_policy(ns, i);

if (ns->ns_Ip.nx_ip_interface[i].nx_interface_link_up == NX_FALSE)
ami_event(NETEVENT_LINK_DOWN, i, 0UL);
Expand Down Expand Up @@ -3270,7 +3280,7 @@ static LONG ami_ns_interface_add_locked(const AmiIfConfig *cfg,
ns->ns_IfaceCount = (UWORD)(slot + 1);

ami_ns_park_unaddressed(ns, (UWORD)slot);
ami_ns_apply_priority(ns, (UWORD)slot);
ami_ns_apply_interface_policy(ns, (UWORD)slot);
ns->ns_Ip.nx_ip_interface[slot].nx_interface_ip_conflict_notify_handler =
ami_ns_ip_conflict;

Expand Down
1 change: 1 addition & 0 deletions src/sana2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_library(aminetxduo_sana2 STATIC
sana2_device.c
sana2_policy.c
sana2_copy.c
sana2_rx.c
sana2_tx.c
Expand Down
38 changes: 38 additions & 0 deletions src/sana2/sana2_policy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* Pure device-name defaults shared by the driver and host netstack tests. */
#include "aminetxduo/sana2.h"

static char lower_ascii(char c)
{
if (c >= 'A' && c <= 'Z')
return (char)(c + ('a' - 'A'));
return c;
}

static int name_equal(const char *a, const char *b)
{
while (*a != '\0' && *b != '\0')
{
if (lower_ascii(*a++) != lower_ascii(*b++))
return 0;
}
return *a == *b;
}

ULONG ami_sana2_default_tcp_ack_max(const char *device)
{
const char *base = device;
const char *p;

if (device == NULL)
return 0;

for (p = device; *p != '\0'; p++)
if (*p == '/' || *p == ':')
base = p + 1;

if (name_equal(base, "anxwifipi.device") ||
name_equal(base, "wifipi.device"))
return 11680UL;

return 0;
}
1 change: 1 addition & 0 deletions tests/netstack/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,7 @@ if(NOT CMAKE_CROSSCOMPILING)
# because that is the shipping default and its handful of entry points are
# already answered.
set(NETSTACK_HOST_TU
"${CMAKE_SOURCE_DIR}/src/sana2/sana2_policy.c"
"${CMAKE_SOURCE_DIR}/src/netstack/netstack.c"
"${CMAKE_SOURCE_DIR}/src/netstack/netstack_caller.c"
"${CMAKE_SOURCE_DIR}/src/netstack/netstack_claim.c"
Expand Down
47 changes: 47 additions & 0 deletions tests/netstack/host/test_config_routes_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,58 @@ static void t_loopback_then_specific_route(void)
h_down();
}

static void t_interface_tcp_ack_policy(void)
{
static const struct {
const char *device;
ULONG requested;
ULONG expected;
} cases[] = {
{ "RAM:anxwifipi.device", 0, 11680UL },
{ "genet.device", 0, 0 },
{ "genet.device", 8192UL, 8192UL },
{ "RAM:anxwifipi.device", 4096UL, 4096UL }
};
AmiIfConfig cfg;
UWORD index;
unsigned int i;

printf("interface: TCP ACK policy follows the attached device and override\n");

for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++)
{
nsh_reset();
CHECK(netstack_startup_loopback() == AMI_NET_OK,
"the loopback stack starts for the ACK policy case");

memset(&cfg, 0, sizeof(cfg));
strcpy(cfg.name, "testif");
strcpy(cfg.device, cases[i].device);
cfg.iptype = AMI_IPTYPE_STATIC;
cfg.address = 0xC0A80132UL;
cfg.netmask = 0xFFFFFF00UL;
cfg.up = TRUE;
cfg.configured = TRUE;
cfg.tcp_ack_max = cases[i].requested;
index = 99;

CHECK(netstack_interface_start(&cfg, &index) == AMI_NET_OK && index == 0,
"the selected interface attaches in slot zero");
if (index == 0)
CHECK(netstack_get()->ns_Ip.nx_ip_interface[index]
.nx_interface_tcp_ack_threshold_max == cases[i].expected,
"the attached interface carries its own ACK ceiling");

h_down();
}
}

int main(void)
{
t_startup_installs();
t_late_address_and_operator_delete();
t_loopback_then_specific_route();
t_interface_tcp_ack_policy();

printf("\n%lu checks, %lu failure(s)\n", h_checks, h_failures);
return h_failures == 0 ? 0 : 1;
Expand Down
1 change: 1 addition & 0 deletions tests/sana2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ add_test(NAME sana2_driver COMMAND test_sana2_driver)
add_executable(test_sana2_device
host/test_sana2_device_host.c
"${CMAKE_SOURCE_DIR}/src/sana2/sana2_device.c"
"${CMAKE_SOURCE_DIR}/src/sana2/sana2_policy.c"
"${CMAKE_SOURCE_DIR}/src/sana2/sana2_txmeta.c")

target_compile_options(test_sana2_device PRIVATE
Expand Down
8 changes: 8 additions & 0 deletions tests/sana2/host/test_sana2_device_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -1810,6 +1810,14 @@ static void case_bursty_wifi_reads(void)
h_check(ami_sana2_default_ip_reads(NULL) == 0,
"an unnamed driver keeps the ordinary BPS ladder");

h_check(ami_sana2_default_tcp_ack_max("DEVS:Networks/anxwifipi.device") == 11680UL,
"the shipped WiFiPi driver gets the measured ACK ceiling");
h_check(ami_sana2_default_tcp_ack_max("WIFIPI.DEVICE") == 11680UL,
"the upstream WiFiPi name gets the same ceiling");
h_check(ami_sana2_default_tcp_ack_max("genet.device") == 0 &&
ami_sana2_default_tcp_ack_max(NULL) == 0,
"wired and unnamed drivers keep the port ACK ceiling");

/* The DEVICE= lines Install-AmiNetXDuo writes for Wi-Fi, system layout
and self-contained drawer: neither carries IPREQUESTS. */
h_check(ami_sana2_default_ip_reads("DEVS:Networks/anxwifipi.device") == 128 &&
Expand Down
2 changes: 1 addition & 1 deletion third_party/netxduo
5 changes: 4 additions & 1 deletion tools/check-image-size.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ BUDGETS=(
# 364,664 with the bounded S2_OFFLINE: timer-bounded reply wait, abort,
# and an abandoned request kept with its interface until the device
# replies (#90, 5473e26e), 2026-09-26 (micro 204,052, minimal 238,240).
"default:src/bsdsocket/bsdsocket.library:365000"
# 365,468 with per-interface TCP ACK policy (#109): config parsing and
# publishing the WiFi/default-or-explicit ceiling into each NX_INTERFACE.
# This adds 468 bytes over the previous resident-image budget.
"default:src/bsdsocket/bsdsocket.library:366000"
# 41,412 after stateless receive-checksum verification was added to the
# EL3 and word/long NE2000 direct paths, 2026-09-15. 43,620 with
# ANXD_CMD_RX_BATCH in the shell (claim, completion, staging copy, the
Expand Down
Loading