mirror of
https://github.com/soypat/lneto.git
synced 2026-08-03 06:27:52 +00:00
813b7b5e57
* good code today * add netdev.Runner * round off APIs more * better interface method documentation * improve DHCP netstack API * add pico w netdev example * remove temp file * keep thinking about this. this is hard :/ * fix ci * small fixer * working dhcp * fix rebase API mismatches * begin adding espradio example * keep working on espradio, icmp not working * fix icmp by fixing dhcp
142 lines
4.0 KiB
Go
142 lines
4.0 KiB
Go
package netdev
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync/atomic"
|
|
|
|
"github.com/soypat/lneto"
|
|
)
|
|
|
|
// Runner orchestrates an Interface and a Stack asynchronously.
|
|
type Runner[C any] struct {
|
|
running atomic.Uint32
|
|
// buflen stores the length of data inside buf. It is used as a buffer acquisition synchronizing primitive.
|
|
buflen atomic.Uint32
|
|
// pktlost is incremented each time an incoming packet is lost due to insufficient buffer size.
|
|
pktlost atomic.Uint64
|
|
tx, rx atomic.Uint64
|
|
// buf stores actual data.
|
|
buf []byte
|
|
// bufsaux is used as ana argument to stack processing so that no allocations are performed
|
|
bufsaux [1][]byte
|
|
sizesaux [1]int
|
|
handlerTriggered bool
|
|
deviceIsPollOnly bool
|
|
}
|
|
|
|
func (r *Runner[C]) Run(ctx context.Context, iface Interface[C], stack Stack, backoff lneto.BackoffStrategy) error {
|
|
if stack == nil || backoff == nil {
|
|
return errors.New("nil arguments to Run")
|
|
}
|
|
if !r.acquire() {
|
|
return errors.New("runner currently running.")
|
|
}
|
|
defer func() {
|
|
iface.dev.SetEthRecvHandler(nil)
|
|
r.release()
|
|
}()
|
|
r.rx.Store(0)
|
|
r.tx.Store(0)
|
|
r.buflen.Store(0)
|
|
r.pktlost.Store(0)
|
|
r.handlerTriggered = false
|
|
r.deviceIsPollOnly = false
|
|
bufsize := iface.bufsize()
|
|
if cap(r.buf) < bufsize {
|
|
r.buf = make([]byte, bufsize)
|
|
}
|
|
r.buf = r.buf[:bufsize]
|
|
iface.dev.SetEthRecvHandler(r.recvEthHandler)
|
|
|
|
// backoffs stores number of consecutive times no data was sent/received.
|
|
var backoffs uint
|
|
for ctx.Err() == nil {
|
|
n1, _ := r.processRx(stack, 0)
|
|
eoff, efrm, err := iface.dev.EthPoll(r.buf)
|
|
n2, _ := r.processRx(stack, 0)
|
|
if efrm > 0 && n2 == 0 {
|
|
r.deviceIsPollOnly = true
|
|
r.buflen.Store(uint32(eoff + efrm))
|
|
r.processRx(stack, eoff)
|
|
} else if efrm > 0 && n2 > 0 {
|
|
return errors.New("device both returns nonzero poll read and calls, choose one")
|
|
} else if err != nil {
|
|
println("err EthPoll:", err.Error())
|
|
}
|
|
|
|
// Now do Tx, but first acquire buffer.
|
|
if !r.buflen.CompareAndSwap(0, 1) {
|
|
continue // Oh no, async data received, go back to Rx processing.
|
|
}
|
|
r.bufsaux = [1][]byte{r.buf}
|
|
|
|
err = stack.EgressPackets(r.bufsaux[:], r.sizesaux[:], iface.frameOff)
|
|
n := r.sizesaux[0]
|
|
if err != nil {
|
|
println("err EgressPackets:", err.Error())
|
|
} else if n > 0 {
|
|
if n+iface.frameOff > len(r.buf) {
|
|
return errors.New("EgressPackets returned invalid written data given frameOffset and argument buffer size")
|
|
}
|
|
err = iface.dev.SendOffsetEthFrame(r.bufsaux[0][:n+iface.frameOff])
|
|
r.tx.Add(uint64(n + iface.frameOff))
|
|
if err != nil {
|
|
println("err SendOffsetEthFrame:", err.Error())
|
|
}
|
|
}
|
|
r.buflen.Store(0) // Release buffer.
|
|
if n1 > 0 || n2 > 0 || efrm > 0 || n > 0 {
|
|
backoffs = 0
|
|
} else {
|
|
backoff.Do(backoffs)
|
|
backoffs++
|
|
}
|
|
}
|
|
return ctx.Err()
|
|
}
|
|
|
|
// PrintDebug
|
|
//
|
|
// Deprecated: Might be given other shape in future, but this is not how we do debugging. use freely meanwhile.
|
|
func (r *Runner[C]) PrintDebug() {
|
|
print("RUNNER: tx|rx:", r.tx.Load(), "|", r.rx.Load(),
|
|
" devpollonly:", r.deviceIsPollOnly, " pktlost:", r.pktlost.Load(),
|
|
" handles:", r.handlerTriggered, " bufsize:", len(r.buf),
|
|
"\n")
|
|
}
|
|
|
|
func (r *Runner[C]) acquire() bool {
|
|
return r.running.CompareAndSwap(0, 1)
|
|
}
|
|
|
|
func (r *Runner[C]) release() {
|
|
if r.running.Load()&1 == 0 {
|
|
panic("release of unacquired resource")
|
|
}
|
|
r.running.Store(0)
|
|
}
|
|
|
|
// recvEthHandler is called asynchronously. Should be as fast as possible. Do not block inside.
|
|
func (r *Runner[C]) recvEthHandler(incomingEthernet []byte) {
|
|
if !r.buflen.CompareAndSwap(0, uint32(len(incomingEthernet))) {
|
|
// Failed to acquire buffer, packet dropped.
|
|
r.pktlost.Add(1)
|
|
return
|
|
}
|
|
copy(r.buf, incomingEthernet)
|
|
}
|
|
|
|
// processRx is called after a packet is received asynchronously and compied to buffer via recvEthHandler
|
|
func (r *Runner[C]) processRx(stack Stack, ethFrameOff int) (int, error) {
|
|
r.handlerTriggered = true
|
|
n := r.buflen.Load()
|
|
if n == 0 {
|
|
return 0, nil
|
|
}
|
|
r.rx.Add(uint64(n))
|
|
defer r.buflen.Store(0)
|
|
r.bufsaux = [1][]byte{r.buf[:n]}
|
|
return int(n), stack.IngressPackets(r.bufsaux[:], ethFrameOff)
|
|
}
|