Files
Nicholas Wiersma 4b831af52b w5500: initial version the driver (#788)
Thank you, @nrwiersma for working on this and @soypat for review. Now merging.
2025-11-12 09:00:21 +01:00

446 lines
11 KiB
Go

package w5500
import (
"errors"
"net"
"net/netip"
"os"
"runtime"
"time"
"tinygo.org/x/drivers/netdev"
)
type socket struct {
sockn uint8
protocol uint8
port uint16
inUse bool
closed bool
}
func (s *socket) setProtocol(proto byte) *socket {
s.protocol = proto
return s
}
func (s *socket) setPort(port uint16) *socket {
s.port = port
return s
}
func (s *socket) setInUse(inUse bool) *socket {
s.inUse = inUse
return s
}
func (s *socket) setClosed(closed bool) *socket {
s.closed = closed
return s
}
func (s *socket) reset() {
s.protocol = 0
s.port = 0
s.inUse = false
s.closed = false
}
// GetHostByName resolves the given host name to an IP address.
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
d.mu.Lock()
dns := d.dns
d.mu.Unlock()
if dns == nil {
return netip.Addr{}, netdev.ErrNotSupported
}
return dns(name)
}
func (d *Device) Socket(domain int, stype int, protocol int) (int, error) {
if domain != netdev.AF_INET {
return -1, netdev.ErrFamilyNotSupported
}
switch {
case stype == netdev.SOCK_STREAM && protocol == netdev.IPPROTO_TCP:
case stype == netdev.SOCK_DGRAM && protocol == netdev.IPPROTO_UDP:
default:
return -1, errors.New("unsupported combination of socket type and protocol")
}
var proto byte
switch protocol {
case netdev.IPPROTO_TCP:
proto = 1 // TCP
case netdev.IPPROTO_UDP:
proto = 2 // UDP
default:
return -1, netdev.ErrNotSupported
}
d.mu.Lock()
defer d.mu.Unlock()
sockfd, sock, err := d.nextSocket()
if err != nil {
return -1, err
}
d.openSocket(sock.sockn, proto)
sock.setProtocol(proto).setInUse(true)
return sockfd, nil
}
func (d *Device) openSocket(sockn uint8, proto byte) {
d.writeByte(sockMode, sockAddr(sockn), proto&0x0F)
}
func (d *Device) Bind(sockfd int, ip netip.AddrPort) error {
// The IP address is irrelevant. The configured ip will always be used.
port := ip.Port()
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return err
}
if err = d.bindSocket(sock.sockn, port); err != nil {
return errors.New("could not set socket port: " + err.Error())
}
sock.setPort(port)
return nil
}
func (d *Device) bindSocket(sockn uint8, port uint16) error {
d.writeUint16(sockSrcPort, sockAddr(sockn), port)
d.socketSendCmd(sockn, sockCmdOpen)
if d.sockStatus(sockn) == sockStatusClosed {
return errors.New("socket is closed after binding")
}
return nil
}
// SetSockOpt sets the socket option for the given socket file descriptor.
// It is not supported by the W5500, so it always returns an error.
func (d *Device) SetSockOpt(int, int, int, any) error {
return netdev.ErrNotSupported
}
// Connect establishes a connection to the specified host and port or ip and port.
//
// If the host is an empty string, it will use the provided ip address and port,
// otherwise it will resolve the host name to an IP address.
func (d *Device) Connect(sockfd int, host string, ip netip.AddrPort) error {
destIP := ip.Addr()
if host != "" {
var err error
destIP, err = d.GetHostByName(host)
if err != nil {
return errors.New("could not resolve host " + host + ":" + err.Error())
}
}
if !destIP.IsValid() || !destIP.Is4() {
return errors.New("invalid destination IP address: " + destIP.String())
}
port := ip.Port()
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return err
}
d.write(sockDestIP, sockAddr(sock.sockn), destIP.AsSlice())
d.writeUint16(sockDestPort, sockAddr(sock.sockn), port)
d.socketSendCmd(sock.sockn, sockCmdOpen)
return nil
}
// Listen sets the socket to listen for incoming connections on the specified socket file descriptor.
//
// The backlog parameter is ignored, as the W5500 does not support it.
func (d *Device) Listen(sockfd int, _ int) error {
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return err
}
if sock.protocol != 1 { // Only TCP sockets can listen
return errors.New("not a TCP socket")
}
if err = d.listen(sock.sockn); err != nil {
return errors.New("could not send listen command: " + err.Error())
}
return nil
}
func (d *Device) listen(sockn uint8) error {
state := d.sockStatus(sockn)
if state != sockStatusInit {
return errors.New("socket is not in the initial state")
}
d.socketSendCmd(sockn, sockCmdListen)
return nil
}
// Accept waits for an incoming connection on the specified socket file descriptor.
func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error) {
d.mu.Lock()
defer d.mu.Unlock()
lsock, err := d.socket(sockfd)
if err != nil {
return -1, netip.AddrPort{}, errors.New("could not get socket: " + err.Error())
}
if err = d.waitForEstablished(lsock.sockn); err != nil {
return -1, netip.AddrPort{}, err
}
// Acquire a new socket for the listening connection.
csockfd, csock, err := d.nextSocket()
if err != nil {
return -1, netip.AddrPort{}, err
}
// Swap the socket numbers of the client and listening sockets.
lsock.sockn, csock.sockn = csock.sockn, lsock.sockn
// Rebind the listening socket to the local address and port and start listening.
d.openSocket(lsock.sockn, lsock.protocol)
if err = d.bindSocket(lsock.sockn, lsock.port); err != nil {
return -1, netip.AddrPort{}, errors.New("could not bind listening socket: " + err.Error())
}
if err = d.listen(lsock.sockn); err != nil {
return -1, netip.AddrPort{}, errors.New("could not set listening socket: " + err.Error())
}
csock.setInUse(true)
remoteIP := d.remoteIP(csock.sockn)
return csockfd, remoteIP, nil
}
func (d *Device) waitForEstablished(sockn uint8) error {
for {
status := d.sockStatus(sockn)
switch status {
case sockStatusEstablished:
return nil
case sockStatusClosed:
return net.ErrClosed
case sockStatusCloseWait:
// The server closed the connection, so we need to reset the socket
// and set it to listen again.
if err := d.listen(sockn); err != nil {
return errors.New("could not set socket to listen: " + err.Error())
}
}
d.irqPoll(sockn, sockIntConnect|sockIntDisconnect, time.Time{})
}
}
func (d *Device) remoteIP(sockn uint8) netip.AddrPort {
var rip [4]byte
d.read(sockDestIP, sockAddr(sockn), rip[:])
var rport [2]byte
d.read(sockDestPort, sockAddr(sockn), rport[:])
return netip.AddrPortFrom(netip.AddrFrom4(rip), uint16(rport[0])<<8|uint16(rport[1]))
}
// Send sends data to the socket with the given file descriptor.
// It blocks until all data is sent or the deadline is reached.
func (d *Device) Send(sockfd int, buf []byte, _ int, deadline time.Time) (int, error) {
bufLen := len(buf)
if bufLen <= d.maxSockSize {
// Fast path for small buffers.
return d.sendChunk(sockfd, buf, deadline)
}
var n int
for i := 0; i < bufLen; i += d.maxSockSize {
end := i + d.maxSockSize
if end > bufLen {
end = bufLen
}
sent, err := d.sendChunk(sockfd, buf[i:end], deadline)
if err != nil {
return n, errors.New("could not send chunk: " + err.Error())
}
n += sent
}
return n, nil
}
func (d *Device) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return 0, errors.New("could not get socket: " + err.Error())
}
if sock.closed {
return 0, os.ErrClosed
}
bufLen := uint16(len(buf))
if err = d.waitForFreeBuffer(sock.sockn, bufLen, deadline); err != nil {
return 0, err
}
sendPtr := d.readUint16(sockTXWritePtr, sockAddr(sock.sockn))
d.write(sendPtr, sock.sockn<<2|0b10, buf)
d.writeUint16(sockTXWritePtr, sockAddr(sock.sockn), sendPtr+bufLen)
d.writeByte(sockCmd, sockAddr(sock.sockn), sockCmdSend)
irq := d.irqPoll(sock.sockn, sockIntSendOK|sockIntDisconnect|sockIntTimeout, deadline)
switch {
case irq == sockIntUnknown:
return 0, os.ErrDeadlineExceeded
case irq&sockIntDisconnect != 0:
sock.setClosed(true)
return 0, net.ErrClosed
case irq&sockIntTimeout != 0:
return 0, netdev.ErrTimeout
default:
return int(bufLen), nil
}
}
func (d *Device) waitForFreeBuffer(sockn uint8, len uint16, deadline time.Time) error {
for {
freeSize := d.readUint16(sockTXFreeSize, sockAddr(sockn))
if freeSize >= len {
return nil
}
if !deadline.IsZero() && time.Now().After(deadline) {
return netdev.ErrTimeout
}
status := d.sockStatus(sockn)
switch status {
case sockStatusEstablished, sockStatusCloseWait:
default:
return errors.New("socket is not in a valid state for sending data")
}
d.mu.Unlock()
time.Sleep(time.Millisecond)
d.mu.Lock()
}
}
// Recv reads data from the socket with the given file descriptor into the provided buffer.
// It blocks until data is available or the deadline is reached.
func (d *Device) Recv(sockfd int, buf []byte, _ int, deadline time.Time) (int, error) {
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return 0, errors.New("could not get socket: " + err.Error())
}
if sock.closed {
return 0, os.ErrClosed
}
size, err := d.waitForData(sock, deadline)
if err != nil {
return 0, err
}
recvPtr := d.readUint16(sockRXReadPtr, sockAddr(sock.sockn))
buf = buf[:min(size, len(buf))]
d.read(recvPtr, sock.sockn<<2|0b00011, buf)
d.writeUint16(sockRXReadPtr, sockAddr(sock.sockn), recvPtr+uint16(len(buf)))
d.socketSendCmd(sock.sockn, sockCmdRecv)
return len(buf), nil
}
func (d *Device) waitForData(sock *socket, deadline time.Time) (int, error) {
for {
recvdSize := d.readUint16(sockRXReceivedSize, sockAddr(sock.sockn))
if recvdSize > 0 {
return int(recvdSize), nil
}
irq := d.irqPoll(sock.sockn, sockIntReceive|sockIntDisconnect, deadline)
switch {
case irq == sockIntUnknown:
return 0, os.ErrDeadlineExceeded
case irq&sockIntDisconnect != 0:
sock.setClosed(true)
return 0, net.ErrClosed
}
}
}
// Close closes the socket with the given file descriptor.
func (d *Device) Close(sockfd int) error {
d.mu.Lock()
defer d.mu.Unlock()
sock, err := d.socket(sockfd)
if err != nil {
return err
}
d.socketSendCmd(sock.sockn, sockCmdClose)
sock.reset()
return nil
}
func (d *Device) nextSocket() (int, *socket, error) {
for i, sock := range d.sockets {
if sock.inUse {
continue
}
return i, sock, nil
}
return -1, nil, netdev.ErrNoMoreSockets
}
func (d *Device) socket(sockfd int) (*socket, error) {
if sockfd < 0 || sockfd >= len(d.sockets) {
return nil, netdev.ErrInvalidSocketFd
}
return d.sockets[sockfd], nil
}
func (d *Device) socketSendCmd(sockn uint8, cmd byte) {
d.writeByte(sockCmd, sockAddr(sockn), cmd)
for d.readByte(sockCmd, sockAddr(sockn)) != 0 {
runtime.Gosched()
}
}
func (d *Device) sockStatus(sockn uint8) int {
return int(d.readByte(sockStatus, sockAddr(sockn)))
}
func sockAddr(sockn uint8) uint8 {
return sockn<<2 | 0b0001
}