blob: 834d6b74b85ca1087057281627c1cea60c88827a [file] [log] [blame]
#include "wlan_phy.h"
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <wlan/common/phy.h>
#include "bus.h"
namespace wlan {
namespace rtl88xx {
namespace {
constexpr char kWlanPhyDeviceName[] = "rtl88xx-wlanphy";
constexpr uint16_t kWlanPhyIfaceId = 1;
} // namespace
WlanPhy::WlanPhy() : zx_device_(nullptr), wlan_mac_(nullptr) {}
WlanPhy::~WlanPhy() {
ZX_DEBUG_ASSERT_MSG(zx_device_ == nullptr, "rtl88xx: WlanPhy did not unbind zx_device_\n");
ZX_DEBUG_ASSERT_MSG(wlan_mac_ == nullptr, "rtl88xx: WlanPhy leaked wlan_mac_\n");
zx_device_ = nullptr;
wlan_mac_ = nullptr;
}
// static
zx_status_t WlanPhy::Create(zx_device_t* bus_device) {
std::unique_ptr<WlanPhy> wlan_phy(new WlanPhy());
zx_status_t status = ZX_OK;
std::unique_ptr<Bus> bus;
status = Bus::Create(bus_device, &bus);
if (status != ZX_OK) {
return status;
}
status = Device::Create(std::move(bus), &wlan_phy->device_);
if (status != ZX_OK) {
return status;
}
static zx_protocol_device_t device_ops = {
.version = DEVICE_OPS_VERSION,
.unbind = [](void* ctx) { reinterpret_cast<WlanPhy*>(ctx)->Unbind(); },
.release = [](void* ctx) { reinterpret_cast<WlanPhy*>(ctx)->Release(); },
};
static wlanphy_impl_protocol_ops_t wlanphy_impl_ops = {
.query = [](void* ctx, wlanphy_impl_info_t* info) -> zx_status_t {
return reinterpret_cast<WlanPhy*>(ctx)->Query(info);
},
.create_iface = [](void* ctx, const wlanphy_impl_create_iface_req_t* req,
uint16_t* out_iface_id) -> zx_status_t {
return reinterpret_cast<WlanPhy*>(ctx)->CreateIface(req, out_iface_id);
},
.destroy_iface = [](void* ctx, uint16_t id) -> zx_status_t {
return reinterpret_cast<WlanPhy*>(ctx)->DestroyIface(id);
},
.set_country = [](void* ctx, const wlanphy_country_t* country) -> zx_status_t {
return reinterpret_cast<WlanPhy*>(ctx)->SetCountry(country);
},
.get_country = [](void* ctx, wlanphy_country_t* out_country) -> zx_status_t {
return reinterpret_cast<WlanPhy*>(ctx)->GetCountry(out_country);
},
};
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = kWlanPhyDeviceName,
.ctx = static_cast<void*>(wlan_phy.get()),
.ops = &device_ops,
.proto_id = ZX_PROTOCOL_WLANPHY_IMPL,
.proto_ops = static_cast<void*>(&wlanphy_impl_ops),
};
status = device_add(bus_device, &args, &wlan_phy->zx_device_);
if (status != ZX_OK) {
zxlogf(ERROR, "rtl88xx: WlanPhy failed to create zx_device_: %s",
zx_status_get_string(status));
return status;
}
// The lifetime of this wlan_phy instance is now managed by the devhost.
wlan_phy.release();
return ZX_OK;
}
void WlanPhy::Unbind() {
zx_device_t* const to_remove = zx_device_;
zx_device_ = nullptr;
// Call device_unbind_reply() last, as it may call Release().
device_unbind_reply(to_remove);
}
void WlanPhy::Release() { delete this; }
zx_status_t WlanPhy::Query(wlanphy_impl_info_t* info) {
if (wlan_mac_ == nullptr) {
return ZX_ERR_NOT_FOUND;
}
return wlan_mac_->Query(info);
}
zx_status_t WlanPhy::CreateIface(const wlanphy_impl_create_iface_req_t* req,
uint16_t* out_iface_id) {
if (wlan_mac_ != nullptr) {
return ZX_ERR_ALREADY_BOUND;
}
WlanMac* wlan_mac = nullptr;
const zx_status_t status = device_->CreateWlanMac(zx_device_, &wlan_mac);
if (status != ZX_OK) {
return status;
}
wlan_mac_ = wlan_mac;
*out_iface_id = kWlanPhyIfaceId;
return ZX_OK;
}
zx_status_t WlanPhy::DestroyIface(uint16_t id) {
if (wlan_mac_ == nullptr) {
return ZX_ERR_NOT_FOUND;
}
if (id != kWlanPhyIfaceId) {
return ZX_ERR_NOT_FOUND;
}
const zx_status_t status = wlan_mac_->Destroy();
if (status != ZX_OK) {
return status;
}
// The lifetime of `wlan_mac_` is managed by the devhost, so we do not delete it here.
wlan_mac_ = nullptr;
return ZX_OK;
}
zx_status_t WlanPhy::SetCountry(const wlanphy_country_t* country) {
if (country == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
zxlogf(ERROR, "rtl88xx: SetCountry to [%s] not implemented",
wlan::common::Alpha2ToStr(country->alpha2).c_str());
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t WlanPhy::GetCountry(wlanphy_country_t* out_country) {
if (out_country == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
zxlogf(ERROR, "rtl88xx: GetCountry not implemented");
return ZX_ERR_NOT_SUPPORTED;
}
} // namespace rtl88xx
} // namespace wlan
zx_status_t rtl88xx_bind_wlan_phy(void* ctx, zx_device_t* device) {
// Forward to the actual implementation in C++.
return ::wlan::rtl88xx::WlanPhy::Create(device);
}
static constexpr zx_driver_ops_t rtl88xx_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = &rtl88xx_bind_wlan_phy;
return ops;
}();
ZIRCON_DRIVER_BEGIN(rtl88xx, rtl88xx_driver_ops, "zircon", "0.1", 8)
BI_GOTO_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI, 0),
BI_ABORT_IF(NE, BIND_PCI_VID, 0x10ec), // Realtek PCI VID
BI_MATCH_IF(EQ, BIND_PCI_DID, 0x0000), BI_ABORT(), BI_LABEL(0),
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB),
BI_ABORT_IF(NE, BIND_USB_VID, 0x0bda), // Realtek USB VID
BI_MATCH_IF(EQ, BIND_USB_PID, 0xc820), // UM821C04_3V3 test board
ZIRCON_DRIVER_END(rtl88xx)