blob: 7930b7ede21988bfa5d9105b959d81a909d29b21 [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.
#ifndef SRC_CONNECTIVITY_NETWORK_DRIVERS_NETWORK_DEVICE_DEVICE_SESSION_H_
#define SRC_CONNECTIVITY_NETWORK_DRIVERS_NETWORK_DEVICE_DEVICE_SESSION_H_
#include <lib/async/dispatcher.h>
#include <lib/fidl/cpp/wire/server.h>
#include <lib/fzl/pinned-vmo.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/stdcompat/span.h>
#include <lib/zx/event.h>
#include <lib/zx/fifo.h>
#include <lib/zx/vmo.h>
#include <fbl/array.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/ref_counted.h>
#include "data_structs.h"
#include "definitions.h"
#include "device_interface.h"
#include "device_port.h"
#include "public/locks.h"
#include "rx_queue.h"
#include "src/connectivity/lib/network-device/buffer_descriptor/buffer_descriptor.h"
#include "tx_queue.h"
namespace network::internal {
// A device port attached to a session.
//
// This class provides safe access to device ports owned by a DeviceInterface.
class AttachedPort {
public:
// Helper function with TA annotations that bridges the gap between parent's locks and local
// locking requirements; TA is not otherwise able to tell that the |parent| and |parent_| are the
// same entity.
void AssertParentControlLockShared(DeviceInterface& parent)
__TA_ASSERT_SHARED(parent_->control_lock()) __TA_REQUIRES_SHARED(parent.control_lock()) {
ZX_DEBUG_ASSERT(parent_ == &parent);
}
// Calls provided function f with the attached port.
template <typename F>
auto WithPort(F f) __TA_REQUIRES_SHARED(parent_->control_lock()) {
return f(*port_);
}
// Returns the Rx frame types we're subscribed to on this attached port.
cpp20::span<const netdev::wire::FrameType> frame_types() const {
return cpp20::span(frame_types_.begin(), frame_type_count_);
}
/// Returns true if the attached port's salt matches the provided |salt|.
bool SaltMatches(uint8_t salt) __TA_REQUIRES_SHARED(parent_->control_lock()) {
return WithPort([salt](DevicePort& port) { return port.id().salt == salt; });
}
AttachedPort(DeviceInterface* parent, DevicePort* port,
cpp20::span<const netdev::wire::FrameType> frame_types)
: parent_(parent),
port_(port),
frame_types_([&frame_types]() {
decltype(frame_types_) t;
ZX_ASSERT(frame_types.size() <= t.size());
std::copy(frame_types.begin(), frame_types.end(), t.begin());
return t;
}()),
frame_type_count_(static_cast<uint32_t>(frame_types.size())) {}
private:
// NB: Fields can't be const because we want AttachedPort to allow assignment operator.
// Attached parent pointer, not owned;
DeviceInterface* parent_ = nullptr;
// Attached port pointer, not owned;
DevicePort* port_ = nullptr;
std::array<netdev::wire::FrameType, netdev::wire::kMaxFrameTypes> frame_types_{};
uint32_t frame_type_count_ = 0;
};
// A client session with a network device interface.
//
// Session will spawn a thread that will handle the fuchsia.hardware.network.Session FIDL control
// plane calls and service the Tx FIFO associated with the client session.
//
// It is invalid to destroy a Session that has outstanding buffers, that is, buffers that are
// currently owned by the interface's Rx or Tx queues.
class Session : public fbl::DoublyLinkedListable<std::unique_ptr<Session>>,
public fidl::WireServer<netdev::Session> {
public:
~Session() override;
// Creates a new session with the provided parameters.
//
// The session will service fuchsia.hardware.network.Session FIDL calls on the provided `control`
// channel.
//
// All control plane calls are operated on the provided `dispatcher`, and a dedicated thread will
// be spawned to handle data fast path operations (tx data plane).
//
// Returns the session and its data path FIFOs.
static zx::result<std::pair<std::unique_ptr<Session>, netdev::wire::Fifos>> Create(
async_dispatcher_t* dispatcher, netdev::wire::SessionInfo& info, fidl::StringView name,
DeviceInterface* parent);
bool IsPrimary() const;
bool IsListen() const;
bool IsPaused() const;
// Checks if this session is eligible to take over the primary session from `current_primary`.
// `current_primary` can be null, meaning there's no current primary session.
bool ShouldTakeOverPrimary(const Session* current_primary) const;
// Installs session tx listeners. Panics if called twice.
void InstallTx() __TA_REQUIRES(parent_->tx_lock());
// Uninstalls session tx listeners. Must be called before destroying if
// |InstallTx| was called. No-op if |InstallTx| was not called.
void UninstallTx() __TA_REQUIRES(parent_->tx_lock());
// Helper functions with TA annotations that bridges the gap between parent's locks and local
// locking requirements; TA is not otherwise able to tell that the |parent| and |parent_| are the
// same entity.
void AssertParentControlLock(DeviceInterface& parent) __TA_ASSERT(parent_->control_lock())
__TA_REQUIRES(parent.control_lock()) {
ZX_DEBUG_ASSERT(parent_ == &parent);
}
void AssertParentControlLockShared(DeviceInterface& parent)
__TA_ASSERT_SHARED(parent_->control_lock()) __TA_REQUIRES_SHARED(parent.control_lock()) {
ZX_DEBUG_ASSERT(parent_ == &parent);
}
void AssertParentRxLock(DeviceInterface& parent) __TA_ASSERT(parent_->rx_lock())
__TA_REQUIRES(parent.rx_lock()) {
ZX_DEBUG_ASSERT(parent_ == &parent);
}
void AssertParentTxLock(DeviceInterface& parent) __TA_ASSERT(parent_->tx_lock())
__TA_REQUIRES(parent.tx_lock()) {
ZX_DEBUG_ASSERT(parent_ == &parent);
}
// FIDL interface implementation:
void Attach(AttachRequestView request, AttachCompleter::Sync& _completer) override;
void Detach(DetachRequestView request, DetachCompleter::Sync& _completer) override;
void Close(CloseCompleter::Sync& _completer) override;
zx_status_t AttachPort(const netdev::wire::PortId& port_id,
cpp20::span<const netdev::wire::FrameType> frame_types);
zx_status_t DetachPort(const netdev::wire::PortId& port_id);
// Sets the return code for a tx descriptor.
void MarkTxReturnResult(uint16_t descriptor, zx_status_t status);
// Returns tx descriptors to the session client.
void ReturnTxDescriptors(const uint16_t* descriptors, size_t count);
// Signals the session thread to stop servicing the session channel and FIFOs.
// When the session thread is finished, it notifies the DeviceInterface parent through
// `NotifyDeadSession`.
void Kill();
// Loads rx descriptors into the provided session transaction, fetching more from the rx FIFO if
// needed.
zx_status_t LoadRxDescriptors(RxQueue::SessionTransaction& transact);
// Sets the data in the space buffer `buff` to region described by `descriptor_index`.
zx_status_t FillRxSpace(uint16_t descriptor_index,
fuchsia_hardware_network_driver::wire::RxSpaceBuffer* buff);
// Completes rx for a single frame described by `frame_info`.
//
// Returns true if the buffers comprising `frame_info` can immediately be reused.
bool CompleteRx(const RxFrameInfo& frame_info) __TA_REQUIRES_SHARED(parent_->control_lock())
__TA_REQUIRES(parent_->rx_lock());
// Completes rx by copying the data from another session into one of this session's available rx
// buffers.
//
// Returns true iff the frame information was enqueued in the session rx queue.
bool CompleteRxWith(const Session& owner, const RxFrameInfo& frame_info)
__TA_REQUIRES_SHARED(parent_->control_lock()) __TA_REQUIRES(parent_->rx_lock());
// Marks a single rx space buffer as complete, but does not consume it as it was unfulfilled.
// Returns true iff the session is active and space buffers can be reused.
bool CompleteUnfulfilledRx() __TA_REQUIRES(parent_->rx_lock());
// Copies data from a tx frame from another session into one of this session's available rx
// buffers.
bool ListenFromTx(const Session& owner, uint16_t owner_index) __TA_REQUIRES(parent_->rx_lock())
__TA_REQUIRES_SHARED(parent_->control_lock());
// Commits pending rx buffers, sending them back to the session client.
void CommitRx() __TA_REQUIRES(parent_->rx_lock());
// Returns true iff the session is subscribed to frame_type on port.
bool IsSubscribedToFrameType(uint8_t port, netdev::wire::FrameType frame_type)
__TA_REQUIRES_SHARED(parent_->control_lock());
inline void TxTaken() { in_flight_tx_++; }
inline void RxTaken() { in_flight_rx_++; }
inline void StopRx() __TA_REQUIRES(parent_->rx_lock()) { rx_valid_ = false; }
[[nodiscard]] inline bool ShouldDestroy() {
if (in_flight_rx_ == 0 && in_flight_tx_ == 0) {
bool expect = false;
// Only ever return true for ShouldDestroy once so the caller can schedule destruction
// asynchronously after ShouldDestroy returns true and have a guarantee that it won't be
// possible to schedule destruction for the same object twice.
return scheduled_destruction_.compare_exchange_strong(expect, true);
}
return false;
}
const fbl::RefPtr<RefCountedFifo>& rx_fifo() { return fifo_rx_; }
const zx::fifo& tx_fifo() const { return fifo_tx_; }
const char* name() const { return name_.data(); }
bool IsDying() const __TA_REQUIRES_SHARED(parent_->control_lock()) { return dying_; }
// Notifies session of port destruction.
//
// Returns true iff the session should be stopped after detaching from the port.
bool OnPortDestroyed(uint8_t port_id) __TA_REQUIRES(parent_->control_lock());
// Sets the internal references to the data VMO.
//
// Must only be called when the Session hasn't yet been allocated a VMO id, will abort otherwise.
void SetDataVmo(uint8_t vmo_id, DataVmoStore::StoredVmo* vmo);
// Clears internal references to data VMO, returning the vmo_id that was associated with this
// session.
uint8_t ClearDataVmo();
// Fetch tx descriptors from the FIFO and queue them in the parent |DeviceInterface|'s TxQueue.
zx_status_t FetchTx(TxQueue::SessionTransaction& transaction)
__TA_EXCLUDES(parent_->control_lock(), parent_->rx_lock()) __TA_REQUIRES(parent_->tx_lock());
// Binds |channel| to this session. Must only be called once.
void Bind(fidl::ServerEnd<netdev::Session> channel);
private:
inline void RxReturned(size_t count) { ZX_ASSERT(in_flight_rx_.fetch_sub(count) >= count); }
inline void TxReturned(size_t count) { ZX_ASSERT(in_flight_tx_.fetch_sub(count) >= count); }
Session(async_dispatcher_t* dispatcher, netdev::wire::SessionInfo& info, fidl::StringView name,
DeviceInterface* parent);
zx::result<netdev::wire::Fifos> Init();
void OnUnbind(fidl::UnbindInfo info, fidl::ServerEnd<netdev::Session> channel);
// Detaches a port from the session.
//
// If |salt| is provided, only succeeds if |salt| matches currently attached port's value.
//
// Returns zx::ok(true) if the session was attached to the port and the session transitioned to
// paused.
//
// Returns zx::ok(false) if the session was attached to the port but it remains running attached
// to other ports.
//
// Returns zx::error otherwise.
zx::result<bool> DetachPortLocked(uint8_t port_id, std::optional<uint8_t> salt)
__TA_REQUIRES(parent_->control_lock());
buffer_descriptor_t* checked_descriptor(uint16_t index);
const buffer_descriptor_t* checked_descriptor(uint16_t index) const;
buffer_descriptor_t& descriptor(uint16_t index);
const buffer_descriptor_t& descriptor(uint16_t index) const;
cpp20::span<uint8_t> data_at(uint64_t offset, uint64_t len) const;
// Loads a completed rx buffer information back into the descriptor with the provided index.
zx_status_t LoadRxInfo(const RxFrameInfo& info) __TA_REQUIRES(parent_->rx_lock());
// Loads all rx descriptors that are already available into the given transaction.
bool LoadAvailableRxDescriptors(RxQueue::SessionTransaction& transact);
// Fetches rx descriptors from the rx FIFO.
zx_status_t FetchRxDescriptors() __TA_REQUIRES(parent_->rx_lock());
async_dispatcher_t* const dispatcher_;
const std::array<char, netdev::wire::kMaxSessionName + 1> name_;
// `MAX_VMOS` is used as a marker for invalid VMO identifier.
// The destructor checks that vmo_id is set to `MAX_VMOS`, which verifies that `ReleaseDataVmo`
// was called before destruction.
uint8_t vmo_id_ = MAX_VMOS;
// Unowned pointer to data VMO stored in DeviceInterface.
// Set by Session::Create.
DataVmoStore::StoredVmo* data_vmo_ = nullptr;
std::optional<fidl::ServerBindingRef<netdev::Session>> binding_;
// The control channel is only set by the session teardown process if an epitaph must be sent when
// all the buffers are properly reclaimed. It is set to the channel that was previously bound in
// the `binding_` Server.
std::optional<fidl::ServerEnd<netdev::Session>> control_channel_;
const zx::vmo vmo_descriptors_;
fzl::VmoMapper descriptors_;
fbl::RefPtr<RefCountedFifo> fifo_rx_;
zx::fifo fifo_tx_;
std::atomic<bool> paused_;
const uint16_t descriptor_count_;
const uint64_t descriptor_length_;
const netdev::wire::SessionFlags flags_;
// AttachedPorts information. Parent device is responsible for detaching ports from sessions
// before destroying them.
std::array<std::optional<AttachedPort>, MAX_PORTS> attached_ports_
__TA_GUARDED(parent_->control_lock());
// Pointer to parent network device, not owned.
DeviceInterface* const parent_;
std::unique_ptr<uint16_t[]> rx_return_queue_ __TA_GUARDED(parent_->rx_lock());
size_t rx_return_queue_count_ __TA_GUARDED(parent_->rx_lock()) = 0;
std::unique_ptr<uint16_t[]> rx_avail_queue_ __TA_GUARDED(parent_->rx_lock());
size_t rx_avail_queue_count_ __TA_GUARDED(parent_->rx_lock()) = 0;
bool rx_valid_ __TA_GUARDED(parent_->rx_lock()) = true;
// True if the session is currently being destroyed, i.e. session is unbound
// and waiting for its buffers to be returned from the device.
bool dying_ __TA_GUARDED(parent_->control_lock()) = false;
std::atomic<size_t> in_flight_tx_ = 0;
std::atomic<size_t> in_flight_rx_ = 0;
std::atomic<bool> scheduled_destruction_ = false;
std::optional<TxQueue::SessionKey> tx_ticket_ __TA_GUARDED(parent_->tx_lock());
DISALLOW_COPY_ASSIGN_AND_MOVE(Session);
};
} // namespace network::internal
#endif // SRC_CONNECTIVITY_NETWORK_DRIVERS_NETWORK_DEVICE_DEVICE_SESSION_H_