| // Copyright 2017 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 "device.h" |
| |
| #include "probe_sequence.h" |
| |
| #include <ddk/device.h> |
| #include <lib/zx/thread.h> |
| #include <lib/zx/time.h> |
| #include <wlan/common/channel.h> |
| #include <wlan/common/logging.h> |
| #include <wlan/mlme/ap/ap_mlme.h> |
| #include <wlan/mlme/client/bss.h> |
| #include <wlan/mlme/client/client_mlme.h> |
| #include <wlan/mlme/debug.h> |
| #include <wlan/mlme/mesh/mesh_mlme.h> |
| #include <wlan/mlme/service.h> |
| #include <wlan/mlme/timer.h> |
| #include <wlan/mlme/timer_manager.h> |
| #include <wlan/mlme/validate_frame.h> |
| #include <wlan/mlme/wlan.h> |
| #include <wlan/protocol/ioctl.h> |
| #include <zircon/assert.h> |
| #include <zircon/compiler.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/port.h> |
| |
| #include <cinttypes> |
| #include <cstdarg> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstring> |
| #include <limits> |
| #include <utility> |
| |
| namespace wlan { |
| |
| namespace wlan_minstrel = ::fuchsia::wlan::minstrel; |
| |
| #define DEV(c) static_cast<Device*>(c) |
| static zx_protocol_device_t wlan_device_ops = { |
| .version = DEVICE_OPS_VERSION, |
| .unbind = [](void* ctx) { DEV(ctx)->WlanUnbind(); }, |
| .release = [](void* ctx) { DEV(ctx)->WlanRelease(); }, |
| .ioctl = [](void* ctx, uint32_t op, const void* in_buf, size_t in_len, void* out_buf, |
| size_t out_len, size_t* out_actual) -> zx_status_t { |
| return DEV(ctx)->WlanIoctl(op, in_buf, in_len, out_buf, out_len, out_actual); |
| }, |
| }; |
| |
| static zx_protocol_device_t eth_device_ops = { |
| .version = DEVICE_OPS_VERSION, |
| .unbind = [](void* ctx) { DEV(ctx)->EthUnbind(); }, |
| .release = [](void* ctx) { DEV(ctx)->EthRelease(); }, |
| }; |
| |
| static wlanmac_ifc_t wlanmac_ifc_ops = { |
| .status = [](void* cookie, uint32_t status) { DEV(cookie)->WlanmacStatus(status); }, |
| .recv = [](void* cookie, uint32_t flags, const void* data, size_t length, |
| wlan_rx_info_t* info) { DEV(cookie)->WlanmacRecv(flags, data, length, info); }, |
| .complete_tx = [](void* cookie, wlan_tx_packet_t* pkt, |
| zx_status_t status) { DEV(cookie)->WlanmacCompleteTx(pkt, status); }, |
| .indication = [](void* cookie, uint32_t ind) { DEV(cookie)->WlanmacIndication(ind); }, |
| .report_tx_status = |
| [](void* cookie, const wlan_tx_status_t* tx_status) { |
| DEV(cookie)->WlanmacReportTxStatus(tx_status); |
| }, |
| .hw_scan_complete = |
| [](void* cookie, const wlan_hw_scan_result_t* result) { |
| DEV(cookie)->WlanmacHwScanComplete(result); |
| }, |
| }; |
| |
| static ethmac_protocol_ops_t ethmac_ops = { |
| .query = [](void* ctx, uint32_t options, ethmac_info_t* info) -> zx_status_t { |
| return DEV(ctx)->EthmacQuery(options, info); |
| }, |
| .stop = [](void* ctx) { DEV(ctx)->EthmacStop(); }, |
| .start = [](void* ctx, const ethmac_ifc_t* ifc) -> zx_status_t { |
| return DEV(ctx)->EthmacStart(ifc); |
| }, |
| .queue_tx = [](void* ctx, uint32_t options, ethmac_netbuf_t* netbuf) -> zx_status_t { |
| return DEV(ctx)->EthmacQueueTx(options, netbuf); |
| }, |
| .set_param = [](void* ctx, uint32_t param, int32_t value, const void* data, size_t data_size) |
| -> zx_status_t { return DEV(ctx)->EthmacSetParam(param, value, data, data_size); }, |
| }; |
| #undef DEV |
| |
| Device::Device(zx_device_t* device, wlanmac_protocol_t wlanmac_proto, |
| std::shared_ptr<component::Services> services) |
| : parent_(device), |
| wlanmac_proxy_(wlanmac_proto), |
| services_(services), |
| fidl_msg_buf_(ZX_CHANNEL_MAX_MSG_BYTES) { |
| debugfn(); |
| state_ = fbl::AdoptRef(new DeviceState); |
| } |
| |
| Device::~Device() { |
| debugfn(); |
| ZX_DEBUG_ASSERT(!work_thread_.joinable()); |
| } |
| |
| // Disable thread safety analysis, as this is a part of device initialization. All thread-unsafe |
| // work should occur before multiple threads are possible (e.g., before MainLoop is started and |
| // before DdkAdd() is called), or locks should be held. |
| zx_status_t Device::Bind() __TA_NO_THREAD_SAFETY_ANALYSIS { |
| debugfn(); |
| |
| zx_status_t status = zx::port::create(0, &port_); |
| if (status != ZX_OK) { |
| errorf("could not create port: %d\n", status); |
| return status; |
| } |
| |
| status = wlanmac_proxy_.Query(0, &wlanmac_info_); |
| if (status != ZX_OK) { |
| errorf("could not query wlanmac device: %d\n", status); |
| return status; |
| } |
| |
| status = ValidateWlanMacInfo(wlanmac_info_); |
| if (status != ZX_OK) { |
| errorf("could not bind wlanmac device with invalid wlanmac info\n"); |
| return status; |
| } |
| |
| status = wlanmac_proxy_.Start(&wlanmac_ifc_ops, this); |
| if (status != ZX_OK) { |
| errorf("failed to start wlanmac device: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| state_->set_address(common::MacAddr(wlanmac_info_.ifc_info.mac_addr)); |
| |
| fbl::unique_ptr<Mlme> mlme; |
| |
| // mac_role is a bitfield, but only a single value is supported for an interface |
| switch (wlanmac_info_.ifc_info.mac_role) { |
| case WLAN_MAC_ROLE_CLIENT: |
| infof("Initialize a client MLME.\n"); |
| mlme.reset(new ClientMlme(this)); |
| break; |
| case WLAN_MAC_ROLE_AP: |
| infof("Initialize an AP MLME.\n"); |
| mlme.reset(new ApMlme(this)); |
| break; |
| case WLAN_MAC_ROLE_MESH: |
| infof("Initialize a mesh MLME.\n"); |
| mlme.reset(new MeshMlme(this)); |
| break; |
| default: |
| errorf("unsupported MAC role: %u\n", wlanmac_info_.ifc_info.mac_role); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| ZX_DEBUG_ASSERT(mlme != nullptr); |
| status = mlme->Init(); |
| if (status != ZX_OK) { |
| errorf("could not initialize MLME: %d\n", status); |
| return status; |
| } |
| dispatcher_.reset(new Dispatcher(this, std::move(mlme))); |
| if ((wlanmac_info_.ifc_info.driver_features & WLAN_DRIVER_FEATURE_TX_STATUS_REPORT) && |
| !(wlanmac_info_.ifc_info.driver_features & WLAN_DRIVER_FEATURE_RATE_SELECTION)) { |
| zx_status_t status = CreateMinstrel(wlanmac_info_.ifc_info.driver_features); |
| if (ZX_OK == status) { debugmstl("Minstrel Manager created successfully.\n"); } |
| } |
| |
| work_thread_ = std::thread(&Device::MainLoop, this); |
| |
| bool wlan_added = false; |
| |
| status = AddWlanDevice(); |
| if (status == ZX_OK) { |
| wlan_added = true; |
| status = AddEthDevice(); |
| } |
| |
| // Clean up if either device add failed. |
| if (status != ZX_OK) { |
| errorf("could not add device err=%d\n", status); |
| zx_status_t shutdown_status = QueueDevicePortPacket(DevicePacket::kShutdown); |
| if (shutdown_status != ZX_OK) { |
| ZX_PANIC("wlan: could not send shutdown loop message: %d\n", shutdown_status); |
| } |
| if (work_thread_.joinable()) { work_thread_.join(); } |
| |
| // Remove the wlan device if it was successfully added. |
| if (wlan_added) { device_remove(zxdev_); } |
| } else { |
| debugf("device added\n"); |
| } |
| |
| return status; |
| } |
| |
| zx_status_t Device::AddWlanDevice() { |
| device_add_args_t args = {}; |
| args.version = DEVICE_ADD_ARGS_VERSION; |
| args.name = "wlan"; |
| args.ctx = this; |
| args.ops = &wlan_device_ops; |
| args.proto_id = ZX_PROTOCOL_WLANIF; |
| return device_add(parent_, &args, &zxdev_); |
| } |
| |
| zx_status_t Device::AddEthDevice() { |
| device_add_args_t args = {}; |
| args.version = DEVICE_ADD_ARGS_VERSION; |
| args.name = "wlan-ethernet"; |
| args.ctx = this; |
| args.ops = ð_device_ops; |
| args.proto_id = ZX_PROTOCOL_ETHMAC; |
| args.proto_ops = ðmac_ops; |
| return device_add(zxdev_, &args, ðdev_); |
| } |
| |
| fbl::unique_ptr<Packet> Device::PreparePacket(const void* data, size_t length, Packet::Peer peer) { |
| fbl::unique_ptr<Buffer> buffer = GetBuffer(length); |
| if (buffer == nullptr) { |
| errorf("could not get buffer for packet of length %zu\n", length); |
| return nullptr; |
| } |
| |
| auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), length)); |
| packet->set_peer(peer); |
| zx_status_t status = packet->CopyFrom(data, length, 0); |
| if (status != ZX_OK) { |
| errorf("could not copy to packet: %d\n", status); |
| return nullptr; |
| } |
| return packet; |
| } |
| |
| zx_status_t Device::QueuePacket(fbl::unique_ptr<Packet> packet) { |
| if (packet == nullptr) { return ZX_ERR_NO_RESOURCES; } |
| std::lock_guard<std::mutex> lock(packet_queue_lock_); |
| bool was_empty = packet_queue_.is_empty(); |
| packet_queue_.Enqueue(std::move(packet)); |
| |
| if (was_empty) { |
| zx_status_t status = QueueDevicePortPacket(DevicePacket::kPacketQueued); |
| if (status != ZX_OK) { |
| errorf("could not send packet queued msg err=%d\n", status); |
| packet_queue_.UndoEnqueue(); |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| void Device::WlanUnbind() { |
| debugfn(); |
| { |
| std::lock_guard<std::mutex> lock(lock_); |
| channel_.reset(); |
| dead_ = true; |
| if (port_.is_valid()) { |
| zx_status_t status = QueueDevicePortPacket(DevicePacket::kShutdown); |
| if (status != ZX_OK) { |
| ZX_PANIC("wlan: could not send shutdown loop message: %d\n", status); |
| } |
| } |
| } |
| device_remove(zxdev_); |
| } |
| |
| void Device::WlanRelease() { |
| debugfn(); |
| if (work_thread_.joinable()) { work_thread_.join(); } |
| delete this; |
| } |
| |
| zx_status_t Device::WlanIoctl(uint32_t op, const void* in_buf, size_t in_len, void* out_buf, |
| size_t out_len, size_t* out_actual) { |
| debugfn(); |
| if (op != IOCTL_WLAN_GET_CHANNEL) { return ZX_ERR_NOT_SUPPORTED; } |
| if (out_buf == nullptr || out_actual == nullptr || out_len < sizeof(zx_handle_t)) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| |
| zx::channel out; |
| zx_status_t status = GetChannel(&out); |
| if (status != ZX_OK) { return status; } |
| |
| zx_handle_t* outh = static_cast<zx_handle_t*>(out_buf); |
| *outh = out.release(); |
| *out_actual = sizeof(zx_handle_t); |
| return ZX_OK; |
| } |
| |
| void Device::EthUnbind() { |
| debugfn(); |
| device_remove(ethdev_); |
| } |
| |
| void Device::EthRelease() { |
| debugfn(); |
| // NOTE: we reuse the same ctx for the wlanif and the ethmac, so we do NOT free the memory here. |
| // Since ethdev_ is a child of zxdev_, this release will be called first, followed by |
| // WlanRelease. There's nothing else to clean up here. |
| } |
| |
| zx_status_t Device::EthmacQuery(uint32_t options, ethmac_info_t* info) { |
| debugfn(); |
| if (info == nullptr) return ZX_ERR_INVALID_ARGS; |
| |
| memset(info, 0, sizeof(*info)); |
| memcpy(info->mac, wlanmac_info_.ifc_info.mac_addr, ETH_MAC_SIZE); |
| info->features = ETHMAC_FEATURE_WLAN; |
| if (wlanmac_info_.ifc_info.driver_features & WLAN_DRIVER_FEATURE_SYNTH) { |
| info->features |= ETHMAC_FEATURE_SYNTH; |
| } |
| info->mtu = 1500; |
| info->netbuf_size = sizeof(ethmac_netbuf_t); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Device::EthmacStart(const ethmac_ifc_t* ifc) { |
| debugfn(); |
| ZX_DEBUG_ASSERT(ifc != nullptr); |
| |
| std::lock_guard<std::mutex> lock(lock_); |
| if (ethmac_proxy_ != nullptr) { return ZX_ERR_ALREADY_BOUND; } |
| ethmac_proxy_.reset(new ddk::EthmacIfcProxy(ifc)); |
| return ZX_OK; |
| } |
| |
| void Device::EthmacStop() { |
| debugfn(); |
| |
| std::lock_guard<std::mutex> lock(lock_); |
| if (ethmac_proxy_ == nullptr) { warnf("ethmac not started\n"); } |
| ethmac_proxy_.reset(); |
| } |
| |
| zx_status_t Device::EthmacQueueTx(uint32_t options, ethmac_netbuf_t* netbuf) { |
| // no debugfn() because it's too noisy |
| auto packet = PreparePacket(netbuf->data_buffer, netbuf->data_size, Packet::Peer::kEthernet); |
| if (packet == nullptr) { |
| warnf("could not prepare Ethernet packet with len %zu\n", netbuf->data_size); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| zx_status_t status = QueuePacket(std::move(packet)); |
| if (status != ZX_OK) { warnf("could not queue Ethernet packet err=%d\n", status); } |
| return status; |
| } |
| |
| zx_status_t Device::EthmacSetParam(uint32_t param, int32_t value, const void* data, |
| size_t data_size) { |
| debugfn(); |
| |
| zx_status_t status = ZX_ERR_NOT_SUPPORTED; |
| |
| switch (param) { |
| case ETHMAC_SETPARAM_PROMISC: |
| // See NET-1808: In short, the bridge mode doesn't require WLAN promiscuous mode enabled. |
| // So we give a warning and return OK here to continue the bridging. |
| // TODO(NET-1930): To implement the real promiscuous mode. |
| if (value == 1) { // Only warn when enabling. |
| warnf("WLAN promiscuous not supported yet. see NET-1930\n"); |
| } |
| status = ZX_OK; |
| break; |
| } |
| |
| return status; |
| } |
| |
| void Device::WlanmacStatus(uint32_t status) { |
| debugf("WlanmacStatus %u\n", status); |
| |
| std::lock_guard<std::mutex> lock(lock_); |
| SetStatusLocked(status); |
| } |
| |
| void Device::WlanmacRecv(uint32_t flags, const void* data, size_t length, wlan_rx_info_t* info) { |
| // no debugfn() because it's too noisy |
| auto packet = PreparePacket(data, length, Packet::Peer::kWlan, *info); |
| if (packet == nullptr) { |
| errorf("could not prepare outbound Ethernet packet with len %zu\n", length); |
| return; |
| } |
| |
| zx_status_t status = QueuePacket(std::move(packet)); |
| if (status != ZX_OK) { |
| warnf("could not queue inbound packet with len %zu err=%d\n", length, status); |
| } |
| } |
| |
| void Device::WlanmacCompleteTx(wlan_tx_packet_t* pkt, zx_status_t status) { |
| // TODO(tkilbourn): free memory and complete the ethernet tx (if necessary). For now, we aren't |
| // doing any async transmits in the wlan drivers, so this method shouldn't be called yet. |
| ZX_PANIC("not implemented yet!"); |
| } |
| |
| void Device::WlanmacIndication(uint32_t ind) { |
| debugf("WlanmacIndication %u\n", ind); |
| auto status = QueueDevicePortPacket(DevicePacket::kIndication, ind); |
| if (status != ZX_OK) { warnf("could not queue driver indication packet err=%d\n", status); } |
| } |
| |
| void Device::WlanmacReportTxStatus(const wlan_tx_status_t* tx_status) { |
| zx_packet_user_t user_pkt; |
| static_assert(sizeof(zx_packet_user_t) >= sizeof(wlan_tx_status_t), "tx_status size > 32"); |
| memcpy(user_pkt.c8, reinterpret_cast<const uint8_t*>(tx_status), sizeof(wlan_tx_status_t)); |
| auto status = QueueDevicePortPacketUser(DevicePacket::kReportTxStatus, user_pkt); |
| if (status != ZX_OK) { warnf("could not queue tx status report packet err=%d\n", status); } |
| } |
| |
| void Device::WlanmacHwScanComplete(const wlan_hw_scan_result_t* result) { |
| debugf("WlanmacHwScanComplete %u\n", result->code); |
| auto status = QueueDevicePortPacket(DevicePacket::kHwScanComplete, result->code); |
| if (status != ZX_OK) { errorf("could not queue hw scan complete packet err=%d\n", status); } |
| } |
| |
| zx_status_t Device::GetTimer(uint64_t id, fbl::unique_ptr<Timer>* timer) { |
| ZX_DEBUG_ASSERT(timer != nullptr); |
| ZX_DEBUG_ASSERT(timer->get() == nullptr); |
| ZX_DEBUG_ASSERT(port_.is_valid()); |
| |
| zx::timer t; |
| zx_status_t status = zx::timer::create(0u, ZX_CLOCK_MONOTONIC, &t); |
| if (status != ZX_OK) { return status; } |
| |
| status = t.wait_async(port_, id, ZX_TIMER_SIGNALED, ZX_WAIT_ASYNC_REPEATING); |
| if (status != ZX_OK) { return status; } |
| timer->reset(new SystemTimer(id, std::move(t))); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Device::DeliverEthernet(Span<const uint8_t> eth_frame) { |
| if (eth_frame.size() > ETH_FRAME_MAX_SIZE) { |
| errorf("Attempted to deliver an ethernet frame of invalid length: %zu\n", eth_frame.size()); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (ethmac_proxy_ != nullptr) { ethmac_proxy_->Recv(eth_frame.data(), eth_frame.size(), 0u); } |
| return ZX_OK; |
| } |
| |
| TxVector GetTxVector(const fbl::unique_ptr<MinstrelRateSelector>& minstrel, |
| const fbl::unique_ptr<Packet>& packet, CBW cbw, PHY phy, uint32_t flags) { |
| const auto fc = packet->field<FrameControl>(0); |
| |
| constexpr size_t kAddr1Offset = 4; |
| ZX_DEBUG_ASSERT(packet->len() >= kAddr1Offset + common::kMacAddrLen); |
| auto peer_addr = common::MacAddr(packet->field<uint8_t>(kAddr1Offset)); |
| |
| if (minstrel != nullptr) { |
| tx_vec_idx_t idx = minstrel->GetTxVectorIdx(*fc, peer_addr, flags); |
| TxVector tv; |
| zx_status_t status = TxVector::FromIdx(idx, &tv); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| return tv; |
| } else { |
| // TODO(NET-645): Choose an optimal MCS for management frames |
| uint8_t mcs = fc->type() == FrameType::kData ? 7 : 3; |
| return { |
| .phy = static_cast<PHY>(phy), |
| .cbw = static_cast<CBW>(cbw), |
| .mcs_idx = mcs, |
| .nss = 1, |
| .gi = WLAN_GI_800NS, |
| }; |
| } |
| } |
| |
| wlan_tx_info_t MakeTxInfo(const fbl::unique_ptr<Packet>& packet, const TxVector& tv, |
| uint32_t has_minstrel, uint32_t flags) { |
| tx_vec_idx_t idx; |
| zx_status_t status = tv.ToIdx(&idx); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| |
| uint32_t valid_fields = |
| WLAN_TX_INFO_VALID_PHY | WLAN_TX_INFO_VALID_CHAN_WIDTH | WLAN_TX_INFO_VALID_MCS; |
| if (has_minstrel) { valid_fields |= WLAN_TX_INFO_VALID_TX_VECTOR_IDX; } |
| |
| const auto fc = packet->field<FrameControl>(0); |
| if (fc->protected_frame()) { flags |= WLAN_TX_INFO_FLAGS_PROTECTED; } |
| |
| return { |
| .phy = static_cast<uint16_t>(tv.phy), |
| .cbw = static_cast<uint8_t>(tv.cbw), |
| .mcs = tv.mcs_idx, |
| .tx_vector_idx = idx, |
| .valid_fields = valid_fields, |
| .tx_flags = flags, |
| }; |
| } |
| |
| zx_status_t Device::SendWlan(fbl::unique_ptr<Packet> packet, CBW cbw, PHY phy, uint32_t flags) { |
| ZX_DEBUG_ASSERT(packet->len() <= std::numeric_limits<uint16_t>::max()); |
| ZX_DEBUG_ASSERT(ValidateFrame("Transmitting a malformed frame", *packet)); |
| |
| auto tv = GetTxVector(minstrel_, packet, cbw, phy, flags); |
| packet->CopyCtrlFrom(MakeTxInfo(packet, tv, minstrel_ != nullptr, flags)); |
| wlan_tx_packet_t tx_pkt = packet->AsWlanTxPacket(); |
| auto status = wlanmac_proxy_.QueueTx(0u, &tx_pkt); |
| // TODO(tkilbourn): remove this once we implement WlanmacCompleteTx and allow wlanmac drivers to |
| // complete transmits asynchronously. |
| ZX_DEBUG_ASSERT(status != ZX_ERR_SHOULD_WAIT); |
| |
| return status; |
| } |
| |
| // Disable thread safety analysis, since these methods are called through an interface from an |
| // object that we know is holding the lock. So taking the lock would be wrong, but there's no way to |
| // convince the compiler that the lock is held. |
| |
| // This *should* be safe, since the worst case is that |
| // the syscall fails, and we return an error. |
| // TODO(tkilbourn): consider refactoring this so we don't have to abandon the safety analysis. |
| zx_status_t Device::SendService(Span<const uint8_t> span) __TA_NO_THREAD_SAFETY_ANALYSIS { |
| if (channel_.is_valid()) { return channel_.write(0u, span.data(), span.size(), nullptr, 0); } |
| return ZX_OK; |
| } |
| |
| // TODO(tkilbourn): figure out how to make sure we have the lock for accessing dispatcher_. |
| zx_status_t Device::SetChannel(wlan_channel_t chan) __TA_NO_THREAD_SAFETY_ANALYSIS { |
| // TODO(porce): Implement == operator for wlan_channel_t, or an equality test function. |
| |
| char buf[80]; |
| snprintf(buf, sizeof(buf), "channel set: from %s to %s", |
| common::ChanStr(state_->channel()).c_str(), common::ChanStr(chan).c_str()); |
| |
| if (chan.primary == state_->channel().primary && chan.cbw == state_->channel().cbw) { |
| warnf("%s suppressed\n", buf); |
| return ZX_OK; |
| } |
| |
| zx_status_t status = wlanmac_proxy_.SetChannel(0u, &chan); |
| if (status != ZX_OK) { |
| errorf("%s change failed (status %d)\n", buf, status); |
| return status; |
| } |
| |
| state_->set_channel(chan); |
| |
| verbosef("%s succeeded\n", buf); |
| return ZX_OK; |
| } |
| |
| zx_status_t Device::SetStatus(uint32_t status) { |
| // Lock is already held when MLME is asked to handle assoc/deassoc packets, which caused this |
| // link status change. |
| SetStatusLocked(status); |
| return ZX_OK; |
| } |
| |
| void Device::SetStatusLocked(uint32_t status) { |
| state_->set_online(status == ETHMAC_STATUS_ONLINE); |
| if (ethmac_proxy_ != nullptr) { ethmac_proxy_->Status(status); } |
| } |
| |
| zx_status_t Device::ConfigureBss(wlan_bss_config_t* cfg) { |
| return wlanmac_proxy_.ConfigureBss(0u, cfg); |
| } |
| |
| zx_status_t Device::EnableBeaconing(wlan_bcn_config_t* bcn_cfg) { |
| if (bcn_cfg != nullptr) { |
| ZX_DEBUG_ASSERT( |
| ValidateFrame("Malformed beacon template", |
| {reinterpret_cast<const uint8_t*>(bcn_cfg->tmpl.packet_head.data_buffer), |
| bcn_cfg->tmpl.packet_head.data_size})); |
| } |
| return wlanmac_proxy_.EnableBeaconing(0u, bcn_cfg); |
| } |
| |
| zx_status_t Device::ConfigureBeacon(fbl::unique_ptr<Packet> beacon) { |
| ZX_DEBUG_ASSERT(beacon.get() != nullptr); |
| if (beacon.get() == nullptr) { return ZX_ERR_INVALID_ARGS; } |
| |
| ZX_DEBUG_ASSERT(ValidateFrame("Malformed beacon template", *beacon)); |
| |
| wlan_tx_packet_t tx_packet = beacon->AsWlanTxPacket(); |
| return wlanmac_proxy_.ConfigureBeacon(0u, &tx_packet); |
| } |
| |
| zx_status_t Device::SetKey(wlan_key_config_t* key_config) { |
| return wlanmac_proxy_.SetKey(0u, key_config); |
| } |
| |
| zx_status_t Device::StartHwScan(const wlan_hw_scan_config_t* scan_config) { |
| return wlanmac_proxy_.StartHwScan(scan_config); |
| } |
| |
| zx_status_t Device::ConfigureAssoc(wlan_assoc_ctx_t* assoc_ctx) { |
| ZX_DEBUG_ASSERT(assoc_ctx != nullptr); |
| // TODO(NET-1385): Minstrel only supports client mode. Add AP mode support later. |
| AddMinstrelPeer(*assoc_ctx); |
| return wlanmac_proxy_.ConfigureAssoc(0u, assoc_ctx); |
| } |
| |
| zx_status_t Device::ClearAssoc(const wlan::common::MacAddr& peer_addr) { |
| if (minstrel_ != nullptr) { minstrel_->RemovePeer(peer_addr); } |
| |
| uint8_t mac[wlan::common::kMacAddrLen]; |
| peer_addr.CopyTo(mac); |
| return wlanmac_proxy_.ClearAssoc(0u, mac); |
| } |
| |
| fbl::RefPtr<DeviceState> Device::GetState() { |
| return state_; |
| } |
| |
| const wlanmac_info_t& Device::GetWlanInfo() const { |
| return wlanmac_info_; |
| } |
| |
| zx_status_t Device::GetMinstrelPeers(wlan_minstrel::Peers* peers_fidl) { |
| if (minstrel_ == nullptr) { return ZX_ERR_NOT_SUPPORTED; } |
| return minstrel_->GetListToFidl(peers_fidl); |
| } |
| |
| zx_status_t Device::GetMinstrelStats(const common::MacAddr& addr, wlan_minstrel::Peer* peer_fidl) { |
| if (minstrel_ == nullptr) { return ZX_ERR_NOT_SUPPORTED; } |
| return minstrel_->GetStatsToFidl(addr, peer_fidl); |
| } |
| |
| void Device::MainLoop() { |
| infof("starting MainLoop\n"); |
| const char kThreadName[] = "wlan-mainloop"; |
| zx::thread::self()->set_property(ZX_PROP_NAME, kThreadName, sizeof(kThreadName)); |
| |
| zx_port_packet_t pkt; |
| bool running = true; |
| while (running) { |
| zx::time timeout = zx::deadline_after(zx::sec(30)); |
| zx_status_t status = port_.wait(timeout, &pkt); |
| std::lock_guard<std::mutex> lock(lock_); |
| if (status == ZX_ERR_TIMED_OUT) { |
| // TODO(tkilbourn): more watchdog checks here? |
| ZX_DEBUG_ASSERT(running); |
| continue; |
| } else if (status != ZX_OK) { |
| if (status == ZX_ERR_BAD_HANDLE) { |
| debugf("port closed, exiting\n"); |
| } else { |
| errorf("error waiting on port: %d\n", status); |
| } |
| break; |
| } |
| |
| switch (pkt.type) { |
| case ZX_PKT_TYPE_USER: |
| ZX_DEBUG_ASSERT(ToPortKeyType(pkt.key) == PortKeyType::kDevice); |
| switch (ToPortKeyId(pkt.key)) { |
| case to_enum_type(DevicePacket::kShutdown): |
| running = false; |
| continue; |
| case to_enum_type(DevicePacket::kIndication): |
| dispatcher_->HwIndication(pkt.status); |
| break; |
| case to_enum_type(DevicePacket::kHwScanComplete): |
| dispatcher_->HwScanComplete(pkt.status); |
| break; |
| case to_enum_type(DevicePacket::kPacketQueued): { |
| PacketQueue queued_packets{}; |
| { |
| std::lock_guard<std::mutex> lock(packet_queue_lock_); |
| queued_packets = packet_queue_.Drain(); |
| } |
| while (!queued_packets.is_empty()) { |
| auto packet = queued_packets.Dequeue(); |
| ZX_DEBUG_ASSERT(packet != nullptr); |
| zx_status_t status = dispatcher_->HandlePacket(std::move(packet)); |
| if (status != ZX_OK) { errorf("could not handle packet err=%d\n", status); } |
| } |
| break; |
| } |
| case to_enum_type(DevicePacket::kReportTxStatus): { |
| auto tx_status = reinterpret_cast<wlan_tx_status_t*>(pkt.user.c8); |
| minstrel_->HandleTxStatusReport(*tx_status); |
| break; |
| } |
| default: |
| errorf("unknown device port key subtype: %" PRIu64 "\n", pkt.user.u64[0]); |
| break; |
| } |
| break; |
| case ZX_PKT_TYPE_SIGNAL_REP: |
| switch (ToPortKeyType(pkt.key)) { |
| case PortKeyType::kMlme: |
| dispatcher_->HandlePortPacket(pkt.key); |
| break; |
| case PortKeyType::kDevice: { |
| ZX_DEBUG_ASSERT(minstrel_ != nullptr); |
| minstrel_->HandleTimeout(); |
| break; |
| } |
| default: |
| errorf("unknown port key: %" PRIu64 "\n", pkt.key); |
| break; |
| } |
| break; |
| case ZX_PKT_TYPE_SIGNAL_ONE: |
| switch (ToPortKeyType(pkt.key)) { |
| case PortKeyType::kService: |
| ProcessChannelPacketLocked(pkt.signal.count); |
| break; |
| default: |
| errorf("unknown port key: %" PRIu64 "\n", pkt.key); |
| break; |
| } |
| break; |
| default: |
| errorf("unknown port packet type: %u\n", pkt.type); |
| break; |
| } |
| } |
| |
| infof("exiting MainLoop\n"); |
| std::lock_guard<std::mutex> lock(lock_); |
| port_.reset(); |
| channel_.reset(); |
| } |
| |
| void Device::ProcessChannelPacketLocked(uint64_t signal_count) { |
| if (!channel_.is_valid()) { |
| // It could be that we closed the channel (e.g., in WlanUnbind()) but there is still |
| // a pending packet in the port that indicates read availability on that channel. |
| // In that case we simply ignore the packet since we can't read from the channel anyway. |
| return; |
| } |
| |
| for (size_t i = 0; i < signal_count; ++i) { |
| uint32_t read = 0; |
| zx_status_t status = channel_.read(0, fidl_msg_buf_.data(), fidl_msg_buf_.size(), &read, |
| nullptr, 0, nullptr); |
| if (status == ZX_ERR_SHOULD_WAIT) { break; } |
| if (status != ZX_OK) { |
| if (status == ZX_ERR_PEER_CLOSED) { |
| infof("channel closed\n"); |
| } else { |
| errorf("could not read channel: %s\n", zx_status_get_string(status)); |
| } |
| channel_.reset(); |
| return; |
| } |
| |
| status = dispatcher_->HandleAnyMlmeMessage({fidl_msg_buf_.data(), read}); |
| if (status != ZX_OK) { |
| errorf("Could not handle service packet: %s\n", zx_status_get_string(status)); |
| } |
| } |
| RegisterChannelWaitLocked(); |
| } |
| |
| zx_status_t Device::RegisterChannelWaitLocked() { |
| zx_signals_t sigs = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED; |
| return channel_.wait_async(port_, ToPortKey(PortKeyType::kService, 0u), sigs, |
| ZX_WAIT_ASYNC_ONCE); |
| } |
| |
| zx_status_t Device::QueueDevicePortPacket(DevicePacket id, uint32_t status) { |
| debugfn(); |
| zx_port_packet_t pkt = {}; |
| pkt.key = ToPortKey(PortKeyType::kDevice, to_enum_type(id)); |
| pkt.type = ZX_PKT_TYPE_USER; |
| pkt.status = status; |
| return port_.queue(&pkt); |
| } |
| |
| zx_status_t Device::QueueDevicePortPacketUser(DevicePacket id, zx_packet_user_t user_pkt) { |
| debugfn(); |
| zx_port_packet_t pkt = {}; |
| pkt.key = ToPortKey(PortKeyType::kDevice, to_enum_type(id)); |
| pkt.type = ZX_PKT_TYPE_USER; |
| pkt.user = user_pkt; |
| return port_.queue(&pkt); |
| } |
| |
| zx_status_t Device::GetChannel(zx::channel* out) { |
| ZX_DEBUG_ASSERT(out != nullptr); |
| |
| std::lock_guard<std::mutex> lock(lock_); |
| if (dead_) { return ZX_ERR_PEER_CLOSED; } |
| if (!port_.is_valid()) { return ZX_ERR_BAD_STATE; } |
| if (channel_.is_valid()) { return ZX_ERR_ALREADY_BOUND; } |
| |
| zx_status_t status = zx::channel::create(0, &channel_, out); |
| if (status != ZX_OK) { |
| errorf("could not create channel: %d\n", status); |
| return status; |
| } |
| |
| status = RegisterChannelWaitLocked(); |
| if (status != ZX_OK) { |
| errorf("could not wait on channel: %d\n", status); |
| out->reset(); |
| channel_.reset(); |
| return status; |
| } |
| |
| infof("channel opened\n"); |
| return ZX_OK; |
| } |
| |
| zx_status_t ValidateWlanMacInfo(const wlanmac_info& wlanmac_info) { |
| for (uint8_t i = 0; i < wlanmac_info.ifc_info.num_bands; i++) { |
| auto bandinfo = wlanmac_info.ifc_info.bands[i]; |
| |
| // Validate channels |
| auto& supported_channels = bandinfo.supported_channels; |
| switch (supported_channels.base_freq) { |
| case common::kBaseFreq5Ghz: |
| for (auto c : supported_channels.channels) { |
| if (c == 0) { // End of the valid channel |
| break; |
| } |
| auto chan = wlan_channel_t{.primary = c, .cbw = CBW20}; |
| if (!common::IsValidChan5Ghz(chan)) { |
| errorf("wlanmac band info for %u MHz has invalid channel %u\n", |
| supported_channels.base_freq, c); |
| errorf("wlanmac info: %s\n", debug::Describe(wlanmac_info).c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| break; |
| case common::kBaseFreq2Ghz: |
| for (auto c : supported_channels.channels) { |
| if (c == 0) { // End of the valid channel |
| break; |
| } |
| auto chan = wlan_channel_t{.primary = c, .cbw = CBW20}; |
| if (!common::IsValidChan2Ghz(chan)) { |
| errorf("wlanmac band info for %u MHz has invalid cahnnel %u\n", |
| supported_channels.base_freq, c); |
| errorf("wlanmac info: %s\n", debug::Describe(wlanmac_info).c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| break; |
| default: |
| errorf("wlanmac band info for %u MHz not supported\n", supported_channels.base_freq); |
| errorf("wlanmac info: %s\n", debug::Describe(wlanmac_info).c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| // Add more sanity check here |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Device::CreateMinstrel(uint32_t features) { |
| debugfn(); |
| fbl::unique_ptr<Timer> timer; |
| ObjectId timer_id; |
| timer_id.set_subtype(to_enum_type(ObjectSubtype::kTimer)); |
| timer_id.set_target(to_enum_type(ObjectTarget::kMinstrel)); |
| auto status = GetTimer(ToPortKey(PortKeyType::kDevice, timer_id.val()), &timer); |
| if (status != ZX_OK) { |
| errorf("could not create minstrel timer: %d\n", status); |
| return status; |
| } |
| const zx::duration minstrel_update_interval = |
| (features & WLAN_DRIVER_FEATURE_SYNTH) != 0 ? zx::msec(10) : zx::msec(100); |
| minstrel_.reset(new MinstrelRateSelector( |
| TimerManager(std::move(timer)), ProbeSequence::RandomSequence(), minstrel_update_interval)); |
| return ZX_OK; |
| } |
| |
| void Device::AddMinstrelPeer(const wlan_assoc_ctx_t& assoc_ctx) { |
| if (minstrel_ == nullptr) { return; } |
| ZX_DEBUG_ASSERT(wlanmac_info_.ifc_info.mac_role == WLAN_MAC_ROLE_CLIENT); |
| minstrel_->AddPeer(assoc_ctx); |
| } |
| |
| } // namespace wlan |