From 2f38333bddcd2ead2bbf87157f3592d8bfa03064 Mon Sep 17 00:00:00 2001 From: Mark Stevens Date: Thu, 15 Oct 2026 18:46:32 +0100 Subject: [PATCH 1/4] Fix the Thumb-2 register-controlled ASR and ROR encodings arm_asr_register() and arm_ror_register() were copied from the EOR helper. When rd == rm they emitted the 16-bit ASRS/RORS with the operands swapped (rd = rm >> rn), and otherwise the 32-bit data processing (shifted register) encoding with opcode 4 or 6, which is rn EOR rm or rn ORN rm, not a shift. The JIT uses ARM_SAR_REG for every int >> variable, so code such as 0x80 >> (bit % 8) gave wrong results once registers were allocated that way: PrefixLengthToSubnetMask() turned a /24 into 135.135.135.0, and a loop printing 0x80 >> (bit % 8) printed zeros. Use the 16-bit form only when rd == rn, and the 32-bit register-controlled shift (type ASR or ROR) otherwise, as arm_lsl_reg() and arm_lsr_reg() already do. ROR by register is not used by the JIT at present; it is fixed because it had the same defect. Co-Authored-By: Claude Opus 5.5 --- src/mono/mono/arch/arm/thumb-codegen.h | 38 ++++++++++++++------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/mono/mono/arch/arm/thumb-codegen.h b/src/mono/mono/arch/arm/thumb-codegen.h index fc37c16e9d6bc5..efc0be67cfc6e7 100644 --- a/src/mono/mono/arch/arm/thumb-codegen.h +++ b/src/mono/mono/arch/arm/thumb-codegen.h @@ -3040,18 +3040,20 @@ arm_asr_imm(void **code, int rd, int rm, int imm) #define ARM_SAR_IMM(p, rd, rm, imm) arm_asr_imm((void **)&p, rd, rm, imm) +/* + * rd = rn >> rm (arithmetic). The 16-bit ASRS shifts its destination, so it + * can only be used when rd == rn; a shift is not commutative, so rd == rm must + * not swap the operands. Every other case is the 32-bit register-controlled + * shift (type ASR). The data processing (constant shift) encoding used before + * computed rn EOR rm. + */ static __THUMB_INLINE__ void arm_asr_register(void **code, int rd, int rn, int rm) { - if ((rd < 8) && (rn < 8) && (rm < 8)) { - if (rd == rm) - arm_dpri16(code, rd, rn, 4); - else if (rd == rn) - arm_dpri16(code, rd, rm, 4); - else - arm_dpc32(code, rd, rn, rm, 0, 0, 4, 0); - } else - arm_dpc32(code, rd, rn, rm, 0, 0, 4, 0); + if ((rd < 8) && (rn < 8) && (rm < 8) && (rd == rn)) + arm_dpri16(code, rd, rm, 4); + else + arm_rcs32(code, rd, rn, rm, 4, 0); } #define ARM_SAR_REG(p, rd, rn, rs) arm_asr_register((void **)&p, rd, rn, rs) @@ -3931,18 +3933,18 @@ arm_ror_imm_rot(void **code, int rd, int rn, int imm, int rot, int cond) #define ARM_ROR_REG_IMM(p, rd, rn, imm, rot) arm_ror_imm_rot((void **) &p, rd, rm, imm, rot, 0) +/* + * rd = rn ROR rm. As for arm_asr_register: the 16-bit RORS only when + * rd == rn, otherwise the 32-bit register-controlled shift (type ROR). The + * data processing (constant shift) encoding used before computed rn ORN rm. + */ static __THUMB_INLINE__ void arm_ror_register(void **code, int rd, int rn, int rm) { - if ((rd < 8) && (rn < 8) && (rm < 8)) { - if (rd == rm) - arm_dpri16(code, rd, rn, 7); - else if (rd == rn) - arm_dpri16(code, rd, rm, 7); - else - arm_dpc32(code, rd, rn, rm, 0, 0, 6, 0); - } else - arm_dpc32(code, rd, rn, rm, 0, 0, 6, 0); + if ((rd < 8) && (rn < 8) && (rm < 8) && (rd == rn)) + arm_dpri16(code, rd, rm, 7); + else + arm_rcs32(code, rd, rn, rm, 6, 0); } #define ARM_ROR_REG_REG(p, rd, rn, rm) arm_ror_register((void **) &p, rd, rn, rm) From e504b6876606f1e2edd459466a79ac3edefaeb57 Mon Sep 17 00:00:00 2001 From: Mark Stevens Date: Thu, 15 Oct 2026 18:46:32 +0100 Subject: [PATCH 2/4] Read the name servers from /meadow0/dns.conf NetworkInterface.GetAllNetworkInterfaces() reads /etc/resolv.conf for the DNS suffix and addresses. Meadow.OS has no /etc, so the read threw DirectoryNotFoundException in LinuxNetworkInterfaceSystemProperties, which catches only FileNotFoundException and UnauthorizedAccessException, and no interface was returned (Meadow.Core then reported the IP address as 255.255.255.255). The NuttX DNS client already reads /meadow0/dns.conf (CONFIG_NETDB_RESOLVCONF_PATH), which uses the resolv.conf format and is written by the OS at boot, so point EtcResolvConfFile, used by every reader, at it. Co-Authored-By: Claude Opus 5.5 --- .../src/System/Net/NetworkInformation/NetworkFiles.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkFiles.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkFiles.cs index bc31a9fabf3072..dd318bb7443791 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkFiles.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkFiles.cs @@ -17,7 +17,10 @@ internal static class NetworkFiles public const string SockstatFile = "/proc/net/sockstat"; public const string Sockstat6File = "/proc/net/sockstat6"; public const string SysClassNetFolder = "/sys/class/net"; - public const string EtcResolvConfFile = "/etc/resolv.conf"; + // Meadow.OS (NuttX) has no /etc; its DNS client reads the name servers from + // /meadow0/dns.conf (CONFIG_NETDB_RESOLVCONF_PATH), which uses the resolv.conf + // format and is written by the OS at boot. + public const string EtcResolvConfFile = "/meadow0/dns.conf"; public const string Tcp4ConnectionsFile = "/proc/net/tcp"; public const string Tcp6ConnectionsFile = "/proc/net/tcp6"; public const string Udp4ConnectionsFile = "/proc/net/udp"; From ac986f43b8dc6c18a0148b0fe5ea64ca327614d7 Mon Sep 17 00:00:00 2001 From: Mark Stevens Date: Thu, 15 Oct 2026 18:46:50 +0100 Subject: [PATCH 3/4] Report the interface type and MAC address on NuttX NuttX getifaddrs() returns no AF_PACKET or AF_LINK entries, so SystemNative_GetNetworkInterfaces() left every interface as NetworkInterfaceType.Unknown with no physical address. Meadow.Core looks for a Wireless80211 interface, so the WiFi adapter's IP address, mask and gateway were reported as 255.255.255.255. On NuttX, take the hardware address from ifa_data (the SIOCGIFHWADDR struct sockaddr) and the type from the interface flags: Meadow.OS's ESP32 driver (espcp) marks its interface with MEADOW_IFF_WIFI (bit 14, which NuttX leaves free), the loopback device is recognised by name because NuttX does not set IFF_LOOPBACK on it, and anything else is Ethernet. This replaces the IFF_WIFI handling of Legacy Meadow's NuttxNetworkInterface. Co-Authored-By: Claude Opus 5.5 --- src/mono/cmake/nuttx-compat.h | 6 +++++ .../System.Native/pal_interfaceaddresses.c | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/mono/cmake/nuttx-compat.h b/src/mono/cmake/nuttx-compat.h index 7f56fc3c96fc9f..668acf4637a0c7 100644 --- a/src/mono/cmake/nuttx-compat.h +++ b/src/mono/cmake/nuttx-compat.h @@ -62,4 +62,10 @@ #define IFF_ALLMULTI 0 #endif +/* Meadow.OS interface flag: espcp (the ESP32 WiFi coprocessor driver) sets it + * on the interface it reports, so that the interface can be identified as + * WiFi. It must match IFF_WIFI in Meadow.OS F7MeadowOS/espcp/espcp_usrsock.h + * and uses a bit that NuttX leaves free. */ +#define MEADOW_IFF_WIFI (1 << 14) + #endif /* _NUTTX_COMPAT_H */ diff --git a/src/native/libs/System.Native/pal_interfaceaddresses.c b/src/native/libs/System.Native/pal_interfaceaddresses.c index dfaafe5c22c87e..fa5c08bd9a3dcd 100644 --- a/src/native/libs/System.Native/pal_interfaceaddresses.c +++ b/src/native/libs/System.Native/pal_interfaceaddresses.c @@ -440,6 +440,28 @@ int32_t SystemNative_GetNetworkInterfaces(int32_t * interfaceCount, NetworkInter // OperationalState returns whether the interface can transmit data packets. // The administrator must have enabled the interface (IFF_UP), and the cable must be plugged in (IFF_RUNNING). nii->OperationalState = ((ifaddrsEntry->ifa_flags & (IFF_UP|IFF_RUNNING)) == (IFF_UP|IFF_RUNNING)) ? OperationalStatus_Up : OperationalStatus_Down; + +#if defined(__NuttX__) + // NuttX getifaddrs() returns no link-layer (AF_PACKET/AF_LINK) entries. The + // hardware address is in ifa_data (a struct sockaddr from SIOCGIFHWADDR, the + // address in sa_data), and Meadow.OS marks its WiFi interface with + // MEADOW_IFF_WIFI, so take the type and the address from those. The NuttX + // loopback device does not set IFF_LOOPBACK, so it is recognised by name. + if ((strcmp(ifa_name, "lo") == 0) || ((ifaddrsEntry->ifa_flags & IFF_LOOPBACK) != 0)) + { + nii->HardwareType = NetworkInterfaceType_Loopback; + } + else + { + nii->HardwareType = ((ifaddrsEntry->ifa_flags & MEADOW_IFF_WIFI) != 0) ? NetworkInterfaceType_Wireless80211 : NetworkInterfaceType_Ethernet; + } + if (ifaddrsEntry->ifa_data != NULL) + { + struct sockaddr* hwaddr = (struct sockaddr*)ifaddrsEntry->ifa_data; + nii->NumAddressBytes = IFHWADDRLEN; + memcpy_s(&nii->AddressBytes, sizeof_member(NetworkInterfaceInfo, AddressBytes), (uint8_t*)hwaddr->sa_data, IFHWADDRLEN); + } +#endif } if (ifaddrsEntry->ifa_addr == NULL) From 8d8eb4ee022f593e9b5869d242874029df44cade Mon Sep 17 00:00:00 2001 From: Mark Stevens Date: Thu, 15 Oct 2026 18:46:50 +0100 Subject: [PATCH 4/4] Report the gateway on NuttX The Linux NetworkInformation reads gateways only from /proc/net/route, which NuttX does not have, so GatewayAddresses was always empty. When there is no route file, LinuxIPInterfaceProperties now asks SystemNative_EnumerateGatewayAddressesForInterface(), as the BSD code does. On NuttX that returns the gateway of Meadow.OS's active interface from meadow_os_get_gateway_address(), the call Legacy Meadow's NuttxIPInterfaceProperties used. The OS answer is passed on as it is; over WiFi the OS does not yet learn the DHCP gateway and answers 0.0.0.0 (Meadow.OS issue #703). Co-Authored-By: Claude Opus 5.5 --- .../LinuxIPInterfaceProperties.cs | 30 +++++++++++++++++ .../System.Native/pal_interfaceaddresses.c | 32 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/LinuxIPInterfaceProperties.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/LinuxIPInterfaceProperties.cs index 6930f757dc6ef4..44b0e2d001f3cb 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/LinuxIPInterfaceProperties.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/LinuxIPInterfaceProperties.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Runtime.Versioning; namespace System.Net.NetworkInformation @@ -67,9 +68,38 @@ public GatewayIPAddressInformationCollection GetGatewayAddresses(LinuxNetworkInt StringParsingHelpers.ParseIPv6GatewayAddressesFromRouteFile(collection, systemProperties.IPv6Routes, _linuxNetworkInterface.Name, _linuxNetworkInterface.Index); } + // Meadow.OS (NuttX) has no /proc/net/route; its native layer reports the gateway. + if ((systemProperties.IPv4Routes == null) && (systemProperties.IPv6Routes == null)) + { + AddNativeGatewayAddresses(collection, _linuxNetworkInterface.Index); + } + return new GatewayIPAddressInformationCollection(collection); } + private struct GatewayContext + { + internal List _collection; + } + + private static unsafe void AddNativeGatewayAddresses(List collection, int interfaceIndex) + { + GatewayContext context; + context._collection = collection; + + // A failure leaves the collection as it is: no gateway is known. + Interop.Sys.EnumerateGatewayAddressesForInterface(&context, (uint)interfaceIndex, &OnGatewayFound); + } + + [UnmanagedCallersOnly] + private static unsafe void OnGatewayFound(void* pContext, Interop.Sys.IpAddressInfo* gatewayAddressInfo) + { + GatewayContext* context = (GatewayContext*)pContext; + + IPAddress ipAddress = new IPAddress(((ReadOnlySpan)gatewayAddressInfo->AddressBytes)[..gatewayAddressInfo->NumAddressBytes]); + context->_collection.Add(new SimpleGatewayIPAddressInformation(ipAddress)); + } + private InternalIPAddressCollection GetDhcpServerAddresses() { List internalCollection = new List(); diff --git a/src/native/libs/System.Native/pal_interfaceaddresses.c b/src/native/libs/System.Native/pal_interfaceaddresses.c index fa5c08bd9a3dcd..26bc21e7a0fe6a 100644 --- a/src/native/libs/System.Native/pal_interfaceaddresses.c +++ b/src/native/libs/System.Native/pal_interfaceaddresses.c @@ -27,6 +27,7 @@ #include #elif defined(__NuttX__) #include +#include #endif #include #include @@ -718,6 +719,37 @@ int32_t SystemNative_EnumerateGatewayAddressesForInterface(void* context, uint32 free(buffer); return 0; } +#elif defined(__NuttX__) +// Meadow.OS keeps the gateway of the active network interface in its configuration, +// set when the network connects (WiFi through the ESP32 coprocessor has no NuttX +// routing table), and returns it as a dotted string. The OS answer is passed on as +// it is, 0.0.0.0 included: over WiFi the OS does not yet learn the DHCP gateway +// (Meadow.OS issue #703). +extern int meadow_os_get_gateway_address(char* buffer); + +int32_t SystemNative_EnumerateGatewayAddressesForInterface(void* context, uint32_t interfaceIndex, GatewayAddressFound onGatewayFound) +{ + char address[INET_ADDRSTRLEN]; + struct in_addr gateway; + + if (meadow_os_get_gateway_address(address) < 0) + { + errno = ENOTSUP; + return -1; + } + + if (inet_pton(AF_INET, address, &gateway) == 1) + { + IpAddressInfo iai; + memset(&iai, 0, sizeof(IpAddressInfo)); + iai.InterfaceIndex = interfaceIndex; + iai.NumAddressBytes = NUM_BYTES_IN_IPV4_ADDRESS; + memcpy_s(iai.AddressBytes, sizeof_member(IpAddressInfo, AddressBytes), &gateway.s_addr, sizeof(gateway.s_addr)); + onGatewayFound(context, &iai); + } + + return 0; +} #else int32_t SystemNative_EnumerateGatewayAddressesForInterface(void* context, uint32_t interfaceIndex, GatewayAddressFound onGatewayFound) {