w5500: initial version the driver (#788)

Thank you, @nrwiersma for working on this and @soypat for review. Now merging.
This commit is contained in:
Nicholas Wiersma
2025-11-12 10:00:21 +02:00
committed by GitHub
parent 09fd01340b
commit 4b831af52b
6 changed files with 923 additions and 0 deletions
+37
View File
@@ -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
}
}
+1
View File
@@ -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=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/w5500/main.go
# network examples (espat)
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
# network examples (wifinina)
+104
View File
@@ -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
View File
@@ -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
}
+88
View File
@@ -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
View File
@@ -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
}