blob: 8ba3b0a96c432b5d5601d905375fa40dcd3c0bc5 [file] [log] [blame]
// Copyright 2017 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 "platform-bus.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
#include <ddk/platform-defs.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/unique_ptr.h>
#include <fuchsia/boot/c/fidl.h>
#include <fuchsia/sysinfo/c/fidl.h>
#include <zircon/boot/driver-config.h>
#include <zircon/process.h>
#include <zircon/syscalls/iommu.h>
#include "cpu-trace.h"
#include "platform-composite-device.h"
namespace platform_bus {
zx_status_t PlatformBus::IommuGetBti(uint32_t iommu_index, uint32_t bti_id,
zx::bti* out_bti) {
if (iommu_index != 0) {
return ZX_ERR_OUT_OF_RANGE;
}
return zx::bti::create(iommu_handle_, 0, bti_id, out_bti);
}
zx_status_t PlatformBus::PBusRegisterProtocol(uint32_t proto_id, const void* protocol,
size_t protocol_size) {
if (!protocol || protocol_size < sizeof(ddk::AnyProtocol)) {
return ZX_ERR_INVALID_ARGS;
}
switch (proto_id) {
case ZX_PROTOCOL_GPIO_IMPL: {
gpio_ = ddk::GpioImplProtocolClient(static_cast<const gpio_impl_protocol_t*>(protocol));
break;
}
case ZX_PROTOCOL_CLOCK_IMPL: {
clk_ = ddk::ClockImplProtocolClient(static_cast<const clock_impl_protocol_t*>(protocol));
break;
}
case ZX_PROTOCOL_POWER_IMPL: {
power_ = ddk::PowerImplProtocolClient(static_cast<const power_impl_protocol_t*>(protocol));
break;
}
case ZX_PROTOCOL_IOMMU: {
iommu_ = ddk::IommuProtocolClient(static_cast<const iommu_protocol_t*>(protocol));
break;
}
case ZX_PROTOCOL_SYSMEM: {
sysmem_ = ddk::SysmemProtocolClient(static_cast<const sysmem_protocol_t*>(protocol));
break;
}
case ZX_PROTOCOL_AMLOGIC_CANVAS: {
canvas_ = ddk::AmlogicCanvasProtocolClient(
static_cast<const amlogic_canvas_protocol_t*>(protocol));
break;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
fbl::AutoLock lock(&proto_completion_mutex_);
sync_completion_signal(&proto_completion_);
return ZX_OK;
}
zx_status_t PlatformBus::PBusDeviceAdd(const pbus_dev_t* pdev) {
if (!pdev->name) {
return ZX_ERR_INVALID_ARGS;
}
zx_device_t* parent_dev;
if (pdev->vid == PDEV_VID_GENERIC && pdev->pid == PDEV_PID_GENERIC &&
pdev->did == PDEV_DID_KPCI) {
// Add PCI root at top level.
parent_dev = parent();
} else {
parent_dev = zxdev();
}
fbl::unique_ptr<platform_bus::PlatformDevice> dev;
auto status = PlatformDevice::Create(pdev, parent_dev, this, &dev);
if (status != ZX_OK) {
return status;
}
status = dev->Start();
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = dev.release();
return ZX_OK;
}
zx_status_t PlatformBus::PBusProtocolDeviceAdd(uint32_t proto_id, const pbus_dev_t* pdev) {
if (!pdev->name) {
return ZX_ERR_INVALID_ARGS;
}
fbl::unique_ptr<platform_bus::ProtocolDevice> dev;
auto status = ProtocolDevice::Create(pdev, zxdev(), this, &dev);
if (status != ZX_OK) {
return status;
}
// Protocol devices run in our devhost.
status = dev->Start();
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = dev.release();
// Wait for protocol implementation driver to register its protocol.
ddk::AnyProtocol dummy_proto;
proto_completion_mutex_.Acquire();
while (DdkGetProtocol(proto_id, &dummy_proto) == ZX_ERR_NOT_SUPPORTED) {
sync_completion_reset(&proto_completion_);
proto_completion_mutex_.Release();
zx_status_t status = sync_completion_wait(&proto_completion_, ZX_SEC(10));
if (status != ZX_OK) {
zxlogf(ERROR, "%s sync_completion_wait(protocol %08x) failed: %d\n", __FUNCTION__,
proto_id, status);
return status;
}
proto_completion_mutex_.Acquire();
}
proto_completion_mutex_.Release();
return ZX_OK;
}
zx_status_t PlatformBus::PBusGetBoardInfo(pdev_board_info_t* out_info) {
memcpy(out_info, &board_info_, sizeof(board_info_));
return ZX_OK;
}
zx_status_t PlatformBus::PBusSetBoardInfo(const pbus_board_info_t* info) {
board_info_.board_revision = info->board_revision;
return ZX_OK;
}
zx_status_t PlatformBus::PBusRegisterSysSuspendCallback(const pbus_sys_suspend_t* suspend_cbin) {
suspend_cb_ = *suspend_cbin;
return ZX_OK;
}
zx_status_t PlatformBus::PBusCompositeDeviceAdd(const pbus_dev_t* pdev,
const device_component_t* components_list,
size_t components_count,
uint32_t coresident_device_index) {
if (!pdev || !pdev->name) {
return ZX_ERR_INVALID_ARGS;
}
if (coresident_device_index == 0) {
zxlogf(ERROR, "%s: coresident_device_index cannot be zero\n", __func__);
return ZX_ERR_INVALID_ARGS;
}
fbl::unique_ptr<platform_bus::CompositeDevice> dev;
auto status = CompositeDevice::Create(pdev, zxdev(), this, &dev);
if (status != ZX_OK) {
return status;
}
status = dev->Start();
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = dev.release();
device_component_t components[components_count + 1];
memcpy(&components[1], components_list, components_count * sizeof(components[1]));
constexpr zx_bind_inst_t root_match[] = {
BI_MATCH(),
};
const zx_bind_inst_t pdev_match[] = {
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, pdev->vid),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, pdev->pid),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, pdev->did),
};
const device_component_part_t pdev_component[] = {
{ countof(root_match), root_match },
{ countof(pdev_match), pdev_match },
};
components[0].parts_count = fbl::count_of(pdev_component);
components[0].parts = pdev_component;
const zx_device_prop_t props[] = {
{ BIND_PLATFORM_DEV_VID, 0, pdev->vid },
{ BIND_PLATFORM_DEV_PID, 0, pdev->pid },
{ BIND_PLATFORM_DEV_DID, 0, pdev->did },
};
return DdkAddComposite(pdev->name, props, fbl::count_of(props), components,
components_count + 1, coresident_device_index);
}
zx_status_t PlatformBus::DdkGetProtocol(uint32_t proto_id, void* out) {
switch (proto_id) {
case ZX_PROTOCOL_PBUS: {
auto proto = static_cast<pbus_protocol_t*>(out);
proto->ctx = this;
proto->ops = &pbus_protocol_ops_;
return ZX_OK;
}
case ZX_PROTOCOL_GPIO_IMPL:
if (gpio_) {
gpio_->GetProto(static_cast<gpio_impl_protocol_t*>(out));
return ZX_OK;
}
break;
case ZX_PROTOCOL_POWER_IMPL:
if (power_) {
power_->GetProto(static_cast<power_impl_protocol_t*>(out));
return ZX_OK;
}
break;
case ZX_PROTOCOL_CLOCK_IMPL:
if (clk_) {
clk_->GetProto(static_cast<clock_impl_protocol_t*>(out));
return ZX_OK;
}
break;
case ZX_PROTOCOL_SYSMEM:
if (sysmem_) {
sysmem_->GetProto(static_cast<sysmem_protocol_t*>(out));
return ZX_OK;
}
break;
case ZX_PROTOCOL_AMLOGIC_CANVAS:
if (canvas_) {
canvas_->GetProto(static_cast<amlogic_canvas_protocol_t*>(out));
return ZX_OK;
}
break;
case ZX_PROTOCOL_IOMMU:
if (iommu_) {
iommu_->GetProto(static_cast<iommu_protocol_t*>(out));
} else {
// return default implementation
auto proto = static_cast<iommu_protocol_t*>(out);
proto->ctx = this;
proto->ops = &iommu_protocol_ops_;
return ZX_OK;
}
break;
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PlatformBus::GetBootItem(uint32_t type, uint32_t extra, zx::vmo* vmo,
uint32_t* length) {
return fuchsia_boot_ItemsGet(items_svc_.get(), type, extra, vmo->reset_and_get_address(),
length);
}
zx_status_t PlatformBus::GetBootItem(uint32_t type, uint32_t extra, fbl::Array<uint8_t>* out) {
zx::vmo vmo;
uint32_t length;
zx_status_t status = GetBootItem(type, extra, &vmo, &length);
if (status != ZX_OK) {
return status;
}
if (vmo.is_valid()) {
fbl::Array<uint8_t> data(new uint8_t[length], length);
status = vmo.read(data.get(), 0, data.size());
if (status != ZX_OK) {
return status;
}
*out = std::move(data);
}
return ZX_OK;
}
void PlatformBus::DdkRelease() {
delete this;
}
typedef struct {
void* pbus_instance;
} sysdev_suspend_t;
static zx_status_t sys_device_suspend(void* ctx, uint32_t flags) {
auto* p = reinterpret_cast<sysdev_suspend_t*>(ctx);
auto* pbus = reinterpret_cast<class PlatformBus*>(p->pbus_instance);
if (pbus != nullptr) {
pbus_sys_suspend_t suspend_cb = pbus->suspend_cb();
if (suspend_cb.callback != nullptr) {
return suspend_cb.callback(suspend_cb.ctx, flags);
}
}
return ZX_ERR_NOT_SUPPORTED;
}
static void sys_device_release(void* ctx) {
auto* p = reinterpret_cast<sysdev_suspend_t*>(ctx);
delete p;
}
// cpu-trace provides access to the cpu's tracing and performance counters.
// As such the "device" is the cpu itself.
static zx_status_t InitCpuTrace(zx_device_t* parent, zx_handle_t dummy_iommu_handle) {
zx_handle_t cpu_trace_bti;
zx_status_t status = zx_bti_create(dummy_iommu_handle, 0, CPU_TRACE_BTI_ID, &cpu_trace_bti);
if (status != ZX_OK) {
zxlogf(ERROR, "platform-bus: error %d in bti_create(cpu_trace_bti)\n", status);
return status;
}
status = publish_cpu_trace(cpu_trace_bti, parent);
if (status != ZX_OK) {
// This is not fatal.
zxlogf(INFO, "publish_cpu_trace returned %d\n", status);
}
return status;
}
static zx_protocol_device_t sys_device_proto = []() {
zx_protocol_device_t result;
result.version = DEVICE_OPS_VERSION;
result.suspend = sys_device_suspend;
result.release = sys_device_release;
return result;
}();
zx_status_t PlatformBus::Create(zx_device_t* parent, const char* name, zx::channel items_svc) {
// This creates the "sys" device.
sys_device_proto.version = DEVICE_OPS_VERSION;
// The suspend op needs to get access to the PBus instance, to be able to
// callback the ACPI suspend hook. Introducing a level of indirection here
// to allow us to update the PBus instance in the device context after creating
// the device.
fbl::AllocChecker ac;
fbl::unique_ptr<uint8_t[]> ptr(new (&ac) uint8_t[sizeof(sysdev_suspend_t)]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto* suspend_buf = reinterpret_cast<sysdev_suspend_t*>(ptr.get());
suspend_buf->pbus_instance = nullptr;
device_add_args_t args = {};
args.version = DEVICE_ADD_ARGS_VERSION;
args.name = "sys";
args.ops = &sys_device_proto;
args.flags = DEVICE_ADD_NON_BINDABLE;
args.ctx = suspend_buf;
// Create /dev/sys.
zx_device_t* sys_root;
auto status = device_add(parent, &args, &sys_root);
if (status != ZX_OK) {
return status;
} else {
__UNUSED auto* dummy = ptr.release();
}
// Add child of sys for the board driver to bind to.
fbl::unique_ptr<platform_bus::PlatformBus> bus(
new (&ac) platform_bus::PlatformBus(sys_root, std::move(items_svc)));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
suspend_buf->pbus_instance = bus.get();
status = bus->Init();
if (status != ZX_OK) {
return status;
}
// Create /dev/sys/cpu-trace.
// But only do so if we have an iommu handle. Normally we do, but tests
// may create us without a root resource, and thus without the iommu
// handle.
if (bus->iommu_handle_.is_valid()) {
status = InitCpuTrace(sys_root, bus->iommu_handle_.get());
if (status != ZX_OK) {
// This is not fatal. Error message already printed.
}
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = bus.release();
return ZX_OK;
}
PlatformBus::PlatformBus(zx_device_t* parent, zx::channel items_svc)
: PlatformBusType(parent), items_svc_(std::move(items_svc)) {
sync_completion_reset(&proto_completion_);
}
zx_status_t PlatformBus::Init() {
zx_status_t status;
// Set up a dummy IOMMU protocol to use in the case where our board driver
// does not set a real one.
zx_iommu_desc_dummy_t desc;
// Please do not use get_root_resource() in new code. See ZX-1467.
zx::unowned_resource root_resource(get_root_resource());
if (root_resource->is_valid()) {
status = zx::iommu::create(*root_resource, ZX_IOMMU_TYPE_DUMMY, &desc, sizeof(desc),
&iommu_handle_);
if (status != ZX_OK) {
return status;
}
}
// Read kernel driver.
zx::vmo vmo;
uint32_t length;
uint8_t interrupt_controller_type = fuchsia_sysinfo_InterruptControllerType_UNKNOWN;
#if __x86_64__
interrupt_controller_type = fuchsia_sysinfo_InterruptControllerType_APIC;
#else
status = GetBootItem(ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_GIC_V2, &vmo, &length);
if (status != ZX_OK) {
return status;
}
if (vmo.is_valid()) {
interrupt_controller_type = fuchsia_sysinfo_InterruptControllerType_GIC_V2;
}
status = GetBootItem(ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_GIC_V3, &vmo, &length);
if (status != ZX_OK) {
return status;
}
if (vmo.is_valid()) {
interrupt_controller_type = fuchsia_sysinfo_InterruptControllerType_GIC_V3;
}
#endif
// Publish interrupt controller type to sysinfo driver
status = device_publish_metadata(
parent(), "/dev/misc/sysinfo", DEVICE_METADATA_INTERRUPT_CONTROLLER_TYPE,
&interrupt_controller_type, sizeof(interrupt_controller_type));
if (status != ZX_OK) {
zxlogf(ERROR, "device_publish_metadata(interrupt_controller_type) failed: %d\n", status);
return status;
}
// Read platform ID.
status = GetBootItem(ZBI_TYPE_PLATFORM_ID, 0, &vmo, &length);
if (status != ZX_OK) {
return status;
}
if (vmo.is_valid()) {
zbi_platform_id_t platform_id;
status = vmo.read(&platform_id, 0, sizeof(platform_id));
if (status != ZX_OK) {
return status;
}
zxlogf(INFO, "platform bus: VID: %u PID: %u board: \"%s\"\n", platform_id.vid,
platform_id.pid, platform_id.board_name);
board_info_.vid = platform_id.vid;
board_info_.pid = platform_id.pid;
memcpy(board_info_.board_name, platform_id.board_name, sizeof(board_info_.board_name));
// Publish board name to sysinfo driver
status = device_publish_metadata(parent(), "/dev/misc/sysinfo", DEVICE_METADATA_BOARD_NAME,
platform_id.board_name, sizeof(platform_id.board_name));
if (status != ZX_OK) {
zxlogf(ERROR, "device_publish_metadata(board_name) failed: %d\n", status);
return status;
}
} else {
#if __x86_64__
// For x86_64, we might not find the ZBI_TYPE_PLATFORM_ID, old bootloaders
// won't support this, for example. If this is the case, cons up the VID/PID here
// to allow the acpi board driver to load and bind.
board_info_.vid = PDEV_VID_INTEL;
board_info_.pid = PDEV_PID_X86;
strncpy(board_info_.board_name, "x86_64", sizeof(board_info_.board_name));
// Publish board name to sysinfo driver
status = device_publish_metadata(parent(), "/dev/misc/sysinfo", DEVICE_METADATA_BOARD_NAME,
"x86_64", sizeof("x86_64"));
if (status != ZX_OK) {
zxlogf(ERROR, "device_publish_metadata(board_name) failed: %d\n", status);
return status;
}
#else
zxlogf(ERROR, "platform_bus: ZBI_TYPE_PLATFORM_ID not found\n");
return ZX_ERR_INTERNAL;
#endif
}
// This is optionally set later by the board driver.
board_info_.board_revision = 0;
// Then we attach the platform-bus device below it.
zx_device_prop_t props[] = {
{BIND_PLATFORM_DEV_VID, 0, board_info_.vid},
{BIND_PLATFORM_DEV_PID, 0, board_info_.pid},
};
status = DdkAdd("platform", DEVICE_ADD_INVISIBLE, props, fbl::count_of(props));
if (status != ZX_OK) {
return status;
}
fbl::Array<uint8_t> board_data;
status = GetBootItem(ZBI_TYPE_DRV_BOARD_PRIVATE, 0, &board_data);
if (status != ZX_OK) {
return status;
}
if (board_data) {
status = DdkAddMetadata(DEVICE_METADATA_BOARD_PRIVATE, board_data.get(), board_data.size());
if (status != ZX_OK) {
return status;
}
}
DdkMakeVisible();
return ZX_OK;
}
zx_status_t platform_bus_create(void* ctx, zx_device_t* parent, const char* name,
const char* args, zx_handle_t handle) {
return platform_bus::PlatformBus::Create(parent, name, zx::channel(handle));
}
static zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops;
ops.version = DRIVER_OPS_VERSION;
ops.create = platform_bus_create;
return ops;
}();
} // namespace platform_bus
ZIRCON_DRIVER_BEGIN(platform_bus, platform_bus::driver_ops, "zircon", "0.1", 1)
// devmgr loads us directly, so we need no binding information here
BI_ABORT_IF_AUTOBIND,
ZIRCON_DRIVER_END(platform_bus)