blob: 8e0df09605f074f9fefc59571ee08389c3bce7c9 [file] [log] [blame]
// Copyright 2023 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.
#include "softmac_bridge.h"
#include <fidl/fuchsia.driver.framework/cpp/driver/fidl.h>
#include <fidl/fuchsia.wlan.softmac/cpp/driver/fidl.h>
#include <fidl/fuchsia.wlan.softmac/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/driver/component/cpp/start_completer.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/fdf/cpp/dispatcher.h>
#include <lib/fdf/dispatcher.h>
#include <lib/fit/function.h>
#include <lib/sync/cpp/completion.h>
#include <lib/trace-engine/types.h>
#include <lib/zx/result.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <wlan/drivers/fidl_bridge.h>
#include <wlan/drivers/log.h>
#include "softmac_ifc_bridge.h"
#include "src/connectivity/wlan/drivers/wlansoftmac/rust_driver/c-binding/bindings.h"
namespace wlan::drivers::wlansoftmac {
using ::wlan::drivers::fidl_bridge::FidlErrorToStatus;
using ::wlan::drivers::fidl_bridge::ForwardResult;
SoftmacBridge::SoftmacBridge(fidl::SharedClient<fuchsia_driver_framework::Node> node_client,
fdf::SharedClient<fuchsia_wlan_softmac::WlanSoftmac>&& softmac_client,
std::shared_ptr<std::mutex> ethernet_proxy_lock,
ddk::EthernetIfcProtocolClient* ethernet_proxy,
std::optional<uint32_t>* cached_ethernet_status)
: node_client_(std::move(node_client)),
softmac_client_(std::move(softmac_client)),
ethernet_proxy_lock_(std::move(ethernet_proxy_lock)),
ethernet_proxy_(ethernet_proxy),
cached_ethernet_status_(cached_ethernet_status) {
WLAN_TRACE_DURATION();
}
SoftmacBridge::~SoftmacBridge() { WLAN_TRACE_DURATION(); }
// Start the bridged wlansoftmac driver and forward calls from that driver through this one.
//
// This function starts the bridged driver by posting a task to the current dispatcher.
// The provided `sta_shutdown_handler` will be called on the return value of
// `wlansoftmac_c::start_and_run_bridged_wlansoftmac` called in the posted task.
//
// Note that `wlansoftmac_c::start_and_run_bridged_wlansoftmac` may exit with an error during
// startup of the bridged driver. Thus, `sta_shutdown_handler` cannot depend on any operations
// performed by the bridged driver. In general, the `sta_shutdown_handler` should cause this
// driver to stop if `wlansoftmac_c::start_and_run_bridged_wlansoftmac` returns an error because
// this driver is in a defunct state without a bridged driver.
zx::result<std::unique_ptr<SoftmacBridge>> SoftmacBridge::New(
fidl::SharedClient<fuchsia_driver_framework::Node> node_client,
fdf::StartCompleter start_completer, fit::callback<void(zx_status_t)> shutdown_completer,
fdf::SharedClient<fuchsia_wlan_softmac::WlanSoftmac>&& softmac_client,
std::shared_ptr<std::mutex> ethernet_proxy_lock, ddk::EthernetIfcProtocolClient* ethernet_proxy,
std::optional<uint32_t>* cached_ethernet_status) {
WLAN_TRACE_DURATION();
auto softmac_bridge = std::unique_ptr<SoftmacBridge>(
new SoftmacBridge(std::move(node_client), std::move(softmac_client),
std::move(ethernet_proxy_lock), ethernet_proxy, cached_ethernet_status));
auto softmac_bridge_endpoints = fidl::CreateEndpoints<fuchsia_wlan_softmac::WlanSoftmacBridge>();
if (softmac_bridge_endpoints.is_error()) {
FDF_LOG(ERROR, "Failed to create WlanSoftmacBridge endpoints: %s",
softmac_bridge_endpoints.status_string());
start_completer(zx::error(softmac_bridge_endpoints.error_value()));
return softmac_bridge_endpoints.take_error();
}
{
WLAN_LAMBDA_TRACE_DURATION("WlanSoftmacBridge server binding");
softmac_bridge->softmac_bridge_server_ =
std::make_unique<fidl::ServerBinding<fuchsia_wlan_softmac::WlanSoftmacBridge>>(
fdf::Dispatcher::GetCurrent()->async_dispatcher(),
std::move(softmac_bridge_endpoints->server), softmac_bridge.get(),
[node_client = softmac_bridge->node_client_.Clone()](fidl::UnbindInfo info) mutable {
WLAN_LAMBDA_TRACE_DURATION("WlanSoftmacBridge close_handler");
if (info.is_user_initiated()) {
FDF_LOG(INFO, "WlanSoftmacBridge server closed.");
} else {
FDF_LOG(ERROR, "WlanSoftmacBridge unexpectedly closed: %s",
info.lossy_description());
// Initiate asynchronous teardown of the fuchsia.driver.framework/Node proxy
// to cause the driver framework to stop this driver. Stopping this driver is
// appropriate when the bridge has an abnormal shutdown because otherwise this
// driver would be in an unusable state.
node_client.AsyncTeardown();
}
});
}
auto bridged_start_completer = std::make_unique<fit::callback<void(zx_status_t)>>(
[start_completer = std::move(start_completer)](zx_status_t status) mutable {
WLAN_LAMBDA_TRACE_DURATION("bridged_start_completer");
start_completer(zx::make_result(status));
});
auto bridged_shutdown_completer = std::make_unique<fit::callback<void(zx_status_t)>>(
[shutdown_completer = std::move(shutdown_completer)](zx_status_t status) mutable {
WLAN_LAMBDA_TRACE_DURATION("bridged_shutdown_completer");
shutdown_completer(status);
});
FDF_LOG(INFO, "Starting up Rust WlanSoftmac...");
auto start_result = start_bridged_wlansoftmac(
bridged_start_completer.release(),
[](void* start_completer, zx_status_t status) {
WLAN_LAMBDA_TRACE_DURATION("start_completer");
ZX_ASSERT_MSG(start_completer != nullptr, "Received NULL start_completer.");
// Safety: `start_completer` is now owned by this function, so it's safe
// to cast it to a non-const pointer.
auto bridged_start_completer =
static_cast<fit::callback<void(zx_status_t)>*>(start_completer);
// Skip the check for whether completer has already been
// called. This is the only location where completer is
// called, and its deallocated immediately after. Thus, such a
// check would be a use-after-free violation.
(*bridged_start_completer)(status);
delete bridged_start_completer;
},
bridged_shutdown_completer.release(),
[](void* shutdown_completer, zx_status_t status) {
WLAN_LAMBDA_TRACE_DURATION("shutdown_completer");
ZX_ASSERT_MSG(shutdown_completer != nullptr, "Received NULL shutdown_completer.");
// Safety: `shutdown_completer` is now owned by this function, so it's safe
// to cast it to a non-const pointer.
auto bridged_shutdown_completer =
static_cast<fit::callback<void(zx_status_t)>*>(shutdown_completer);
// Skip the check for whether completer has already been
// called. This is the only location where completer is
// called, and its deallocated immediately after. Thus, such a
// check would be a use-after-free violation.
(*bridged_shutdown_completer)(status);
delete bridged_shutdown_completer;
},
ethernet_rx_t{
.ctx = softmac_bridge.get(),
.transfer = &SoftmacBridge::EthernetRx,
},
wlan_tx_t{
.ctx = softmac_bridge.get(),
.transfer = &SoftmacBridge::WlanTx,
},
softmac_bridge_endpoints->client.TakeHandle().release());
if (start_result != ZX_OK) {
return fit::error(start_result);
}
return fit::success(std::move(softmac_bridge));
}
void SoftmacBridge::StopBridgedDriver(fit::callback<void()> stop_completer) {
WLAN_TRACE_DURATION();
softmac_ifc_bridge_->StopBridgedDriver(std::move(stop_completer));
}
void SoftmacBridge::Query(QueryCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->Query().Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::Query>(completer.ToAsync()));
}
void SoftmacBridge::QueryDiscoverySupport(QueryDiscoverySupportCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->QueryDiscoverySupport().Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::QueryDiscoverySupport>(completer.ToAsync()));
}
void SoftmacBridge::QueryMacSublayerSupport(QueryMacSublayerSupportCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->QueryMacSublayerSupport().Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::QueryMacSublayerSupport>(
completer.ToAsync()));
}
void SoftmacBridge::QuerySecuritySupport(QuerySecuritySupportCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->QuerySecuritySupport().Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::QuerySecuritySupport>(completer.ToAsync()));
}
void SoftmacBridge::QuerySpectrumManagementSupport(
QuerySpectrumManagementSupportCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->QuerySpectrumManagementSupport().Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::QuerySpectrumManagementSupport>(
completer.ToAsync()));
}
void SoftmacBridge::Start(StartRequest& request, StartCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
auto endpoints = fdf::CreateEndpoints<fuchsia_wlan_softmac::WlanSoftmacIfc>();
if (endpoints.is_error()) {
FDF_LOG(ERROR, "Creating end point error: %s", endpoints.status_string());
completer.Reply(fit::error(endpoints.status_value()));
return;
}
auto softmac_ifc_bridge_client_endpoint = std::move(request.ifc_bridge());
// See the `fuchsia.wlan.mlme/WlanSoftmacBridge.Start` documentation about the FFI
// provided by the `ethernet_tx` field.
auto ethernet_tx = reinterpret_cast<const ethernet_tx_t*>( // NOLINT(performance-no-int-to-ptr)
request.ethernet_tx());
// See the `fuchsia.wlan.mlme/WlanSoftmacBridge.Start` documentation about the FFI
// provided by the `wlan_rx` field.
auto wlan_rx = reinterpret_cast<const wlan_rx_t*>( // NOLINT(performance-no-int-to-ptr)
request.wlan_rx());
auto softmac_ifc_bridge = SoftmacIfcBridge::New(node_client_.Clone(), ethernet_tx, wlan_rx,
std::move(endpoints->server),
std::move(softmac_ifc_bridge_client_endpoint));
if (softmac_ifc_bridge.is_error()) {
FDF_LOG(ERROR, "Failed to create SoftmacIfcBridge: %s", softmac_ifc_bridge.status_string());
completer.Reply(fit::error(softmac_ifc_bridge.status_value()));
return;
}
softmac_ifc_bridge_ = *std::move(softmac_ifc_bridge);
fidl::Request<fuchsia_wlan_softmac::WlanSoftmac::Start> fdf_request;
fdf_request.ifc(std::move(endpoints->client));
softmac_client_->Start(std::move(fdf_request))
.Then([completer = completer.ToAsync()](
fdf::Result<fuchsia_wlan_softmac::WlanSoftmac::Start>& result) mutable {
if (result.is_error()) {
auto status = FidlErrorToStatus(result.error_value());
FDF_LOG(ERROR, "Failed getting start result (FIDL error %s)",
zx_status_get_string(status));
completer.Reply(fit::error(status));
} else {
fuchsia_wlan_softmac::WlanSoftmacBridgeStartResponse fidl_response(
std::move(result.value().sme_channel()));
completer.Reply(fit::ok(std::move(fidl_response)));
}
});
}
void SoftmacBridge::SetEthernetStatus(SetEthernetStatusRequest& request,
SetEthernetStatusCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
std::lock_guard<std::mutex> lock(*ethernet_proxy_lock_);
if (ethernet_proxy_->is_valid()) {
ethernet_proxy_->Status(request.status());
} else {
*cached_ethernet_status_ = request.status();
}
completer.Reply();
}
void SoftmacBridge::SetChannel(SetChannelRequest& request, SetChannelCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->SetChannel(request).Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::SetChannel>(completer.ToAsync()));
}
void SoftmacBridge::JoinBss(JoinBssRequest& request, JoinBssCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->JoinBss(request).Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::JoinBss>(completer.ToAsync()));
}
void SoftmacBridge::EnableBeaconing(EnableBeaconingRequest& request,
EnableBeaconingCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->EnableBeaconing(request).Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::EnableBeaconing>(completer.ToAsync()));
}
void SoftmacBridge::DisableBeaconing(DisableBeaconingCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->DisableBeaconing().Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::DisableBeaconing>(completer.ToAsync()));
}
void SoftmacBridge::InstallKey(InstallKeyRequest& request, InstallKeyCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->InstallKey(request).Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::InstallKey>(completer.ToAsync()));
}
void SoftmacBridge::NotifyAssociationComplete(NotifyAssociationCompleteRequest& request,
NotifyAssociationCompleteCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->NotifyAssociationComplete(request).Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::NotifyAssociationComplete>(
completer.ToAsync()));
}
void SoftmacBridge::ClearAssociation(ClearAssociationRequest& request,
ClearAssociationCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->ClearAssociation(request).Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::ClearAssociation>(completer.ToAsync()));
}
void SoftmacBridge::StartPassiveScan(StartPassiveScanRequest& request,
StartPassiveScanCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->StartPassiveScan(request).Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::StartPassiveScan>(completer.ToAsync()));
}
void SoftmacBridge::StartActiveScan(StartActiveScanRequest& request,
StartActiveScanCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->StartActiveScan(request).Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::StartActiveScan>(completer.ToAsync()));
}
void SoftmacBridge::CancelScan(CancelScanRequest& request, CancelScanCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->CancelScan(request).Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::CancelScan>(completer.ToAsync()));
}
void SoftmacBridge::UpdateWmmParameters(UpdateWmmParametersRequest& request,
UpdateWmmParametersCompleter::Sync& completer) {
WLAN_TRACE_DURATION();
softmac_client_->UpdateWmmParameters(request).Then(
ForwardResult<fuchsia_wlan_softmac::WlanSoftmac::UpdateWmmParameters>(completer.ToAsync()));
}
zx::result<> SoftmacBridge::EthernetTx(std::unique_ptr<eth::BorrowedOperation<>> op,
trace_async_id_t async_id) const {
return softmac_ifc_bridge_->EthernetTx(std::move(op), async_id);
}
// Queues a packet for transmission.
//
// The returned status only indicates whether `SoftmacBridge` successfully queued the
// packet and not that the packet was successfully sent.
zx_status_t SoftmacBridge::WlanTx(void* ctx, const uint8_t* payload, size_t payload_size) {
auto self = static_cast<const SoftmacBridge*>(ctx);
WLAN_TRACE_DURATION();
auto fidl_request = fidl::Unpersist<fuchsia_wlan_softmac::WlanTxTransferRequest>(
cpp20::span(payload, payload_size));
if (!fidl_request.is_ok()) {
FDF_LOG(ERROR, "Failed to unpersist WlanTx.Transfer request: %s",
fidl_request.error_value().status_string());
return ZX_ERR_INTERNAL;
}
if (!fidl_request->async_id()) {
FDF_LOG(ERROR, "QueueWlanTx request missing async_id field.");
return ZX_ERR_INTERNAL;
}
auto async_id = fidl_request->async_id().value();
if (!fidl_request->arena()) {
FDF_LOG(ERROR, "QueueWlanTx request missing arena field.");
auto status = ZX_ERR_INTERNAL;
WLAN_TRACE_ASYNC_END_TX(async_id, status);
return status;
}
auto arena = fdf::Arena(reinterpret_cast<fdf_arena_t*>( // NOLINT(performance-no-int-to-ptr)
fidl_request->arena().value()));
if (!fidl_request->packet_address() || !fidl_request->packet_size() ||
!fidl_request->packet_info()) {
FDF_LOG(ERROR, "QueueWlanTx request missing required field(s).");
auto status = ZX_ERR_INTERNAL;
WLAN_TRACE_ASYNC_END_TX(async_id, status);
return status;
}
auto buffer = reinterpret_cast<uint8_t*>( // NOLINT(performance-no-int-to-ptr)
fidl_request->packet_address().value());
if (buffer == nullptr) {
FDF_LOG(ERROR, "QueueWlanTx contains NULL packet address.");
auto status = ZX_ERR_INTERNAL;
WLAN_TRACE_ASYNC_END_TX(async_id, status);
return status;
}
auto buffer_len = fidl_request->packet_size().value();
ZX_DEBUG_ASSERT(buffer_len <= std::numeric_limits<uint16_t>::max());
fuchsia_wlan_softmac::wire::WlanTxPacket fdf_request;
fdf_request.mac_frame = fidl::VectorView<uint8_t>::FromExternal(buffer, buffer_len);
fdf_request.info = fidl::ToWire(arena, fidl_request->packet_info().value());
// Queue the frame to be sent by the vendor driver, but don't block this thread on
// the returned status. Supposing an error preventing transmission beyond this point
// occurred, MLME would handle the failure the same as other transmission failures
// inherent in the 802.11 protocol itself. In general, MLME cannot soley rely on
// detecting transmission failures via this function's return value, so it's
// unnecessary to block the MLME thread any longer once the well-formed packet has
// been sent to the vendor driver.
//
// Unlike other error logging above, it's critical this callback logs an error
// if there is one because the error may otherwise be silently discarded since
// MLME will not receive the error.
self->softmac_client_.wire(arena)
->QueueTx(fdf_request)
.Then(
[arena = std::move(arena), loc = cpp20::source_location::current(), async_id](
fdf::WireUnownedResult<fuchsia_wlan_softmac::WlanSoftmac::QueueTx>& result) mutable {
auto status = result.status();
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to queue frame in the vendor driver: %s",
zx_status_get_string(status));
WLAN_TRACE_ASYNC_END_TX(async_id, status);
} else {
WLAN_TRACE_ASYNC_END_TX(async_id, ZX_OK);
}
});
return ZX_OK;
}
zx_status_t SoftmacBridge::EthernetRx(void* ctx, const uint8_t* payload, size_t payload_size) {
auto self = static_cast<const SoftmacBridge*>(ctx);
WLAN_TRACE_DURATION();
auto fidl_request = fidl::Unpersist<fuchsia_wlan_softmac::EthernetRxTransferRequest>(
cpp20::span(payload, payload_size));
if (!fidl_request.is_ok()) {
FDF_LOG(ERROR, "Failed to unpersist EthernetRx.Transfer request: %s",
fidl_request.error_value().status_string());
return ZX_ERR_INTERNAL;
}
if (!fidl_request->packet_address() || !fidl_request->packet_size()) {
FDF_LOG(ERROR, "EthernetRx.Transfer request missing required field(s).");
return ZX_ERR_INTERNAL;
}
if (fidl_request->packet_size().value() > ETH_FRAME_MAX_SIZE) {
FDF_LOG(ERROR, "Attempted to deliver an ethernet frame of invalid length: %zu",
fidl_request->packet_size().value());
return ZX_ERR_INVALID_ARGS;
}
std::lock_guard<std::mutex> lock(*self->ethernet_proxy_lock_);
if (self->ethernet_proxy_->is_valid()) {
self->ethernet_proxy_->Recv(
reinterpret_cast<const uint8_t*>( // NOLINT(performance-no-int-to-ptr)
fidl_request->packet_address().value()),
fidl_request->packet_size().value(), 0u);
}
return ZX_OK;
}
} // namespace wlan::drivers::wlansoftmac