zephyr-cp: working TCP sockets and TLS on the Pico W boards - #21
Open
tyeth wants to merge 3 commits into
Open
Conversation
Nothing in the port, the SoC defaults or these boards' Kconfig turned CONFIG_NET_TCP on, so every SOCK_STREAM socket failed inside net_context_get() with EPROTOTYPE -- which socketpool reported as a generic "Out of sockets" -- and the web workflow's listener never opened either. Enable TCP on both boards, and raise the fd table to 16: Zephyr sized it from the subsystems' declared needs (13 here), which the DHCPv4 server and the socket-service eventfd eat into. Three socketpool fixes found on the way: - A socket object is allocated with a finaliser and zeroed before zsock_socket() runs. If that failed, num stayed 0 and the finaliser later called zsock_shutdown(0). fd 0 belongs to the socket service's eventfd, whose shorter vtable has no shutdown slot, and the CPU branched into cdc_acm_1's data: "USAGE FAULT / Illegal use of the EPSR", pc 0x200003f8, lr z_impl_zsock_shutdown, system halted. Mark the object closed (num = -1) before attempting creation. - Report errno from a failed zsock_socket() (ENOENT: no net_context left, EPROTOTYPE: protocol off, ENFILE: fd table) instead of the fixed message. - Accepted sockets did not inherit the listener's timeout: the Python-facing accept path only set num, so a fresh object read as timeout 0 and the ssl layer's first recv on an accepted TLS connection raised EAGAIN before the handshake could finish. Inherit it, as the other ports do. Measured on a Pico 2 W with the softAP up: 11 TCP sockets open before ENOENT (12 net_contexts, one held by the DHCPv4 server). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wrap_socket(server_side=True) configured mbedTLS with VERIFY_REQUIRED whenever the context had the default root bundle attached, which every ssl.SSLContext() does. On a server that means "require a client certificate", so a plain HTTPS server -- load_cert_chain() and nothing else -- failed every handshake with MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE (-0x7480) and the client saw a timeout. Default server-side contexts to VERIFY_NONE, as CPython's do (CERT_NONE for servers); a context that loaded its own CA with load_verify_locations() still verifies clients. Client-side behaviour is unchanged. Seen on a Pico 2 W serving a captive portal over its softAP; the same code runs on every port using the shared mbedTLS module. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… boards Two Kconfig gaps kept an HTTPS server (or client) from ever completing a handshake on these boards: - Zephyr's mbedTLS 4 default serves secp256r1 through the p256-m PSA driver and leaves the builtin ECP module out. mbedTLS then defines the dummy MBEDTLS_ECP_MAX_BITS 1, and ssl.h sizes the TLS 1.2 premaster buffer (union mbedtls_ssl_premaster_secret._pms_ecdh[MBEDTLS_ECP_MAX_BYTES]) from it: sizeof(handshake->premaster) was 1 in the firmware (DWARF). Every ECDHE key agreement then failed -- "psa_raw_key_agreement() returned -138 (-0x008a)", PSA_ERROR_BUFFER_TOO_SMALL for a 32-byte shared secret -- and the raw PSA status leaked through mbedtls_ssl_read() as OSError 138 on the first read of any TLS connection. Disabling the p256-m driver brings the builtin ECP module back and the buffer is 32 bytes again. This is an upstream sizing bug (ssl.h should size that buffer from PSA when the ECP module is absent); the Kconfig is the workaround until it is fixed. - PEM parsing was not compiled in, so load_cert_chain() with the usual fullchain.pem / key.pem could only fail; the collector's certstore is PEM. Verified on a Pico 2 W: an RSA-2048 PEM certificate served from CIRCUITPY over the softAP; an ESP32-C6 client completed the handshake and received "HTTP/1.0 200 OK" in 2.0 s. Flash +1,868 B (PEM) and +4,716 B (builtin ECP instead of p256-m) on the Pico 2 W; no static RAM change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 9, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three commits, each a thing that had never worked on these boards and was found while wiring up the captive portal (# softAP PR follows):
CONFIG_NET_TCPfor the Pico W / Pico 2 W; everySOCK_STREAMsocket failed innet_context_get()withEPROTOTYPE, reported by socketpool as "Out of sockets". Enabled, fd table raised to 16, and three socketpool fixes: a failedsocket()left an object withnum == 0whose finaliser later calledzsock_shutdown(0)on the socket service's eventfd and branched intocdc_acm_1's data (usage fault, halt); errno is now reported; accepted sockets inherit the listener's timeout (they read as non-blocking, so the first ssl recv raisedEAGAIN).sslserver-side contexts demanded a client certificate (MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE) because the default root bundle setVERIFY_REQUIRED. Servers now default toVERIFY_NONElike CPython;load_verify_locations()still enables client verification. This is ashared-module/sslchange and affects every port.handshake->premasterfrom a dummyMBEDTLS_ECP_MAX_BITS 1—sizeof(premaster) == 1in the DWARF — andpsa_raw_key_agreement()returnedPSA_ERROR_BUFFER_TOO_SMALL(-138) which leaked out asOSError 138. Disabling p256-m restores a 32-byte buffer (upstream sizing bug; Kconfig workaround). PEM parsing enabled forload_cert_chain()withfullchain.pem/key.pem.Verified on hardware: Pico 2 W serving an RSA-2048 PEM cert over its softAP, ESP32-C6 client →
HTTP/1.0 200 OKin 2.0 s. 11 TCP sockets open beforeENOENT(12 net_contexts, one held by DHCP).Size, Pico 2 W: TCP + fd table: RAM 240,432 → 250,240 B (+9,808 B), flash 1,158,036 → 1,171,756 B (+13,720 B); PEM +1,868 B flash; builtin ECP instead of p256-m +4,716 B flash. Final image of this series: 1,176,616 B flash (75.20 %), 250,240 B RAM (47.00 %).
🤖 Generated with Claude Code