Files
deadprogram a74770b2e6 rtl8720dn: implement ConnectModeAP
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-01 07:40:46 +02:00

865 lines
18 KiB
Go

// Package rtl8720dn implements TCP wireless communication over UART
// talking to a RealTek rtl8720dn module.
//
// 01/2023 sfeldma@gmail.com Heavily modified to use netdev interface
package rtl8720dn // import "tinygo.org/x/drivers/rtl8720dn"
import (
"encoding/hex"
"errors"
"fmt"
"io"
"machine"
"net"
"net/netip"
"strings"
"sync"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/netlink"
)
var _debug debug = debugBasic
//var _debug debug = debugBasic | debugNetdev
//var _debug debug = debugBasic | debugNetdev | debugRpc
var (
driverName = "Realtek rtl8720dn Wifi network device driver (rtl8720dn)"
)
const (
O_NONBLOCK = 1 // note: different value than syscall.O_NONBLOCK (0x800)
RTW_MODE_STA = 0x00000001
defaultChannel = 6
)
type sock int32
type socket struct {
protocol int
inuse bool
}
type Config struct {
// Enable
En machine.Pin
// UART config
Uart *machine.UART
Tx machine.Pin
Rx machine.Pin
Baudrate uint32
}
type rtl8720dn struct {
cfg *Config
notifyCb func(netlink.Event)
mu sync.Mutex
uart *machine.UART
seq uint64
debug bool
params *netlink.ConnectParams
netConnected bool
driverShown bool
deviceShown bool
killWatchdog chan bool
// keyed by sock as returned by rpc_lwip_socket()
sockets map[sock]*socket
}
func newSocket(protocol int) *socket {
return &socket{protocol: protocol, inuse: true}
}
func New(cfg *Config) *rtl8720dn {
return &rtl8720dn{
debug: (_debug & debugRpc) != 0,
cfg: cfg,
sockets: make(map[sock]*socket),
killWatchdog: make(chan bool),
}
}
func (r *rtl8720dn) startDhcpc() error {
if result := r.rpc_tcpip_adapter_dhcpc_start(0); result == -1 {
return netdev.ErrStartingDHCPClient
}
return nil
}
func (r *rtl8720dn) connectToAP() error {
if len(r.params.Ssid) == 0 {
return netlink.ErrMissingSSID
}
if len(r.params.Passphrase) != 0 && len(r.params.Passphrase) < 8 {
return netlink.ErrShortPassphrase
}
if debugging(debugBasic) {
fmt.Printf("Connecting to Wifi SSID '%s'...", r.params.Ssid)
}
// Start the connection process
securityType := uint32(0) // RTW_SECURITY_OPEN
if len(r.params.Passphrase) != 0 {
securityType = 0x00400004 // RTW_SECURITY_WPA2_AES_PSK
}
result := r.rpc_wifi_connect(r.params.Ssid, r.params.Passphrase, securityType, -1, 0)
if result != 0 {
if debugging(debugBasic) {
fmt.Printf("FAILED\r\n")
}
return netlink.ErrConnectFailed
}
if debugging(debugBasic) {
fmt.Printf("CONNECTED\r\n")
}
if r.notifyCb != nil {
r.notifyCb(netlink.EventNetUp)
}
return r.startDhcpc()
}
func (r *rtl8720dn) startDhcps() error {
if result := r.rpc_tcpip_adapter_dhcps_start(0); result == -1 {
return netdev.ErrStartingDHCPServer
}
return nil
}
func (r *rtl8720dn) startAP() error {
if len(r.params.Ssid) == 0 {
return netlink.ErrMissingSSID
}
if len(r.params.Passphrase) != 0 && len(r.params.Passphrase) < 8 {
return netlink.ErrShortPassphrase
}
if debugging(debugBasic) {
fmt.Printf("Starting Wifi AP as SSID '%s'...", r.params.Ssid)
}
// Start the connection process
securityType := uint32(0) // RTW_SECURITY_OPEN
if len(r.params.Passphrase) != 0 {
securityType = 0x00400004 // RTW_SECURITY_WPA2_AES_PSK
}
result := r.rpc_wifi_start_ap(r.params.Ssid, r.params.Passphrase, securityType, defaultChannel)
if result != 0 {
if debugging(debugBasic) {
fmt.Printf("FAILED\r\n")
}
return netlink.ErrConnectFailed
}
if debugging(debugBasic) {
fmt.Printf("LISTENING\r\n")
}
if r.notifyCb != nil {
r.notifyCb(netlink.EventNetUp)
}
return r.startDhcps()
}
func (r *rtl8720dn) showDriver() {
if r.driverShown {
return
}
if debugging(debugBasic) {
fmt.Printf("\r\n")
fmt.Printf("%s\r\n\r\n", driverName)
fmt.Printf("Driver version : %s\r\n", drivers.Version)
}
r.driverShown = true
}
func (r *rtl8720dn) initWifi() error {
if result := r.rpc_tcpip_adapter_init(); result == -1 {
return fmt.Errorf("TCP/IP adapter init failed")
}
if result := r.rpc_wifi_off(); result == -1 {
return errors.New("Error turning off WiFi")
}
if result := r.rpc_wifi_on(RTW_MODE_STA); result == -1 {
return errors.New("Error turning on WiFi")
}
if result := r.rpc_wifi_disconnect(); result == -1 {
return errors.New("Error disconnecting WiFi")
}
return nil
}
func (r *rtl8720dn) setupUART() {
r.uart = r.cfg.Uart
r.uart.Configure(machine.UARTConfig{TX: r.cfg.Tx,
RX: r.cfg.Rx, BaudRate: r.cfg.Baudrate})
}
func (r *rtl8720dn) start() error {
en := r.cfg.En
if en == 0 {
return fmt.Errorf("Must set Config.En")
}
en.Configure(machine.PinConfig{Mode: machine.PinOutput})
en.Low()
time.Sleep(100 * time.Millisecond)
en.High()
time.Sleep(1000 * time.Millisecond)
r.setupUART()
return r.initWifi()
}
func (r *rtl8720dn) stop() {
r.rpc_tcpip_adapter_stop(0)
r.cfg.En.Low()
}
func (r *rtl8720dn) showDevice() {
if r.deviceShown {
return
}
if debugging(debugBasic) {
fmt.Printf("RTL8720 firmware version : %s\r\n", r.getFwVersion())
fmt.Printf("MAC address : %s\r\n", r.getMACAddr())
fmt.Printf("\r\n")
}
r.deviceShown = true
}
func (r *rtl8720dn) showIP() {
if debugging(debugBasic) {
ip, subnet, gateway, _ := r.getIP()
fmt.Printf("\r\n")
fmt.Printf("DHCP-assigned IP : %s\r\n", ip)
fmt.Printf("DHCP-assigned subnet : %s\r\n", subnet)
fmt.Printf("DHCP-assigned gateway : %s\r\n", gateway)
fmt.Printf("\r\n")
}
}
func (r *rtl8720dn) networkDown() bool {
result := r.rpc_wifi_is_connected_to_ap()
return result != 0
}
func (r *rtl8720dn) watchdog() {
ticker := time.NewTicker(r.params.WatchdogTimeout)
for {
select {
case <-r.killWatchdog:
return
case <-ticker.C:
r.mu.Lock()
if r.networkDown() {
if debugging(debugBasic) {
fmt.Printf("Watchdog: Wifi NOT CONNECTED, trying again...\r\n")
}
if r.notifyCb != nil {
r.notifyCb(netlink.EventNetDown)
}
r.netConnect(false)
}
r.mu.Unlock()
}
}
}
func (r *rtl8720dn) netConnect(reset bool) error {
if reset {
if err := r.start(); err != nil {
return err
}
}
r.showDevice()
retry:
for i := 0; r.params.Retries == 0 || i < r.params.Retries; i++ {
switch r.params.ConnectMode {
case netlink.ConnectModeAP:
if err := r.startAP(); err != nil {
if err == netlink.ErrConnectFailed {
continue
}
return err
}
break retry
default:
if err := r.connectToAP(); err != nil {
if err == netlink.ErrConnectFailed {
continue
}
return err
}
break retry
}
}
if r.networkDown() {
return netlink.ErrConnectFailed
}
r.showIP()
return nil
}
func (r *rtl8720dn) NetConnect(params *netlink.ConnectParams) error {
r.mu.Lock()
defer r.mu.Unlock()
if r.netConnected {
return netlink.ErrConnected
}
r.params = params
r.showDriver()
if err := r.netConnect(true); err != nil {
return err
}
r.netConnected = true
if r.params.WatchdogTimeout != 0 {
go r.watchdog()
}
return nil
}
func (r *rtl8720dn) netDisconnect() {
r.disconnect()
}
func (r *rtl8720dn) NetDisconnect() {
r.mu.Lock()
defer r.mu.Unlock()
if !r.netConnected {
return
}
if r.params.WatchdogTimeout != 0 {
r.killWatchdog <- true
}
r.netDisconnect()
r.stop()
r.netConnected = false
if debugging(debugBasic) {
fmt.Printf("\r\nDisconnected from Wifi SSID '%s'\r\n\r\n", r.params.Ssid)
}
if r.notifyCb != nil {
r.notifyCb(netlink.EventNetDown)
}
}
func (r *rtl8720dn) NetNotify(cb func(netlink.Event)) {
r.notifyCb = cb
}
func (r *rtl8720dn) GetHostByName(name string) (netip.Addr, error) {
if debugging(debugNetdev) {
fmt.Printf("[GetHostByName] name: %s\r\n", name)
}
r.mu.Lock()
defer r.mu.Unlock()
var ip [4]byte
result := r.rpc_netconn_gethostbyname(name, ip[:])
if result == -1 {
return netip.Addr{}, netdev.ErrHostUnknown
}
addr, ok := netip.AddrFromSlice(ip[:])
if !ok {
return netip.Addr{}, netdev.ErrMalAddr
}
return addr, nil
}
func (r *rtl8720dn) GetHardwareAddr() (net.HardwareAddr, error) {
if debugging(debugNetdev) {
fmt.Printf("[GetHardwareAddr]\r\n")
}
r.mu.Lock()
defer r.mu.Unlock()
mac := strings.ReplaceAll(r.getMACAddr(), ":", "")
addr, err := hex.DecodeString(mac)
return net.HardwareAddr(addr), err
}
func (r *rtl8720dn) Addr() (netip.Addr, error) {
if debugging(debugNetdev) {
fmt.Printf("[GetIPAddr]\r\n")
}
r.mu.Lock()
defer r.mu.Unlock()
ip, _, _, err := r.getIP()
return ip, err
}
func (r *rtl8720dn) clientTLS() uint32 {
client := r.rpc_wifi_ssl_client_create()
r.rpc_wifi_ssl_init(client)
r.rpc_wifi_ssl_set_timeout(client, 120*1000 /* usec? */)
return client
}
// See man socket(2) for standard Berkely sockets for Socket, Bind, etc.
// The driver strives to meet the function and semantics of socket(2).
func (r *rtl8720dn) Socket(domain int, stype int, protocol int) (int, error) {
if debugging(debugNetdev) {
fmt.Printf("[Socket] domain: %d, type: %d, protocol: %d\r\n",
domain, stype, protocol)
}
switch domain {
case netdev.AF_INET:
default:
return -1, netdev.ErrFamilyNotSupported
}
var newSock int32
r.mu.Lock()
defer r.mu.Unlock()
switch {
case protocol == netdev.IPPROTO_TCP && stype == netdev.SOCK_STREAM:
newSock = r.rpc_lwip_socket(netdev.AF_INET, netdev.SOCK_STREAM,
netdev.IPPROTO_TCP)
case protocol == netdev.IPPROTO_TLS && stype == netdev.SOCK_STREAM:
// TODO Investigate: using client number as socket number;
// TODO this may cause a problem if mixing TLS and non-TLS sockets?
newSock = int32(r.clientTLS())
case protocol == netdev.IPPROTO_UDP && stype == netdev.SOCK_DGRAM:
newSock = r.rpc_lwip_socket(netdev.AF_INET, netdev.SOCK_DGRAM,
netdev.IPPROTO_UDP)
default:
return -1, netdev.ErrProtocolNotSupported
}
if newSock == -1 {
return -1, netdev.ErrNoMoreSockets
}
socket := newSocket(protocol)
r.sockets[sock(newSock)] = socket
return int(newSock), nil
}
func ipToName(ip netip.AddrPort) []byte {
name := make([]byte, 16)
name[0] = 0x00
name[1] = netdev.AF_INET
name[2] = byte(ip.Port() >> 8)
name[3] = byte(ip.Port())
if ip.Addr().Is4() {
addr := ip.Addr().As4()
name[4] = byte(addr[0])
name[5] = byte(addr[1])
name[6] = byte(addr[2])
name[7] = byte(addr[3])
}
return name
}
func nameToIp(name []byte) netip.AddrPort {
port := uint16(name[2])<<8 | uint16(name[3])
addr, _ := netip.AddrFromSlice(name[4:8])
return netip.AddrPortFrom(addr, port)
}
func (r *rtl8720dn) Bind(sockfd int, ip netip.AddrPort) error {
if debugging(debugNetdev) {
fmt.Printf("[Bind] sockfd: %d, addr: %s\r\n", sockfd, ip)
}
r.mu.Lock()
defer r.mu.Unlock()
var sock = sock(sockfd)
var socket = r.sockets[sock]
var name = ipToName(ip)
switch socket.protocol {
case netdev.IPPROTO_TCP, netdev.IPPROTO_UDP:
result := r.rpc_lwip_bind(int32(sock), name, uint32(len(name)))
if result == -1 {
return fmt.Errorf("Bind to %s failed", ip)
}
default:
return netdev.ErrProtocolNotSupported
}
return nil
}
func (r *rtl8720dn) Connect(sockfd int, host string, ip netip.AddrPort) error {
port := ip.Port()
if debugging(debugNetdev) {
if host == "" {
fmt.Printf("[Connect] sockfd: %d, addr: %s\r\n", sockfd, ip)
} else {
fmt.Printf("[Connect] sockfd: %d, host: %s:%d\r\n", sockfd, host, port)
}
}
r.mu.Lock()
defer r.mu.Unlock()
var sock = sock(sockfd)
var socket = r.sockets[sock]
var name = ipToName(ip)
// Start the connection
switch socket.protocol {
case netdev.IPPROTO_TCP, netdev.IPPROTO_UDP:
result := r.rpc_lwip_connect(int32(sock), name, uint32(len(name)))
if result == -1 {
return fmt.Errorf("Connect to %s failed", ip)
}
case netdev.IPPROTO_TLS:
result := r.rpc_wifi_start_ssl_client(uint32(sock),
host, uint32(port), 0)
if result == -1 {
return fmt.Errorf("Connect to %s:%d failed", host, port)
}
}
return nil
}
func (r *rtl8720dn) Listen(sockfd int, backlog int) error {
if debugging(debugNetdev) {
fmt.Printf("[Listen] sockfd: %d\r\n", sockfd)
}
r.mu.Lock()
defer r.mu.Unlock()
var sock = sock(sockfd)
var socket = r.sockets[sock]
switch socket.protocol {
case netdev.IPPROTO_TCP:
result := r.rpc_lwip_listen(int32(sock), int32(backlog))
if result == -1 {
return fmt.Errorf("Listen failed")
}
result = r.rpc_lwip_fcntl(int32(sock), netdev.F_SETFL, O_NONBLOCK)
if result == -1 {
return fmt.Errorf("Fcntl failed")
}
case netdev.IPPROTO_UDP:
result := r.rpc_lwip_listen(int32(sock), int32(backlog))
if result == -1 {
return fmt.Errorf("Listen failed")
}
default:
return netdev.ErrProtocolNotSupported
}
return nil
}
func (r *rtl8720dn) Accept(sockfd int) (int, netip.AddrPort, error) {
if debugging(debugNetdev) {
fmt.Printf("[Accept] sockfd: %d\r\n", sockfd)
}
r.mu.Lock()
defer r.mu.Unlock()
var newSock int32
var lsock = sock(sockfd)
var socket = r.sockets[lsock]
var name = ipToName(netip.AddrPort{})
switch socket.protocol {
case netdev.IPPROTO_TCP:
default:
return -1, netip.AddrPort{}, netdev.ErrProtocolNotSupported
}
for {
// Accept() will be sleeping most of the time, checking for a
// new clients every 1/10 sec.
r.mu.Unlock()
time.Sleep(100 * time.Millisecond)
r.mu.Lock()
// Check if a client connected. O_NONBLOCK is set on lsock.
namelen := uint32(len(name))
newSock = r.rpc_lwip_accept(int32(lsock), name, &namelen)
if newSock == -1 {
// No new client
time.Sleep(100 * time.Millisecond)
continue
}
// Get remote peer ip:port
namelen = uint32(len(name))
result := r.rpc_lwip_getpeername(int32(newSock), name, &namelen)
if result == -1 {
return -1, netip.AddrPort{}, fmt.Errorf("Getpeername failed")
}
raddr := nameToIp(name)
// If we've already seen this socket, we can re-use
// the socket and return it. But, only if the socket
// is closed. If it's not closed, we'll just come back
// later to reuse it.
clientSocket, ok := r.sockets[sock(newSock)]
if ok {
// Wait for client to Close
if clientSocket.inuse {
continue
}
// Reuse client socket
return int(newSock), raddr, nil
}
// Create new socket for client and return fd
r.sockets[sock(newSock)] = newSocket(socket.protocol)
return int(newSock), raddr, nil
}
}
func (r *rtl8720dn) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
var sock = sock(sockfd)
var socket = r.sockets[sock]
// Check if we've timed out
if !deadline.IsZero() {
if time.Now().After(deadline) {
return -1, netdev.ErrTimeout
}
}
switch socket.protocol {
case netdev.IPPROTO_TCP, netdev.IPPROTO_UDP:
result := r.rpc_lwip_send(int32(sock), buf, 0x00000008)
if result == -1 {
return -1, fmt.Errorf("Send error")
}
return int(result), nil
case netdev.IPPROTO_TLS:
result := r.rpc_wifi_send_ssl_data(uint32(sock), buf, uint16(len(buf)))
if result == -1 {
return -1, fmt.Errorf("TLS Send error")
}
return int(result), nil
}
return -1, netdev.ErrProtocolNotSupported
}
func (r *rtl8720dn) Send(sockfd int, buf []byte, flags int,
deadline time.Time) (int, error) {
if debugging(debugNetdev) {
fmt.Printf("[Send] sockfd: %d, len(buf): %d, flags: %d\r\n",
sockfd, len(buf), flags)
}
r.mu.Lock()
defer r.mu.Unlock()
// Break large bufs into chunks
chunkSize := 1436
for i := 0; i < len(buf); i += chunkSize {
end := i + chunkSize
if end > len(buf) {
end = len(buf)
}
_, err := r.sendChunk(sockfd, buf[i:end], deadline)
if err != nil {
return -1, err
}
}
return len(buf), nil
}
func (r *rtl8720dn) Recv(sockfd int, buf []byte, flags int,
deadline time.Time) (int, error) {
if debugging(debugNetdev) {
fmt.Printf("[Recv] sockfd: %d, len(buf): %d, flags: %d\r\n",
sockfd, len(buf), flags)
}
r.mu.Lock()
defer r.mu.Unlock()
var sock = sock(sockfd)
var socket = r.sockets[sock]
var length = len(buf)
var n int32
// Limit length read size to chunk large read requests
if length > 1436 {
length = 1436
}
for {
// Check if we've timed out
if !deadline.IsZero() {
if time.Now().After(deadline) {
return -1, netdev.ErrTimeout
}
}
switch socket.protocol {
case netdev.IPPROTO_TCP, netdev.IPPROTO_UDP:
n = r.rpc_lwip_recv(int32(sock), buf[:length],
uint32(length), 0x00000008, 0)
case netdev.IPPROTO_TLS:
n = r.rpc_wifi_get_ssl_receive(uint32(sock),
buf[:length], int32(length))
}
if n < 0 {
r.mu.Unlock()
time.Sleep(100 * time.Millisecond)
r.mu.Lock()
continue
} else if n == 0 {
if debugging(debugNetdev) {
fmt.Printf("[<--Recv] sockfd: %d, n: %d EOF\r\n",
sock, n)
}
return -1, io.EOF
}
if debugging(debugNetdev) {
fmt.Printf("[<--Recv] sockfd: %d, n: %d\r\n",
sock, n)
}
return int(n), nil
}
}
func (r *rtl8720dn) Close(sockfd int) error {
if debugging(debugNetdev) {
fmt.Printf("[Close] sockfd: %d\r\n", sockfd)
}
r.mu.Lock()
defer r.mu.Unlock()
var sock = sock(sockfd)
var socket = r.sockets[sock]
var result int32
if !socket.inuse {
return nil
}
switch socket.protocol {
case netdev.IPPROTO_TCP, netdev.IPPROTO_UDP:
result = r.rpc_lwip_close(int32(sock))
case netdev.IPPROTO_TLS:
r.rpc_wifi_stop_ssl_socket(uint32(sock))
r.rpc_wifi_ssl_client_destroy(uint32(sock))
}
if result == -1 {
return netdev.ErrClosingSocket
}
socket.inuse = false
return nil
}
func (r *rtl8720dn) SetSockOpt(sockfd int, level int, opt int, value interface{}) error {
if debugging(debugNetdev) {
fmt.Printf("[SetSockOpt] sockfd: %d\r\n", sockfd)
}
return netdev.ErrNotSupported
}
func (r *rtl8720dn) disconnect() error {
result := r.rpc_wifi_disconnect()
if result == -1 {
return fmt.Errorf("Error disconnecting Wifi")
}
return nil
}
func (r *rtl8720dn) getFwVersion() string {
return r.rpc_system_version()
}
func (r *rtl8720dn) getMACAddr() string {
var mac [18]uint8
r.rpc_wifi_get_mac_address(mac[:])
return string(mac[:])
}
func (r *rtl8720dn) getIP() (ip, subnet, gateway netip.Addr, err error) {
var ip_info [12]byte
result := r.rpc_tcpip_adapter_get_ip_info(0, ip_info[:])
if result == -1 {
err = fmt.Errorf("Get IP info failed")
return
}
ip, _ = netip.AddrFromSlice(ip_info[0:4])
subnet, _ = netip.AddrFromSlice(ip_info[4:8])
gateway, _ = netip.AddrFromSlice(ip_info[8:12])
return
}