| // 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_DEVICE_INTERFACE_H_ |
| #define SRC_CONNECTIVITY_NETWORK_DRIVERS_NETWORK_DEVICE_DEVICE_DEVICE_INTERFACE_H_ |
| |
| #include <fidl/fuchsia.hardware.network.driver/cpp/driver/fidl.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/fit/function.h> |
| |
| #include "data_structs.h" |
| #include "definitions.h" |
| #include "device_port.h" |
| #include "diagnostics_service.h" |
| #include "port_watcher.h" |
| #include "public/locks.h" |
| #include "public/network_device.h" |
| |
| namespace network::testing { |
| class NetworkDeviceTest; |
| class FakeNetworkDeviceImpl; |
| namespace banjo { |
| class FakeNetworkDeviceImpl; |
| } |
| } // namespace network::testing |
| |
| namespace network::internal { |
| class RxQueue; |
| class RxSessionTransaction; |
| |
| class TxQueue; |
| |
| class Session; |
| class AttachedPort; |
| using SessionList = fbl::SizedDoublyLinkedList<std::unique_ptr<Session>>; |
| |
| struct RefCountedFifo : public fbl::RefCounted<RefCountedFifo> { |
| zx::fifo fifo; |
| }; |
| |
| // Contains information about a filled Rx descriptor. |
| // |
| // Used to convey fulfilled rx frames in terms of descriptor indices. |
| struct SessionRxBuffer { |
| uint16_t descriptor; |
| uint32_t offset; |
| uint32_t length; |
| }; |
| |
| // Helper struct containing information on an incoming complete rx frame. |
| // |
| // Used to cache common calculation and reduce number of arguments in functions. |
| struct RxFrameInfo { |
| const fuchsia_hardware_network_driver::wire::BufferMetadata& meta; |
| uint8_t port_id_salt; |
| cpp20::span<const SessionRxBuffer> buffers; |
| uint32_t total_length; |
| }; |
| |
| enum class DeviceStatus { STARTING, STARTED, STOPPING, STOPPED }; |
| |
| enum class PendingDeviceOperation { NONE, START, STOP }; |
| |
| class DeviceInterface; |
| |
| class DeviceInterface : public fidl::WireServer<netdev::Device>, |
| public fdf::WireServer<netdriver::NetworkDeviceIfc>, |
| public ::network::NetworkDeviceInterface { |
| public: |
| static zx::result<std::unique_ptr<DeviceInterface>> Create( |
| const DeviceInterfaceDispatchers& dispatchers, |
| std::unique_ptr<NetworkDeviceImplBinder>&& binder); |
| ~DeviceInterface() override; |
| |
| // Public NetworkDevice API. |
| void Teardown(fit::callback<void()> callback) override; |
| zx_status_t Bind(fidl::ServerEnd<netdev::Device> req) override; |
| zx_status_t BindPort(uint8_t port_id, fidl::ServerEnd<netdev::Port> req) override; |
| |
| // NetworkDeviceIfc implementation. |
| void PortStatusChanged(netdriver::wire::NetworkDeviceIfcPortStatusChangedRequest* request, |
| fdf::Arena& arena, PortStatusChangedCompleter::Sync& completer) override; |
| void AddPort(netdriver::wire::NetworkDeviceIfcAddPortRequest* request, fdf::Arena& arena, |
| AddPortCompleter::Sync& completer) override; |
| void RemovePort(netdriver::wire::NetworkDeviceIfcRemovePortRequest* request, fdf::Arena& arena, |
| RemovePortCompleter::Sync& completer) override; |
| void CompleteRx(netdriver::wire::NetworkDeviceIfcCompleteRxRequest* request, fdf::Arena& arena, |
| CompleteRxCompleter::Sync& completer) override; |
| void CompleteTx(netdriver::wire::NetworkDeviceIfcCompleteTxRequest* request, fdf::Arena& arena, |
| CompleteTxCompleter::Sync& completer) override; |
| void Snoop(netdriver::wire::NetworkDeviceIfcSnoopRequest* request, fdf::Arena& arena, |
| SnoopCompleter::Sync& completer) override; |
| |
| uint16_t rx_fifo_depth() const; |
| uint16_t tx_fifo_depth() const; |
| |
| // Returns the device-owned buffer count threshold at which we should trigger RxQueue work. If the |
| // number of buffers on device is less than or equal to the threshold, we should attempt to fetch |
| // more buffers. |
| uint16_t rx_notify_threshold() const { return device_info_.rx_threshold().value_or(0); } |
| |
| TxQueue& tx_queue() { return *tx_queue_; } |
| |
| SharedLock& control_lock() __TA_RETURN_CAPABILITY(control_lock_) { return control_lock_; } |
| fbl::Mutex& rx_lock() __TA_RETURN_CAPABILITY(rx_lock_) { return rx_lock_; } |
| fbl::Mutex& tx_lock() __TA_RETURN_CAPABILITY(tx_lock_) { return tx_lock_; } |
| const netdriver::DeviceImplInfo& info() { return device_info_; } |
| |
| // Loads rx path descriptors from the primary session into a session transaction. |
| zx_status_t LoadRxDescriptors(RxSessionTransaction& transact) __TA_REQUIRES_SHARED(control_lock_); |
| |
| // Operates workflow for when a session is started. If the session is eligible to take over the |
| // primary spot, it'll be elected the new primary session. If there was no primary session before, |
| // the data path will be started BEFORE the new session is elected as primary, |
| void SessionStarted(Session& session) __TA_RELEASE(control_lock_); |
| // Operates workflow for when a session is stopped. If there's another session that is eligible to |
| // take over the primary spot, it'll be elected the new primary session. Otherwise, the data path |
| // will be stopped. |
| void SessionStopped(Session& session) __TA_RELEASE(control_lock_); |
| |
| // If a primary session exists, primary_rx_fifo returns a reference-counted pointer to the primary |
| // session's Rx FIFO. Otherwise, the returned pointer is null. |
| fbl::RefPtr<RefCountedFifo> primary_rx_fifo(); |
| |
| // Commits all pending rx buffers in all active sessions. |
| void CommitAllSessions() __TA_REQUIRES_SHARED(control_lock_) __TA_REQUIRES(rx_lock_); |
| // Copies the received data described by `buff` to all sessions other than `owner`. |
| void CopySessionData(const Session& owner, const RxFrameInfo& frame_info) |
| __TA_REQUIRES_SHARED(control_lock_) __TA_REQUIRES(rx_lock_); |
| // Notifies all listening sessions of a new tx transaction from session `owner` and descriptor |
| // `owner_index`. |
| void ListenSessionData(const Session& owner, cpp20::span<const uint16_t> descriptors) |
| __TA_REQUIRES(tx_lock_) __TA_EXCLUDES(control_lock_, rx_lock_); |
| |
| // Notifies that a batch of Tx frames has been returned. |
| // |
| // If was_full is true, all active sessions are notified that device tx space has freed up. |
| // Checks if dead sessions are ready to be destroyed due to buffers returning. |
| void NotifyTxReturned(bool was_full); |
| // Sends the provided space buffers in `rx` to the device implementation. |
| void QueueRxSpace(cpp20::span<netdriver::wire::RxSpaceBuffer> rx) |
| __TA_EXCLUDES(control_lock_, rx_lock_, tx_lock_); |
| // Sends the provided transmit buffers in `tx` to the device implementation. |
| void QueueTx(cpp20::span<netdriver::wire::TxBuffer> tx) |
| __TA_EXCLUDES(control_lock_, rx_lock_, tx_lock_); |
| bool IsDataPlaneOpen() __TA_REQUIRES_SHARED(control_lock_); |
| |
| // Called by sessions when they're no longer running. If the dead session has any outstanding |
| // buffers with the device implementation, it'll be kept in `dead_sessions_` until all the buffers |
| // are safely returned and we own all the buffers again. |
| void NotifyDeadSession(Session& dead_session); |
| |
| // FIDL protocol implementation. |
| void GetInfo(GetInfoCompleter::Sync& completer) override; |
| void OpenSession(OpenSessionRequestView request, OpenSessionCompleter::Sync& completer) override; |
| void GetPort(GetPortRequestView request, GetPortCompleter::Sync& _completer) override; |
| void GetPortWatcher(GetPortWatcherRequestView request, |
| GetPortWatcherCompleter::Sync& _completer) override; |
| void Clone(CloneRequestView request, CloneCompleter::Sync& _completer) override; |
| |
| // Returns the current port salt for the provided base port ID. |
| // |
| // If the port with |base_id| does not currently exist, returns the value of |
| // the previously existing port with the same |base_id| or the initial salt |
| // value. |
| uint8_t GetPortSalt(uint8_t base_id) __TA_REQUIRES_SHARED(control_lock_) { |
| return ports_[base_id].salt; |
| } |
| |
| // Notifies of |frame_length| bytes received on port with |base_id|. |
| void NotifyPortRxFrame(uint8_t base_id, uint64_t frame_length) |
| __TA_REQUIRES_SHARED(control_lock_); |
| |
| // Acquires a port for use in a Session. |
| // |
| // Sessions are notified of ports that are no longer safe to use by the DeviceInterface through |
| // Session::DetachPort. |
| // |
| // NB: The validity of the returned AttachedPort is not really guaranteed by the type system, but |
| // by the fact that DeviceInterface will detach all ports from sessions before continuing. |
| zx::result<AttachedPort> AcquirePort(netdev::wire::PortId port_id, |
| cpp20::span<const netdev::wire::FrameType> rx_frame_types) |
| __TA_REQUIRES(control_lock_); |
| |
| // Event observer hook for Rx queue packets. |
| void NotifyRxQueuePacket(uint64_t key); |
| // Event observer hook for Tx complete. |
| void NotifyTxComplete(); |
| |
| DiagnosticsService& diagnostics() { return diagnostics_; } |
| |
| private: |
| friend testing::NetworkDeviceTest; |
| friend testing::FakeNetworkDeviceImpl; |
| friend testing::banjo::FakeNetworkDeviceImpl; |
| |
| // Helper class to keep track of clients bound to DeviceInterface. |
| class Binding : public fbl::DoublyLinkedListable<std::unique_ptr<Binding>> { |
| public: |
| static zx_status_t Bind(DeviceInterface* interface, fidl::ServerEnd<netdev::Device> channel) |
| __TA_REQUIRES(interface->control_lock_); |
| void Unbind(); |
| |
| private: |
| Binding() = default; |
| std::optional<fidl::ServerBindingRef<netdev::Device>> binding_; |
| }; |
| using BindingList = fbl::SizedDoublyLinkedList<std::unique_ptr<Binding>>; |
| |
| enum class TeardownState { |
| RUNNING, |
| BINDINGS, |
| PORT_WATCHERS, |
| PORTS, |
| SESSIONS, |
| DEVICE_IMPL, |
| IFC_BINDING, |
| BINDER, |
| FINISHED |
| }; |
| |
| zx_status_t Init(std::unique_ptr<NetworkDeviceImplBinder>&& binder); |
| explicit DeviceInterface(const DeviceInterfaceDispatchers& dispatchers); |
| |
| // Starts the data path with the device implementation. |
| void StartDevice() __TA_EXCLUDES(control_lock_, tx_lock_, rx_lock_); |
| void StartDeviceLocked() __TA_RELEASE(control_lock_) __TA_EXCLUDES(tx_lock_, rx_lock_); |
| // Stops the data path with the device implementation. |
| // |
| // If continue_teardown is provided, teardown continuation will be attempted before notifying the |
| // underlying device of stoppage. |
| void StopDevice(std::optional<TeardownState> continue_teardown = std::nullopt) |
| __TA_RELEASE(control_lock_) __TA_EXCLUDES(tx_lock_, rx_lock_); |
| // Starts the device implementation with `DeviceStarted` as its callback. |
| void StartDeviceInner() __TA_EXCLUDES(control_lock_); |
| // Stops the device implementation with `DeviceStopped` as its callback. |
| void StopDeviceInner() __TA_EXCLUDES(control_lock_); |
| // Helper inner function to stop a session. |
| // |
| // Returns true if the device should be stopped. |
| bool SessionStoppedInner(Session& session) __TA_REQUIRES(control_lock_); |
| |
| // Callback given to the device implementation for the `Start` call. The data path is considered |
| // open only once the device is started. |
| void DeviceStarted() __TA_RELEASE(control_lock_); |
| // Callback given to the device implementation for the `Stop` call. All outstanding buffers are |
| // automatically reclaimed once the device is considered stopped. If a teardown is pending, |
| // `DeviceStopped` will complete the teardown BEFORE all buffers are reclaimed and all the |
| // sessions are destroyed. |
| void DeviceStopped(); |
| |
| PendingDeviceOperation SetDeviceStatus(DeviceStatus status) __TA_REQUIRES(control_lock_); |
| |
| // Notifies the device implementation that the VMO used by the provided session will no longer be |
| // used. It is called right before sessions are destroyed. |
| // ReleaseVMO acquires the vmos_lock_ internally, so we mark it as excluding the vmos_lock_. |
| void ReleaseVmo(Session& session, fit::callback<void()>&& on_complete) |
| __TA_REQUIRES(control_lock_); |
| |
| // Continues a teardown process, if one is running. |
| // |
| // The provided state is the expected state that the teardown process is in. If the given state is |
| // not the current teardown state, no processing will happen. Otherwise, the teardown process will |
| // continue if the pre-conditions to move between teardown states are met. |
| // |
| // Returns true if the teardown is completed and execution should be stopped. |
| // ContinueTeardown is marked with many thread analysis lock exclusions so it can acquire those |
| // locks internally and evaluate the teardown progress. |
| bool ContinueTeardown(TeardownState state) __TA_RELEASE(control_lock_) |
| __TA_EXCLUDES(tx_lock_, rx_lock_); |
| |
| // Calls f with a const std::unique_ptr<DevicePort>& to the DevicePort referenced by port_id |
| // or nullptr if no ports with that id are installed. |
| // |
| // Returns the value returned by the call to f. |
| // |
| // It is unsafe to use the provided DevicePort outside of the scope of the callback f. |
| template <typename F> |
| auto WithPort(uint8_t port_id, F f) __TA_REQUIRES_SHARED(control_lock_) { |
| if (port_id >= ports_.size()) { |
| const std::unique_ptr<DevicePort> null_port; |
| return f(null_port); |
| } |
| return f(ports_[port_id].port); |
| } |
| void OnPortTeardownComplete(DevicePort& port); |
| |
| // Destroys all dead sessions that report they can be destroyed through `Session::CanDestroy`. |
| void PruneDeadSessions() __TA_REQUIRES_SHARED(control_lock_); |
| // Notifies all sessions that the transmit queue has available spots to take in transmit frames. |
| void NotifyTxQueueAvailable() __TA_REQUIRES_SHARED(control_lock_); |
| |
| zx_status_t CanCreatePortWithId(uint8_t port_id) __TA_REQUIRES(control_lock_); |
| |
| netdriver::DeviceImplInfo device_info_; |
| DiagnosticsService diagnostics_; |
| const DeviceInterfaceDispatchers dispatchers_; |
| // Only used to keep a network device shim alive during the device's lifetime. |
| std::unique_ptr<NetworkDeviceImplBinder> binder_; |
| std::optional<fdf::ServerBindingRef<netdriver::NetworkDeviceIfc>> ifc_binding_; |
| fdf::WireSharedClient<netdriver::NetworkDeviceImpl> device_impl_; |
| std::array<netdev::wire::RxAcceleration, netdev::wire::kMaxAccelFlags> accel_rx_; |
| std::array<netdev::wire::TxAcceleration, netdev::wire::kMaxAccelFlags> accel_tx_; |
| |
| std::unique_ptr<Session> primary_session_ __TA_GUARDED(control_lock_); |
| SessionList sessions_ __TA_GUARDED(control_lock_); |
| uint32_t active_primary_sessions_ __TA_GUARDED(control_lock_) = 0; |
| |
| struct PortSlot { |
| std::unique_ptr<DevicePort> port; |
| uint8_t salt; |
| }; |
| std::array<PortSlot, MAX_PORTS> ports_ __TA_GUARDED(control_lock_); |
| |
| SessionList dead_sessions_ __TA_GUARDED(control_lock_); |
| |
| // We don't need to keep any data associated with the VMO ids, we use the slab to guarantee |
| // non-overlapping unique identifiers within a set of valid IDs. |
| DataVmoStore vmo_store_ __TA_GUARDED(control_lock_); |
| BindingList bindings_ __TA_GUARDED(control_lock_); |
| PortWatcher::List port_watchers_ __TA_GUARDED(control_lock_); |
| |
| TeardownState teardown_state_ __TA_GUARDED(control_lock_) = TeardownState::RUNNING; |
| fit::callback<void()> teardown_callback_ __TA_GUARDED(control_lock_); |
| |
| PendingDeviceOperation pending_device_op_ = PendingDeviceOperation::NONE; |
| std::atomic_bool has_listen_sessions_ = false; |
| |
| std::unique_ptr<TxQueue> tx_queue_; |
| std::unique_ptr<RxQueue> rx_queue_; |
| |
| DeviceStatus device_status_ __TA_GUARDED(control_lock_) = DeviceStatus::STOPPED; |
| |
| fbl::Mutex rx_lock_; |
| fbl::Mutex tx_lock_ __TA_ACQUIRED_AFTER(rx_lock_); |
| SharedLock control_lock_ __TA_ACQUIRED_AFTER(tx_lock_, rx_lock_); |
| |
| // Event hooks used in tests: |
| fit::function<void(const char*)> evt_session_started_; |
| // NB: This will be called with control_lock_ held. |
| fit::function<void(const char*)> evt_session_died_; |
| fit::function<void(uint64_t)> evt_rx_queue_packet_; |
| fit::function<void()> evt_tx_complete_; |
| }; |
| |
| } // namespace network::internal |
| |
| #endif // SRC_CONNECTIVITY_NETWORK_DRIVERS_NETWORK_DEVICE_DEVICE_DEVICE_INTERFACE_H_ |