| // Copyright 2021 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 "third_party/iwlwifi/platform/wlansoftmac-device.h" |
| |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| |
| #include <memory> |
| |
| extern "C" { |
| #include "third_party/iwlwifi/mvm/mvm.h" |
| } // extern "C" |
| |
| #include "third_party/iwlwifi/platform/ieee80211.h" |
| #include "third_party/iwlwifi/platform/mvm-mlme.h" |
| #include "third_party/iwlwifi/platform/mvm-sta.h" |
| #include "third_party/iwlwifi/platform/scoped_utils.h" |
| #include "third_party/iwlwifi/platform/stats.h" |
| |
| #define CHECK_DELETE_IN_PROGRESS_WITHOUT_ERRSYNTAX(mvmvif, resp) \ |
| do { \ |
| if (mvmvif->delete_in_progress) { \ |
| IWL_WARN(mvmvif, "Interface is in the process of being deleted"); \ |
| completer.buffer(arena).Reply(resp); \ |
| return; \ |
| } \ |
| } while (0) |
| |
| #define CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif) \ |
| do { \ |
| if (mvmvif->delete_in_progress) { \ |
| IWL_WARN(mvmvif, "Interface is in the process of being deleted"); \ |
| completer.buffer(arena).ReplyError(ZX_ERR_BAD_STATE); \ |
| return; \ |
| } \ |
| } while (0) |
| |
| namespace wlan::iwlwifi { |
| |
| WlanSoftmacDevice::WlanSoftmacDevice(iwl_trans* drvdata, uint16_t iface_id, |
| struct iwl_mvm_vif* mvmvif) |
| : mvmvif_(mvmvif), drvdata_(drvdata) { |
| vif_.type = NL80211_IFTYPE_STATION; |
| vif_.drv_priv = mvmvif; |
| mac_init(mvmvif_, drvdata_, iface_id); |
| } |
| |
| WlanSoftmacDevice::~WlanSoftmacDevice() { mac_release(mvmvif_); } |
| |
| // Max size of WlanSoftmacQueryResponse. |
| constexpr size_t kWlanSoftmacQueryResponseBufferSize = |
| fidl::MaxSizeInChannel<fuchsia_wlan_softmac::wire::WlanSoftmacQueryResponse, |
| fidl::MessageDirection::kSending>(); |
| |
| void WlanSoftmacDevice::Query(fdf::Arena& arena, QueryCompleter::Sync& completer) { |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| |
| fidl::Arena<kWlanSoftmacQueryResponseBufferSize> table_arena; |
| fuchsia_wlan_softmac::wire::WlanSoftmacQueryResponse resp; |
| zx_status_t status = mac_query(mvmvif_, &resp, table_arena); |
| |
| if (status != ZX_OK) { |
| IWL_ERR(this, "failed query: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| completer.buffer(arena).ReplySuccess(resp); |
| } |
| |
| void WlanSoftmacDevice::QueryDiscoverySupport(fdf::Arena& arena, |
| QueryDiscoverySupportCompleter::Sync& completer) { |
| fuchsia_wlan_common::wire::DiscoverySupport out_resp; |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| mac_query_discovery_support(&out_resp); |
| completer.buffer(arena).ReplySuccess(out_resp); |
| } |
| |
| void WlanSoftmacDevice::QueryMacSublayerSupport(fdf::Arena& arena, |
| QueryMacSublayerSupportCompleter::Sync& completer) { |
| fuchsia_wlan_common::wire::MacSublayerSupport out_resp; |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| mac_query_mac_sublayer_support(&out_resp); |
| completer.buffer(arena).ReplySuccess(out_resp); |
| } |
| |
| void WlanSoftmacDevice::QuerySecuritySupport(fdf::Arena& arena, |
| QuerySecuritySupportCompleter::Sync& completer) { |
| fuchsia_wlan_common::wire::SecuritySupport out_resp; |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| mac_query_security_support(&out_resp); |
| completer.buffer(arena).ReplySuccess(out_resp); |
| } |
| |
| void WlanSoftmacDevice::QuerySpectrumManagementSupport( |
| fdf::Arena& arena, QuerySpectrumManagementSupportCompleter::Sync& completer) { |
| fuchsia_wlan_common::wire::SpectrumManagementSupport out_resp; |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| mac_query_spectrum_management_support(&out_resp); |
| completer.buffer(arena).ReplySuccess(out_resp); |
| } |
| |
| void WlanSoftmacDevice::Start(StartRequestView request, fdf::Arena& arena, |
| StartCompleter::Sync& completer) { |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| zx::channel out_mlme_channel; |
| |
| client_ = fdf::WireSyncClient<fuchsia_wlan_softmac::WlanSoftmacIfc>(std::move(request->ifc)); |
| |
| zx_status_t status = mac_start(mvmvif_, this, (zx_handle_t*)(&out_mlme_channel)); |
| if (status != ZX_OK) { |
| IWL_ERR(this, "failed mac start: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| |
| return; |
| } |
| |
| completer.buffer(arena).ReplySuccess(std::move(out_mlme_channel)); |
| } |
| |
| void WlanSoftmacDevice::Stop(fdf::Arena& arena, StopCompleter::Sync& completer) { |
| // Remove the stop logic from destructor if higher layer calls this function correctly. |
| ap_mvm_sta_.reset(); |
| mac_stop(mvmvif_); |
| completer.buffer(arena).Reply(); |
| } |
| |
| void WlanSoftmacDevice::QueueTx(QueueTxRequestView request, fdf::Arena& arena, |
| QueueTxCompleter::Sync& completer) { |
| iwl_stats_inc(IWL_STATS_CNT_DATA_FROM_MLME); |
| // Delayed transmission is never used right now. |
| if (ap_mvm_sta_ == nullptr) { |
| IWL_ERR(this, "No ap_sta is found."); |
| completer.buffer(arena).ReplyError(ZX_ERR_BAD_STATE); |
| return; |
| } |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| zx_status_t status = ZX_OK; |
| const auto& packet = request->packet; |
| if (packet.mac_frame.count() > WLAN_MSDU_MAX_LEN) { |
| IWL_ERR(mvmvif_, "Frame size is to large (%lu). expect less than %lu.\n", |
| packet.mac_frame.count(), WLAN_MSDU_MAX_LEN); |
| completer.buffer(arena).ReplyError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| ieee80211_mac_packet mac_packet = {}; |
| mac_packet.common_header = |
| reinterpret_cast<const ieee80211_frame_header*>(packet.mac_frame.data()); |
| mac_packet.header_size = ieee80211_get_header_len(mac_packet.common_header); |
| if (mac_packet.header_size > packet.mac_frame.count()) { |
| IWL_ERR(mvmvif_, "TX packet header size %zu too large for data size %zu\n", |
| mac_packet.header_size, packet.mac_frame.count()); |
| completer.buffer(arena).ReplyError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| mac_packet.body = packet.mac_frame.data() + mac_packet.header_size; |
| mac_packet.body_size = packet.mac_frame.count() - mac_packet.header_size; |
| if (ieee80211_pkt_is_protected(mac_packet.common_header)) { |
| switch (ieee80211_get_frame_type(mac_packet.common_header)) { |
| case ieee80211_frame_type::IEEE80211_FRAME_TYPE_MGMT: |
| mac_packet.info.control.hw_key = ap_mvm_sta_->GetKey(WLAN_KEY_TYPE_IGTK); |
| break; |
| case ieee80211_frame_type::IEEE80211_FRAME_TYPE_DATA: |
| mac_packet.info.control.hw_key = ap_mvm_sta_->GetKey(WLAN_KEY_TYPE_PAIRWISE); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| auto lock = std::lock_guard(mvmvif_->mvm->mutex); |
| status = iwl_mvm_mac_tx(mvmvif_, ap_mvm_sta_->iwl_mvm_sta(), &mac_packet); |
| if (status != ZX_OK) { |
| IWL_ERR(this, "failed mac tx: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| // Delayed transmission is never used right now, setting enqueue_pending to false; |
| completer.buffer(arena).ReplySuccess(); |
| } |
| |
| // Reject the request that firmware doesn't allow. See fxb/89911 for more context. |
| bool WlanSoftmacDevice::IsValidChannel(const fuchsia_wlan_common::wire::WlanChannel* channel) { |
| if (channel->cbw == fuchsia_wlan_common::ChannelBandwidth::kCbw40 || |
| channel->cbw == fuchsia_wlan_common::ChannelBandwidth::kCbw40Below) { |
| if (channel->primary >= 10 && channel->primary <= 14) { |
| IWL_WARN(mvmvif_, "The 40%sMHz bandwidth is not supported on the channel %d.\n", |
| channel->cbw == fuchsia_wlan_common::ChannelBandwidth::kCbw40Below ? "-" : "", |
| channel->primary); |
| return false; |
| } |
| } |
| |
| if (channel->primary <= 14 && channel->cbw >= fuchsia_wlan_common::ChannelBandwidth::kCbw80) { |
| IWL_WARN(mvmvif_, "The 80+MHz bandwidth is not supported on the 2.4GHz band (channel %d).\n", |
| channel->primary); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void WlanSoftmacDevice::SetChannel(SetChannelRequestView request, fdf::Arena& arena, |
| SetChannelCompleter::Sync& completer) { |
| zx_status_t status = ZX_OK; |
| |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| |
| if (!IsValidChannel(&request->channel())) { |
| IWL_WARN(this, "Invalid channel."); |
| completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED); |
| return; |
| } |
| |
| // If the AP sta already exists, it probably was left from the previous association attempt. |
| // Remove it first. |
| wlan_channel_t channel = { |
| .primary = request->channel().primary, |
| .secondary80 = request->channel().secondary80, |
| }; |
| |
| switch (request->channel().cbw) { |
| case fuchsia_wlan_common::wire::ChannelBandwidth::kCbw20: |
| channel.cbw = CHANNEL_BANDWIDTH_CBW20; |
| break; |
| case fuchsia_wlan_common::wire::ChannelBandwidth::kCbw40: |
| channel.cbw = CHANNEL_BANDWIDTH_CBW40; |
| break; |
| case fuchsia_wlan_common::wire::ChannelBandwidth::kCbw40Below: |
| channel.cbw = CHANNEL_BANDWIDTH_CBW40BELOW; |
| break; |
| case fuchsia_wlan_common::wire::ChannelBandwidth::kCbw80: |
| channel.cbw = CHANNEL_BANDWIDTH_CBW80; |
| break; |
| case fuchsia_wlan_common::wire::ChannelBandwidth::kCbw160: |
| channel.cbw = CHANNEL_BANDWIDTH_CBW160; |
| break; |
| case fuchsia_wlan_common::wire::ChannelBandwidth::kCbw80P80: |
| channel.cbw = CHANNEL_BANDWIDTH_CBW80P80; |
| break; |
| default: |
| IWL_ERR(this, "Bandwidth (%u) is not supported", |
| static_cast<uint32_t>(request->channel().cbw)); |
| completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED); |
| return; |
| } |
| |
| status = mac_set_channel(mvmvif_, &channel); |
| if (status != ZX_OK) { |
| IWL_ERR(this, "failed mac set channel: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| completer.buffer(arena).ReplySuccess(); |
| } // namespace wlan::iwlwifi |
| |
| void WlanSoftmacDevice::JoinBss(JoinBssRequestView request, fdf::Arena& arena, |
| JoinBssCompleter::Sync& completer) { |
| zx_status_t status = ZX_OK; |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| |
| // Ensure the request has the necessary fields |
| if (!request->join_request.has_bssid()) { |
| IWL_ERR(this, "bssid info not available in join request\n"); |
| return; |
| } |
| |
| // Unassoc if it was assocated. |
| if (ap_mvm_sta_ != nullptr) { |
| IWL_INFO(this, "AP sta already exist. Unassociate it first.\n"); |
| |
| if ((status = mac_leave_bss(mvmvif_)) != ZX_OK) { |
| IWL_ERR(this, "failed mac unconfigure bss: %s\n", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| mvmvif_->bss_conf.assoc = false; |
| ap_mvm_sta_.reset(); |
| } |
| |
| if ((status = mac_join_bss(&vif_, &request->join_request)) != ZX_OK) { |
| IWL_ERR(this, "failed mac configure bss: %s\n", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(mvmvif_->mac_role == WLAN_MAC_ROLE_CLIENT); |
| std::unique_ptr<MvmSta> ap_mvm_sta; |
| if ((status = MvmSta::Create(mvmvif_, request->join_request.bssid().begin(), &ap_mvm_sta)) != |
| ZX_OK) { |
| IWL_ERR(this, "failed creating MvmSta: %s\n", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| ap_mvm_sta_ = std::move(ap_mvm_sta); |
| completer.buffer(arena).ReplySuccess(); |
| } |
| |
| void WlanSoftmacDevice::EnableBeaconing(EnableBeaconingRequestView request, fdf::Arena& arena, |
| EnableBeaconingCompleter::Sync& completer) { |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| zx_status_t status = mac_enable_beaconing(mvmvif_, request); |
| if (status != ZX_OK) { |
| // Expected for now since this is not supported yet in iwlwifi driver. |
| IWL_ERR(this, "failed mac enable beaconing: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| completer.buffer(arena).ReplySuccess(); |
| } |
| |
| void WlanSoftmacDevice::DisableBeaconing(fdf::Arena& arena, |
| DisableBeaconingCompleter::Sync& completer) { |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| zx_status_t status = mac_disable_beaconing(mvmvif_); |
| if (status != ZX_OK) { |
| // Expected for now since this is not supported yet in iwlwifi driver. |
| IWL_ERR(this, "failed mac disable beaconing: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| completer.buffer(arena).ReplySuccess(); |
| } |
| |
| void WlanSoftmacDevice::InstallKey(InstallKeyRequestView request, fdf::Arena& arena, |
| InstallKeyCompleter::Sync& completer) { |
| if (ap_mvm_sta_ == nullptr) { |
| IWL_ERR(this, "Ap sta does not exist."); |
| completer.buffer(arena).ReplyError(ZX_ERR_BAD_STATE); |
| return; |
| } |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| zx_status_t status = ap_mvm_sta_->InstallKey(request); |
| if (status != ZX_OK) { |
| IWL_ERR(this, "failed InstallKey: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| completer.buffer(arena).ReplySuccess(); |
| } |
| |
| void WlanSoftmacDevice::NotifyAssociationComplete( |
| NotifyAssociationCompleteRequestView request, fdf::Arena& arena, |
| NotifyAssociationCompleteCompleter::Sync& completer) { |
| if (ap_mvm_sta_ == nullptr) { |
| IWL_ERR(this, "Ap sta does not exist."); |
| completer.buffer(arena).ReplyError(ZX_ERR_BAD_STATE); |
| return; |
| } |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| zx_status_t status = mac_notify_association_complete(mvmvif_, &request->assoc_cfg); |
| if (status != ZX_OK) { |
| IWL_ERR(this, "failed mac configure assoc: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| completer.buffer(arena).ReplySuccess(); |
| } |
| |
| void WlanSoftmacDevice::ClearAssociation(ClearAssociationRequestView request, fdf::Arena& arena, |
| ClearAssociationCompleter::Sync& completer) { |
| zx_status_t status = ZX_OK; |
| |
| if (!request->has_peer_addr()) { |
| IWL_ERR(this, "Request does not contain peer address."); |
| completer.buffer(arena).ReplyError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| if (ap_mvm_sta_ == nullptr) { |
| IWL_ERR(this, "Ap sta does not exist."); |
| completer.buffer(arena).ReplyError(ZX_ERR_BAD_STATE); |
| return; |
| } |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| |
| // Mark the station is no longer associated. This must be set before we start operating on the STA |
| // instance. |
| mvmvif_->bss_conf.assoc = false; |
| ap_mvm_sta_.reset(); |
| |
| if ((status = mac_clear_association(mvmvif_, request->peer_addr().data())) != ZX_OK) { |
| IWL_ERR(this, "failed clear assoc: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| completer.buffer(arena).ReplySuccess(); |
| } |
| |
| void WlanSoftmacDevice::StartPassiveScan(StartPassiveScanRequestView request, fdf::Arena& arena, |
| StartPassiveScanCompleter::Sync& completer) { |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| uint64_t out_scan_id; |
| zx_status_t status = mac_start_passive_scan(mvmvif_, request, &out_scan_id); |
| if (status != ZX_OK) { |
| IWL_ERR(this, "failed start passive scan: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| fidl::Arena fidl_arena; |
| auto builder = |
| fuchsia_wlan_softmac::wire::WlanSoftmacStartPassiveScanResponse::Builder(fidl_arena); |
| builder.scan_id(out_scan_id); |
| completer.buffer(arena).ReplySuccess(builder.Build()); |
| } |
| |
| void WlanSoftmacDevice::StartActiveScan(StartActiveScanRequestView request, fdf::Arena& arena, |
| StartActiveScanCompleter::Sync& completer) { |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| uint64_t out_scan_id; |
| zx_status_t status = mac_start_active_scan(mvmvif_, request, &out_scan_id); |
| if (status != ZX_OK) { |
| IWL_ERR(this, "failed start active scan: %s", zx_status_get_string(status)); |
| completer.buffer(arena).ReplyError(status); |
| return; |
| } |
| |
| fidl::Arena fidl_arena; |
| auto builder = |
| fuchsia_wlan_softmac::wire::WlanSoftmacStartActiveScanResponse::Builder(fidl_arena); |
| builder.scan_id(out_scan_id); |
| completer.buffer(arena).ReplySuccess(builder.Build()); |
| } |
| |
| void WlanSoftmacDevice::CancelScan(CancelScanRequestView request, fdf::Arena& arena, |
| CancelScanCompleter::Sync& completer) { |
| // TODO(fxbug.dev/107743): Implement. |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void WlanSoftmacDevice::UpdateWmmParameters(UpdateWmmParametersRequestView request, |
| fdf::Arena& arena, |
| UpdateWmmParametersCompleter::Sync& completer) { |
| IWL_ERR(this, "needs porting"); |
| CHECK_DELETE_IN_PROGRESS_WITH_ERRSYNTAX(mvmvif_); |
| completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void WlanSoftmacDevice::ServiceConnectHandler( |
| fdf_dispatcher_t* dispatcher, fdf::ServerEnd<fuchsia_wlan_softmac::WlanSoftmac> server_end) { |
| fdf::BindServer(dispatcher, std::move(server_end), this); |
| } |
| |
| void WlanSoftmacDevice::Recv(fuchsia_wlan_softmac::wire::WlanRxPacket* rx_packet) { |
| auto arena = fdf::Arena::Create(0, 0); |
| if (arena.is_error()) { |
| IWL_ERR(this, "Failed to create Arena in WlanSoftmacDevice::Recv().\n"); |
| return; |
| } |
| auto result = client_.buffer(*std::move(arena))->Recv(*rx_packet); |
| if (!result.ok()) { |
| IWL_ERR(this, "Failed to send rx frames up in WlanSoftmacDevice::Recv(). Status: %d\n", |
| result.status()); |
| } |
| } |
| |
| void WlanSoftmacDevice::NotifyScanComplete(const zx_status_t status, const uint64_t scan_id) { |
| auto arena = fdf::Arena::Create(0, 0); |
| if (arena.is_error()) { |
| IWL_ERR(this, |
| "Failed to create Arena in WlanSoftmacDevice::NotifyScanComplete(). " |
| "scan_id=%zu, status=%s\n", |
| scan_id, zx_status_get_string(status)); |
| return; |
| } |
| |
| fidl::Arena fidl_arena; |
| auto builder = |
| fuchsia_wlan_softmac::wire::WlanSoftmacIfcNotifyScanCompleteRequest::Builder(fidl_arena); |
| builder.status(status); |
| builder.scan_id(scan_id); |
| |
| auto result = client_.buffer(*std::move(arena))->NotifyScanComplete(builder.Build()); |
| if (!result.ok()) { |
| IWL_ERR( |
| this, |
| "Failed to send scan complete notification up in WlanSoftmacDevice::NotifyScanComplete(). " |
| "result.status: %d, scan_id=%zu, status=%s\n", |
| result.status(), scan_id, zx_status_get_string(status)); |
| } |
| } |
| |
| } // namespace wlan::iwlwifi |