| #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) |