blob: fb1d8e1a05cf3c073da2cde42c4a9c305da7b1f6 [file] [log] [blame]
// Copyright 2020 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.
//go:build !build_with_native_toolchain
// +build !build_with_native_toolchain
package netdevice
import (
"context"
"fmt"
"math"
"math/bits"
"sync"
"syscall/zx"
"syscall/zx/fidl"
"gen/netstack/link/netdevice"
"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/network"
"go.uber.org/multierr"
"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/network.h>
import "C"
const descriptorLength uint64 = C.sizeof_buffer_descriptor_t
const tag = "netdevice"
const emptyLinkAddress tcpip.LinkAddress = ""
type bufferDescriptor = C.buffer_descriptor_t
type basePortId = uint8
type PortMode int
const (
_ PortMode = iota
PortModeEthernet
PortModeIp
)
// Client is a client for a network device that implements the
// fuchsia.hardware.network.Device protocol.
type Client struct {
device *network.DeviceWithCtxInterface
session *network.SessionWithCtxInterface
deviceInfo network.DeviceInfo
config SessionConfig
data fifo.MappedVMO
descriptors fifo.MappedVMO
handler netdevice.Handler
mu struct {
sync.RWMutex
closed bool
runningChan chan struct{}
ports map[basePortId]*Port
}
}
var _ link.Controller = (*Port)(nil)
var _ link.Observer = (*Port)(nil)
var _ stack.LinkEndpoint = (*Port)(nil)
var _ stack.GSOEndpoint = (*Port)(nil)
// Port is the instantiation of a network interface backed by a
// netdevice port.
type Port struct {
// Used to wait for goroutine teardown. See Attach and Wait.
dispatcherWg sync.WaitGroup
cancelDispatch context.CancelFunc
client *Client
port *network.PortWithCtxInterface
portInfo network.PortInfo
watcher *network.StatusWatcherWithCtxInterface
linkAddress tcpip.LinkAddress
macAddressing *network.MacAddressingWithCtxInterface
mode PortMode
state struct {
mu struct {
sync.Mutex
dispatcher stack.NetworkDispatcher
closed bool
onLinkClosed func()
onLinkOnlineChanged func(bool)
}
}
mtu struct {
mu struct {
sync.Mutex
value uint32
}
}
}
func (p *Port) MTU() uint32 {
p.mtu.mu.Lock()
mtu := p.mtu.mu.value
p.mtu.mu.Unlock()
return mtu
}
func (*Port) Capabilities() stack.LinkEndpointCapabilities {
return 0
}
func (*Port) MaxHeaderLength() uint16 {
return 0
}
func (p *Port) LinkAddress() tcpip.LinkAddress {
return p.linkAddress
}
// write writes a list of packets to the device.
func (c *Client) write(port network.PortId, pkts stack.PacketBufferList, protocol tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
c.mu.RLock()
defer c.mu.RUnlock()
if c.mu.closed {
return 0, &tcpip.ErrClosedForSend{}
}
return c.handler.ProcessWrite(pkts, func(descriptorIndex *uint16, pkt *stack.PacketBuffer) {
descriptor := c.getDescriptor(*descriptorIndex)
// Reset descriptor to default values before filling it.
c.resetTxDescriptor(descriptor)
data := c.getDescriptorData(descriptor)
n := 0
for _, v := range pkt.Views() {
if w := copy(data[n:], v); w != len(v) {
panic(fmt.Sprintf("failed to copy packet data to descriptor %d, want %d got %d bytes", descriptorIndex, len(v), w))
} else {
n += w
}
}
var frameType network.FrameType
if len(pkt.LinkHeader().View()) != 0 {
frameType = network.FrameTypeEthernet
} else {
switch protocol {
case header.IPv4ProtocolNumber:
frameType = network.FrameTypeIpv4
case header.IPv6ProtocolNumber:
frameType = network.FrameTypeIpv6
default:
_ = syslog.ErrorTf(tag, "can't identify outgoing packet type")
}
}
// Pad tx frame to device requirements.
for ; n < int(c.deviceInfo.MinTxBufferLength); n++ {
data[n] = 0
}
descriptor.port_id.base = C.uchar(port.Base)
descriptor.port_id.salt = C.uchar(port.Salt)
descriptor.info_type = C.uint(network.InfoTypeNoInfo)
descriptor.frame_type = C.uchar(frameType)
descriptor.data_length = C.uint(n)
})
}
func (p *Port) WritePacket(_ stack.RouteInfo, proto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
var pkts stack.PacketBufferList
pkts.PushBack(pkt)
_, err := p.client.write(p.portInfo.Id, pkts, proto)
return err
}
func (p *Port) WritePackets(_ stack.RouteInfo, pkts stack.PacketBufferList, proto tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
return p.client.write(p.portInfo.Id, pkts, proto)
}
func (p *Port) WriteRawPacket(pkt *stack.PacketBuffer) tcpip.Error {
var pkts stack.PacketBufferList
pkts.PushBack(pkt)
// TODO(https://fxbug.dev/86725): Frame type detection may not work for implementing
// packet sockets.
_, err := p.client.write(p.portInfo.Id, pkts, pkt.NetworkProtocolNumber)
return err
}
func (p *Port) Attach(dispatcher stack.NetworkDispatcher) {
p.state.mu.Lock()
p.state.mu.dispatcher = dispatcher
onLinkClosed := p.state.mu.onLinkClosed
p.state.mu.Unlock()
closeWithError := func(reason error) {
if err := p.Close(); err != nil {
_ = syslog.WarnTf(tag, "error closing port endpoint on detach (%s): %s", reason, err)
} else {
_ = syslog.WarnTf(tag, "closed port endpoint: %s", reason)
}
}
// dispatcher may be nil when the NIC in stack.Stack is being removed.
if dispatcher == nil {
closeWithError(fmt.Errorf("RemoveNIC"))
return
}
ctx, cancel := context.WithCancel(context.Background())
p.cancelDispatch = cancel
p.dispatcherWg.Add(1)
go func() {
defer p.dispatcherWg.Done()
if err := func() error {
for {
status, err := p.watcher.WatchStatus(ctx)
if err != nil {
return fmt.Errorf("WatchStatus failed: %w", err)
}
if err := p.client.config.checkValidityForPort(status); err != nil {
return fmt.Errorf("invalid port status for session: %w", err)
}
if status.HasMtu() {
p.mtu.mu.Lock()
p.mtu.mu.value = status.GetMtu()
p.mtu.mu.Unlock()
}
p.state.mu.Lock()
fn := p.state.mu.onLinkOnlineChanged
p.state.mu.Unlock()
fn(status.HasFlags() && status.GetFlags()&network.StatusFlagsOnline != 0)
}
}(); err != nil {
closeWithError(err)
}
}()
// Spawn a goroutine to notify link closure once all the routines are done.
go func() {
p.dispatcherWg.Wait()
onLinkClosed()
}()
}
func (p *Port) Close() error {
p.client.mu.Lock()
// Remove from parent.
delete(p.client.mu.ports, p.portInfo.GetId().Base)
p.client.mu.Unlock()
p.state.mu.Lock()
defer p.state.mu.Unlock()
if p.state.mu.closed {
return nil
}
p.state.mu.closed = true
if p.cancelDispatch != nil {
p.cancelDispatch()
}
var err error
if p.macAddressing != nil {
err = p.macAddressing.Close()
}
return multierr.Combine(p.port.Close(), p.watcher.Close(), err)
}
func (c *Client) Run(ctx context.Context) {
c.mu.Lock()
closed := c.mu.closed
oldRunningChan := c.mu.runningChan
runningChan := make(chan struct{})
c.mu.runningChan = runningChan
defer func() {
close(runningChan)
}()
c.mu.Unlock()
if oldRunningChan != nil {
panic("can't call Run twice on the same client")
}
if closed {
panic("can't call Run on a client that is already closed")
}
ctx, cancel := context.WithCancel(ctx)
detachWithError := func(reason error) {
cancel()
c.handler.DetachTx()
if _, err := c.closeInner(); err != nil {
_ = syslog.WarnTf(tag, "error closing device on detach (%s): %s", reason, err)
} else {
_ = syslog.WarnTf(tag, "closed device: %s", reason)
}
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
<-ctx.Done()
detachWithError(fmt.Errorf("context done %w", ctx.Err()))
}()
wg.Add(1)
go func() {
defer wg.Done()
if err := c.handler.TxReceiverLoop(func(descriptorIndex *uint16) bool {
descriptor := c.getDescriptor(*descriptorIndex)
return network.TxReturnFlags(descriptor.return_flags)&network.TxReturnFlagsTxRetError == 0
}); err != nil {
detachWithError(fmt.Errorf("TX read loop: %w", err))
}
_ = syslog.VLogTf(syslog.DebugVerbosity, tag, "TX read loop finished")
}()
wg.Add(1)
go func() {
wg.Done()
if err := c.handler.TxSenderLoop(); err != nil {
detachWithError(fmt.Errorf("TX write loop: %w", err))
}
_ = syslog.VLogTf(syslog.DebugVerbosity, tag, "TX write loop finished")
}()
wg.Add(1)
go func() {
defer wg.Done()
if err := c.handler.RxLoop(func(descriptorIndex *uint16) {
descriptor := c.getDescriptor(*descriptorIndex)
data := c.getDescriptorData(descriptor)
view := buffer.NewView(len(data))
view = view[:copy(view, data)]
var protocolNumber tcpip.NetworkProtocolNumber
switch network.FrameType(descriptor.frame_type) {
case network.FrameTypeIpv4:
protocolNumber = header.IPv4ProtocolNumber
case network.FrameTypeIpv6:
protocolNumber = header.IPv6ProtocolNumber
}
portId := basePortId(descriptor.port_id.base)
c.mu.RLock()
port, ok := c.mu.ports[portId]
c.mu.RUnlock()
if ok {
if want, got := port.portInfo.GetId().Salt, uint8(descriptor.port_id.salt); want == got {
port.state.mu.Lock()
dispatcher := port.state.mu.dispatcher
port.state.mu.Unlock()
if dispatcher != nil {
dispatcher.DeliverNetworkPacket(emptyLinkAddress, emptyLinkAddress, protocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: view.ToVectorisedView(),
}))
}
} else {
// This can happen if the port flaps on the device while frames
// are propagating in the FIFO.
_ = syslog.WarnTf(tag, "received frame on port %d with bad salt %d, want %d", portId, got, want)
}
} else {
// This can happen if the port is detached from the client while frames
// are propagating in the FIFO.
_ = syslog.WarnTf(tag, "received frame for unknown port: %d", portId)
}
// This entry is going back to the driver; it can be reused.
c.resetRxDescriptor(descriptor)
}); err != nil {
detachWithError(fmt.Errorf("RX loop: %w", err))
}
_ = syslog.VLogTf(syslog.DebugVerbosity, tag, "Rx loop finished")
}()
wg.Wait()
}
func (p *Port) IsAttached() bool {
p.state.mu.Lock()
attached := p.state.mu.dispatcher != nil
p.state.mu.Unlock()
return attached
}
func (p *Port) Wait() {
p.dispatcherWg.Wait()
}
func (*Port) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
func (*Port) AddHeader(_, _ tcpip.LinkAddress, _ tcpip.NetworkProtocolNumber, _ *stack.PacketBuffer) {
}
// GSOMaxSize implements stack.GSOEndpoint.
func (*Port) GSOMaxSize() uint32 {
// There's no limit on how much data we can take in a single software GSO write.
return math.MaxUint32
}
// SupportedGSO implements stack.GSOEndpoint.
func (*Port) SupportedGSO() stack.SupportedGSO {
// TODO(https://fxbug.dev/76010): expose hardware offloading capabilities.
return stack.SWGSOSupported
}
func (p *Port) Up() error {
result, err := p.client.session.Attach(context.Background(), p.portInfo.GetId(), p.subscriptionFrameTypes())
if err != nil {
return err
}
switch w := result.Which(); w {
case network.SessionAttachResultResponse:
return nil
case network.SessionAttachResultErr:
return &zx.Error{
Status: zx.Status(result.Err),
Text: "failed to attach session",
}
default:
panic(fmt.Sprintf("unexpected session.Attach variant %d", w))
}
}
func (p *Port) Down() error {
result, err := p.client.session.Detach(context.Background(), p.portInfo.GetId())
if err != nil {
return err
}
switch w := result.Which(); w {
case network.SessionDetachResultResponse:
return nil
case network.SessionDetachResultErr:
return &zx.Error{
Status: zx.Status(result.Err),
Text: "failed to detach session",
}
default:
panic(fmt.Sprintf("unexpected session.Detach variant %d", w))
}
}
func (p *Port) SetOnLinkClosed(f func()) {
p.state.mu.Lock()
p.state.mu.onLinkClosed = f
p.state.mu.Unlock()
}
func (p *Port) SetOnLinkOnlineChanged(f func(bool)) {
p.state.mu.Lock()
p.state.mu.onLinkOnlineChanged = f
p.state.mu.Unlock()
}
func (p *Port) SetPromiscuousMode(enabled bool) error {
if p.macAddressing == nil {
return fmt.Errorf("promiscuous mode not supported on device")
}
var mode network.MacFilterMode
if enabled {
mode = network.MacFilterModePromiscuous
} else {
// NOTE: Netstack currently is not capable of handling multicast
// filters, promiscuous mode = false means receive all multicasts still.
mode = network.MacFilterModeMulticastPromiscuous
}
if status, err := p.macAddressing.SetMode(context.Background(), mode); err != nil {
return err
} else if zx.Status(status) != zx.ErrOk {
return &zx.Error{
Status: zx.Status(status),
Text: "fuchsia.hardware.network/MacAddressing.SetMode",
}
}
return nil
}
func (p *Port) DeviceClass() network.DeviceClass {
return p.portInfo.Class
}
// Close closes the client and disposes of all its resources.
func (c *Client) Close() error {
running, err := c.closeInner()
if running != nil {
<-running
}
// NB: descriptors and data VMOs are closed only after all the goroutines
// in Run are closed to prevent data races.
return multierr.Combine(err, c.data.Close(), c.descriptors.Close())
}
func (c *Client) closeInner() (chan struct{}, error) {
runningChan, ports, err := func() (chan struct{}, map[basePortId]*Port, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.mu.closed {
return c.mu.runningChan, nil, nil
}
c.mu.closed = true
ports := c.mu.ports
c.mu.ports = nil
return c.mu.runningChan, ports, multierr.Combine(
c.device.Close(),
// Session also has a Close method, make sure we're calling the ChannelProxy
// one.
((*fidl.ChannelProxy)(c.session)).Close(),
c.handler.RxFifo.Close(),
c.handler.TxFifo.Close(),
)
}()
for _, port := range ports {
err = multierr.Append(err, port.Close())
}
return runningChan, err
}
// getDescriptor returns the shared memory representing the descriptor indexed
// by d.
func (c *Client) getDescriptor(d uint16) *bufferDescriptor {
offset := uint64(d) * c.config.DescriptorLength
return (*bufferDescriptor)(c.descriptors.GetPointer(offset))
}
// getDescriptorData returns the shared contiguous memory for a descriptor.
func (c *Client) getDescriptorData(desc *bufferDescriptor) []byte {
if desc.chain_length != 0 {
panic(fmt.Sprintf("descriptor chaining not implemented, descriptor requested chain of length %d", desc.chain_length))
}
offset := uint64(desc.offset) + uint64(desc.head_length)
return c.data.GetData(offset, uint64(desc.data_length))
}
// resetTxDescriptor resets the the descriptor's fields that a device
// implementation could have changed. It should only be used for
// Tx buffers.
func (c *Client) resetTxDescriptor(descriptor *bufferDescriptor) {
*descriptor = bufferDescriptor{
info_type: C.uint(network.InfoTypeNoInfo),
offset: descriptor.offset,
head_length: C.ushort(c.config.TxHeaderLength),
// Note: we assert that BufferLength > TxHeaderLength + TxTailLength when
// the session config is created, so we don't have to worry about overflow
// here.
data_length: C.uint(c.config.BufferLength - uint32(c.config.TxHeaderLength) - uint32(c.config.TxTailLength)),
tail_length: C.ushort(c.config.TxTailLength),
}
}
// resetRxDescriptor resets the the descriptor's fields that a device
// implementation could have changed. It should only be used for
// Rx buffers.
func (c *Client) resetRxDescriptor(descriptor *bufferDescriptor) {
*descriptor = bufferDescriptor{
info_type: C.uint(network.InfoTypeNoInfo),
offset: descriptor.offset,
data_length: C.uint(c.config.BufferLength),
}
}
// NewPort creates a new port client for this device.
func (c *Client) NewPort(ctx context.Context, portId network.PortId) (*Port, error) {
portRequest, port, err := network.NewPortWithCtxInterfaceRequest()
if err != nil {
return nil, fmt.Errorf("failed to create port request: %w", err)
}
defer func() {
if port != nil {
_ = port.Close()
}
}()
if err := c.device.GetPort(ctx, portId, portRequest); err != nil {
return nil, fmt.Errorf("failed to get port %d: %w", portId, err)
}
portInfo, err := port.GetInfo(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get port info: %w", err)
}
if !(portInfo.HasId() && portInfo.HasClass() && portInfo.HasRxTypes() && portInfo.HasTxTypes()) {
return nil, fmt.Errorf("incomplete PortInfo: %#v", portInfo)
}
portMode, err := selectPortOperatingMode(portInfo.GetRxTypes())
if err != nil {
return nil, err
}
macRequest, mac, err := network.NewMacAddressingWithCtxInterfaceRequest()
if err != nil {
return nil, fmt.Errorf("failed to create mac request: %w", err)
}
defer func() {
if mac != nil {
_ = mac.Close()
}
}()
if err := port.GetMac(ctx, macRequest); err != nil {
return nil, fmt.Errorf("failed to get mac: %w", err)
}
linkAddress, mac := func() (tcpip.LinkAddress, *network.MacAddressingWithCtxInterface) {
macAddr, err := mac.GetUnicastAddress(ctx)
if err != nil {
// MAC is not supported.
_ = mac.Close()
return emptyLinkAddress, nil
} else {
return tcpip.LinkAddress(macAddr.Octets[:]), mac
}
}()
if mac != nil {
// Set device to multicast promiscuous to match current behavior. When
// Netstack controls multicast filters this can be removed.
if status, err := mac.SetMode(ctx, network.MacFilterModeMulticastPromiscuous); err != nil {
return nil, fmt.Errorf("failed to set multicast promiscuous: %w", err)
} else if zx.Status(status) != zx.ErrOk {
return nil, &zx.Error{
Status: zx.Status(status),
Text: "fuchsia.hardware.network/MacAddressing.SetMode",
}
}
}
portStatus, err := port.GetStatus(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create get port status: %w", err)
}
if !portStatus.HasMtu() {
return nil, fmt.Errorf("missing MTU in port status")
}
if err := c.config.checkValidityForPort(portStatus); err != nil {
return nil, err
}
watcherRequest, watcher, err := network.NewStatusWatcherWithCtxInterfaceRequest()
if err != nil {
return nil, fmt.Errorf("failed to create status watcher request: %w", err)
}
defer func() {
if watcher != nil {
_ = watcher.Close()
}
}()
if err := port.GetStatusWatcher(ctx, watcherRequest, network.MaxStatusBuffer); err != nil {
return nil, fmt.Errorf("failed to create get status watcher: %w", err)
}
portEndpoint := &Port{
client: c,
port: port,
portInfo: portInfo,
watcher: watcher,
linkAddress: linkAddress,
macAddressing: mac,
mode: portMode,
}
portEndpoint.mtu.mu.value = portStatus.GetMtu()
c.mu.Lock()
defer c.mu.Unlock()
baseId := basePortId(portId.Base)
if _, ok := c.mu.ports[baseId]; ok {
return nil, &PortAlreadyBoundError{id: portId}
}
c.mu.ports[baseId] = portEndpoint
// Prevent deferred functions from cleaning up.
mac = nil
port = nil
watcher = nil
return portEndpoint, nil
}
// Mode returns the port's operating mode.
func (p *Port) Mode() PortMode {
return p.mode
}
// subscriptionFrameTypes returns the frame types to use when attaching this
// port to a session, based on the port's operating mode.
func (p *Port) subscriptionFrameTypes() []network.FrameType {
switch mode := p.mode; mode {
case PortModeEthernet:
return []network.FrameType{network.FrameTypeEthernet}
case PortModeIp:
return []network.FrameType{network.FrameTypeIpv4, network.FrameTypeIpv6}
default:
panic(fmt.Sprintf("invalid port mode %d", mode))
}
}
// NewClient creates a new client from a provided network device interface.
func NewClient(ctx context.Context, dev *network.DeviceWithCtxInterface, sessionConfigFactory SessionConfigFactory) (*Client, error) {
_ = syslog.VLogTf(syslog.DebugVerbosity, tag, "creating network device client")
defer func() {
// We always take ownership of the device, close it in case of errors.
if dev != nil {
_ = dev.Close()
}
}()
deviceInfo, err := dev.GetInfo(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get device information: %w", err)
}
if !(deviceInfo.HasMinDescriptorLength() &&
deviceInfo.HasDescriptorVersion() &&
deviceInfo.HasRxDepth() &&
deviceInfo.HasTxDepth() &&
deviceInfo.HasBufferAlignment() &&
deviceInfo.HasMinRxBufferLength() &&
deviceInfo.HasMinTxBufferLength() &&
deviceInfo.HasMinTxBufferHead() &&
deviceInfo.HasMinTxBufferTail() &&
deviceInfo.HasMaxBufferParts()) {
return nil, fmt.Errorf("incomplete DeviceInfo: %#v", deviceInfo)
}
if deviceInfo.HasMaxBufferLength() && deviceInfo.MaxBufferLength == 0 {
return nil, fmt.Errorf("invalid MaxBufferLength: %d, expected != 0", deviceInfo.MaxBufferLength)
}
config, err := sessionConfigFactory.MakeSessionConfig(deviceInfo)
if err != nil {
return nil, fmt.Errorf("session configuration factory failed: %w", err)
}
// Descriptor count must be a power of 2.
config.TxDescriptorCount = 1 << bits.Len16(config.TxDescriptorCount-1)
config.RxDescriptorCount = 1 << bits.Len16(config.RxDescriptorCount-1)
totalDescriptors := uint64(config.TxDescriptorCount + config.RxDescriptorCount)
mappedDataVmo, dataVmo, err := fifo.NewMappedVMO(totalDescriptors*uint64(config.BufferStride), "fuchsia.hardware.network.Device/descriptors")
if err != nil {
return nil, fmt.Errorf("failed to create data VMO: %w", err)
}
mappedDescVmo, descVmo, err := fifo.NewMappedVMO(totalDescriptors*config.DescriptorLength, "fuchsia.hardware.network.Device/data")
if err != nil {
_ = mappedDataVmo.Close()
return nil, fmt.Errorf("failed to create descriptors VMO: %w", err)
}
var sessionInfo network.SessionInfo
sessionInfo.SetDescriptors(descVmo)
sessionInfo.SetData(dataVmo)
sessionInfo.SetDescriptorVersion(C.NETWORK_DEVICE_DESCRIPTOR_VERSION)
sessionInfo.SetDescriptorLength(uint8(config.DescriptorLength / 8))
sessionInfo.SetDescriptorCount(config.RxDescriptorCount + config.TxDescriptorCount)
sessionInfo.SetOptions(config.Options)
sessionResult, err := dev.OpenSession(ctx, "netstack", sessionInfo)
if err != nil {
_ = mappedDataVmo.Close()
_ = mappedDescVmo.Close()
return nil, fmt.Errorf("failed to open device session: %w", err)
}
if sessionResult.Which() == network.DeviceOpenSessionResultErr {
_ = mappedDataVmo.Close()
_ = mappedDescVmo.Close()
return nil, &zx.Error{Status: zx.Status(sessionResult.Err), Text: "fuchsia.hardware.network/Device.OpenSession"}
}
c := &Client{
device: dev,
session: &sessionResult.Response.Session,
deviceInfo: deviceInfo,
config: config,
data: mappedDataVmo,
descriptors: mappedDescVmo,
handler: netdevice.Handler{
TxDepth: uint32(deviceInfo.TxDepth),
RxDepth: uint32(deviceInfo.RxDepth),
RxFifo: sessionResult.Response.Fifos.Rx,
TxFifo: sessionResult.Response.Fifos.Tx,
},
}
c.mu.ports = make(map[basePortId]*Port)
if entries := c.handler.InitRx(c.config.RxDescriptorCount); entries != c.config.RxDescriptorCount {
panic(fmt.Sprintf("bad handler rx queue size: %d, expected %d", entries, c.config.RxDescriptorCount))
}
if entries := c.handler.InitTx(c.config.TxDescriptorCount); entries != c.config.TxDescriptorCount {
panic(fmt.Sprintf("bad handler tx queue size: %d, expected %d", entries, c.config.RxDescriptorCount))
}
c.handler.Stats.Tx.FifoStats = fifo.MakeFifoStats(uint32(c.deviceInfo.TxDepth))
c.handler.Stats.Rx.FifoStats = fifo.MakeFifoStats(uint32(c.deviceInfo.RxDepth))
descriptorIndex := uint16(0)
vmoOffset := uint64(0)
for i := uint16(0); i < c.config.RxDescriptorCount; i++ {
descriptor := c.getDescriptor(descriptorIndex)
*descriptor = bufferDescriptor{
offset: C.ulong(vmoOffset),
}
c.resetRxDescriptor(descriptor)
c.handler.PushInitialRx(descriptorIndex)
vmoOffset += uint64(c.config.BufferStride)
descriptorIndex++
}
for i := uint16(0); i < c.config.TxDescriptorCount; i++ {
descriptor := c.getDescriptor(descriptorIndex)
*descriptor = bufferDescriptor{
offset: C.ulong(vmoOffset),
}
c.resetTxDescriptor(descriptor)
c.handler.PushInitialTx(descriptorIndex)
vmoOffset += uint64(c.config.BufferStride)
descriptorIndex++
}
// Prevent defer function from closing the device.
dev = nil
return c, nil
}
func (p *Port) RxStats() *fifo.RxStats {
return &p.client.handler.Stats.Rx
}
func (p *Port) TxStats() *fifo.TxStats {
return &p.client.handler.Stats.Tx
}
func (p *Port) Class() network.DeviceClass {
return p.portInfo.Class
}
type InvalidPortOperatingModeError struct {
rxTypes []network.FrameType
}
func (e *InvalidPortOperatingModeError) Error() string {
return fmt.Sprintf("can't determine port operating mode for types '%v'", e.rxTypes)
}
type PortAlreadyBoundError struct {
id network.PortId
}
func (e *PortAlreadyBoundError) Error() string {
return fmt.Sprintf("port %d(salt=%d) is already bound", e.id.Base, e.id.Salt)
}
func selectPortOperatingMode(rxTypes []network.FrameType) (PortMode, error) {
seenIpv4 := false
seenIpv6 := false
for _, frameType := range rxTypes {
switch frameType {
case network.FrameTypeEthernet:
return PortModeEthernet, nil
case network.FrameTypeIpv4:
seenIpv4 = true
case network.FrameTypeIpv6:
seenIpv6 = true
default:
panic(fmt.Sprintf("unrecognized frame type %s", frameType))
}
}
// We only support devices with dual IP mode for now.
if seenIpv4 && seenIpv6 {
return PortModeIp, nil
}
return 0, &InvalidPortOperatingModeError{rxTypes: rxTypes}
}