blob: 20aa6db887063c7cce976adc7144fd516a0aea00 [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.
#ifndef SRC_DEVICES_BUS_DRIVERS_PCI_TEST_FAKES_FAKE_CONFIG_H_
#define SRC_DEVICES_BUS_DRIVERS_PCI_TEST_FAKES_FAKE_CONFIG_H_
#include <lib/mmio/mmio.h>
#include <lib/zx/vmo.h>
#include <zircon/hw/pci.h>
#include <cstdint>
#include <fbl/algorithm.h>
#include "../../common.h"
#include "../../config.h"
#include "test_device.h"
namespace pci {
// For most operations a real MmioConfig is fine for working with
// a fake ecam. However, for BAR probing we need to mock the side
// effects of the writes that are used to determine the size of
// the BAR. Fortunately, these writes are always UINT32_MAX and can
// be caught because they are not otherwise valid. If necessary, this
// class can be extended to handle other side-effects as well.
class FakeMmioConfig final : public MmioConfig {
public:
FakeMmioConfig(pci_bdf_t bdf, ddk::MmioView&& view) : MmioConfig(bdf, std::move(view)) {}
void MockBarProbeSideEffects(uint32_t bar_id) const {
uint32_t bar_val = MmioConfig::Read(Config::kBar(bar_id));
auto reg = config::BaseAddress::Get().FromValue(bar_val);
// When probing a BAR, the hardware writes a 0 in every valid bit used
// for an address. The least significant address bit set represents the
// size of the BAR. For example, if our size was 1M we would have an
// unmanipulated 0x00100000
//
// 0x00100000 - 1 = 0x000FFFFF
// ~(0x000FFFFF) = 0xFFF00000
uint32_t size = kTestDeviceBars[bar_id].size;
ZX_ASSERT(size == 0 || fbl::is_pow2(size));
size = ~(size - 1);
if (reg.is_io_space()) {
reg.io()->set_base_address(size);
} else {
reg.mmio()->set_base_address(size);
}
MmioConfig::Write(Config::kBar(bar_id), reg.reg_value());
}
void Write(const PciReg32 addr, uint32_t val) const final {
switch (addr.offset()) {
case Config::kBar(0).offset():
case Config::kBar(1).offset():
case Config::kBar(2).offset():
case Config::kBar(3).offset():
case Config::kBar(4).offset():
case Config::kBar(5).offset(): {
// A 32-bit write of all 1s in these addresses is reserved for
// querying the BAR size as long as it's not the upper half of
// a 64 bit register.
uint32_t bar_id = static_cast<uint32_t>((addr.offset() - Config::kBar(0).offset()) / 4);
if (val == UINT32_MAX && !kTestDeviceBars[bar_id].is_upper_half) {
MockBarProbeSideEffects(bar_id);
break;
}
__FALLTHROUGH;
}
default:
return MmioConfig::Write(addr, val);
}
}
const char* type(void) const final { return "fake_mmio"; }
};
} // namespace pci
#endif // SRC_DEVICES_BUS_DRIVERS_PCI_TEST_FAKES_FAKE_CONFIG_H_