blob: f459013fa8cde0caf754e78aaea820e1fa763811 [file] [log] [blame]
// Copyright 2020 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 "registers.h"
#include <lib/device-protocol/pdev.h>
#include <lib/fidl-async/cpp/bind.h>
#include <lib/fidl/epitaph.h>
#include <ddk/metadata.h>
#include <ddk/protocol/platform/bus.h>
#include "src/devices/registers/drivers/registers/registers-bind.h"
namespace registers {
namespace {
const static std::map<::llcpp::fuchsia::hardware::registers::Mask::Tag, uint8_t> kTagToBytes = {
{::llcpp::fuchsia::hardware::registers::Mask::Tag::kR8, 1},
{::llcpp::fuchsia::hardware::registers::Mask::Tag::kR16, 2},
{::llcpp::fuchsia::hardware::registers::Mask::Tag::kR32, 4},
{::llcpp::fuchsia::hardware::registers::Mask::Tag::kR64, 8},
};
template <typename Ty>
std::optional<Ty> GetMask(const ::llcpp::fuchsia::hardware::registers::Mask& mask) {
if constexpr (std::is_same_v<Ty, uint8_t>) {
// Need cast to compile
return static_cast<Ty>(mask.r8());
}
if constexpr (std::is_same_v<Ty, uint16_t>) {
// Need cast to compile
return static_cast<Ty>(mask.r16());
}
if constexpr (std::is_same_v<Ty, uint32_t>) {
// Need cast to compile
return static_cast<Ty>(mask.r32());
}
if constexpr (std::is_same_v<Ty, uint64_t>) {
// Need cast to compile
return static_cast<Ty>(mask.r64());
}
return std::nullopt;
}
} // namespace
template <typename T>
zx_status_t Register<T>::Init(const RegistersMetadataEntry& config) {
id_ = config.bind_id();
for (const auto& m : config.masks()) {
auto mask = GetMask<T>(m.mask());
if (!mask.has_value()) {
return ZX_ERR_INTERNAL;
}
masks_.emplace(m.mmio_offset(), std::make_pair(mask.value(), m.count()));
}
return ZX_OK;
}
template <typename T>
void Register<T>::RegistersConnect(zx::channel chan) {
zx_status_t status;
char name[20];
snprintf(name, sizeof(name), "register-%lu-thread", id_);
if (!loop_started_ && (status = loop_.StartThread(name)) != ZX_OK) {
zxlogf(ERROR, "%s: failed to start registers thread: %d", __func__, status);
fidl_epitaph_write(chan.get(), status);
return;
}
loop_started_ = true;
status = fidl::BindSingleInFlightOnly(loop_.dispatcher(), std::move(chan), this);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to bind channel: %d", __func__, status);
}
return;
}
// Returns: true if mask requested is covered by allowed mask.
// false if mask requested is not covered by allowed mask or mask is not found.
template <typename T>
bool Register<T>::VerifyMask(T mask, const uint64_t offset) {
auto it = masks_.upper_bound(offset);
if ((offset % sizeof(T)) || (it == masks_.begin())) {
return false;
}
it--;
auto base_address = it->first;
auto reg_mask = it->second.first;
auto reg_count = it->second.second;
return (((offset - base_address) / sizeof(T) < reg_count) &&
// Check that mask requested is covered by allowed mask.
((mask | reg_mask) == reg_mask));
}
template <typename T>
zx_status_t Register<T>::ReadRegister(uint64_t offset, T mask, T* out_value) {
if (!VerifyMask(mask, offset)) {
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&mmio_->locks[offset / sizeof(T)]);
*out_value = mmio_->mmio.ReadMasked(mask, offset);
return ZX_OK;
}
template <typename T>
zx_status_t Register<T>::WriteRegister(uint64_t offset, T mask, T value) {
if (!VerifyMask(mask, offset)) {
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&mmio_->locks[offset / sizeof(T)]);
mmio_->mmio.ModifyBits(value, mask, offset);
return ZX_OK;
}
template <typename T>
zx_status_t RegistersDevice<T>::Init(zx_device_t* parent, Metadata metadata) {
zx_status_t status = ZX_OK;
ddk::PDev pdev(parent);
pdev_device_info_t device_info = {};
if ((status = pdev.GetDeviceInfo(&device_info)) != ZX_OK) {
zxlogf(ERROR, "%s: Could not get device info", __func__);
return status;
}
if (metadata.mmio().count() != device_info.mmio_count) {
zxlogf(ERROR, "%s: MMIO metadata size doesn't match MMIO count.", __func__);
return ZX_ERR_INTERNAL;
}
// Get MMIOs
std::map<uint32_t, std::vector<T>> overlap;
for (uint32_t i = 0; i < device_info.mmio_count; i++) {
std::optional<ddk::MmioBuffer> tmp_mmio;
if ((status = pdev.MapMmio(i, &tmp_mmio)) != ZX_OK) {
zxlogf(ERROR, "%s: Could not get mmio regions", __func__);
return status;
}
auto register_count = tmp_mmio->get_size() / sizeof(T);
if (tmp_mmio->get_size() % sizeof(T)) {
zxlogf(ERROR, "%s: MMIO size does not cover full registers", __func__);
return ZX_ERR_INTERNAL;
}
std::vector<fbl::Mutex> tmp_locks(register_count);
mmios_.emplace(metadata.mmio()[i].id(), std::make_shared<MmioInfo>(MmioInfo{
.mmio = *std::move(tmp_mmio),
.locks = std::move(tmp_locks),
}));
overlap.emplace(metadata.mmio()[i].id(), std::vector<T>(register_count, 0));
}
// Check for overlapping bits.
for (const auto& reg : metadata.registers()) {
if (!reg.has_bind_id() && !reg.has_mmio_id() && !reg.has_masks()) {
// Doesn't have to have all Register IDs.
continue;
}
if (mmios_.find(reg.mmio_id()) == mmios_.end()) {
zxlogf(ERROR, "%s: Invalid MMIO ID %u for Register %u.\n", __func__, reg.mmio_id(),
reg.bind_id());
return ZX_ERR_INTERNAL;
}
for (const auto& m : reg.masks()) {
if (m.mmio_offset() / sizeof(T) >= mmios_[reg.mmio_id()]->locks.size()) {
zxlogf(ERROR, "%s: Invalid offset.\n", __func__);
return ZX_ERR_INTERNAL;
}
if (!m.overlap_check_on()) {
continue;
}
auto bits = overlap[reg.mmio_id()][m.mmio_offset() / sizeof(T)];
auto mask = GetMask<T>(m.mask());
if (!mask.has_value()) {
zxlogf(ERROR, "%s: Invalid mask\n", __func__);
return ZX_ERR_INTERNAL;
}
auto mask_value = mask.value();
if (bits & mask_value) {
zxlogf(ERROR, "%s: Overlapping bits in MMIO ID %u, Register No. %lu, Bit mask 0x%lx\n",
__func__, reg.mmio_id(), m.mmio_offset() / sizeof(T),
static_cast<uint64_t>(bits & mask_value));
return ZX_ERR_INTERNAL;
}
overlap[reg.mmio_id()][m.mmio_offset() / sizeof(T)] |= mask_value;
}
}
// Create Registers
for (auto& reg : metadata.registers()) {
if (!reg.has_bind_id() && !reg.has_mmio_id() && !reg.has_masks()) {
// Doesn't have to have all Register IDs.
continue;
}
fbl::AllocChecker ac;
std::unique_ptr<Register<T>> tmp_register(
new (&ac) Register<T>(this->zxdev(), mmios_[reg.mmio_id()]));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
zx_device_prop_t props[] = {
{BIND_REGISTER_ID, 0, reg.bind_id()},
};
char name[20];
snprintf(name, sizeof(name), "register-%u", reg.bind_id());
auto status = tmp_register->DdkAdd(
ddk::DeviceAddArgs(name).set_flags(DEVICE_ADD_ALLOW_MULTI_COMPOSITE).set_props(props));
if (status != ZX_OK) {
zxlogf(ERROR, "%s: DdkAdd for %s failed %d", __func__, name, status);
return status;
}
auto dev = tmp_register.release();
dev->Init(reg);
}
return ZX_OK;
}
template <typename T>
zx_status_t RegistersDevice<T>::Create(zx_device_t* parent, Metadata metadata) {
fbl::AllocChecker ac;
std::unique_ptr<RegistersDevice<T>> device(new (&ac) RegistersDevice(parent));
if (!ac.check()) {
zxlogf(ERROR, "%s: device object alloc failed", __func__);
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = ZX_OK;
if ((status = device->DdkAdd("registers-device", DEVICE_ADD_NON_BINDABLE)) != ZX_OK) {
zxlogf(ERROR, "%s: DdkAdd failed", __func__);
return status;
}
auto* device_ptr = device.release();
if ((status = device_ptr->Init(parent, std::move(metadata))) != ZX_OK) {
device_ptr->DdkAsyncRemove();
zxlogf(ERROR, "%s: Init failed", __func__);
return status;
}
return ZX_OK;
}
zx_status_t Bind(void* ctx, zx_device_t* parent) {
// Get metadata
size_t size;
auto status = device_get_metadata_size(parent, DEVICE_METADATA_REGISTERS, &size);
if (status != ZX_OK) {
zxlogf(ERROR, "device_get_metadata_size failed %d", status);
return status;
}
size_t actual;
auto bytes = std::make_unique<uint8_t[]>(size);
status = device_get_metadata(parent, DEVICE_METADATA_REGISTERS, bytes.get(), size, &actual);
if (status != ZX_OK) {
zxlogf(ERROR, "device_get_metadata failed %d", status);
return status;
}
if (actual != size) {
zxlogf(ERROR, "device_get_metadata size error %d", status);
return ZX_ERR_INTERNAL;
}
// Parse
fidl::DecodedMessage<Metadata> decoded(bytes.get(), static_cast<uint32_t>(size), nullptr, 0);
if (!decoded.ok() || (decoded.error() != nullptr)) {
zxlogf(ERROR, "Unable to parse metadata %s", decoded.error());
return ZX_ERR_INTERNAL;
}
const auto& metadata = decoded.PrimaryObject();
// Validate
if (!metadata->has_mmio() || !metadata->has_registers()) {
zxlogf(ERROR, "Metadata incomplete");
return ZX_ERR_INTERNAL;
}
for (const auto& mmio : metadata->mmio()) {
if (!mmio.has_id()) {
zxlogf(ERROR, "Metadata incomplete");
return ZX_ERR_INTERNAL;
}
}
bool begin = true;
::llcpp::fuchsia::hardware::registers::Mask::Tag tag;
for (const auto& reg : metadata->registers()) {
if (!reg.has_bind_id() && !reg.has_mmio_id() && !reg.has_masks()) {
// Doesn't have to have all Register IDs.
continue;
}
if (!reg.has_bind_id() || !reg.has_mmio_id() || !reg.has_masks()) {
zxlogf(ERROR, "Metadata incomplete");
return ZX_ERR_INTERNAL;
}
if (begin) {
tag = reg.masks().begin()->mask().which();
begin = false;
}
for (const auto& mask : reg.masks()) {
if (!mask.has_mask() || !mask.has_mmio_offset() || !mask.has_count()) {
zxlogf(ERROR, "Metadata incomplete");
return ZX_ERR_INTERNAL;
}
if (mask.mask().which() != tag) {
zxlogf(ERROR, "Width of registers don't match up.");
return ZX_ERR_INTERNAL;
}
if (mask.mmio_offset() % kTagToBytes.at(tag)) {
zxlogf(ERROR, "%s: Mask with offset 0x%08lx is not aligned", __func__, mask.mmio_offset());
return ZX_ERR_INTERNAL;
}
}
}
// Create devices
switch (tag) {
case ::llcpp::fuchsia::hardware::registers::Mask::Tag::kR8:
status = RegistersDevice<uint8_t>::Create(parent, std::move(*metadata));
break;
case ::llcpp::fuchsia::hardware::registers::Mask::Tag::kR16:
status = RegistersDevice<uint16_t>::Create(parent, std::move(*metadata));
break;
case ::llcpp::fuchsia::hardware::registers::Mask::Tag::kR32:
status = RegistersDevice<uint32_t>::Create(parent, std::move(*metadata));
break;
case ::llcpp::fuchsia::hardware::registers::Mask::Tag::kR64:
status = RegistersDevice<uint64_t>::Create(parent, std::move(*metadata));
break;
}
return status;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = Bind;
return ops;
}();
} // namespace registers
// clang-format off
ZIRCON_DRIVER(registers, registers::driver_ops, "zircon", "0.1");
// clang-format on