blob: 8de696613abfed1e04facaf593847454b91aca4a [file] [log] [blame]
// 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/pcie-iwlwifi-driver.h"
#include <fidl/fuchsia.component.decl/cpp/fidl.h>
#include <fidl/fuchsia.hardware.pci/cpp/wire.h>
#include <lib/async/cpp/task.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <memory>
#include <bind/fuchsia/wlan/phyimpl/cpp/bind.h>
#include <bind/fuchsia/wlan/softmac/cpp/bind.h>
#include "third_party/driver-lib/log/cpp/include/dfv2/wlan/drivers/log_instance.h"
extern "C" {
#include "third_party/iwlwifi/iwl-debug.h"
#include "third_party/iwlwifi/iwl-drv.h"
#include "third_party/iwlwifi/mvm/mvm.h"
#include "third_party/iwlwifi/pcie/entry.h"
}
#include "third_party/iwlwifi/platform/driver-inspector.h"
#include "third_party/iwlwifi/platform/mvm-mlme.h"
#include "third_party/iwlwifi/platform/pci-fidl.h"
#include "third_party/iwlwifi/platform/rcu-manager.h"
#if !CPTCFG_IWLMVM
#error "PcieDevice requires support for MVM firmwares."
#endif // CPTCFG_IWLMVM
namespace wlan {
namespace iwlwifi {
PcieIwlwifiDriver::PcieIwlwifiDriver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher)
: WlanPhyImplDevice(),
DriverBase("iwlwifi", std::move(start_args), std::move(driver_dispatcher)),
node_client_(fidl::WireClient(std::move(node()), dispatcher())) {
pci_dev_ = {};
}
PcieIwlwifiDriver::~PcieIwlwifiDriver() {
// Release the logger instance.
wlan::drivers::log::Instance::Reset();
}
constexpr auto kOpenFlags =
fuchsia_io::wire::OpenFlags::kRightReadable | fuchsia_io::wire::OpenFlags::kNotDirectory;
zx_status_t PcieIwlwifiDriver::LoadFirmware(const char* name, zx_handle_t* vmo, size_t* size) {
std::string full_filename = "/pkg/lib/firmware/";
full_filename.append(name);
auto client = incoming()->Open<fuchsia_io::File>(full_filename.c_str(), kOpenFlags);
if (client.is_error()) {
IWL_WARN(nullptr, "Open firmware file failed: %s", zx_status_get_string(client.error_value()));
return client.error_value();
}
fidl::WireResult backing_memory_result =
fidl::WireCall(*client)->GetBackingMemory(fuchsia_io::wire::VmoFlags::kRead);
if (!backing_memory_result.ok()) {
if (backing_memory_result.is_peer_closed()) {
IWL_WARN(nullptr, "Failed to get backing memory: Peer closed");
return ZX_ERR_NOT_FOUND;
}
IWL_WARN(nullptr, "Failed to get backing memory: %s",
zx_status_get_string(backing_memory_result.status()));
return backing_memory_result.status();
}
const auto* backing_memory = backing_memory_result.Unwrap();
if (backing_memory->is_error()) {
IWL_WARN(nullptr, "Failed to get backing memory: %s",
zx_status_get_string(backing_memory->error_value()));
return backing_memory->error_value();
}
zx::vmo& backing_vmo = backing_memory->value()->vmo;
if (zx_status_t status = backing_vmo.get_prop_content_size(size); status != ZX_OK) {
IWL_WARN(nullptr, "Failed to get vmo size: %s", zx_status_get_string(status));
return status;
}
*vmo = backing_vmo.release();
return ZX_OK;
}
void PcieIwlwifiDriver::on_fidl_error(fidl::UnbindInfo error) {
if (error.status() != ZX_OK) {
IWL_ERR(nullptr, "Unexpected fidl channel close from child node: %s",
zx_status_get_string(error.status()));
return;
}
IWL_INFO(nullptr, "Child node removed successfully.");
}
void PcieIwlwifiDriver::handle_unknown_event(
fidl::UnknownEventMetadata<fuchsia_driver_framework::NodeController> metadata) {}
zx_status_t PcieIwlwifiDriver::AddWlanphyChild() {
fidl::Arena arena;
std::vector offers = {
fdf::MakeOffer<fuchsia_wlan_phyimpl::Service>(arena, "default"),
};
// Set the properties of the node that a driver will bind to.
auto property = fdf::MakeProperty(arena, bind_fuchsia_wlan_phyimpl::WLANPHYIMPL,
bind_fuchsia_wlan_phyimpl::WLANPHYIMPL_DRIVERTRANSPORT);
auto args = fdf::wire::NodeAddArgs::Builder(arena)
.name("iwlwifi-wlanphyimpl")
.properties(fidl::VectorView<fdf::wire::NodeProperty>::FromExternal(&property, 1))
.offers(arena, std::move(offers))
.Build();
auto endpoints = fidl::CreateEndpoints<fdf::NodeController>();
if (endpoints.is_error()) {
return endpoints.error_value();
}
// Adding wlanphy child node. Doing a sync version here to reduce chaos.
auto result = node_client_.sync()->AddChild(std::move(args), std::move(endpoints->server), {});
if (!result.ok()) {
IWL_ERR(nullptr, "Add wlanphy node error due to FIDL error on protocol [Node]: %s",
result.status_string());
return result.status();
}
if (result->is_error()) {
IWL_ERR(nullptr, "Add wlanphy node error: %u", static_cast<uint32_t>(result->error_value()));
return ZX_ERR_INTERNAL;
}
wlanphy_controller_client_.Bind(std::move(endpoints->client), dispatcher(), this);
return ZX_OK;
}
zx_status_t PcieIwlwifiDriver::AddWlansoftmacDevice(uint16_t iface_id, struct iwl_mvm_vif* mvmvif) {
wlan_softmac_device_ = std::make_unique<WlanSoftmacDevice>(pci_dev_.drvdata, iface_id, mvmvif);
auto wlansoftmac = [this](fdf::ServerEnd<fuchsia_wlan_softmac::WlanSoftmac> server_end) {
wlan_softmac_device_->ServiceConnectHandler(driver_dispatcher()->get(), std::move(server_end));
};
// Add the service contains WlanSoftmac protocol to outgoing directory.
fuchsia_wlan_softmac::Service::InstanceHandler wlansoftmac_service_handler(
{.wlan_softmac = wlansoftmac});
auto status =
outgoing()->AddService<fuchsia_wlan_softmac::Service>(std::move(wlansoftmac_service_handler));
if (status.is_error()) {
IWL_ERR(nullptr, "Failed to add service to outgoing directory: %s", status.status_string());
return status.status_value();
}
fidl::Arena arena;
std::vector offers = {
fdf::MakeOffer<fuchsia_wlan_softmac::Service>(arena, "default"),
};
// Set the properties of the node that a driver will bind to.
auto property = fdf::MakeProperty(arena, bind_fuchsia_wlan_softmac::WLANSOFTMAC,
bind_fuchsia_wlan_softmac::WLANSOFTMAC_DRIVERTRANSPORT);
auto args = fdf::wire::NodeAddArgs::Builder(arena)
.name("iwlwifi-wlansoftmac")
.properties(fidl::VectorView<fdf::wire::NodeProperty>::FromExternal(&property, 1))
.offers(arena, std::move(offers))
.Build();
auto endpoints = fidl::CreateEndpoints<fdf::NodeController>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
wlansoftmac_controller_client_ = std::make_optional<fidl::WireClient<fdf::NodeController>>();
wlansoftmac_controller_client_.value().Bind(std::move(endpoints->client), dispatcher(), this);
// Add wlansoftmac child node for the node that this driver is binding to. Doing a sync version
// here to reduce chaos.
auto result = node_client_.sync()->AddChild(std::move(args), std::move(endpoints->server), {});
if (!result.ok()) {
IWL_ERR(nullptr, "Add wlansoftmac node error due to FIDL error on protocol [Node]: %s",
result.status_string());
return result.status();
}
if (result->is_error()) {
IWL_ERR(nullptr, "Add wlansoftmac node error: %u",
static_cast<uint32_t>(result->error_value()));
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
zx_status_t PcieIwlwifiDriver::RemoveWlansoftmacDevice(uint16_t iface_id) {
// The iface_id passed in is not used now, will apply it when iwlwifi driver starts supporting
// more than one wlansoftmac iface.
auto result = wlansoftmac_controller_client_.value()->Remove();
if (!result.ok()) {
IWL_ERR(nullptr, "Softmac child remove failed, FIDL error: %s", result.status_string());
return result.status();
}
wlan_softmac_device_.reset();
auto remove_result = outgoing()->RemoveService<fuchsia_wlan_softmac::Service>();
if (remove_result.is_error()) {
IWL_ERR(nullptr, "Failed to remove wlansoftmac service from outgoing directory: %s.",
remove_result.status_string());
return remove_result.status_value();
}
return ZX_OK;
}
zx::result<> PcieIwlwifiDriver::Start() {
// Take over the logger lifecycle management from DriverBase.
wlan::drivers::log::Instance::Init(0, std::move(logger_));
zx_status_t status = AddWlanPhyImplService();
if (status != ZX_OK) {
IWL_ERR(nullptr, "ServeRuntimeProtocolForV1Devices failed: %s", zx_status_get_string(status));
return zx::error(status);
}
// Initialize the driver.
Init();
return zx::ok();
}
void PcieIwlwifiDriver::PrepareStop(fdf::PrepareStopCompleter completer) {
if (drvdata()) {
// TODO(b/306684649): remove the below workaround once the issue is fixed.
const int mac_id = 0; // Assume only one interface is created.
const auto iwl_trans = reinterpret_cast<struct iwl_trans*>(drvdata());
struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
struct iwl_mvm_vif* mvmvif = mvm->mvmvif[mac_id];
mac_stop(mvmvif);
phy_destroy_iface(drvdata(), mac_id);
}
iwl_pci_remove(&pci_dev_);
zx_handle_close(pci_dev_.dev.bti);
pci_dev_.dev.bti = ZX_HANDLE_INVALID;
ZX_DEBUG_ASSERT(pci_dev_.drvdata == nullptr);
wlan_softmac_device_.reset();
completer(zx::ok());
}
iwl_trans* PcieIwlwifiDriver::drvdata() { return pci_dev_.drvdata; }
const iwl_trans* PcieIwlwifiDriver::drvdata() const { return pci_dev_.drvdata; }
zx_status_t load_firmware_callback_entry(void* ctx, const char* name, zx_handle_t* vmo,
size_t* size) {
return static_cast<PcieIwlwifiDriver*>(ctx)->LoadFirmware(name, vmo, size);
}
zx_status_t PcieIwlwifiDriver::Init() {
zx_status_t status = ZX_OK;
driver_inspector_ = std::make_unique<DriverInspector>(
dispatcher(), outgoing()->component(), DriverInspectorOptions{.root_name = "iwlwifi"});
rcu_manager_ = std::make_unique<RcuManager>(dispatcher());
rcu_manager_->InitForThread();
// Fill in the relevant Fuchsia-specific fields in our driver interface struct.
pci_dev_.dev.load_firmware_ctx = (void*)this;
pci_dev_.dev.load_firmware_callback =
&load_firmware_callback_entry; // Save the namespace address to a C void pointer.
pci_dev_.dev.task_dispatcher = dispatcher();
pci_dev_.dev.irq_dispatcher = dispatcher();
pci_dev_.dev.rcu_manager = static_cast<struct rcu_manager*>(rcu_manager_.get());
pci_dev_.dev.inspector = static_cast<struct driver_inspector*>(driver_inspector_.get());
auto pci_client_end = incoming()->Connect<fuchsia_hardware_pci::Service::Device>();
if (pci_client_end.is_error()) {
IWL_ERR(nullptr, "Failed to connect to PCI service: %s", pci_client_end.status_string());
return pci_client_end.status_value();
}
iwl_pci_connect_fragment_protocol_with_client(std::move(pci_client_end.value()), &pci_dev_.fidl);
if ((status = StartPci()) != ZX_OK) {
IWL_ERR(nullptr, "Failed to Start PCI: %s", zx_status_get_string(status));
return status;
}
ZX_ASSERT_MSG(AddWlanphyChild() == ZX_OK, "AddWlanphyChild failed.");
return ZX_OK;
}
zx_status_t PcieIwlwifiDriver::StartPci() {
zx_status_t status;
if ((status = iwl_pci_get_bti(pci_dev_.fidl, /*index*/ 0, &pci_dev_.dev.bti)) != ZX_OK) {
IWL_ERR(nullptr, "Failed to get PCI BTI: %s", zx_status_get_string(status));
return status;
}
pci_device_info_t pci_info = {};
if ((status = iwl_pci_get_device_info(pci_dev_.fidl, &pci_info)) != ZX_OK) {
return status;
}
uint16_t subsystem_device_id = 0;
if ((status = iwl_pci_read_config16(
pci_dev_.fidl, fidl::ToUnderlying(fuchsia_hardware_pci::Config::kSubsystemId),
&subsystem_device_id)) != ZX_OK) {
IWL_ERR(nullptr, "Failed to read PCI subsystem device ID: %s", zx_status_get_string(status));
return status;
}
IWL_INFO(nullptr, "Device ID: %04x Subsystem Device ID: %04x", pci_info.device_id,
subsystem_device_id);
pci_dev_.device = pci_info.device_id;
pci_dev_.subsystem_device = subsystem_device_id;
// Do iwl_drv_init() before iwl_pci_probe.
if ((status = iwl_drv_init()) != ZX_OK) {
IWL_ERR(nullptr, "Failed to init driver: %s\n", zx_status_get_string(status));
return status;
}
const iwl_pci_device_id* id = nullptr;
if ((status = iwl_pci_find_device_id(pci_info.device_id, subsystem_device_id, &id)) != ZX_OK) {
IWL_ERR(nullptr, "Failed to find PCI config: %s device_id=0x%04x subsys_did=0x%04x",
zx_status_get_string(status), pci_info.device_id, subsystem_device_id);
return status;
}
if ((status = iwl_pci_probe(&pci_dev_, id)) != ZX_OK) {
IWL_ERR(nullptr, "Failed to probe PCI device: %s", zx_status_get_string(status));
// No return here to pass initilization process for tests, add return back after fining the
// test environment.
}
return ZX_OK;
}
zx_status_t PcieIwlwifiDriver::AddWlanPhyImplService() {
// Add the service contains WlanphyImpl protocol to outgoing directory.
auto wlanphy = [this](fdf::ServerEnd<fuchsia_wlan_phyimpl::WlanPhyImpl> server_end) {
// Call the handler inherited from WlanPhyImplDevice.
// Note: The same dispatcher here is used for softmac device, will it affect the data path
// performance?
ServiceConnectHandler(driver_dispatcher()->get(), std::move(server_end));
};
fuchsia_wlan_phyimpl::Service::InstanceHandler wlanphy_service_handler(
{.wlan_phy_impl = wlanphy});
auto status =
outgoing()->AddService<fuchsia_wlan_phyimpl::Service>(std::move(wlanphy_service_handler));
if (status.is_error()) {
IWL_ERR(nullptr, "Failed to add service to outgoing directory: %s", status.status_string());
return status.status_value();
}
return ZX_OK;
}
} // namespace iwlwifi
} // namespace wlan
FUCHSIA_DRIVER_EXPORT(::wlan::iwlwifi::PcieIwlwifiDriver);