mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-18 21:53:56 +00:00
w5500: initial version the driver (#788)
Thank you, @nrwiersma for working on this and @soypat for review. Now merging.
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/netdev"
|
||||||
|
"tinygo.org/x/drivers/w5500"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
Frequency: 33 * machine.MHz,
|
||||||
|
})
|
||||||
|
machine.GPIO17.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
eth := w5500.New(machine.SPI0, machine.GPIO17)
|
||||||
|
eth.Configure(w5500.Config{
|
||||||
|
MAC: net.HardwareAddr{0xee, 0xbe, 0xe9, 0xa9, 0xb6, 0x4f},
|
||||||
|
IP: netip.AddrFrom4([4]byte{192, 168, 1, 2}),
|
||||||
|
SubnetMask: netip.AddrFrom4([4]byte{255, 255, 255, 0}),
|
||||||
|
Gateway: netip.AddrFrom4([4]byte{192, 168, 1, 1}),
|
||||||
|
})
|
||||||
|
netdev.UseNetdev(eth)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if eth.LinkStatus() != w5500.LinkStatusUp {
|
||||||
|
println("Waiting for link to be up")
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -146,6 +146,7 @@ tinygo build -size short -o ./build/test.uf2 -target=nicenano ./examples/sharpme
|
|||||||
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/max6675/main.go
|
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/max6675/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
|
tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/si5351/main.go
|
tinygo build -size short -o ./build/test.hex -target=pico ./examples/si5351/main.go
|
||||||
|
tinygo build -size short -o ./build/test.hex -target=pico ./examples/w5500/main.go
|
||||||
# network examples (espat)
|
# network examples (espat)
|
||||||
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
||||||
# network examples (wifinina)
|
# network examples (wifinina)
|
||||||
|
|||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
package w5500
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func (d *Device) irqPoll(sockn uint8, state uint8, deadline time.Time) uint8 {
|
||||||
|
waitTime := 500 * time.Microsecond
|
||||||
|
for {
|
||||||
|
if !deadline.IsZero() && time.Now().After(deadline) {
|
||||||
|
// If a deadline is set and it has passed, return 0.
|
||||||
|
return sockIntUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
irq := d.readByte(sockInt, sockAddr(sockn)) & 0b00011111
|
||||||
|
if got := irq & state; got != 0 {
|
||||||
|
// Acknowledge the interrupt.
|
||||||
|
d.writeByte(sockInt, sockAddr(sockn), got)
|
||||||
|
|
||||||
|
return got
|
||||||
|
}
|
||||||
|
|
||||||
|
d.mu.Unlock()
|
||||||
|
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
|
||||||
|
// Exponential backoff for polling.
|
||||||
|
waitTime *= 2
|
||||||
|
if waitTime > 10*time.Millisecond {
|
||||||
|
waitTime = 10 * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
d.mu.Lock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) read(addr uint16, bsb uint8, p []byte) {
|
||||||
|
d.cs(false)
|
||||||
|
if len(p) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.sendReadHeader(addr, bsb)
|
||||||
|
_ = d.bus.Tx(nil, p)
|
||||||
|
d.cs(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) readUint16(addr uint16, bsb uint8) uint16 {
|
||||||
|
d.cs(false)
|
||||||
|
d.sendReadHeader(addr, bsb)
|
||||||
|
buf := d.cmdBuf
|
||||||
|
_ = d.bus.Tx(nil, buf[:2])
|
||||||
|
d.cs(true)
|
||||||
|
return uint16(buf[1]) | uint16(buf[0])<<8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) readByte(addr uint16, bsb uint8) byte {
|
||||||
|
d.cs(false)
|
||||||
|
d.sendReadHeader(addr, bsb)
|
||||||
|
r, _ := d.bus.Transfer(byte(0))
|
||||||
|
d.cs(true)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) write(addr uint16, bsb uint8, p []byte) {
|
||||||
|
d.cs(false)
|
||||||
|
if len(p) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.sendWriteHeader(addr, bsb)
|
||||||
|
_ = d.bus.Tx(p, nil)
|
||||||
|
d.cs(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) writeUint16(addr uint16, bsb uint8, v uint16) {
|
||||||
|
d.cs(false)
|
||||||
|
d.sendWriteHeader(addr, bsb)
|
||||||
|
buf := d.cmdBuf
|
||||||
|
buf[0] = byte(v >> 8)
|
||||||
|
buf[1] = byte(v & 0xff)
|
||||||
|
_ = d.bus.Tx(buf[:2], nil)
|
||||||
|
d.cs(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) writeByte(addr uint16, bsb uint8, b byte) {
|
||||||
|
d.cs(false)
|
||||||
|
d.sendWriteHeader(addr, bsb)
|
||||||
|
_, _ = d.bus.Transfer(b)
|
||||||
|
d.cs(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) sendReadHeader(addr uint16, bsb uint8) {
|
||||||
|
buf := d.cmdBuf
|
||||||
|
buf[0] = byte(addr >> 8)
|
||||||
|
buf[1] = byte(addr & 0xff)
|
||||||
|
buf[2] = bsb << 3
|
||||||
|
_ = d.bus.Tx(buf[:], nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) sendWriteHeader(addr uint16, bsb uint8) {
|
||||||
|
buf := d.cmdBuf
|
||||||
|
buf[0] = byte(addr >> 8)
|
||||||
|
buf[1] = byte(addr & 0xff)
|
||||||
|
buf[2] = bsb<<3 | 0b100
|
||||||
|
_ = d.bus.Tx(buf[:], nil)
|
||||||
|
}
|
||||||
+445
@@ -0,0 +1,445 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package w5500
|
||||||
|
|
||||||
|
// Common Registers.
|
||||||
|
const (
|
||||||
|
regMode = 0x0000
|
||||||
|
regGatewayAddr = 0x0001
|
||||||
|
regSubnetMask = 0x0005
|
||||||
|
regMAC = 0x0009
|
||||||
|
regIPAddr = 0x000F
|
||||||
|
regIntLevel = 0x0013
|
||||||
|
regInt = 0x0015
|
||||||
|
regIntMask = 0x0016
|
||||||
|
regSockInt = 0x0017
|
||||||
|
regSockIntMask = 0x0018
|
||||||
|
regRetryTime = 0x0019
|
||||||
|
regRetryN = 0x001B
|
||||||
|
// ... PPP registers, not needed
|
||||||
|
regPHYCfg = 0x002E
|
||||||
|
regChipVer = 0x0039
|
||||||
|
)
|
||||||
|
|
||||||
|
// Socket Registers.
|
||||||
|
const (
|
||||||
|
sockMode = 0x0000
|
||||||
|
sockCmd = 0x0001
|
||||||
|
sockInt = 0x0002
|
||||||
|
sockStatus = 0x0003
|
||||||
|
sockSrcPort = 0x0004
|
||||||
|
sockDestMAC = 0x0006
|
||||||
|
sockDestIP = 0x000C
|
||||||
|
sockDestPort = 0x0010
|
||||||
|
sockMaxSegSize = 0x0012
|
||||||
|
sockIPTOS = 0x0015
|
||||||
|
sockIPTTL = 0x0016
|
||||||
|
sockRXBUFSize = 0x001E
|
||||||
|
sockTXBUFSize = 0x001F
|
||||||
|
sockTXFreeSize = 0x0020
|
||||||
|
sockTXReadPtr = 0x0022
|
||||||
|
sockTXWritePtr = 0x0024
|
||||||
|
sockRXReceivedSize = 0x0026
|
||||||
|
sockRXReadPtr = 0x0028
|
||||||
|
sockRXWritePtr = 0x002A
|
||||||
|
sockIntMask = 0x002C
|
||||||
|
sockKeepInt = 0x002F
|
||||||
|
)
|
||||||
|
|
||||||
|
// Socket Commands.
|
||||||
|
const (
|
||||||
|
sockCmdOpen = 0x01
|
||||||
|
sockCmdClose = 0x10
|
||||||
|
sockCmdListen = 0x02
|
||||||
|
sockCmdConnect = 0x04
|
||||||
|
sockCmdDisconnect = 0x08
|
||||||
|
sockCmdSend = 0x20
|
||||||
|
sockCmdSendMacRaw = 0x21
|
||||||
|
sockCmdSendKeep = 0x22
|
||||||
|
sockCmdRecv = 0x40
|
||||||
|
)
|
||||||
|
|
||||||
|
// Socket Statuses.
|
||||||
|
const (
|
||||||
|
sockStatusClosed = 0x00
|
||||||
|
sockStatusInit = 0x13
|
||||||
|
sockStatusListen = 0x14
|
||||||
|
sockStatusEstablished = 0x17
|
||||||
|
sockStatusCloseWait = 0x1C
|
||||||
|
sockStatusUdp = 0x22
|
||||||
|
sockStatusMacRaw = 0x42
|
||||||
|
// Temporary TCP states
|
||||||
|
sockStatusSynSent = 0x15
|
||||||
|
sockStatusSynRecv = 0x16
|
||||||
|
sockStatusFinWait = 0x18
|
||||||
|
sockStatusClosing = 0x1A
|
||||||
|
sockStatusTimeWait = 0x1B
|
||||||
|
sockStatusLastAck = 0x1D
|
||||||
|
sockStatusUnknown = 0xFF
|
||||||
|
)
|
||||||
|
|
||||||
|
// Socket Interrupts.
|
||||||
|
const (
|
||||||
|
sockIntConnect uint8 = 1 << iota
|
||||||
|
sockIntDisconnect
|
||||||
|
sockIntReceive
|
||||||
|
sockIntTimeout
|
||||||
|
sockIntSendOK
|
||||||
|
|
||||||
|
sockIntUnknown uint8 = 0
|
||||||
|
)
|
||||||
+248
@@ -0,0 +1,248 @@
|
|||||||
|
// Package w5500 implements a driver for the W5500 Ethernet controller.
|
||||||
|
//
|
||||||
|
// The driver supports basic network functionality including TCP and UDP sockets.
|
||||||
|
// It currently does not use the IRQ or RST pins.
|
||||||
|
//
|
||||||
|
// Datasheet: https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf
|
||||||
|
// Product Page: https://wiznet.io/products/ethernet-chips/w5500
|
||||||
|
package w5500
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
"tinygo.org/x/drivers/internal/pin"
|
||||||
|
"tinygo.org/x/drivers/netdev"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ netdev.Netdever = &Device{}
|
||||||
|
|
||||||
|
// Resolver is a function that resolves a hostname to an IP address.
|
||||||
|
type Resolver func(host string) (netip.Addr, error)
|
||||||
|
|
||||||
|
// Device is a driver for the W5500 Ethernet controller.
|
||||||
|
type Device struct {
|
||||||
|
maxSockets int
|
||||||
|
maxSockSize int
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
bus drivers.SPI
|
||||||
|
cs pin.OutputFunc
|
||||||
|
dns Resolver
|
||||||
|
|
||||||
|
sockets []*socket
|
||||||
|
laddr netip.Addr
|
||||||
|
|
||||||
|
cmdBuf [3]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new w5500 driver.
|
||||||
|
func New(bus drivers.SPI, csPin pin.Output) *Device {
|
||||||
|
return &Device{
|
||||||
|
bus: bus,
|
||||||
|
cs: csPin.Set,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the configuration for the device.
|
||||||
|
//
|
||||||
|
// The SPI bus must be fully configured.
|
||||||
|
type Config struct {
|
||||||
|
DNS Resolver
|
||||||
|
|
||||||
|
MAC net.HardwareAddr
|
||||||
|
IP netip.Addr
|
||||||
|
SubnetMask netip.Addr
|
||||||
|
Gateway netip.Addr
|
||||||
|
|
||||||
|
// Optional, default is 8.
|
||||||
|
MaxSockets int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure sets up the device.
|
||||||
|
//
|
||||||
|
// MAC address must be provided. The other fields are optional.
|
||||||
|
func (d *Device) Configure(cfg Config) error {
|
||||||
|
d.cs(true)
|
||||||
|
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
d.dns = cfg.DNS
|
||||||
|
|
||||||
|
d.reset()
|
||||||
|
|
||||||
|
if err := d.setupSockets(cfg.MaxSockets); err != nil {
|
||||||
|
return errors.New("could not setup sockets: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the MAC address and IP configuration.
|
||||||
|
d.write(regMAC, 0, cfg.MAC)
|
||||||
|
d.write(regIPAddr, 0, cfg.IP.AsSlice())
|
||||||
|
d.write(regSubnetMask, 0, cfg.SubnetMask.AsSlice())
|
||||||
|
d.write(regGatewayAddr, 0, cfg.Gateway.AsSlice())
|
||||||
|
d.laddr = cfg.IP
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) setupSockets(maxSockets int) error {
|
||||||
|
if maxSockets == 0 {
|
||||||
|
maxSockets = 8 // Default to 8 sockets if not specified.
|
||||||
|
}
|
||||||
|
switch maxSockets {
|
||||||
|
case 1, 2, 4, 8:
|
||||||
|
// Valid socket counts.
|
||||||
|
default:
|
||||||
|
return errors.New("invalid number of sockets, must be one of 1, 2, 4, or 8")
|
||||||
|
}
|
||||||
|
|
||||||
|
socks := make([]*socket, maxSockets)
|
||||||
|
for i := range socks {
|
||||||
|
socks[i] = &socket{
|
||||||
|
sockn: uint8(i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.maxSockets = maxSockets
|
||||||
|
d.maxSockSize = 16 * 1024 / maxSockets
|
||||||
|
d.sockets = socks
|
||||||
|
|
||||||
|
// Set the RX and TX buffer sizes for each socket.
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
size := byte(d.maxSockSize >> 10)
|
||||||
|
if i >= maxSockets {
|
||||||
|
size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
d.writeByte(sockRXBUFSize, sockAddr(uint8(i)), size)
|
||||||
|
d.writeByte(sockTXBUFSize, sockAddr(uint8(i)), size)
|
||||||
|
}
|
||||||
|
|
||||||
|
mask := byte(0b11111111)
|
||||||
|
switch maxSockets {
|
||||||
|
case 1:
|
||||||
|
mask = 0b00000001
|
||||||
|
case 2:
|
||||||
|
mask = 0b00000011
|
||||||
|
case 4:
|
||||||
|
mask = 0b00001111
|
||||||
|
}
|
||||||
|
d.writeByte(regSockIntMask, 0, mask)
|
||||||
|
d.writeByte(regIntMask, 0, 0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset performs a soft reset.
|
||||||
|
func (d *Device) Reset() {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
d.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) reset() {
|
||||||
|
// RST is bit 7 of regMode.
|
||||||
|
d.writeByte(regMode, 0, 0x80)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHardwareAddr returns the hardware address of the device.
|
||||||
|
func (d *Device) GetHardwareAddr() (net.HardwareAddr, error) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
mac := make([]byte, 6)
|
||||||
|
d.read(regMAC, 0, mac)
|
||||||
|
return mac, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the IP address of the device.
|
||||||
|
func (d *Device) Addr() (netip.Addr, error) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
var ip [4]byte
|
||||||
|
d.read(regIPAddr, 0, ip[:])
|
||||||
|
return netip.AddrFrom4(ip), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAddr sets the IP address of the device.
|
||||||
|
//
|
||||||
|
// The IP address must be a valid IPv4 address.
|
||||||
|
func (d *Device) SetAddr(ip netip.Addr) error {
|
||||||
|
if err := d.setAddress(regIPAddr, ip); err != nil {
|
||||||
|
return errors.New("could not set IP address: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
d.laddr = ip
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSubnetMask sets the subnet mask of the device.
|
||||||
|
//
|
||||||
|
// The subnet mask must be a valid IPv4 address.
|
||||||
|
// It is not checked if the subnet mask is valid for the device's IP address.
|
||||||
|
func (d *Device) SetSubnetMask(mask netip.Addr) error {
|
||||||
|
return d.setAddress(regSubnetMask, mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGateway sets the gateway address of the device.
|
||||||
|
//
|
||||||
|
// The gateway must be a valid IPv4 address.
|
||||||
|
// It is not checked if the gateway is in the same subnet as the device.
|
||||||
|
func (d *Device) SetGateway(gateway netip.Addr) error {
|
||||||
|
return d.setAddress(regGatewayAddr, gateway)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) setAddress(addr uint16, ip netip.Addr) error {
|
||||||
|
if !ip.IsValid() || !ip.Is4() {
|
||||||
|
return errors.New("invalid IP address: " + ip.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
d.write(addr, 0, ip.AsSlice())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinkStatus is the link status of the device.
|
||||||
|
type LinkStatus = uint8
|
||||||
|
|
||||||
|
// LinkStatus values.
|
||||||
|
const (
|
||||||
|
LinkStatusDown LinkStatus = iota
|
||||||
|
LinkStatusUp
|
||||||
|
)
|
||||||
|
|
||||||
|
// LinkStatus returns the current link status of the device.
|
||||||
|
func (d *Device) LinkStatus() LinkStatus {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
return d.readByte(regPHYCfg, 0) & 0b00000001
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinkInfo returns the current link information of the device.
|
||||||
|
func (d *Device) LinkInfo() string {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
linkInfo := d.readByte(regPHYCfg, 0) & 0b00000110
|
||||||
|
|
||||||
|
speed := "10Mbps"
|
||||||
|
if linkInfo&0b00000010 != 0 {
|
||||||
|
speed = "100Mbps"
|
||||||
|
}
|
||||||
|
duplex := "Half Duplex"
|
||||||
|
if linkInfo&0b00000100 != 0 {
|
||||||
|
duplex = "Full Duplex"
|
||||||
|
}
|
||||||
|
return speed + " " + duplex
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user