SM-X WiFi Emulation Information
This document describes the SM-X WiFi device emulated by openMSX
(smxwifi), its I/O interface and how to use it. The SM-X WiFi is built-in
on SM-X computers and on some OCM FPGA computers, not an external cartridge.
Overview
The smxwifi device emulates the DucaSP SM-X WiFi (2020): an I/O-mapped
UART interface between the MSX and an ESP WiFi module, where the ESP side
runs the ESP32-UNAPI firmware. The UART link is either:
- an emulated ESP32 (default), or
- a real ESP32 on a host serial port, attached through the device's RS232 connector with
rs232-raw
This should not be a concern for most users, where the emulated ESP32 will be used. In some cases, user might want to not use the host network / bypass host network limitations without changing firewall / binding / settings, those users can refer to the Using a real ESP32 section.
MSX software uses the adapter through the SM-X WiFi UNAPI BIOS / driver, which implements the TCP-IP UNAPI specification over the UART protocol spoken by the ESP32 firmware.
UNAPI BIOS / driver
To add UNAPI capabilities to the MSX, download ESPUNAPI_IO.rom
from the UNAPI_BIOS_CUSTOM_ESP_V2
It is free for use and required for this device. When using with a real ESP32 module, keep F1 pressed during reset to connect to an AP, configure some features, etc. While you can access this setup with an emulated ESP, no configuration is needed and most of the configurations will not take any effect (as the host system will govern those).
Emulated ESP32
The emulated ESP32 implements the complete TCP-IP UNAPI firmware inside openMSX: the TCP-IP UNAPI specification 1.1 (TCP and UDP connections, DNS resolution, TLS/SSL over TCP active connections) using the network stack of the host operating system. It DOES NOT emulate SSH UNAPI, that is available only attaching a real ESP32.
TLS/SSL support
TLS (spec 1.1, "Use TLS" flag in TCPIP_TCP_OPEN) is supported for TCP
active connections. The emulation uses the OpenSSL runtime library
installed on the host operating system, which is loaded dynamically at
startup.
- If OpenSSL is found, the "Use TLS in TCP active connections" capability bit is advertised and TLS connections can be opened. The OpenSSL version is printed to the openMSX console when the extension is inserted.
- If OpenSSL is not found, the capability bit is not advertised and a
TCPIP_TCP_OPENwith the TLS flag returnsERR_NOT_IMP. A message is printed to the openMSX console when the extension is inserted.
TLS in passive connections is not implemented (the "Use TLS in TCP
passive connections" capability bit is never advertised, and requesting it
returns ERR_NOT_IMP, as the specification requires).
Certificate verification
The "Verify the server certificate" flag of TCPIP_TCP_OPEN is honored: the
server certificate is validated against the root certificates of the host
operating system:
- Windows: the Windows system certificate store (
ROOT) is used. - Linux: the default OpenSSL system trust store is used
(
/etc/ssl/cert.pemand/etc/ssl/certsand friends). - macOS: the
/etc/ssl/cert.pembundle is used (this file is provided by the system).
The server host name sent in the TCPIP_TCP_OPEN command is used for SNI
and, when verification is enabled, to validate the host name in the
certificate (matching the way the ESP32-UNAPI firmware reads the host name
from the command data). When no host name is supplied, the certificate chain
is still validated, but the host name is not checked.
A failed TLS handshake or certificate validation closes the connection; the
close reason (spec 4.5.4, reasons 9-19) is reported by TCPIP_TCP_STATE as
ERR_NO_CONN with the reason code.
Installing OpenSSL
- Linux: OpenSSL is almost always pre-installed (package
openssl/libssl). Nothing to do. - Windows: install a Win64 OpenSSL build, e.g. the one distributed by
slproweb.com or the
"OpenSSL" builds by FireDaemon (which are based
on LibreSSL and use
libssl-4-x64.dll/libcrypto-4-x64.dll). OpenSSL 1.1.x, 3.x and LibreSSL 3.5.x/4.x are all supported. Make sure the OpenSSL DLLs (libssl-*.dll/libcrypto-*.dll) are inPATHor in the openMSX executable directory. - macOS: install via Homebrew:
brew install openssl@3.
Network adapter preference
The IP address the emulated ESP32 reports to the MSX (TCPIP_GET_IPINFO) is
the one other machines use to reach the MSX, so it must be reachable on the
network. When the host has several adapters, the primary physical one is
chosen:
- Wired (Ethernet) — preferred whenever it is up.
- Wireless (Wi-Fi) — used when no wired adapter is up.
- Other physical adapters (PPP, modems, ...).
- Virtual adapters — only used when no physical adapter has a usable IPv4 address, so the emulation still works on machines that only have a virtual adapter (e.g. WSL-only setups).
The loopback adapter and adapters that are down are never used. Tunnels (6to4, ISATAP, Teredo) and VPN software (Tailscale, ZeroTier, WireGuard, ...) count as virtual.
When adapters tie in preference, the first one is used.
Host ports and operating-system services
The emulation binds sockets on the host operating system to emulate the network interface of the ESP32. Consequently, a port that is already in use on the host cannot be used by the device:
- The emulation reports the host's own network configuration to the MSX (its
primary IPv4 address, netmask, gateway and DNS servers, read fresh on each
TCPIP_GET_IPINFOcall; see Network adapter preference above), so other machines on the same network reach the MSX at that address, without any configuration on those machines. When the host has no usable IPv4 configuration the fields report0.0.0.0, except the primary DNS which falls back to the well-known8.8.8.8(DNS lookups are performed on the host anyway, so the reported value is informational). - Opening a connection (TCP or UDP) on a local port that is occupied by a
host application returns
ERR_CONN_EXISTS(or fails). - In particular, well-known ports used by operating-system services are not
available. On Windows, the Windows Time service (W32Time) is started by
default and keeps UDP port 123 in use permanently, so the
TCPIP_SNTP_SET/SNTP functionality cannot use its standard port. To use the SNTP port, the W32Time service must be stopped (see below). - To free a port for the device, stop the host service that occupies it. On
Windows, stop the Windows Time service temporarily with
net stop w32time, or permanently by setting its startup type to "Manual"/"Disabled" in the Services manager (services.msc) or withsc config w32time start= disabled.
Firewalls and inbound connections
The emulation binds sockets on the host operating system, so inbound traffic to the emulated MSX has to pass the host's firewall. This applies to servers running on the MSX (for example an FTP server on port 21) and to "connect-back" connections, where a remote machine connects to the MSX's advertised port (for example the data connection of an FTP client). Outbound connections are normally not affected.
-
Linux (ufw): ufw denies inbound connections by default. Allow the ports used by the MSX applications; for example, for an FTP server and the data connections of an FTP client:
sudo ufw allow from 192.168.0.0/24 to any port 21 proto tcp # FTP server (control) sudo ufw allow from 192.168.0.0/24 to any port 113 proto tcp # FTP client (IDENT listener) sudo ufw allow from 192.168.0.0/24 to any port 1024:65535 proto tcp # FTP data connections(the FTP client opens its data connection on a random port in the range 16384-32767; the FTP server opens its data ports starting at 1024).
UDP traffic is subject to the same inbound deny, and replies to MSX UDP clients are affected as well: TFTP and SNTP on the MSX are clients, so their requests go out, but the replies (and subsequent data packets) arrive on the host as inbound UDP traffic and are dropped unless allowed. Allow the local ports those clients bind - SNTP uses UDP port 123, the TFTP client uses port 69 or a random port in the 16384-32767 range:
sudo ufw allow from 192.168.0.0/24 to any port 123 proto udp # SNTP client (local port) sudo ufw allow from 192.168.0.0/24 to any port 69 proto udp # TFTP client (local port) sudo ufw allow from 192.168.0.0/24 to any port 16384:32767 proto udp # TFTP client (random local port)When in doubt, allow the local port(s) the application uses. Port 69 is privileged like port 21 (see below).
For a quick test the firewall can be disabled temporarily with
sudo ufw disableand re-enabled withsudo ufw enable. iptables- or nftables-based firewalls need equivalent rules. -
Privileged ports: on Linux and macOS, binding a port below 1024 requires privileges. If an MSX application uses such a port (for example an FTP server on port 21), grant the capability once so regular users can bind these ports:
sudo setcap 'cap_net_bind_service=+ep' /path/to/openmsx(the capability must be reapplied after every openMSX update, and the filesystem must support file capabilities). Alternatively,
authbind --deep openmsxcan be used. Windows has no privileged ports.
Technical / Advanced Information
Setting: simulated WiFi link
The smxwifi-emulatedesp32-link-enabled setting (default true) simulates
the WiFi link state of the emulated ESP32: with the setting disabled, the
emulated ESP32 reports its network link as closed (which is what
TCPIP_NET_STATE reports), like a real ESP32 whose WiFi radio is off. It
does not affect a real ESP32 attached through rs232-raw. This is useful
for developers that want to test cable / AP disconnected.
I/O ports
The extension (smxwifi.xml) occupies 2 I/O ports at base 0x06:
| Port | Direction | Function |
|---|---|---|
| base | read | UART RX FIFO byte (from the ESP module) |
| base | write | UART command (baud-rate select, FIFO reset) |
| base + 1 | read | UART status |
| base + 1 | write | UART TX byte (to the ESP module) |
UART status bits:
| Bit | Meaning |
|---|---|
| 0 | RX data available in the FIFO |
| 3 | Quick receive supported |
| 4 | Underrun (read of an empty FIFO), cleared by read |
Quick receive is on by default, it works by safeguarding the read of an UART
RX FIFO byte, if FIFO is empty it will assert WAIT on the BUS (so z80 will
not continue processing) until a byte arrives OR a timeout elapses (about 30
ms); if no data arrives, 0xFF is returned and the underrun bit is set.
UNAPI Driver make use of this feature to accelerate receiving data from
connections, in a way that it is guaranteed that extra bytes are coming, in
all other operations it is always checking if there is a byte available
before reading. The end result is faster receiving at the "expense" of
asserting WAIT for some time. The smxwifi-emulatedesp32-quickrcv-enabled
setting (default true) can be used to turn this feature off.
UART command values (write to the data port base):
| Value | Function |
|---|---|
| 0-9 | Select baud rate (see table below) |
| 20 | Reset the RX FIFO |
Baud rates (value = index):
| Value | Baud rate | Value | Baud rate |
|---|---|---|---|
| 0 | 859372 | 5 | 38400 |
| 1 | 346520 | 6 | 31250 |
| 2 | 231014 | 7 | 19200 |
| 3 | 115200 | 8 | 9600 |
| 4 | 57600 | 9 | 4800 |
The default after reset is baud rate 0 (859372). The UART parameters are always 8 data bits, 1 stop bit, no parity. With the emulated ESP32 the baud rate is not considered (there is no real serial communication).
Using a real ESP32
ESP Firmware
A real ESP32 module can be useful for developers or advanced users that do not want to run into the host network limitations or want to have a separate physical connection to the MSX. This module must run the ESP32-UNAPI firmware, which is built from the ESP32-UNAPI-Firmware repository and flashed to the ESP32. We recommend using an ESP32-C6 or ESP32-S3 board with at least 8 MB of flash; 16 MB is recommended.
Connector and pluggables
Once the extension is inserted, it exposes an RS232 connector named smxwifi.
If you plan to use a real ESP32 module instead of the emulated one, plug
rs232-raw into it to connect the UART to a real ESP32 on a host serial port:
ext smxwifi
plug smxwifi rs232-raw
set rs232-raw-port COM7
As long as the connector has no rs232-raw device plugged in, or its host
serial port is not open (no port selected, port in use, ...), the device
talks to the emulated ESP32, so the adapter keeps working with no real
hardware attached. Plugging a device other than rs232-raw also leaves the
emulated ESP32 in charge.
Note: changes can be done pretty easily on the GUI as well, using the connectors menu.
With rs232-raw the networking happens on the attached real device, so the
host networking considerations of the emulated ESP32 (firewall, ports, TLS)
do not apply.