blob: 7ffbd1bb04b8619eb1b6b0573512494d37de6bbc [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 <fidl/fuchsia.hardware.platform.device/cpp/wire.h>
#include <lib/ddk/metadata.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <string>
#include <bind/fuchsia/register/cpp/bind.h>
#include <fbl/auto_lock.h>
namespace registers {
namespace {
template <typename T>
T GetMask(const fuchsia_hardware_registers::wire::Mask& mask);
template <>
uint8_t GetMask(const fuchsia_hardware_registers::wire::Mask& mask) {
return static_cast<uint8_t>(mask.r8());
}
template <>
uint16_t GetMask(const fuchsia_hardware_registers::wire::Mask& mask) {
return static_cast<uint16_t>(mask.r16());
}
template <>
uint32_t GetMask(const fuchsia_hardware_registers::wire::Mask& mask) {
return static_cast<uint32_t>(mask.r32());
}
template <>
uint64_t GetMask(const fuchsia_hardware_registers::wire::Mask& mask) {
return static_cast<uint64_t>(mask.r64());
}
zx::result<std::vector<uint8_t>> ParseMetadata(
const fidl::VectorView<fuchsia_driver_compat::wire::Metadata>& metadata) {
for (const auto& m : metadata) {
if (m.type == DEVICE_METADATA_REGISTERS) {
size_t size;
auto status = m.data.get_prop_content_size(&size);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to get_prop_content_size %s", zx_status_get_string(status));
continue;
}
std::vector<uint8_t> metadata;
metadata.resize(size);
status = m.data.read(metadata.data(), 0, metadata.size());
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to read %s", zx_status_get_string(status));
continue;
}
return zx::ok(std::move(metadata));
}
}
FDF_LOG(ERROR, "Failed to parse metadata.");
return zx::error(ZX_ERR_NOT_FOUND);
}
template <typename T>
zx::result<> CheckOverlappingBits(
const fidl::ObjectView<fuchsia_hardware_registers::wire::Metadata>& metadata,
const std::map<uint32_t, std::shared_ptr<MmioInfo>>& mmios) {
std::map<uint32_t, std::map<size_t, T>> overlap;
for (const auto& reg : metadata->registers()) {
if (!reg.has_name() || !reg.has_mmio_id() || !reg.has_masks()) {
// Doesn't have to have all Register IDs.
continue;
}
if (mmios.find(reg.mmio_id()) == mmios.end()) {
FDF_LOG(ERROR, "%s: Invalid MMIO ID %u for Register %.*s.\n", __func__, reg.mmio_id(),
static_cast<int>(reg.name().size()), reg.name().data());
return zx::error(ZX_ERR_INTERNAL);
}
for (const auto& m : reg.masks()) {
if (m.mmio_offset() / sizeof(T) >= mmios.at(reg.mmio_id())->locks_.size()) {
FDF_LOG(ERROR, "%s: Invalid offset.\n", __func__);
return zx::error(ZX_ERR_INTERNAL);
}
if (!m.overlap_check_on()) {
continue;
}
if (overlap.find(reg.mmio_id()) == overlap.end()) {
overlap[reg.mmio_id()] = {};
}
if (overlap[reg.mmio_id()].find(m.mmio_offset() / sizeof(T)) ==
overlap[reg.mmio_id()].end()) {
overlap[reg.mmio_id()][m.mmio_offset() / sizeof(T)] = 0;
}
auto& bits = overlap[reg.mmio_id()][m.mmio_offset() / sizeof(T)];
auto mask = GetMask<T>(m.mask());
if (bits & mask) {
FDF_LOG(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));
return zx::error(ZX_ERR_INTERNAL);
}
bits |= mask;
}
}
return zx::ok();
}
zx::result<> ValidateMetadata(
const fidl::ObjectView<fuchsia_hardware_registers::wire::Metadata>& metadata,
const std::map<uint32_t, std::shared_ptr<MmioInfo>>& mmios) {
if (!metadata->has_registers()) {
FDF_LOG(ERROR, "Metadata incomplete");
return zx::error(ZX_ERR_INTERNAL);
}
bool begin = true;
fuchsia_hardware_registers::wire::Mask::Tag tag;
for (const auto& reg : metadata->registers()) {
if (!reg.has_name() || !reg.has_mmio_id() || !reg.has_masks()) {
FDF_LOG(ERROR, "Metadata incomplete");
return zx::error(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()) {
FDF_LOG(ERROR, "Metadata incomplete");
return zx::error(ZX_ERR_INTERNAL);
}
if (mask.mask().Which() != tag) {
FDF_LOG(ERROR, "Width of registers don't match up.");
return zx::error(ZX_ERR_INTERNAL);
}
if (mask.mmio_offset() % SWITCH_BY_TAG(tag, GetSize)) {
FDF_LOG(ERROR, "%s: Mask with offset 0x%08lx is not aligned", __func__, mask.mmio_offset());
return zx::error(ZX_ERR_INTERNAL);
}
}
}
return SWITCH_BY_TAG(tag, CheckOverlappingBits, metadata, mmios);
}
} // namespace
template <typename T>
zx::result<> RegistersDevice::CreateNode(Register<T>& reg) {
auto result =
outgoing()->AddService<fuchsia_hardware_registers::Service>(reg.GetHandler(), reg.id());
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to add service to the outgoing directory");
return result.take_error();
}
// Initialize our compat server.
{
zx::result result =
reg.compat_server_.Initialize(incoming(), outgoing(), node_name(), reg.id());
if (result.is_error()) {
return result.take_error();
}
}
fidl::Arena arena;
auto offers = reg.compat_server_.CreateOffers2(arena);
offers.push_back(fdf::MakeOffer2<fuchsia_hardware_registers::Service>(arena, reg.id()));
auto properties = std::vector{
fdf::MakeProperty(arena, bind_fuchsia_register::NAME, reg.id()),
};
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, "register-" + reg.id())
.offers2(arena, std::move(offers))
.properties(arena, std::move(properties))
.Build();
zx::result controller_endpoints =
fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>();
ZX_ASSERT_MSG(controller_endpoints.is_ok(), "Failed to create controller endpoints: %s",
controller_endpoints.status_string());
{
fidl::WireResult result =
fidl::WireCall(node())->AddChild(args, std::move(controller_endpoints->server), {});
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to add child %s", result.FormatDescription().c_str());
return zx::error(result.status());
}
}
reg.controller_.Bind(std::move(controller_endpoints->client));
return zx::ok();
}
template <typename T>
zx::result<> RegistersDevice::Create(
fuchsia_hardware_registers::wire::RegistersMetadataEntry& reg) {
if (!reg.has_name() || !reg.has_mmio_id() || !reg.has_masks()) {
// Doesn't have to have all Register IDs.
return zx::error(ZX_ERR_BAD_STATE);
}
std::map<uint64_t, std::pair<T, uint32_t>> masks;
for (const auto& m : reg.masks()) {
auto mask = GetMask<T>(m.mask());
masks.emplace(m.mmio_offset(), std::make_pair(mask, m.count()));
}
return std::visit(
[&](auto&& d) { return CreateNode(d); },
registers_.emplace_back(std::in_place_type<Register<T>>, mmios_[reg.mmio_id()],
std::string(reg.name().data(), reg.name().size()), std::move(masks)));
}
zx::result<> RegistersDevice::MapMmio(fuchsia_hardware_registers::wire::Mask::Tag& tag) {
zx::result result = incoming()->Connect<fuchsia_hardware_platform_device::Service::Device>();
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to open pdev service: %s", result.status_string());
return result.take_error();
}
fidl::WireSyncClient pdev(std::move(result.value()));
if (!pdev.is_valid()) {
FDF_LOG(ERROR, "Failed to get pdev");
return zx::error(ZX_ERR_NO_RESOURCES);
}
auto device_info = pdev->GetNodeDeviceInfo();
if (!device_info.ok() || device_info->is_error()) {
FDF_LOG(ERROR, "Could not get device info %s", device_info.FormatDescription().c_str());
return zx::error(device_info.ok() ? device_info->error_value() : device_info.error().status());
}
ZX_ASSERT(device_info->value()->has_mmio_count());
for (uint32_t i = 0; i < device_info->value()->mmio_count(); i++) {
auto mmio = pdev->GetMmioById(i);
if (!mmio.ok() || mmio->is_error()) {
FDF_LOG(ERROR, "Could not get mmio regions %s", mmio.FormatDescription().c_str());
return zx::error(mmio.ok() ? mmio->error_value() : mmio.error().status());
}
if (!mmio->value()->has_vmo() || !mmio->value()->has_size() || !mmio->value()->has_offset()) {
FDF_LOG(ERROR, "GetMmioById(%d) returned invalid MMIO", i);
return zx::error(ZX_ERR_BAD_STATE);
}
zx::result mmio_buffer =
fdf::MmioBuffer::Create(mmio->value()->offset(), mmio->value()->size(),
std::move(mmio->value()->vmo()), ZX_CACHE_POLICY_UNCACHED_DEVICE);
if (mmio_buffer.is_error()) {
FDF_LOG(ERROR, "Failed to map MMIO: %s", mmio_buffer.status_string());
return zx::error(mmio_buffer.error_value());
}
zx::result<MmioInfo> mmio_info = SWITCH_BY_TAG(tag, MmioInfo::Create, std::move(*mmio_buffer));
if (mmio_info.is_error()) {
FDF_LOG(ERROR, "Could not create mmio info %d", mmio_info.error_value());
return zx::error(mmio_info.take_error());
}
mmios_.emplace(i, std::make_shared<MmioInfo>(std::move(*mmio_info)));
}
return zx::ok();
}
zx::result<> RegistersDevice::Start() {
// Get metadata.
std::vector<uint8_t> metadata;
{
zx::result result = incoming()->Connect<fuchsia_driver_compat::Service::Device>();
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to open compat service: %s", result.status_string());
return result.take_error();
}
auto compat = fidl::WireSyncClient(std::move(result.value()));
if (!compat.is_valid()) {
FDF_LOG(ERROR, "Failed to get compat");
return zx::error(ZX_ERR_NO_RESOURCES);
}
auto data = compat->GetMetadata();
if (!data.ok()) {
FDF_LOG(ERROR, "Failed to GetMetadata %s", data.error().FormatDescription().c_str());
return zx::error(data.error().status());
}
if (data->is_error()) {
FDF_LOG(ERROR, "Failed to GetMetadata %s", zx_status_get_string(data->error_value()));
return data->take_error();
}
auto vals = ParseMetadata(data.value()->metadata);
if (vals.is_error()) {
FDF_LOG(ERROR, "Failed to ParseMetadata %s", zx_status_get_string(vals.error_value()));
return vals.take_error();
}
metadata = std::move(vals.value());
}
auto parsed_metadata =
fidl::InplaceUnpersist<fuchsia_hardware_registers::wire::Metadata>(cpp20::span(metadata));
if (parsed_metadata.is_error()) {
FDF_LOG(ERROR, "InplaceUnpersist failed %s",
parsed_metadata.error_value().FormatDescription().c_str());
return zx::error(parsed_metadata.error_value().status());
}
auto tag = parsed_metadata->registers()[0].masks()[0].mask().Which();
// Get mmio.
{
auto result = MapMmio(tag);
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to map MMIOs %d", result.error_value());
return result.take_error();
}
}
// Validate metadata.
{
auto result = ValidateMetadata(parsed_metadata.value(), mmios_);
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to validate metadata %d", result.error_value());
return result.take_error();
}
}
// Create devices.
for (auto& reg : parsed_metadata->registers()) {
auto result = SWITCH_BY_TAG(tag, Create, reg);
if (result.is_error()) {
FDF_LOG(ERROR, "Failed to create device for %s %d",
reg.has_name() ? reg.name().data() : "Unknown", result.error_value());
}
}
return zx::ok();
}
} // namespace registers
FUCHSIA_DRIVER_EXPORT(registers::RegistersDevice);