~sircmpwn/helios

730adc000ccf5d967fcc3b704f3eaf3b90bd0d04 — Drew DeVault 1 year, 6 months ago 8b4661d
fdt: implement address space translation

Signed-off-by: Drew DeVault <sir@cmpwn.com>
2 files changed, 109 insertions(+), 15 deletions(-)

M arch/dev/+aarch64/pl011.ha
M fdt/probe.ha
M arch/dev/+aarch64/pl011.ha => arch/dev/+aarch64/pl011.ha +2 -1
@@ 70,7 70,8 @@ fn pl011_write(cons: *cons::console, buf: []u8) size = {
	return len(buf);
};

def PL011_MAX_CONS: size = 8;
// XXX: Could add more but the first one is fine for now
def PL011_MAX_CONS: size = 1;
let pl011_consbuf: [PL011_MAX_CONS]pl011_console = [
	pl011_console { ... }...
];

M fdt/probe.ha => fdt/probe.ha +107 -14
@@ 1,4 1,5 @@
use endian;
use log;
use strings;

// An FDT prober which matches on the "compatible" property.


@@ 33,9 34,25 @@ export fn register_name(probe: *probe_name) void = {
	probers_name = probe;
};

def MAX_RANGE: size = 8;

export type prober_state = struct {
	scan: *scanner,
	node: size,
	space: translation,
};

export type translation = struct {
	addr_cells: u32,
	size_cells: u32,
	range_buf: [MAX_RANGE]range,
	ranges: []range,
};

export type range = struct {
	child: uintptr,
	parent: uintptr,
	length: size,
};

// Returns a property from the prober's current node.


@@ 59,35 76,64 @@ export fn property(state: *prober_state, name: const str) const []u8 = {

// Returns the reg property from the prober's current node.
export fn reg(state: *prober_state) (uintptr, size) = {
	// TODO: Use #address-cells and #size-cells here
	const reg = property(state, "reg");
	// TODO: Use an iterator design for nodes with multiple registers
	assert(len(reg) == size(u64) * 2);
	const a1: u64 = endian::begetu32(reg[0..4]);
	const a2: u64 = endian::begetu32(reg[4..8]);
	const s1: u64 = endian::begetu32(reg[8..12]);
	const s2: u64 = endian::begetu32(reg[12..16]);
	return ((a1 << 32 | a2): uintptr, (s1 << 32 | s2): size);
	const naddr = state.space.addr_cells;
	const nsize = state.space.addr_cells;
	assert(naddr == 1 || naddr == 2);
	assert(nsize == 1 || nsize == 2); // XXX: Are other sizes useful?

	// TODO: Use an iterator design for nodes with several registers
	assert(len(reg) == (naddr + nsize) * size(u32));

	let i = 0z;
	let addr = getcell(reg, &i, naddr): uintptr;
	const sz = getcell(reg, &i, nsize): size;

	for (let i = 0z; i < len(state.space.ranges); i += 1) {
		const range = state.space.ranges[i];
		const min = range.child;
		const max = range.child + range.length: uintptr;
		if (min <= addr && addr < max) {
			addr -= range.child;
			addr += range.parent;
			break;
		};
	};

	return (addr, sz);
};

// Runs the FDT prober.
export fn probe(scan: *scanner) (void | invalid) = {
	// TODO: Deal with #address-cells, #size-cells, ranges, and other
	// hierarchical traits
	let tok = next(scan)?;
	assert(tok is begin_node);

	probe_node(scan, "/")?;
	probe_node(scan, "/", &translation {
		addr_cells = 2,
		size_cells = 1,
		...
	})?;

	let tok = next(scan)?;
	assert(tok is end);
};

fn probe_node(scan: *scanner, name: str) (void | invalid) = {
fn probe_node(
	scan: *scanner,
	name: str,
	space: *translation,
) (void | invalid) = {
	let state = prober_state {
		scan = scan,
		node = save(scan),
		space = *space,
	};
	let child_space = translation {
		addr_cells = 2,
		size_cells = 1,
		...
	};
	child_space.ranges = child_space.range_buf[..0];

	match (getprober_name(name)) {
	case let probe: *probe_name =>


@@ 103,12 149,21 @@ fn probe_node(scan: *scanner, name: str) (void | invalid) = {
		match (tok) {
		case let node: begin_node =>
			const (name, _) = strings::cut(node, "@");
			probe_node(scan, name)?;
			probe_node(scan, name, &child_space)?;
		case let prop: prop =>
			const (name, val) = prop;
			if (name == "compatible") {
			switch (name) {
			case "compatible" =>
				const names = strings::fromutf8(val);
				probe_dev(&state, names);
			case "#address-cells" =>
				child_space.addr_cells = endian::begetu32(val);
			case "#size-cells" =>
				child_space.size_cells = endian::begetu32(val);
			case "ranges" =>
				probe_ranges(space, &child_space, val);
			case =>
				yield;
			};
		case end_node =>
			break;


@@ 118,6 173,31 @@ fn probe_node(scan: *scanner, name: str) (void | invalid) = {
	};
};

// Processes a "ranges" property and stores the translation ranges in the
// provided child address translation space.
fn probe_ranges(
	parent: *translation,
	child: *translation,
	ranges: []u8,
) void = {
	let i = 0z;
	for (i < len(ranges)) {
		const child_addr = getcell(ranges, &i, child.addr_cells);
		const parent_addr = getcell(ranges, &i, parent.addr_cells);
		const length = getcell(ranges, &i, child.size_cells);
		if (len(child.ranges) > MAX_RANGE) {
			log::printfln("Warning: device tree ranges property exceeds maximum translations of {}",
				MAX_RANGE);
			break;
		};
		static append(child.ranges, range {
			child = child_addr: uintptr,
			parent = parent_addr: uintptr,
			length = length: size,
		});
	};
};

fn probe_dev(state: *prober_state, names: str) void = {
	const tok = strings::tokenize(names, "\0");
	for (true) {


@@ 181,3 261,16 @@ fn getprober_name(name: str) nullable *probe_name = {
	};
	return null;
};

// Fetches a cell of the given number of words from a buffer, advancing the
// provided index pointer appropriately.
fn getcell(buf: []u8, b: *size, cells: u32) u64 = {
	let cell = 0u64;
	for (let i = 0u32; i < cells; i += 1) {
		const val = endian::begetu32(buf[*b..]);
		cell <<= size(u32) / 8;
		cell |= val;
		*b += size(u32);
	};
	return cell;
};