blob: d1acc5d1580a4bf33c5196610e94bc926137eb6a [file]
// Copyright 2023 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 <lib/boot-options/boot-options.h>
#include <lib/devicetree/matcher.h>
#include <lib/fit/defer.h>
#include <span>
#include "lib/boot-shim/devicetree.h"
namespace boot_shim {
devicetree::ScanState DevicetreeChosenNodeMatcherBase::HandleTtyNode(
const devicetree::NodePath& path, const devicetree::PropertyDecoder& decoder) {
auto [compatible, interrupts, reg_property, reg_offset] =
decoder.FindProperties("compatible", "interrupts", "reg", "reg-offset");
// Without this we cant figure out what driver to use.
if (!compatible) {
return devicetree::ScanState::kActive;
}
// No MMIO region, we cant do anything.
if (!reg_property) {
return devicetree::ScanState::kActive;
}
auto reg = reg_property->AsReg(decoder);
if (!reg) {
return devicetree::ScanState::kActive;
}
auto compatible_list = compatible->AsStringList();
if (!compatible_list) {
return devicetree::ScanState::kActive;
}
// Verify that the `tty.type` has the right prefix, vendor.
bool possible_tty =
tty_->vendor.empty() || std::ranges::any_of(*compatible_list, [this](std::string_view entry) {
return entry.starts_with(tty_->vendor);
});
if (!possible_tty || !uart_selector_(decoder)) {
return devicetree::ScanState::kActive;
}
if (tty_->index > tty_index_) {
(*tty_index_)++;
return devicetree::ScanState::kActive;
}
// We matched a uart driver AND we are the right index.
return SetUpUart(decoder, *reg, reg_offset, interrupts);
}
devicetree::ScanState DevicetreeChosenNodeMatcherBase::SetUpUart(
const devicetree::PropertyDecoder& decoder, devicetree::RegProperty& reg,
const std::optional<devicetree::PropertyValue>& reg_offset,
const std::optional<devicetree::PropertyValue>& interrupts) {
auto addr = reg[0].address();
if (addr) {
auto translated_addr = decoder.TranslateAddress(*addr);
if (!translated_addr) {
return devicetree::ScanState::kDone;
}
if (!uart_selector_(decoder)) {
return devicetree::ScanState::kDone;
}
uart_config_->mmio_phys = *translated_addr;
uart_config_->irq = 0;
if (reg_offset) {
if (auto offset = reg_offset->AsUint32()) {
uart_config_->mmio_phys += *offset;
} else {
OnError("Failed to parse 'reg-offset' property from UART node.");
}
}
if (!interrupts) {
Log("Warning: UART Device does not provide interrupt cells. Proceeding with irq = 0.\n");
} else {
uart_irq_ = DevicetreeIrqResolver(interrupts->AsBytes());
if (auto result = uart_irq_.ResolveIrqController(decoder); result.is_ok()) {
if (!*result) {
return devicetree::ScanState::kActive;
}
SetUartIrq();
}
}
}
return devicetree::ScanState::kDone;
}
devicetree::ScanState DevicetreeChosenNodeMatcherBase::HandleBootstrapStdout(
const devicetree::NodePath& path, const devicetree::PropertyDecoder& decoder) {
auto resolved_path = decoder.ResolvePath(stdout_path_);
if (resolved_path.is_error()) {
if (resolved_path.error_value() == devicetree::PropertyDecoder::PathResolveError::kNoAliases) {
return devicetree::ScanState::kNeedsPathResolution;
}
return devicetree::ScanState::kDone;
}
// for hand off.
resolved_stdout_ = *resolved_path;
switch (path.CompareWith(*resolved_path)) {
case devicetree::NodePath::Comparison::kEqual:
break;
case devicetree::NodePath::Comparison::kParent:
case devicetree::NodePath::Comparison::kIndirectAncestor:
return devicetree::ScanState::kActive;
default:
return devicetree::ScanState::kDoneWithSubtree;
}
auto [compatible, interrupts, reg_property, reg_offset] =
decoder.FindProperties("compatible", "interrupts", "reg", "reg-offset");
// Without this we cant figure out what driver to use.
if (!compatible) {
return devicetree::ScanState::kDone;
}
// No MMIO region, we cant do anything.
if (!reg_property) {
return devicetree::ScanState::kDone;
}
auto reg = reg_property->AsReg(decoder);
if (!reg) {
return devicetree::ScanState::kDone;
}
return SetUpUart(decoder, *reg, reg_offset, interrupts);
}
devicetree::ScanState DevicetreeChosenNodeMatcherBase::OnNode(
const devicetree::NodePath& path, const devicetree::PropertyDecoder& decoder) {
if (path.CompareWith("/aliases") == devicetree::NodePath::Comparison::kEqual) {
for (auto prop : decoder.properties()) {
constexpr std::string_view kSerialPrefix = "serial";
if (prop.name.starts_with(kSerialPrefix)) {
std::string_view index_str = prop.name.substr(kSerialPrefix.size());
auto index = BootOptions::ParseInt(index_str);
if (index && *index >= 0 && static_cast<size_t>(*index) < serial_aliases_.size()) {
size_t idx = static_cast<size_t>(*index);
serial_aliases_[idx] = prop.value.AsString().value_or("");
}
}
}
return devicetree::ScanState::kActive;
}
if (found_chosen_) {
if (uart_irq_.NeedsInterruptParent()) {
if (auto result = uart_irq_.ResolveIrqController(decoder); result.is_ok()) {
if (!*result) {
return devicetree::ScanState::kActive;
}
SetUartIrq();
}
return devicetree::ScanState::kDone;
}
if (!stdout_path_.empty()) {
return HandleBootstrapStdout(path, decoder);
}
if (tty_) {
if (tty_index_) {
return HandleTtyNode(path, decoder);
}
return devicetree::ScanState::kDoneWithSubtree;
}
return devicetree::ScanState::kDone;
}
switch (path.CompareWith("/chosen")) {
case devicetree::NodePath::Comparison::kParent:
case devicetree::NodePath::Comparison::kIndirectAncestor:
return devicetree::ScanState::kActive;
case devicetree::NodePath::Comparison::kMismatch:
case devicetree::NodePath::Comparison::kChild:
case devicetree::NodePath::Comparison::kIndirectDescendent:
return devicetree::ScanState::kDoneWithSubtree;
case devicetree::NodePath::Comparison::kEqual:
found_chosen_ = true;
break;
};
// We are on /chosen, pull the cmdline, zbi and uart device path.
auto [bootargs, stdout_path, legacy_stdout_path, ramdisk_start, ramdisk_end] =
decoder.FindProperties("bootargs", "stdout-path", "linux,stdout-path", "linux,initrd-start",
"linux,initrd-end");
if (bootargs) {
if (auto cmdline = bootargs->AsString()) {
cmdline_ = *cmdline;
}
}
// If stdout-path is present, use it. Otherwise we will try to find a tty device via the command
// line.
if (stdout_path) {
stdout_path_ = stdout_path->AsString().value_or("");
} else if (legacy_stdout_path) {
stdout_path_ = legacy_stdout_path->AsString().value_or("");
}
// Make it just contain the prefix. This string can be formatted as 'path:UART_ARGS'.
// Where UART_ARGS can be things such as baud rate, parity, etc. We only need |path| from the
// format.
if (!stdout_path_.empty()) {
stdout_path_ = stdout_path_.substr(0, stdout_path_.find(':'));
} else {
// If we haven't found the stdout path, try to resolve the tty via the command line.
tty_ = boot_shim::TtyFromCmdline(cmdline_);
if (tty_) {
// Check if the tty is an alias in the aliases node.
if (auto resolved_path = boot_shim::ResolveTtyAlias(*tty_, serial_aliases_)) {
stdout_path_ = *resolved_path;
Log("DevicetreeChosenNodeMatcherBase::OnNode: resolved tty via aliases\n");
}
}
}
if (ramdisk_start && ramdisk_end) {
// RISC V and ARM disagree on what the type of these fields are. In both cases its an integer,
// but sometimes is u32 and sometimes is u64. So based on the number of bytes, guess.
std::optional<uint64_t> address_start =
ramdisk_start->AsBytes().size() > sizeof(uint32_t)
? ramdisk_start->AsUint64()
: static_cast<std::optional<uint64_t>>(ramdisk_start->AsUint32());
std::optional<uint64_t> address_end =
ramdisk_end->AsBytes().size() > sizeof(uint32_t)
? ramdisk_end->AsUint64()
: static_cast<std::optional<uint64_t>>(ramdisk_end->AsUint32());
if (!address_start) {
OnError("Failed to parse chosen node's \"linux,initrd-start\" property.");
return devicetree::ScanState::kActive;
}
if (!address_end) {
OnError("Failed to parse chosen node's \"linux,initrd-end\" property.");
return devicetree::ScanState::kActive;
}
ramdisk_ = std::span<const std::byte>(reinterpret_cast<const std::byte*>(*address_start),
static_cast<size_t>(*address_end - *address_start));
}
return devicetree::ScanState::kActive;
}
} // namespace boot_shim