blob: 0c0cf75b61f1620b532e753bc0cac3ccc08a7f09 [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 "garnet/bin/guest/vmm/arch/arm64/pl011.h"
#include <endian.h>
#include <stdio.h>
#include <libzbi/zbi.h>
#include <zircon/boot/driver-config.h>
#include "garnet/bin/guest/vmm/guest.h"
#include "lib/fxl/logging.h"
__BEGIN_CDECLS;
#include <libfdt.h>
__END_CDECLS;
// clang-format off
// PL011 registers.
enum class Pl011Register : uint64_t {
DR = 0x00,
FR = 0x18,
IBRD = 0x24,
FBRD = 0x28,
LCR = 0x2c,
CR = 0x30,
IFLS = 0x34,
IMSC = 0x38,
ICR = 0x44,
};
static constexpr uint64_t kPl011PhysBase = 0x808300000;
static constexpr uint64_t kPl011Size = 0x1000;
// clang-format on
Pl011::Pl011(zx::socket socket) : socket_(std::move(socket)) {}
zx_status_t Pl011::Init(Guest* guest) {
return guest->CreateMapping(TrapType::MMIO_SYNC, kPl011PhysBase, kPl011Size,
0, this);
}
zx_status_t Pl011::Read(uint64_t addr, IoValue* value) const {
switch (static_cast<Pl011Register>(addr)) {
case Pl011Register::CR: {
std::lock_guard<std::mutex> lock(mutex_);
value->u16 = control_;
return ZX_OK;
}
case Pl011Register::FR:
case Pl011Register::IMSC:
value->u16 = 0;
return ZX_OK;
default:
FXL_LOG(ERROR) << "Unhandled PL011 address read 0x" << std::hex << addr;
return ZX_ERR_IO;
}
}
zx_status_t Pl011::Write(uint64_t addr, const IoValue& value) {
switch (static_cast<Pl011Register>(addr)) {
case Pl011Register::CR: {
std::lock_guard<std::mutex> lock(mutex_);
control_ = value.u16;
return ZX_OK;
}
case Pl011Register::DR:
Print(value.u8);
return ZX_OK;
case Pl011Register::IBRD:
case Pl011Register::FBRD:
case Pl011Register::ICR:
case Pl011Register::IFLS:
case Pl011Register::IMSC:
case Pl011Register::LCR:
return ZX_OK;
default:
FXL_LOG(ERROR) << "Unhandled PL011 address write 0x" << std::hex << addr;
return ZX_ERR_IO;
}
}
void Pl011::Print(uint8_t ch) {
std::lock_guard<std::mutex> lock(mutex_);
tx_buffer_[tx_offset_++] = ch;
if (tx_offset_ < kBufferSize && ch != '\r') {
return;
}
size_t actual;
zx_status_t status = socket_.write(0, tx_buffer_, tx_offset_, &actual);
if (status != ZX_OK || actual != tx_offset_) {
FXL_LOG(WARNING) << "PL011 output partial or dropped";
}
tx_offset_ = 0;
}
zx_status_t Pl011::ConfigureZbi(void* zbi_base, size_t zbi_max) const {
dcfg_simple_t zbi_uart = {
.mmio_phys = kPl011PhysBase,
.irq = 111,
};
zbi_result_t res =
zbi_append_section(zbi_base, zbi_max, sizeof(zbi_uart),
ZBI_TYPE_KERNEL_DRIVER, KDRV_PL011_UART, 0, &zbi_uart);
return res == ZBI_RESULT_OK ? ZX_OK : ZX_ERR_INTERNAL;
}
zx_status_t Pl011::ConfigureDtb(void* dtb) const {
uint64_t reg_val[2] = {htobe64(kPl011PhysBase), htobe64(kPl011Size)};
int node_off =
fdt_node_offset_by_prop_value(dtb, -1, "reg", reg_val, sizeof(reg_val));
if (node_off < 0) {
FXL_LOG(ERROR) << "Failed to find PL011 in DTB";
return ZX_ERR_INTERNAL;
}
int ret = fdt_node_check_compatible(dtb, node_off, "arm,pl011");
if (ret != 0) {
FXL_LOG(ERROR) << "Device with PL011 registers is not PL011 compatible";
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}