blob: dd6bb5cc7c2df541a0a80570388aab913ebd74de [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// +build !build_with_native_toolchain
package eth
import (
"context"
"fmt"
"math"
"reflect"
"sync"
"syscall/zx"
"unsafe"
"gen/netstack/link/eth"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link/fifo"
syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go"
"fidl/fuchsia/hardware/ethernet"
"fidl/fuchsia/hardware/network"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// #include <zircon/device/ethernet.h>
// #include <zircon/types.h>
import "C"
const tag = "eth"
const bufferSize = 2048
// Buffer is a segment of memory backed by a mapped VMO.
//
// A Buffer must not outlive its VMO's mapping.
// A Buffer's head must not change (by slicing or appending).
type Buffer []byte
type IOBuffer struct {
fifo.MappedVMO
}
func (iob *IOBuffer) buffer(i int32) Buffer {
return iob.GetData(uint64(i*bufferSize), bufferSize)
}
func (iob *IOBuffer) index(b Buffer) int {
return int((*(*reflect.SliceHeader)(unsafe.Pointer(&b))).Data-uintptr(iob.GetPointer(0))) / bufferSize
}
func (iob *IOBuffer) entry(b Buffer) eth.FifoEntry {
i := iob.index(b)
return eth.NewFifoEntry(uint32(i*bufferSize), uint16(len(b)), int32(i))
}
func (iob *IOBuffer) BufferFromEntry(e eth.FifoEntry) Buffer {
return iob.buffer(e.Index())[:e.Length()]
}
var _ link.Controller = (*Client)(nil)
var _ link.Observer = (*Client)(nil)
var _ stack.LinkEndpoint = (*Client)(nil)
var _ stack.GSOEndpoint = (*Client)(nil)
// Client is an ethernet client.
//
// It connects to a zircon ethernet driver using a FIFO-based protocol.
// The protocol is described in system/fidl/fuchsia-hardware-ethernet/ethernet.fidl.
type Client struct {
dispatcher stack.NetworkDispatcher
wg sync.WaitGroup
Info ethernet.Info
device ethernet.DeviceWithCtx
iob IOBuffer
topopath, filepath string
mu struct {
sync.Mutex
closed bool
onLinkClosed func()
onLinkOnlineChanged func(bool)
}
handler eth.Handler
}
// NewClient creates a new ethernet Client.
func NewClient(clientName string, topopath, filepath string, device ethernet.DeviceWithCtx) (*Client, error) {
if status, err := device.SetClientName(context.Background(), clientName); err != nil {
return nil, err
} else if err := checkStatus(status, "SetClientName"); err != nil {
return nil, err
}
// TODO(fxbug.dev/20540): once we support IGMP, don't automatically set multicast promisc true
if status, err := device.ConfigMulticastSetPromiscuousMode(context.Background(), true); err != nil {
return nil, err
} else if err := checkStatus(status, "ConfigMulticastSetPromiscuousMode"); err != nil {
// Some drivers - most notably virtio - don't support this setting.
if err.(*zx.Error).Status != zx.ErrNotSupported {
return nil, err
}
_ = syslog.WarnTf(tag, "%s", err)
}
info, err := device.GetInfo(context.Background())
if err != nil {
return nil, err
}
status, fifos, err := device.GetFifos(context.Background())
if err != nil {
return nil, err
} else if err := checkStatus(status, "GetFifos"); err != nil {
return nil, err
}
c := &Client{
Info: info,
device: device,
topopath: topopath,
filepath: filepath,
handler: eth.Handler{
TxDepth: fifos.TxDepth,
RxDepth: fifos.RxDepth,
RxFifo: fifos.Rx,
TxFifo: fifos.Tx,
},
}
rxStorage := int(c.handler.InitRx(uint16(fifos.RxDepth * 2)))
txStorage := int(c.handler.InitTx(uint16(fifos.TxDepth * 2)))
{
mappedVmo, vmo, err := fifo.NewMappedVMO(bufferSize*uint64(rxStorage+txStorage), fmt.Sprintf("ethernet.Device.IoBuffer: %s", topopath))
if err != nil {
_ = c.Close()
return nil, fmt.Errorf("eth: NewMappedVMO: %w", err)
}
c.iob = IOBuffer{MappedVMO: mappedVmo}
if status, err := device.SetIoBuffer(context.Background(), vmo); err != nil {
_ = c.Close()
return nil, fmt.Errorf("eth: cannot set IO VMO: %w", err)
} else if err := checkStatus(status, "SetIoBuffer"); err != nil {
_ = c.Close()
return nil, fmt.Errorf("eth: cannot set IO VMO: %w", err)
}
}
var bufferIndex int32
for i := 0; i < rxStorage; i++ {
b := c.iob.buffer(bufferIndex)
bufferIndex++
c.handler.PushInitialRx(c.iob.entry(b))
}
for i := 0; i < txStorage; i++ {
b := c.iob.buffer(bufferIndex)
bufferIndex++
c.handler.PushInitialTx(c.iob.entry(b))
}
c.handler.Stats.Rx.FifoStats = fifo.MakeFifoStats(fifos.RxDepth)
c.handler.Stats.Tx.FifoStats = fifo.MakeFifoStats(fifos.TxDepth)
return c, nil
}
func (c *Client) MTU() uint32 { return c.Info.Mtu }
func (c *Client) Capabilities() stack.LinkEndpointCapabilities {
// TODO(tamird/brunodalbo): expose hardware offloading capabilities.
return stack.CapabilitySoftwareGSO
}
func (c *Client) MaxHeaderLength() uint16 {
return 0
}
func (c *Client) LinkAddress() tcpip.LinkAddress {
return tcpip.LinkAddress(c.Info.Mac.Octets[:])
}
func (c *Client) write(pkts stack.PacketBufferList) (int, tcpip.Error) {
return c.handler.ProcessWrite(pkts, func(entry *eth.FifoEntry, pkt *stack.PacketBuffer) {
entry.SetLength(bufferSize)
b := c.iob.BufferFromEntry(*entry)
var used int
for _, v := range pkt.Views() {
used += copy(b[used:], v)
}
*entry = c.iob.entry(b[:used])
})
}
func (c *Client) WritePacket(_ stack.RouteInfo, _ *stack.GSO, _ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
var pkts stack.PacketBufferList
pkts.PushBack(pkt)
_, err := c.write(pkts)
return err
}
func (c *Client) WritePackets(_ stack.RouteInfo, _ *stack.GSO, pkts stack.PacketBufferList, _ tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
return c.write(pkts)
}
func (c *Client) Attach(dispatcher stack.NetworkDispatcher) {
c.dispatcher = dispatcher
detachWithError := func(cause error) {
c.mu.Lock()
closed := c.mu.closed
c.mu.Unlock()
if closed {
return
}
if err := c.Close(); err != nil {
_ = syslog.WarnTf(tag, "error closing device on detach (caused by %s): %s", cause, err)
} else {
_ = syslog.WarnTf(tag, "closed device due to %s", cause)
}
}
// dispatcher may be nil when the NIC in stack.Stack is being removed.
if dispatcher == nil {
detachWithError(fmt.Errorf("RemoveNIC"))
return
}
c.wg.Add(1)
go func() {
defer c.wg.Done()
if err := c.handler.TxReceiverLoop(func(entry *eth.FifoEntry) bool {
return entry.Flags()&eth.FifoTXOK != 0
}); err != nil {
detachWithError(fmt.Errorf("TX read loop error: %w", err))
}
}()
c.wg.Add(1)
go func() {
defer c.wg.Done()
if err := c.handler.TxSenderLoop(); err != nil {
detachWithError(fmt.Errorf("TX write loop error: %w", err))
}
}()
c.wg.Add(1)
go func() {
defer c.wg.Done()
if err := c.handler.RxLoop(func(entry *eth.FifoEntry) {
// Process inbound packet.
var emptyLinkAddress tcpip.LinkAddress
dispatcher.DeliverNetworkPacket(emptyLinkAddress, emptyLinkAddress, 0, &stack.PacketBuffer{
Data: append(buffer.View(nil), c.iob.BufferFromEntry(*entry)...).ToVectorisedView(),
})
// This entry is going back to the driver; it can be reused.
entry.SetLength(bufferSize)
}, zx.Signals(ethernet.SignalStatus), func() {
// Process ethernetStatusSignal.
status, err := c.device.GetStatus(context.Background())
if err != nil {
_ = syslog.InfoTf(tag, "fuchsia.hardware.ethernet.Device.GetStatus() error = %s", err)
return
}
_ = syslog.InfoTf(tag, "fuchsia.hardware.ethernet.Device.GetStatus() = %s", status)
c.mu.Lock()
fn := c.mu.onLinkOnlineChanged
c.mu.Unlock()
fn(status&ethernet.DeviceStatusOnline != 0)
}); err != nil {
detachWithError(fmt.Errorf("RX loop error: %w", err))
}
}()
// Spawn a goroutine to clean up the mapped memory once all the handler
// loops are done.
go func() {
c.wg.Wait()
if err := c.iob.Close(); err != nil {
_ = syslog.WarnTf(tag, "failed to close IO buffer: %s", err)
}
c.mu.Lock()
fn := c.mu.onLinkClosed
c.mu.Unlock()
fn()
}()
}
func (c *Client) IsAttached() bool {
return c.dispatcher != nil
}
// Wait implements stack.LinkEndpoint. It blocks until an error in the dispatch
// goroutine(s) spawned in Attach occurs.
func (c *Client) Wait() {
c.wg.Wait()
}
// ARPHardwareType implements stack.LinkEndpoint.
func (*Client) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
// AddHeader implements stack.LinkEndpoint.
func (*Client) AddHeader(_, _ tcpip.LinkAddress, _ tcpip.NetworkProtocolNumber, _ *stack.PacketBuffer) {
}
func (c *Client) GSOMaxSize() uint32 {
// There's no limit on how much data we can take in a single software GSO write.
return math.MaxUint32
}
func checkStatus(status int32, text string) error {
if status := zx.Status(status); status != zx.ErrOk {
return &zx.Error{Status: status, Text: text}
}
return nil
}
func (c *Client) SetOnLinkClosed(f func()) {
c.mu.Lock()
c.mu.onLinkClosed = f
c.mu.Unlock()
}
func (c *Client) SetOnLinkOnlineChanged(f func(bool)) {
c.mu.Lock()
c.mu.onLinkOnlineChanged = f
c.mu.Unlock()
}
func (c *Client) Topopath() string {
return c.topopath
}
func (c *Client) Filepath() string {
return c.filepath
}
// Up enables the interface.
func (c *Client) Up() error {
if status, err := c.device.Start(context.Background()); err != nil {
return err
} else if err := checkStatus(status, "Start"); err != nil {
return err
}
return nil
}
// Down disables the interface.
func (c *Client) Down() error {
return c.device.Stop(context.Background())
}
// Close closes a Client, releasing any held resources.
func (c *Client) Close() error {
c.mu.Lock()
closed := c.mu.closed
c.mu.closed = true
c.mu.Unlock()
if closed {
return nil
}
c.handler.DetachTx()
if err := c.device.Stop(context.Background()); err != nil {
_ = syslog.WarnTf(tag, "fuchsia.hardware.ethernet.Device.Stop() for path %q failed: %s", c.topopath, err)
}
if iface, ok := c.device.(*ethernet.DeviceWithCtxInterface); ok {
if err := iface.Close(); err != nil {
_ = syslog.WarnTf(tag, "failed to close device handle: %s", err)
}
} else {
_ = syslog.WarnTf(tag, "can't close device interface of type %T", c.device)
}
if err := c.handler.TxFifo.Close(); err != nil {
_ = syslog.WarnTf(tag, "failed to close tx fifo: %s", err)
}
if err := c.handler.RxFifo.Close(); err != nil {
_ = syslog.WarnTf(tag, "failed to close rx fifo: %s", err)
}
// Additional cleanup is performed by the watcher goroutine spawned in
// Attach once all the io loops are done.
return nil
}
func (c *Client) SetPromiscuousMode(enabled bool) error {
c.mu.Lock()
defer c.mu.Unlock()
if status, err := c.device.SetPromiscuousMode(context.Background(), enabled); err != nil {
return err
} else if err := checkStatus(status, "SetPromiscuousMode"); err != nil {
return err
}
return nil
}
// ListenTX tells the ethernet driver to reflect all transmitted
// packets back to this ethernet client.
func (c *Client) ListenTX() error {
if status, err := c.device.ListenStart(context.Background()); err != nil {
return err
} else if err := checkStatus(status, "ListenStart"); err != nil {
return err
}
return nil
}
func (c *Client) DeviceClass() network.DeviceClass {
if c.Info.Features&ethernet.FeaturesWlan != 0 {
return network.DeviceClassWlan
}
return network.DeviceClassEthernet
}
func (c *Client) RxStats() *fifo.RxStats {
return &c.handler.Stats.Rx
}
func (c *Client) TxStats() *fifo.TxStats {
return &c.handler.Stats.Tx
}