22 changed files with 835 additions and 10 deletions
@ -0,0 +1,99 @@ |
|||||
|
#include "led-device.h" |
||||
|
#include <cstring> |
||||
|
#include <cstdio> |
||||
|
#include <sstream> |
||||
|
#include <algorithm> |
||||
|
|
||||
|
led_device_t::led_device_t(const sim_t *sim, reg_t base, size_t size, uint32_t num_leds) |
||||
|
: sim(sim), base_addr(base), dev_size(size), |
||||
|
num_leds(std::min(num_leds, 32u)), led_state(0) { |
||||
|
printf("LED device init at 0x%lx, size: 0x%lx, num_leds: %u\n", |
||||
|
base_addr, dev_size, this->num_leds); |
||||
|
} |
||||
|
|
||||
|
bool led_device_t::load(reg_t addr, size_t len, uint8_t* bytes) { |
||||
|
printf("led_device_t::load from address: 0x%lx, length: %zu\n", |
||||
|
base_addr + addr, len); |
||||
|
|
||||
|
if (addr == 0 && len == 4) { |
||||
|
uint32_t masked_state = led_state & ((1u << num_leds) - 1); |
||||
|
memcpy(bytes, &masked_state, 4); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
bool led_device_t::store(reg_t addr, size_t len, const uint8_t* bytes) { |
||||
|
printf("led_device_t::store to address: 0x%lx, length: %zu\n", |
||||
|
base_addr + addr, len); |
||||
|
|
||||
|
if (addr == 0 && len == 4) { |
||||
|
uint32_t new_state; |
||||
|
memcpy(&new_state, bytes, 4); |
||||
|
led_state = new_state & ((1u << num_leds) - 1); |
||||
|
|
||||
|
printf("LED state changed to: 0x%x\n", led_state); |
||||
|
printf("LED pattern (%u LEDs): ", num_leds); |
||||
|
for (int i = num_leds - 1; i >= 0; i--) { |
||||
|
printf("%c", (led_state & (1 << i)) ? ' ' : '-'); |
||||
|
} |
||||
|
printf(" (0x%x)\n", led_state); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
size_t led_device_t::size() { |
||||
|
return dev_size; |
||||
|
} |
||||
|
|
||||
|
led_device_t* led_parse_from_fdt(const void *fdt, const sim_t *sim, |
||||
|
reg_t *base, std::vector<std::string> args) { |
||||
|
if (args.size() < 3) { |
||||
|
fprintf(stderr, "Error: led device requires 3 arguments (base, size, num_leds)\n"); |
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
*base = std::stoull(args[0], nullptr, 0); |
||||
|
reg_t size = std::stoull(args[1], nullptr, 0); |
||||
|
uint32_t num_leds = std::stoul(args[2], nullptr, 0); |
||||
|
|
||||
|
if (num_leds == 0 || num_leds > 32) { |
||||
|
fprintf(stderr, "Error: num_leds must be between 1 and 32, got %u\n", num_leds); |
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
return new led_device_t(sim, *base, size, num_leds); |
||||
|
} catch (const std::exception& e) { |
||||
|
fprintf(stderr, "Error parsing LED device params: %s\n", e.what()); |
||||
|
return nullptr; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
std::string led_generate_dts(const sim_t *sim, const std::vector<std::string> &args) { |
||||
|
std::ostringstream s; |
||||
|
reg_t base = 0x10001000; |
||||
|
reg_t size = 0x1000; |
||||
|
uint32_t num_leds = 8; |
||||
|
|
||||
|
if (args.size() >= 3) { |
||||
|
base = std::stoull(args[0], nullptr, 0); |
||||
|
size = std::stoull(args[1], nullptr, 0); |
||||
|
num_leds = std::stoul(args[2], nullptr, 0); |
||||
|
} |
||||
|
|
||||
|
s << std::hex |
||||
|
<< " led@" << base << " {\n" |
||||
|
<< " compatible = \"led\";\n" |
||||
|
<< " reg = <0x0 0x" << base << " 0x0 0x" << size << ">;\n" |
||||
|
<< std::dec |
||||
|
<< " num-leds = <" << num_leds << ">;\n" |
||||
|
<< std::hex |
||||
|
<< " spike,plugin-params = \"0x" << base << ",0x" << size << "," << std::dec << num_leds << "\";\n" |
||||
|
<< " };\n"; |
||||
|
|
||||
|
return s.str(); |
||||
|
} |
||||
|
|
||||
|
REGISTER_DEVICE(led_device, led_parse_from_fdt, led_generate_dts) |
||||
@ -0,0 +1,26 @@ |
|||||
|
#ifndef LED_DEVICE_H |
||||
|
#define LED_DEVICE_H |
||||
|
|
||||
|
#include <riscv/sim.h> |
||||
|
#include <vector> |
||||
|
#include <string> |
||||
|
|
||||
|
class led_device_t : public abstract_device_t { |
||||
|
public: |
||||
|
led_device_t(const sim_t *sim, reg_t base, size_t size, uint32_t num_leds); |
||||
|
|
||||
|
bool load(reg_t addr, size_t len, uint8_t* bytes) override; |
||||
|
bool store(reg_t addr, size_t len, const uint8_t* bytes) override; |
||||
|
size_t size() override; |
||||
|
|
||||
|
private: |
||||
|
const sim_t *sim; |
||||
|
reg_t base_addr; |
||||
|
size_t dev_size; |
||||
|
uint32_t num_leds; |
||||
|
uint32_t led_state; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
|
||||
|
#endif // LED_DEVICE_H
|
||||
@ -0,0 +1,52 @@ |
|||||
|
/dts-v1/; |
||||
|
/ { |
||||
|
#address-cells = <2>; |
||||
|
#size-cells = <2>; |
||||
|
|
||||
|
cpus { |
||||
|
#address-cells = <1>; |
||||
|
#size-cells = <0>; |
||||
|
timebase-frequency = <10000000>; |
||||
|
|
||||
|
CPU0: cpu@0 { |
||||
|
device_type = "cpu"; |
||||
|
reg = <0>; |
||||
|
riscv,isa = "rv64imafdc"; |
||||
|
|
||||
|
CPU0_intc: interrupt-controller { |
||||
|
#interrupt-cells = <1>; |
||||
|
interrupt-controller; |
||||
|
compatible = "riscv,cpu-intc"; |
||||
|
}; |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
memory@90000000 { |
||||
|
device_type = "memory"; |
||||
|
reg = <0x00000000 0x90000000 0x00000000 0x10000000>; |
||||
|
}; |
||||
|
|
||||
|
clint@2000000 { |
||||
|
compatible = "riscv,clint0"; |
||||
|
reg = <0x0 0x2000000 0x0 0xc0000>; |
||||
|
}; |
||||
|
|
||||
|
PLIC: plic@c000000 { |
||||
|
compatible = "riscv,plic0"; |
||||
|
reg = <0x0 0xc000000 0x0 0x1000000>; |
||||
|
riscv,ndev = <0x1f>; |
||||
|
riscv,max-priority = <0xf>; |
||||
|
#interrupt-cells = <1>; |
||||
|
interrupt-controller; |
||||
|
}; |
||||
|
|
||||
|
uart0: serial@10000000 { |
||||
|
compatible = "ns16550a"; |
||||
|
reg = <0x0 0x10000000 0x0 0x100>; |
||||
|
clock-frequency = <10000000>; |
||||
|
interrupts = <10>; |
||||
|
reg-shift = <0>; |
||||
|
reg-io-width = <1>; |
||||
|
}; |
||||
|
|
||||
|
}; |
||||
@ -0,0 +1,36 @@ |
|||||
|
/dts-v1/; |
||||
|
|
||||
|
/ { |
||||
|
#address-cells = <2>; |
||||
|
#size-cells = <2>; |
||||
|
|
||||
|
cpus { // Container for all CPU cores |
||||
|
#address-cells = <1>; // CPU children use 1 cell for hartid |
||||
|
#size-cells = <0>; // CPU children don't specify size |
||||
|
timebase-frequency = <10000000>; // Timer ticks at 10 MHz (for OS timing) |
||||
|
|
||||
|
CPU0: cpu@0 { // First CPU core (label: CPU0, unit address: 0) |
||||
|
device_type = "cpu"; // Identifies this as a CPU device |
||||
|
reg = <0>; // Hartid = 0 (hardware thread ID) |
||||
|
riscv,isa = "rv64imafdc"; // ISA: RV64 with I,M,A,F,D,C extensions |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
memory@90000000 { |
||||
|
device_type = "memory"; |
||||
|
reg = <0x0 0x90000000 0x0 0x00010000>; |
||||
|
}; |
||||
|
|
||||
|
led1@10001000 { |
||||
|
compatible = "led_device"; |
||||
|
reg = <0x0 0x10001000 0x0 0x1000>; |
||||
|
spike,plugin-params = "16"; |
||||
|
}; |
||||
|
|
||||
|
led2@10002000 { |
||||
|
compatible = "led_device"; |
||||
|
reg = <0x0 0x10002000 0x0 0x1000>; |
||||
|
spike,plugin-params = "8"; |
||||
|
}; |
||||
|
}; |
||||
@ -0,0 +1,26 @@ |
|||||
|
/dts-v1/; |
||||
|
|
||||
|
/ { |
||||
|
#address-cells = <2>; |
||||
|
#size-cells = <2>; |
||||
|
|
||||
|
cpus { // Container for all CPU cores |
||||
|
#address-cells = <1>; // CPU children use 1 cell for hartid |
||||
|
#size-cells = <0>; // CPU children don't specify size |
||||
|
timebase-frequency = <10000000>; // Timer ticks at 10 MHz (for OS timing) |
||||
|
|
||||
|
CPU0: cpu@0 { // First CPU core (label: CPU0, unit address: 0) |
||||
|
device_type = "cpu"; // Identifies this as a CPU device |
||||
|
reg = <0>; // Hartid = 0 (hardware thread ID) |
||||
|
riscv,isa = "rv64imafdc"; // ISA: RV64 with I,M,A,F,D,C extensions |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
led@10001000 { |
||||
|
compatible = "led_device"; |
||||
|
reg = <0x0 0x10001000 0x0 0x00001000>; |
||||
|
spike,plugin-params = "16"; |
||||
|
}; |
||||
|
|
||||
|
}; |
||||
@ -0,0 +1,23 @@ |
|||||
|
/dts-v1/; |
||||
|
/ { |
||||
|
#address-cells = <1>; |
||||
|
#size-cells = <1>; |
||||
|
|
||||
|
cpus { // Container for all CPU cores |
||||
|
#address-cells = <1>; // CPU children use 1 cell for hartid |
||||
|
#size-cells = <0>; // CPU children don't specify size |
||||
|
timebase-frequency = <10000000>; // Timer ticks at 10 MHz (for OS timing) |
||||
|
|
||||
|
CPU0: cpu@0 { // First CPU core (label: CPU0, unit address: 0) |
||||
|
device_type = "cpu"; // Identifies this as a CPU device |
||||
|
reg = <0>; // Hartid = 0 (hardware thread ID) |
||||
|
riscv,isa = "rv64imafdc"; // ISA: RV64 with I,M,A,F,D,C extensions |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
memory@80000000 { |
||||
|
device_type = "memory"; |
||||
|
reg = <0x80000000 0x10000000>; // 256MB |
||||
|
}; |
||||
|
}; |
||||
@ -0,0 +1,24 @@ |
|||||
|
/dts-v1/; |
||||
|
/ { |
||||
|
#address-cells = <2>; |
||||
|
#size-cells = <2>; |
||||
|
|
||||
|
cpus { // Container for all CPU cores |
||||
|
#address-cells = <1>; // CPU children use 1 cell for hartid |
||||
|
#size-cells = <0>; // CPU children don't specify size |
||||
|
timebase-frequency = <10000000>; // Timer ticks at 10 MHz (for OS timing) |
||||
|
|
||||
|
CPU0: cpu@0 { // First CPU core (label: CPU0, unit address: 0) |
||||
|
device_type = "cpu"; // Identifies this as a CPU device |
||||
|
reg = <0>; // Hartid = 0 (hardware thread ID) |
||||
|
riscv,isa = "rv64imafdc"; // ISA: RV64 with I,M,A,F,D,C extensions |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
memory@80000000 { |
||||
|
device_type = "memory"; |
||||
|
reg = <0x0 0x20000000 0x0 0x40000000>; // 1GB |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
@ -0,0 +1,23 @@ |
|||||
|
/dts-v1/; |
||||
|
/ { |
||||
|
#address-cells = <2>; |
||||
|
#size-cells = <1>; |
||||
|
|
||||
|
cpus { // Container for all CPU cores |
||||
|
#address-cells = <1>; // CPU children use 1 cell for hartid |
||||
|
#size-cells = <0>; // CPU children don't specify size |
||||
|
timebase-frequency = <10000000>; // Timer ticks at 10 MHz (for OS timing) |
||||
|
|
||||
|
CPU0: cpu@0 { // First CPU core (label: CPU0, unit address: 0) |
||||
|
device_type = "cpu"; // Identifies this as a CPU device |
||||
|
reg = <0>; // Hartid = 0 (hardware thread ID) |
||||
|
riscv,isa = "rv64imafdc"; // ISA: RV64 with I,M,A,F,D,C extensions |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
memory@100000000 { |
||||
|
device_type = "memory"; |
||||
|
reg = <0x1 0x00000000 0x20000000>; // 512MB at 4GB |
||||
|
}; |
||||
|
}; |
||||
@ -0,0 +1,28 @@ |
|||||
|
/dts-v1/; |
||||
|
/ { |
||||
|
#address-cells = <2>; |
||||
|
#size-cells = <2>; |
||||
|
|
||||
|
cpus { // Container for all CPU cores |
||||
|
#address-cells = <1>; // CPU children use 1 cell for hartid |
||||
|
#size-cells = <0>; // CPU children don't specify size |
||||
|
timebase-frequency = <10000000>; // Timer ticks at 10 MHz (for OS timing) |
||||
|
|
||||
|
CPU0: cpu@0 { // First CPU core (label: CPU0, unit address: 0) |
||||
|
device_type = "cpu"; // Identifies this as a CPU device |
||||
|
reg = <0>; // Hartid = 0 (hardware thread ID) |
||||
|
riscv,isa = "rv64imafdc"; // ISA: RV64 with I,M,A,F,D,C extensions |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
memory@80000000 { |
||||
|
device_type = "memory"; |
||||
|
reg = <0x0 0x80000000 0x0 0x20000000>; |
||||
|
}; |
||||
|
|
||||
|
memory@A0000000 { |
||||
|
device_type = "memory"; |
||||
|
reg = <0x0 0xA0000000 0x0 0x20000000>; |
||||
|
}; |
||||
|
}; |
||||
@ -0,0 +1,263 @@ |
|||||
|
#include <riscv/sim.h> |
||||
|
#include <riscv/dtb_discovery.h> |
||||
|
#include <riscv/devices.h> |
||||
|
#include <riscv/mmu.h> |
||||
|
#include <fdt/libfdt.h> |
||||
|
#include <fstream> |
||||
|
#include <iostream> |
||||
|
#include <map> |
||||
|
#include <vector> |
||||
|
#include <string> |
||||
|
#include <filesystem> |
||||
|
#include <cstring> |
||||
|
#include <dlfcn.h> |
||||
|
|
||||
|
#include "led-device.h" |
||||
|
|
||||
|
struct expected_mem_t { |
||||
|
reg_t base; |
||||
|
}; |
||||
|
|
||||
|
struct expected_device_t { |
||||
|
const std::type_info* compatible; |
||||
|
reg_t base; |
||||
|
}; |
||||
|
|
||||
|
struct test_case_t { |
||||
|
std::vector<expected_mem_t> memory; |
||||
|
std::vector<expected_device_t> devices; |
||||
|
}; |
||||
|
|
||||
|
// Key: pair of (filename, dtb_discovery_enabled)
|
||||
|
using test_key_t = std::pair<std::string, bool>; |
||||
|
|
||||
|
static std::map<test_key_t, test_case_t> test_cases_expected_results = { |
||||
|
// With DTB discovery enabled
|
||||
|
{ {"mem-64bit-single.dtb", true}, { |
||||
|
{{0x20000000ULL}}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}} |
||||
|
}}, |
||||
|
{ {"mem-64bit-single.dtb", false}, { |
||||
|
{}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}} |
||||
|
}}, |
||||
|
{ {"mem-32bit.dtb", true}, { |
||||
|
{{0x80000000ULL}}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}} |
||||
|
}}, |
||||
|
{ {"mem-32bit.dtb", false}, { |
||||
|
{}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}} |
||||
|
}}, |
||||
|
{ {"mem-multiple-nodes.dtb", true}, { |
||||
|
{{0x80000000ULL}, {0xA0000000ULL}}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}} |
||||
|
}}, |
||||
|
{ {"mem-multiple-nodes.dtb", false}, { |
||||
|
{}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}} |
||||
|
}}, |
||||
|
{ {"mem-mixed-cells-sizes.dtb", true}, { |
||||
|
{{0x100000000ULL}}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}} |
||||
|
}}, |
||||
|
{ {"mem-mixed-cells-sizes.dtb", false}, { |
||||
|
{}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}} |
||||
|
}}, |
||||
|
{ {"devices-single-led.dtb", true}, { |
||||
|
{}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}, |
||||
|
{&typeid(led_device_t), 0x10001000ULL}} |
||||
|
}}, |
||||
|
{ {"devices-single-led.dtb", false}, { |
||||
|
{}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}} |
||||
|
}}, |
||||
|
{ {"devices-multiple-leds.dtb", true}, { |
||||
|
{{0x90000000ULL}}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}, |
||||
|
{&typeid(led_device_t), 0x10001000ULL}, |
||||
|
{&typeid(led_device_t), 0x10002000ULL}} |
||||
|
}}, |
||||
|
{ {"devices-multiple-leds.dtb", false}, { |
||||
|
{}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}} |
||||
|
}}, |
||||
|
{ {"devices-clint-plic-uart.dtb", true}, { |
||||
|
{{0x90000000ULL}}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}, |
||||
|
{&typeid(clint_t), 0x2000000ULL}, |
||||
|
{&typeid(plic_t), 0xc000000ULL}, |
||||
|
{&typeid(ns16550_t), 0x10000000ULL}} |
||||
|
}}, |
||||
|
{ {"devices-clint-plic-uart.dtb", false}, { |
||||
|
{}, |
||||
|
{{&typeid(debug_module_t), 0x0ULL}, |
||||
|
{&typeid(clint_t), 0x2000000ULL}, |
||||
|
{&typeid(plic_t), 0xc000000ULL}, |
||||
|
{&typeid(ns16550_t), 0x10000000ULL}} |
||||
|
}} |
||||
|
}; |
||||
|
|
||||
|
bool check_memory_devices(const std::map<reg_t, abstract_device_t*>& devices, |
||||
|
const std::vector<expected_mem_t>& expected_memory) { |
||||
|
bool passed = true; |
||||
|
|
||||
|
for (const auto& mem : expected_memory) { |
||||
|
auto it = devices.find(mem.base); |
||||
|
|
||||
|
if (it == devices.end()) { |
||||
|
std::cerr << "Memory NOT found at 0x" << std::hex << mem.base << std::endl; |
||||
|
passed = false; |
||||
|
} else if (dynamic_cast<mem_t*>(it->second) == nullptr) { |
||||
|
std::cerr << "Device at 0x" << std::hex << mem.base |
||||
|
<< " is not mem_t type" << std::endl; |
||||
|
passed = false; |
||||
|
} else { |
||||
|
std::cout << "Memory found at 0x" << std::hex << mem.base << std::endl; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return passed; |
||||
|
} |
||||
|
|
||||
|
bool check_devices(const std::map<reg_t, abstract_device_t*>& devices, |
||||
|
const std::vector<expected_device_t>& expected_devices) { |
||||
|
bool passed = true; |
||||
|
|
||||
|
for (const auto& expected_device : expected_devices) { |
||||
|
auto it = devices.find(expected_device.base); |
||||
|
|
||||
|
if (it == devices.end()) { |
||||
|
std::cerr << "Expected device not found at 0x" << std::hex << expected_device.base << std::endl; |
||||
|
passed = false; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
const std::type_info& actual_type = typeid(*it->second); |
||||
|
|
||||
|
if (*expected_device.compatible != actual_type) { |
||||
|
std::cerr << "Device at 0x" << std::hex << expected_device.base |
||||
|
<< " has wrong type. Expected: " << expected_device.compatible->name() |
||||
|
<< ", Got: " << actual_type.name() << std::endl; |
||||
|
passed = false; |
||||
|
} else { |
||||
|
std::cout << "Expected device found at 0x" << std::hex << expected_device.base |
||||
|
<< " Type: " << actual_type.name() << std::endl; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return passed; |
||||
|
} |
||||
|
|
||||
|
bool check_no_unexpected_devices(const std::map<reg_t, abstract_device_t*>& devices, |
||||
|
const test_case_t& expected) { |
||||
|
size_t expected_count = expected.memory.size() + expected.devices.size(); |
||||
|
|
||||
|
if (devices.size() != expected_count) { |
||||
|
std::cerr << "Device count mismatch. Expected: " << expected_count |
||||
|
<< ", Got: " << devices.size() << std::endl; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
bool compare_devices(sim_t& sim, const test_case_t& expected) { |
||||
|
const auto& devices = sim.get_bus().get_devices(); |
||||
|
|
||||
|
bool no_extra_ok = check_no_unexpected_devices(devices, expected); |
||||
|
bool memory_ok = check_memory_devices(devices, expected.memory); |
||||
|
bool devices_ok= check_devices(devices, expected.devices); |
||||
|
|
||||
|
return memory_ok && devices_ok && no_extra_ok; |
||||
|
} |
||||
|
|
||||
|
bool run_test(const std::string& dtb_path, bool dtb_discovery, |
||||
|
const std::string& pk, const std::string& executable) { |
||||
|
// Extract filename from dtb path
|
||||
|
std::filesystem::path p(dtb_path); |
||||
|
std::string dtb_filename = p.filename().string(); |
||||
|
|
||||
|
// Create test key
|
||||
|
test_key_t test_key = {dtb_filename, dtb_discovery}; |
||||
|
|
||||
|
// Find expected layout
|
||||
|
auto test_it = test_cases_expected_results.find(test_key); |
||||
|
if (test_it == test_cases_expected_results.end()) { |
||||
|
std::cout << "Error: No expected result for '" << dtb_filename |
||||
|
<< "' with dtb_discovery=" << (dtb_discovery ? "true" : "false") << "\n"; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
const auto& expected = test_it->second; |
||||
|
std::cout << "Testing: " << dtb_filename << "\n"; |
||||
|
std::cout << "DTB Discovery: " << (dtb_discovery ? "enabled" : "disabled") << "\n"; |
||||
|
|
||||
|
// Setup simulator
|
||||
|
cfg_t cfg; |
||||
|
std::vector<device_factory_sargs_t> devices; |
||||
|
std::vector<std::string> htif_args{pk, executable}; |
||||
|
debug_module_config_t dm_config; |
||||
|
std::vector<std::pair<reg_t, abstract_mem_t*>> mems; |
||||
|
|
||||
|
sim_t sim(&cfg, false, |
||||
|
mems, |
||||
|
devices, |
||||
|
dtb_discovery, |
||||
|
htif_args, |
||||
|
dm_config, |
||||
|
nullptr, |
||||
|
true, |
||||
|
dtb_path.c_str(), |
||||
|
false, |
||||
|
nullptr, |
||||
|
std::nullopt); |
||||
|
|
||||
|
// Compare devices
|
||||
|
bool result = compare_devices(sim, expected); |
||||
|
|
||||
|
if (result) { |
||||
|
std::cout << "✓ TEST PASSED: " << dtb_filename |
||||
|
<< " (dtb_discovery=" << (dtb_discovery ? "enabled" : "disabled") << ")\n\n"; |
||||
|
} else { |
||||
|
std::cout << "✗ TEST FAILED: " << dtb_filename |
||||
|
<< " (dtb_discovery=" << (dtb_discovery ? "enabled" : "disabled") << ")\n\n"; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
int main(int argc, char **argv) { |
||||
|
if (argc != 4) { |
||||
|
std::cerr << "Usage: " << argv[0] << " --dtb=<file> <pk> <executable>\n"; |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
std::string dtb_path; |
||||
|
std::string pk; |
||||
|
std::string executable; |
||||
|
|
||||
|
// Parse arguments
|
||||
|
int arg_idx = 1; |
||||
|
|
||||
|
// First argument must be --dtb=
|
||||
|
std::string dtb_arg = argv[arg_idx++]; |
||||
|
if (dtb_arg.find("--dtb=") == 0) { |
||||
|
dtb_path = dtb_arg.substr(strlen("--dtb=")); |
||||
|
} else { |
||||
|
std::cerr << "Error: First argument must be --dtb=<file>\n"; |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
// Remaining arguments are pk and executable
|
||||
|
pk = argv[arg_idx++]; |
||||
|
executable = argv[arg_idx]; |
||||
|
|
||||
|
// Run tests with both dtb_discovery values
|
||||
|
bool test_dtb_discovery_disabled = run_test(dtb_path, false, pk, executable); |
||||
|
bool test_dtb_discovery_enabled = run_test(dtb_path, true, pk, executable); |
||||
|
|
||||
|
return !(test_dtb_discovery_disabled && test_dtb_discovery_enabled); |
||||
|
} |
||||
@ -0,0 +1,142 @@ |
|||||
|
#include "dtb_discovery.h" |
||||
|
#include "libfdt.h" |
||||
|
#include "dts.h" |
||||
|
#include <iostream> |
||||
|
#include <sstream> |
||||
|
#include <cstdlib> |
||||
|
|
||||
|
|
||||
|
namespace { |
||||
|
std::vector<std::string> split_csv(const std::string& str) { |
||||
|
std::vector<std::string> result; |
||||
|
std::stringstream ss(str); |
||||
|
std::string item; |
||||
|
while (std::getline(ss, item, ',')) { |
||||
|
result.push_back(item); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
std::string join_csv(const std::vector<std::string>& vec) { |
||||
|
std::string result; |
||||
|
for (size_t i = 0; i < vec.size(); ++i) { |
||||
|
result += vec[i]; |
||||
|
if (i < vec.size() - 1) result += ","; |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
std::vector<std::string> get_device_params(const void* fdt, int offset) { |
||||
|
int param_len; |
||||
|
const char* params = (char*)fdt_getprop(fdt, offset, "spike,plugin-params", ¶m_len); |
||||
|
return params ? split_csv(params) : std::vector<std::string>(); |
||||
|
} |
||||
|
|
||||
|
// Helper to parse reg property
|
||||
|
bool parse_reg_property(const void* fdt, int offset, const char* node_name, |
||||
|
uint64_t& base, uint64_t& size) { |
||||
|
int len = 0; |
||||
|
const fdt32_t* reg_prop = (const fdt32_t*)fdt_getprop(fdt, offset, "reg", &len); |
||||
|
if (!reg_prop) { |
||||
|
fprintf(stderr, "DTB node '%s' missing 'reg' property\n", node_name); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
int addr_cells = fdt_address_cells(fdt, 0); |
||||
|
int size_cells = fdt_size_cells(fdt, 0); |
||||
|
if (addr_cells < 1 || size_cells < 1) { |
||||
|
fprintf(stderr, "Invalid #address-cells or #size-cells for node '%s'\n", node_name); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
int entry_cells = addr_cells + size_cells; |
||||
|
int entry_size = entry_cells * sizeof(fdt32_t); |
||||
|
if ((len % entry_size) != 0) { |
||||
|
fprintf(stderr, "DTB node '%s' has malformed 'reg'\n", node_name); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
const fdt32_t* p = reg_prop; |
||||
|
base = 0; |
||||
|
size = 0; |
||||
|
|
||||
|
// Parse base address
|
||||
|
for (int i = 0; i < addr_cells; ++i) |
||||
|
base = (base << 32) | fdt32_to_cpu(p[i]); |
||||
|
|
||||
|
// Parse size
|
||||
|
for (int i = 0; i < size_cells; ++i) |
||||
|
size = (size << 32) | fdt32_to_cpu(p[addr_cells + i]); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// Core platform devices handled by Spike internally
|
||||
|
const std::set<std::string> SPIKE_DEFAULT_DEVICES = { |
||||
|
"riscv,plic0", |
||||
|
"sifive,plic-1.0.0", |
||||
|
"ns16550a", |
||||
|
"riscv,clint0" |
||||
|
}; |
||||
|
|
||||
|
bool should_skip_device(const std::string& device_name) { |
||||
|
return SPIKE_DEFAULT_DEVICES.find(device_name) != SPIKE_DEFAULT_DEVICES.end(); |
||||
|
} |
||||
|
} // namespace
|
||||
|
|
||||
|
namespace dtb_discovery { |
||||
|
|
||||
|
void discover_devices_from_dtb(const void* fdt, |
||||
|
std::vector<device_factory_sargs_t>& factories) { |
||||
|
for (const auto& [device_name, factory] : mmio_device_map()) { |
||||
|
//Spike default devices are not managed by dtb discovery feature
|
||||
|
if (should_skip_device(device_name)) continue; |
||||
|
|
||||
|
int offset = -1; |
||||
|
while ((offset = fdt_node_offset_by_compatible(fdt, offset, device_name.c_str())) >= 0) { |
||||
|
const char* node_name = fdt_get_name(fdt, offset, NULL); |
||||
|
|
||||
|
uint64_t base = 0, size = 0; |
||||
|
if (!parse_reg_property(fdt, offset, node_name, base, size)) { |
||||
|
exit(1); |
||||
|
} |
||||
|
|
||||
|
auto parsed_args = get_device_params(fdt, offset); |
||||
|
|
||||
|
// Prepend base and size to arguments
|
||||
|
std::vector<std::string> full_args; |
||||
|
full_args.push_back(std::to_string(base)); |
||||
|
full_args.push_back(std::to_string(size)); |
||||
|
full_args.insert(full_args.end(), parsed_args.begin(), parsed_args.end()); |
||||
|
|
||||
|
fprintf(stdout, "DTB discovered device: %s - type: %s - base: 0x%016" PRIx64 |
||||
|
" - size: 0x%016" PRIx64 " - params: %s\n", |
||||
|
node_name, device_name.c_str(), base, size, |
||||
|
(parsed_args.empty() ? "" : join_csv(parsed_args).c_str())); |
||||
|
|
||||
|
factories.push_back({factory, full_args}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void discover_memory_from_dtb(const void* fdt, |
||||
|
std::vector<std::pair<reg_t, abstract_mem_t*>>& mems) { |
||||
|
int offset = -1; |
||||
|
while ((offset = fdt_node_offset_by_prop_value( |
||||
|
fdt, offset, "device_type", "memory", sizeof("memory"))) >= 0) { |
||||
|
const char* node_name = fdt_get_name(fdt, offset, nullptr); |
||||
|
|
||||
|
uint64_t base = 0, size = 0; |
||||
|
if (!parse_reg_property(fdt, offset, node_name, base, size)) { |
||||
|
exit(1); |
||||
|
} |
||||
|
|
||||
|
fprintf(stdout, |
||||
|
"DTB memory device: %s - Memory initialized at [0x%016" PRIx64 ", 0x%016" PRIx64 ")\n", |
||||
|
node_name, base, base + size); |
||||
|
|
||||
|
mems.emplace_back(base, new mem_t(size)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} // namespace dtb_discovery
|
||||
@ -0,0 +1,21 @@ |
|||||
|
#ifndef _RISCV_DTB_DISCOVERY_H |
||||
|
#define _RISCV_DTB_DISCOVERY_H |
||||
|
|
||||
|
#include "sim.h" |
||||
|
#include <vector> |
||||
|
#include <string> |
||||
|
|
||||
|
class bus_t; |
||||
|
class abstract_mem_t; |
||||
|
|
||||
|
namespace dtb_discovery { |
||||
|
|
||||
|
void discover_devices_from_dtb(const void* fdt, |
||||
|
std::vector<device_factory_sargs_t>& factories); |
||||
|
|
||||
|
void discover_memory_from_dtb(const void* fdt, std::vector<std::pair<reg_t, abstract_mem_t*>>& mems); |
||||
|
|
||||
|
} // namespace dtb_discovery
|
||||
|
|
||||
|
|
||||
|
#endif // _RISCV_DTB_DISCOVERY_H
|
||||
Loading…
Reference in new issue