blob: 24942deac5c3a1d7273433f3e3f9b572204aef34 [file] [log] [blame]
// Copyright 2019 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.
// This file is to fake the PCIe transportation layer and the firmware
// in order to test upper layers of the iwlwifi driver.
//
// The simulated behaviors are implemented in the 'trans_ops_sim_trans',
// which is a 'struct iwl_trans_ops'.
//
// This file also calls device_add() to register 'wlanphy_impl_protocol_ops_t'
// so that we can simulate the MLME (the user of this softmac driver) to test
// the iface functions.
//
// After iwl_sim_trans_transport_alloc() is called, memory containing iwl_trans +
// sim_trans_priv is returned. To access the simulated-transportation-specific
// variable, use IWL_TRANS_GET_SIM_TRANS(trans) to get it.
#include "src/iwlwifi/test/sim-trans.h"
#include <fuchsia/hardware/wlan/mac/c/banjo.h>
#include <fuchsia/hardware/wlanphyimpl/c/banjo.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fake-bti/bti.h>
#include <zircon/status.h>
extern "C" {
#include "src/iwlwifi/fw/api/alive.h"
#include "src/iwlwifi/fw/api/commands.h"
#include "src/iwlwifi/iwl-config.h"
#include "src/iwlwifi/iwl-drv.h"
#include "src/iwlwifi/iwl-trans.h"
#include "src/iwlwifi/mvm/mvm.h"
}
#include "src/iwlwifi/platform/mvm-mlme.h"
#include "src/iwlwifi/platform/wlanphy-impl-device.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
using wlan::testing::IWL_TRANS_GET_SIM_TRANS;
using wlan::testing::sim_trans_priv;
using wlan::testing::SimMvm;
namespace {
// SimTransDevice to appropriately handle unbind and release.
class SimTransDevice : public ::wlan::iwlwifi::WlanphyImplDevice {
public:
explicit SimTransDevice(zx_device_t* parent, iwl_trans* drvdata)
: WlanphyImplDevice(parent), drvdata_(drvdata){};
void DdkInit(::ddk::InitTxn txn) override { txn.Reply(ZX_OK); }
void DdkUnbind(::ddk::UnbindTxn txn) override {
struct iwl_trans* trans = drvdata_;
if (trans->drv) {
iwl_drv_stop(trans->drv);
}
free(trans);
txn.Reply();
};
iwl_trans* drvdata() override { return drvdata_; }
const iwl_trans* drvdata() const override { return drvdata_; }
private:
iwl_trans* drvdata_ = nullptr;
};
} // namespace
// Send a fake packet from FW to unblock one wait in mvm->notif_wait.
static void rx_fw_notification(struct iwl_trans* trans, uint8_t cmd, const void* data,
size_t size) {
struct iwl_mvm* mvm = IWL_OP_MODE_GET_MVM(trans->op_mode);
iwl_rx_packet pkt = {};
pkt.len_n_flags = (size & FH_RSCSR_FRAME_SIZE_MSK) + sizeof(pkt.hdr);
pkt.hdr.cmd = cmd;
std::string buffer(sizeof(pkt) + size, '\0');
std::memcpy(buffer.data(), &pkt, sizeof(pkt));
std::memcpy(buffer.data() + sizeof(pkt), data, size);
iwl_notification_wait_notify(&mvm->notif_wait, reinterpret_cast<iwl_rx_packet*>(buffer.data()));
}
// Notify the mvm->notif_wait to unblock the waiting.
static void unblock_notif_wait(struct iwl_trans* trans) {
struct iwl_mvm* mvm = IWL_OP_MODE_GET_MVM(trans->op_mode);
iwl_notification_notify(&mvm->notif_wait);
}
static zx_status_t iwl_sim_trans_start_hw(struct iwl_trans* iwl_trans, bool low_power) {
return ZX_OK;
}
static void iwl_sim_trans_op_mode_leave(struct iwl_trans* iwl_trans) {}
static zx_status_t iwl_sim_trans_start_fw(struct iwl_trans* trans, const struct fw_img* fw,
bool run_in_rfkill) {
// Kick off the firmware.
//
// Since we don't have a real firmware to load, there will be no notification from the firmware.
// Fake a RX packet's behavior so that we won't get blocked in the iwl_mvm_mac_start().
mvm_alive_resp resp = {};
resp.status = htole16(IWL_ALIVE_STATUS_OK);
rx_fw_notification(trans, iwl_legacy_cmds::MVM_ALIVE, &resp, sizeof(resp));
return ZX_OK;
}
static void iwl_sim_trans_fw_alive(struct iwl_trans* trans, uint32_t scd_addr) {}
static void iwl_sim_trans_stop_device(struct iwl_trans* trans, bool low_power) {}
static zx_status_t iwl_sim_trans_send_cmd(struct iwl_trans* trans, struct iwl_host_cmd* cmd) {
bool notify_wait;
zx_status_t ret = IWL_TRANS_GET_SIM_TRANS(trans)->fw->SendCmd(cmd, &notify_wait);
// On real hardware, some particular commands would reply a packet to unblock the wait.
// However, in the simulated firmware, we don't generate the packet. We unblock it directly.
if (notify_wait) {
unblock_notif_wait(trans);
}
return ret;
}
static zx_status_t iwl_sim_trans_tx(struct iwl_trans* trans, ieee80211_mac_packet* pkt,
const struct iwl_device_cmd* dev_cmd, int queue) {
return ZX_ERR_NOT_SUPPORTED;
}
static void iwl_sim_trans_reclaim(struct iwl_trans* trans, int queue, int ssn) {}
static bool iwl_sim_trans_txq_enable(struct iwl_trans* trans, int queue, uint16_t ssn,
const struct iwl_trans_txq_scd_cfg* cfg,
zx_duration_t queue_wdg_timeout) {
return false;
}
static void iwl_sim_trans_txq_disable(struct iwl_trans* trans, int queue, bool configure_scd) {}
static void iwl_sim_trans_write8(struct iwl_trans* trans, uint32_t ofs, uint8_t val) {}
static void iwl_sim_trans_write32(struct iwl_trans* trans, uint32_t ofs, uint32_t val) {}
static uint32_t iwl_sim_trans_read32(struct iwl_trans* trans, uint32_t ofs) {
return __INT32_MAX__;
}
static uint32_t iwl_sim_trans_read_prph(struct iwl_trans* trans, uint32_t ofs) {
return __INT32_MAX__;
}
static void iwl_sim_trans_write_prph(struct iwl_trans* trans, uint32_t ofs, uint32_t val) {}
static zx_status_t iwl_sim_trans_read_mem(struct iwl_trans* trans, uint32_t addr, void* buf,
size_t dwords) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t iwl_sim_trans_write_mem(struct iwl_trans* trans, uint32_t addr, const void* buf,
size_t dwords) {
return ZX_ERR_NOT_SUPPORTED;
}
static void iwl_sim_trans_configure(struct iwl_trans* trans,
const struct iwl_trans_config* trans_cfg) {}
static void iwl_sim_trans_set_pmi(struct iwl_trans* trans, bool state) {}
static void iwl_sim_trans_sw_reset(struct iwl_trans* trans) {}
static bool iwl_sim_trans_grab_nic_access(struct iwl_trans* trans, unsigned long* flags) {
return false;
}
static void iwl_sim_trans_release_nic_access(struct iwl_trans* trans, unsigned long* flags) {}
static void iwl_sim_trans_set_bits_mask(struct iwl_trans* trans, uint32_t reg, uint32_t mask,
uint32_t value) {}
static void iwl_sim_trans_ref(struct iwl_trans* trans) {}
static void iwl_sim_trans_unref(struct iwl_trans* trans) {}
static zx_status_t iwl_sim_trans_suspend(struct iwl_trans* trans) { return ZX_ERR_NOT_SUPPORTED; }
static void iwl_sim_trans_resume(struct iwl_trans* trans) {}
static zx_status_t iwl_sim_trans_wait_tx_queues_empty(struct iwl_trans* trans, uint32_t txq_bm) {
return ZX_OK;
}
static zx_status_t iwl_sim_trans_wait_txq_empty(struct iwl_trans* trans, int queue) {
return ZX_OK;
}
static struct iwl_trans_ops trans_ops_sim_trans = {
.start_hw = iwl_sim_trans_start_hw,
.op_mode_leave = iwl_sim_trans_op_mode_leave,
.start_fw = iwl_sim_trans_start_fw,
.fw_alive = iwl_sim_trans_fw_alive,
.stop_device = iwl_sim_trans_stop_device,
.send_cmd = iwl_sim_trans_send_cmd,
.tx = iwl_sim_trans_tx,
.reclaim = iwl_sim_trans_reclaim,
.txq_enable = iwl_sim_trans_txq_enable,
.txq_disable = iwl_sim_trans_txq_disable,
.wait_tx_queues_empty = iwl_sim_trans_wait_tx_queues_empty,
.wait_txq_empty = iwl_sim_trans_wait_txq_empty,
.write8 = iwl_sim_trans_write8,
.write32 = iwl_sim_trans_write32,
.read32 = iwl_sim_trans_read32,
.read_prph = iwl_sim_trans_read_prph,
.write_prph = iwl_sim_trans_write_prph,
.read_mem = iwl_sim_trans_read_mem,
.write_mem = iwl_sim_trans_write_mem,
.configure = iwl_sim_trans_configure,
.set_pmi = iwl_sim_trans_set_pmi,
.sw_reset = iwl_sim_trans_sw_reset,
.grab_nic_access = iwl_sim_trans_grab_nic_access,
.release_nic_access = iwl_sim_trans_release_nic_access,
.set_bits_mask = iwl_sim_trans_set_bits_mask,
.ref = iwl_sim_trans_ref,
.unref = iwl_sim_trans_unref,
.suspend = iwl_sim_trans_suspend,
.resume = iwl_sim_trans_resume,
#if 0 // NEEDS_PORTING
void (*d3_suspend)(struct iwl_trans* trans, bool test, bool reset);
int (*d3_resume)(struct iwl_trans* trans, enum iwl_d3_status* status, bool test, bool reset);
/* 22000 functions */
int (*txq_alloc)(struct iwl_trans* trans, __le16 flags, uint8_t sta_id, uint8_t tid, int cmd_id,
int size, unsigned int queue_wdg_timeout);
void (*txq_free)(struct iwl_trans* trans, int queue);
int (*rxq_dma_data)(struct iwl_trans* trans, int queue, struct iwl_trans_rxq_dma_data* data);
void (*txq_set_shared_mode)(struct iwl_trans* trans, uint32_t txq_id, bool shared);
void (*freeze_txq_timer)(struct iwl_trans* trans, unsigned long txqs, bool freeze);
void (*block_txq_ptrs)(struct iwl_trans* trans, bool block);
struct iwl_trans_dump_data* (*dump_data)(struct iwl_trans* trans, uint32_t dump_mask);
void (*debugfs_cleanup)(struct iwl_trans* trans);
#endif // NEEDS_PORTING
};
// iwl_trans_alloc() will allocate memory containing iwl_trans + sim_trans_priv.
static struct iwl_trans* iwl_sim_trans_transport_alloc(struct device* dev,
const struct iwl_cfg* cfg, SimMvm* fw) {
struct iwl_trans* iwl_trans =
iwl_trans_alloc(sizeof(struct sim_trans_priv), dev, cfg, &trans_ops_sim_trans);
IWL_TRANS_GET_SIM_TRANS(iwl_trans)->fw = fw;
return iwl_trans;
}
// This function intends to be like this because we want to mimic the transport_pcie_bind().
// But definitely can be refactored into the SimTransport::Init().
// 'out_trans' is used to return the new allocated 'struct iwl_trans'.
static zx_status_t sim_transport_bind(SimMvm* fw, struct device* dev,
struct iwl_trans** out_iwl_trans,
wlan::iwlwifi::WlanphyImplDevice** out_device) {
zx_status_t status = ZX_OK;
const struct iwl_cfg* cfg = &iwl7265_2ac_cfg;
struct iwl_trans* iwl_trans = iwl_sim_trans_transport_alloc(dev, cfg, fw);
if (!iwl_trans) {
return ZX_ERR_INTERNAL;
}
ZX_ASSERT(out_iwl_trans);
auto device = std::make_unique<SimTransDevice>(dev->zxdev, iwl_trans);
status = device->DdkAdd("sim-iwlwifi-wlanphyimpl", DEVICE_ADD_NON_BINDABLE);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to add wlanphyimpl device: %s", zx_status_get_string(status));
return status;
}
iwl_trans->zxdev = device->zxdev();
status = iwl_drv_init();
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to init driver: %s", zx_status_get_string(status));
goto remove_dev;
}
status = iwl_drv_start(iwl_trans, &iwl_trans->drv);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to start driver: %s", zx_status_get_string(status));
goto remove_dev;
}
status = iwl_mvm_mac_start(IWL_OP_MODE_GET_MVM(iwl_trans->op_mode));
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to start mac: %s", zx_status_get_string(status));
goto remove_dev;
}
*out_iwl_trans = iwl_trans;
*out_device = device.release();
return ZX_OK;
remove_dev:
device.release()->DdkAsyncRemove();
return status;
}
namespace wlan::testing {
SimTransport::SimTransport(::wlan::simulation::Environment* env, zx_device_t* parent)
: SimMvm(env), device_{}, iwl_trans_(nullptr) {
task_loop_ = std::make_unique<::async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
task_loop_->StartThread("iwlwifi-test-task-worker", nullptr);
irq_loop_ = std::make_unique<::async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
irq_loop_->StartThread("iwlwifi-test-irq-worker", nullptr);
device_.zxdev = parent;
device_.task_dispatcher = task_loop_->dispatcher();
device_.irq_dispatcher = irq_loop_->dispatcher();
fake_bti_create(&device_.bti);
}
SimTransport::~SimTransport() {
if (sim_device_) {
sim_device_->DdkAsyncRemove();
mock_ddk::ReleaseFlaggedDevices(sim_device_->zxdev());
}
zx_handle_close(device_.bti);
}
zx_status_t SimTransport::Init() {
return sim_transport_bind(this, &device_, &iwl_trans_, &sim_device_);
}
struct iwl_trans* SimTransport::iwl_trans() {
return iwl_trans_;
}
const struct iwl_trans* SimTransport::iwl_trans() const { return iwl_trans_; }
wlan::iwlwifi::WlanphyImplDevice* SimTransport::sim_device() { return sim_device_; }
const wlan::iwlwifi::WlanphyImplDevice* SimTransport::sim_device() const { return sim_device_; }
} // namespace wlan::testing