blob: bd2b7ee1cffdaea7d7fb2156a985438349e5cfef [file] [log] [blame]
// Copyright 2018 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 "phy-device.h"
#include <fuchsia/hardware/wlanphy/c/banjo.h>
#include <fuchsia/wlan/device/llcpp/fidl.h>
#include <fuchsia/wlan/internal/cpp/fidl.h>
#include <stdio.h>
#include <algorithm>
#include <ddk/debug.h>
#include <wlan/common/element.h>
#include <wlan/common/phy.h>
#include "ddktl/fidl.h"
#include "driver.h"
#include "iface-device.h"
namespace wlan {
namespace testing {
namespace wlan_common = ::fuchsia::wlan::common;
namespace wlan_device = ::fuchsia::wlan::device;
namespace wlan_internal = ::fuchsia::wlan::internal;
#define DEV(c) static_cast<PhyDevice*>(c)
static zx_protocol_device_t wlanphy_test_device_ops = {
.version = DEVICE_OPS_VERSION,
.unbind = [](void* ctx) { DEV(ctx)->Unbind(); },
.release = [](void* ctx) { DEV(ctx)->Release(); },
.message = [](void* ctx, fidl_incoming_msg_t* msg,
fidl_txn_t* txn) { return DEV(ctx)->Message(msg, txn); },
};
#undef DEV
static wlanphy_protocol_ops_t wlanphy_test_ops = {
.dummy = nullptr,
};
class DeviceConnector : public llcpp::fuchsia::wlan::device::Connector::Interface {
public:
DeviceConnector(PhyDevice* device) : device_(device) {}
void Connect(::zx::channel request, ConnectCompleter::Sync& _completer) override {
device_->Connect(std::move(request));
}
private:
PhyDevice* device_;
};
PhyDevice::PhyDevice(zx_device_t* device) : parent_(device) {}
zx_status_t PhyDevice::Bind() {
zxlogf(INFO, "wlan::testing::phy::PhyDevice::Bind()");
dispatcher_ = std::make_unique<wlan::common::Dispatcher<wlan_device::Phy>>(wlanphy_async_t());
device_add_args_t args = {};
args.version = DEVICE_ADD_ARGS_VERSION;
args.name = "wlanphy-test";
args.ctx = this;
args.ops = &wlanphy_test_device_ops;
args.proto_id = ZX_PROTOCOL_WLANPHY;
args.proto_ops = &wlanphy_test_ops;
zx_status_t status = device_add(parent_, &args, &zxdev_);
if (status != ZX_OK) {
printf("wlanphy-test: could not add test device: %d\n", status);
}
return status;
}
void PhyDevice::Unbind() {
zxlogf(INFO, "wlan::testing::PhyDevice::Unbind()");
std::lock_guard<std::mutex> guard(lock_);
dispatcher_.reset();
device_unbind_reply(zxdev_);
}
void PhyDevice::Release() {
zxlogf(INFO, "wlan::testing::PhyDevice::Release()");
delete this;
}
zx_status_t PhyDevice::Message(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
DeviceConnector connector(this);
llcpp::fuchsia::wlan::device::Connector::Dispatch(&connector, msg, &transaction);
return transaction.Status();
}
namespace {
wlan_device::PhyInfo get_info() {
wlan_device::PhyInfo info;
info.supported_mac_roles.resize(0);
info.supported_mac_roles.push_back(wlan_device::MacRole::CLIENT);
info.supported_mac_roles.push_back(wlan_device::MacRole::AP);
return info;
}
} // namespace
void PhyDevice::Query(QueryCallback callback) {
zxlogf(INFO, "wlan::testing::phy::PhyDevice::Query()");
wlan_device::QueryResponse resp;
resp.info = get_info();
callback(std::move(resp));
}
void PhyDevice::CreateIface(wlan_device::CreateIfaceRequest req, CreateIfaceCallback callback) {
zxlogf(INFO, "CreateRequest: role=%u", req.role);
std::lock_guard<std::mutex> guard(lock_);
wlan_device::CreateIfaceResponse resp;
// We leverage wrapping of unsigned ints to cycle back through ids to find an unused one.
bool found_unused = false;
uint16_t id = next_id_;
while (!found_unused) {
if (ifaces_.count(id) > 0) {
id++;
// If we wrap all the way around, something is very wrong.
if (next_id_ == id) {
break;
}
} else {
found_unused = true;
}
}
ZX_DEBUG_ASSERT(found_unused);
if (!found_unused) {
resp.status = ZX_ERR_NO_RESOURCES;
callback(std::move(resp));
return;
}
wlan_info_mac_role_t role = 0;
switch (req.role) {
case wlan_device::MacRole::CLIENT:
role = WLAN_INFO_MAC_ROLE_CLIENT;
break;
case wlan_device::MacRole::AP:
role = WLAN_INFO_MAC_ROLE_AP;
break;
case wlan_device::MacRole::MESH:
role = WLAN_INFO_MAC_ROLE_MESH;
break;
default:
resp.status = ZX_ERR_NOT_SUPPORTED;
callback(std::move(resp));
return;
}
// Create the interface device and bind it.
auto macdev = std::make_unique<IfaceDevice>(zxdev_, role);
zx_status_t status = macdev->Bind();
if (status != ZX_OK) {
zxlogf(ERROR, "could not bind child wlanmac device: %d", status);
resp.status = status;
callback(std::move(resp));
return;
}
// Memory management follows the device lifecycle at this point. The only way an interface can be
// removed is through this phy device, either through a DestroyIface call or by the phy going
// away, so it should be safe to store the raw pointer.
ifaces_[id] = macdev.release();
// Since we successfully used the id, increment the next id counter.
next_id_ = id + 1;
resp.iface_id = id;
resp.status = ZX_OK;
callback(std::move(resp));
}
void PhyDevice::DestroyIface(wlan_device::DestroyIfaceRequest req, DestroyIfaceCallback callback) {
zxlogf(INFO, "DestroyRequest: id=%u", req.id);
wlan_device::DestroyIfaceResponse resp;
std::lock_guard<std::mutex> guard(lock_);
auto intf = ifaces_.find(req.id);
if (intf == ifaces_.end()) {
resp.status = ZX_ERR_NOT_FOUND;
callback(std::move(resp));
return;
}
device_async_remove(intf->second->zxdev());
// Remove the device from our map. We do NOT free the memory, since the devhost owns it and will
// call release when it's safe to free the memory.
ifaces_.erase(req.id);
resp.status = ZX_OK;
callback(std::move(resp));
}
void PhyDevice::SetCountry(wlan_device::CountryCode req, SetCountryCallback callback) {
zxlogf(INFO, "testing/PHY: SetCountry [%s]", wlan::common::Alpha2ToStr(req.alpha2).c_str());
callback(ZX_OK);
}
void PhyDevice::GetCountry(GetCountryCallback callback) {
zxlogf(INFO, "testing/PHY: GetCountry");
callback(fit::error(ZX_ERR_NOT_SUPPORTED));
}
void PhyDevice::ClearCountry(ClearCountryCallback callback) {
zxlogf(INFO, "testing/PHY: ClearCountry");
callback(ZX_OK);
}
zx_status_t PhyDevice::Connect(zx::channel request) {
return dispatcher_->AddBinding(std::move(request), this);
}
} // namespace testing
} // namespace wlan