blob: 8c0fd665d56846d62275aed6f8b1478da3f1683c [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "arch/arm64/periphmap.h"
#include <debug.h>
#include <lib/console.h>
#include <stdint.h>
#include <arch/defines.h>
#include <ktl/algorithm.h>
#include <ktl/bit.h>
#include <ktl/optional.h>
#include <lk/init.h>
#include <phys/handoff.h>
#include <ktl/enforce.h>
namespace {
// Backed by permanent hand-off memory.
ktl::span<const MappedMmioRange> gPeriphRanges;
void RecordPeriphRanges(uint level) {
constexpr size_t kExpectedMax = 4;
gPeriphRanges = gPhysHandoff->periph_ranges.get();
if (gPeriphRanges.size() > kExpectedMax) {
dprintf(INFO, "Warning: unexpected large number of PERIPHERAL ranges: %zu",
gPeriphRanges.size());
}
}
LK_INIT_HOOK(record_periph_ranges, RecordPeriphRanges, LK_INIT_LEVEL_EARLIEST)
struct Phys2VirtTrait {
static uint64_t src(const MappedMmioRange& r) { return r.paddr; }
static uint64_t dst(const MappedMmioRange& r) { return reinterpret_cast<uintptr_t>(r.data()); }
};
struct Virt2PhysTrait {
static uint64_t src(const MappedMmioRange& r) { return reinterpret_cast<uintptr_t>(r.data()); }
static uint64_t dst(const MappedMmioRange& r) { return r.paddr; }
};
template <typename Fetch>
struct PeriphUtil {
// Translate (without range checking) the (virt|phys) peripheral provided to
// its (phys|virt) counterpart using the provided range.
static uint64_t Translate(const MappedMmioRange& range, uint64_t addr) {
return addr - Fetch::src(range) + Fetch::dst(range);
}
// Find the index (if any) of the peripheral range which contains the
// (virt|phys) address <addr>
static ktl::optional<uint32_t> LookupNdx(uint64_t addr) {
for (uint32_t i = 0; i < ktl::size(gPeriphRanges); ++i) {
const auto& range = gPeriphRanges[i];
if (range.size_bytes() == 0) {
break;
} else if (addr >= Fetch::src(range)) {
uint64_t offset = addr - Fetch::src(range);
if (offset < range.size_bytes()) {
return {i};
}
}
}
return {};
}
// Map the (virt|phys) peripheral provided to its (phys|virt) counterpart (if
// any)
static ktl::optional<uint64_t> Map(uint64_t addr) {
auto ndx = LookupNdx(addr);
if (ndx.has_value()) {
return Translate(gPeriphRanges[ndx.value()], addr);
}
return {};
}
};
using Phys2Virt = PeriphUtil<Phys2VirtTrait>;
using Virt2Phys = PeriphUtil<Virt2PhysTrait>;
template <typename T>
uint64_t rd_reg(vaddr_t addr) {
return static_cast<uint64_t>(reinterpret_cast<volatile T*>(addr)[0]);
}
template <typename T>
void wr_reg(vaddr_t addr, uint64_t val) {
reinterpret_cast<volatile T*>(addr)[0] = static_cast<T>(val);
}
// Note; the choice of these values must also align with the definitions in the
// options array below.
enum class AccessWidth {
Byte = 0,
Halfword = 1,
Word = 2,
Doubleword = 3,
};
constexpr struct {
const char* tag;
void (*print)(uint64_t);
uint64_t (*rd)(vaddr_t);
void (*wr)(vaddr_t, uint64_t);
uint32_t byte_width;
} kDumpModOptions[] = {
{
.tag = "byte",
.print = [](uint64_t val) { printf(" %02" PRIx64, val); },
.rd = rd_reg<uint8_t>,
.wr = wr_reg<uint8_t>,
.byte_width = 1,
},
{
.tag = "halfword",
.print = [](uint64_t val) { printf(" %04" PRIx64, val); },
.rd = rd_reg<uint16_t>,
.wr = wr_reg<uint16_t>,
.byte_width = 2,
},
{
.tag = "word",
.print = [](uint64_t val) { printf(" %08" PRIx64, val); },
.rd = rd_reg<uint32_t>,
.wr = wr_reg<uint32_t>,
.byte_width = 4,
},
{
.tag = "doubleword",
.print = [](uint64_t val) { printf(" %016" PRIx64, val); },
.rd = rd_reg<uint64_t>,
.wr = wr_reg<uint64_t>,
.byte_width = 8,
},
};
zx_status_t dump_periph(paddr_t phys, uint64_t count, AccessWidth width) {
const auto& opt = kDumpModOptions[static_cast<uint32_t>(width)];
// Sanity check count
if (!count) {
printf("Illegal count %lu\n", count);
return ZX_ERR_INVALID_ARGS;
}
uint64_t byte_amt = count * opt.byte_width;
paddr_t phys_end_addr = phys + byte_amt - 1;
// Sanity check alignment.
if (phys & (opt.byte_width - 1)) {
printf("%016lx is not aligned to a %u byte boundary!\n", phys, opt.byte_width);
return ZX_ERR_INVALID_ARGS;
}
// Validate that the entire requested range fits within a single mapping.
auto start_ndx = Phys2Virt::LookupNdx(phys);
auto end_ndx = Phys2Virt::LookupNdx(phys_end_addr);
if (!start_ndx.has_value() || !end_ndx.has_value() || (start_ndx.value() != end_ndx.value())) {
printf("Physical range [%016lx, %016lx] is not contained in a single mapping!\n", phys,
phys_end_addr);
return ZX_ERR_INVALID_ARGS;
}
// OK, all of our sanity checks are complete. Time to start dumping data.
constexpr uint32_t bytes_per_line = 16;
const uint64_t count_per_line = bytes_per_line / opt.byte_width;
vaddr_t virt = Phys2Virt::Translate(gPeriphRanges[start_ndx.value()], phys);
vaddr_t virt_end_addr = virt + byte_amt;
printf("Dumping %lu %s%s starting at phys 0x%016lx\n", count, opt.tag, count == 1 ? "" : "s",
phys);
while (virt < virt_end_addr) {
printf("%016lx :", phys);
for (uint64_t i = 0; (i < count_per_line) && (virt < virt_end_addr);
++i, virt += opt.byte_width) {
opt.print(opt.rd(virt));
}
phys += bytes_per_line;
printf("\n");
}
return ZX_OK;
}
zx_status_t mod_periph(paddr_t phys, uint64_t val, AccessWidth width) {
const auto& opt = kDumpModOptions[static_cast<uint32_t>(width)];
// Sanity check alignment.
if (phys & (opt.byte_width - 1)) {
printf("%016lx is not aligned to a %u byte boundary!\n", phys, opt.byte_width);
return ZX_ERR_INVALID_ARGS;
}
// Translate address
auto vaddr = Phys2Virt::Map(phys);
if (!vaddr.has_value()) {
printf("Physical addr %016lx in not in the peripheral mappings!\n", phys);
}
// Perform the write, then report what we did.
opt.wr(vaddr.value(), val);
printf("Wrote");
opt.print(val);
printf(" to phys addr %016lx\n", phys);
return ZX_OK;
}
int cmd_peripheral_map(int argc, const cmd_args* argv, uint32_t flags) {
auto usage = [cmd = argv[0].str](bool not_enough_args = false) -> zx_status_t {
if (not_enough_args) {
printf("not enough arguments\n");
}
printf("usage:\n");
printf("%s dump\n", cmd);
printf("%s phys2virt <addr>\n", cmd);
printf("%s virt2phys <addr>\n", cmd);
printf(
"%s dd|dw|dh|db <phys_addr> [<count>] :: Dump <count> (double|word|half|byte) from "
"<phys_addr> (count default = 1)\n",
cmd);
printf(
"%s md|mw|mh|mb <phys_addr> <value> :: Write the contents of <value> to the "
"(double|word|half|byte) at <phys_addr>\n",
cmd);
return ZX_ERR_INTERNAL;
};
if (argc < 2) {
return usage(true);
}
if (!strcmp(argv[1].str, "dump")) {
for (const auto& range : gPeriphRanges) {
DEBUG_ASSERT(range.size_bytes() > 0);
printf("Phys [%016lx, %016lx) ==> Virt [%16p, %016lx) (len 0x%08lx)\n", range.paddr,
range.paddr + range.size_bytes(), range.data(),
reinterpret_cast<uintptr_t>(range.data()) + range.size_bytes(), range.size_bytes());
}
printf("Dumped %zu defined peripheral map ranges\n", gPeriphRanges.size());
} else if (!strcmp(argv[1].str, "phys2virt") || !strcmp(argv[1].str, "virt2phys")) {
if (argc < 3) {
return usage(true);
}
bool phys_src = !strcmp(argv[1].str, "phys2virt");
auto map_fn = phys_src ? Phys2Virt::Map : Virt2Phys::Map;
auto res = map_fn(argv[2].u);
if (res.has_value()) {
printf("%016lx ==> %016lx\n", argv[2].u, res.value());
} else {
printf("Failed to find the %s address 0x%016lx in the peripheral mappings.\n",
phys_src ? "physical" : "virtual", argv[2].u);
}
} else if ((argv[1].str[0] == 'd') || (argv[1].str[0] == 'm')) {
// If this is a valid display or modify command, its length will be exactly 2.
if (strlen(argv[1].str) != 2) {
return usage();
}
// Parse the next letter to figure out the width of the operation.
AccessWidth width;
switch (argv[1].str[1]) {
case 'd':
width = AccessWidth::Doubleword;
break;
case 'w':
width = AccessWidth::Word;
break;
case 'h':
width = AccessWidth::Halfword;
break;
case 'b':
width = AccessWidth::Byte;
break;
default:
return usage();
}
paddr_t phys_addr = argv[2].u;
if (argv[1].str[0] == 'd') {
// Dump commands have a default count of 1
return dump_periph(phys_addr, (argc < 4) ? 1 : argv[3].u, width);
} else {
// Modify commands are required to have a value.
return (argc < 4) ? usage(true) : mod_periph(phys_addr, argv[3].u, width);
}
} else {
return usage();
}
return ZX_OK;
}
} // namespace
vaddr_t periph_paddr_to_vaddr(paddr_t paddr) {
auto ret = Phys2Virt::Map(paddr);
return ret.has_value() ? ret.value() : 0;
}
STATIC_COMMAND_START
STATIC_COMMAND_MASKED("pm", "peripheral mapping commands", &cmd_peripheral_map, CMD_AVAIL_ALWAYS)
STATIC_COMMAND_END(pmap)