656 lines
18 KiB
Zig
656 lines
18 KiB
Zig
const std = @import("std");
|
|
const Fdt = @This();
|
|
|
|
pub const MAGIC = 0xd00dfeed;
|
|
|
|
pub const Token = enum(u32) {
|
|
begin_node = 0x01,
|
|
end_node = 0x02,
|
|
prop = 0x03,
|
|
nop = 0x04,
|
|
end = 0x09,
|
|
};
|
|
|
|
pub const RawHeader = extern struct {
|
|
magic: u32,
|
|
totalsize: u32,
|
|
off_dt_struct: u32,
|
|
off_dt_strings: u32,
|
|
off_mem_rsvmap: u32,
|
|
version: u32,
|
|
last_comp_version: u32,
|
|
boot_cpuid_phys: u32,
|
|
size_dt_strings: u32,
|
|
size_dt_struct: u32,
|
|
};
|
|
|
|
pub const ReserveEntry = extern struct {
|
|
address: u64,
|
|
size: u64,
|
|
|
|
pub fn toNative(self: *const ReserveEntry) struct { address: u64, size: u64 } {
|
|
return .{
|
|
.address = std.mem.bigToNative(u64, self.address),
|
|
.size = std.mem.bigToNative(u64, self.size),
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Property = struct {
|
|
name: []const u8,
|
|
data: []const u8,
|
|
|
|
pub fn asU32(self: Property) ?u32 {
|
|
if (self.data.len != 4) return null;
|
|
return std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.data.ptr))).*);
|
|
}
|
|
|
|
pub fn asU64(self: Property) ?u64 {
|
|
if (self.data.len != 8) return null;
|
|
return std.mem.bigToNative(u64, @as(*const u64, @alignCast(@ptrCast(self.data.ptr))).*);
|
|
}
|
|
|
|
pub fn asString(self: Property) ?[]const u8 {
|
|
if (self.data.len == 0) return null;
|
|
for (self.data, 0..) |c, i| {
|
|
if (c == 0) return self.data[0..i];
|
|
}
|
|
return self.data;
|
|
}
|
|
|
|
pub fn asStringList(self: Property) StringListIterator {
|
|
return .{ .data = self.data };
|
|
}
|
|
|
|
pub fn asU32Array(self: Property) U32ArrayIterator {
|
|
return .{ .data = self.data };
|
|
}
|
|
|
|
pub fn asU64Array(self: Property) U64ArrayIterator {
|
|
return .{ .data = self.data };
|
|
}
|
|
|
|
pub fn isEmpty(self: Property) bool {
|
|
return self.data.len == 0;
|
|
}
|
|
};
|
|
|
|
pub const StringListIterator = struct {
|
|
data: []const u8,
|
|
pos: usize = 0,
|
|
|
|
pub fn next(self: *StringListIterator) ?[]const u8 {
|
|
if (self.pos >= self.data.len) return null;
|
|
|
|
const start = self.pos;
|
|
while (self.pos < self.data.len and self.data[self.pos] != 0) {
|
|
self.pos += 1;
|
|
}
|
|
const end = self.pos;
|
|
self.pos += 1;
|
|
|
|
if (start == end) return null;
|
|
return self.data[start..end];
|
|
}
|
|
};
|
|
|
|
pub const U32ArrayIterator = struct {
|
|
data: []const u8,
|
|
pos: usize = 0,
|
|
|
|
pub fn next(self: *U32ArrayIterator) ?u32 {
|
|
if (self.pos + 4 > self.data.len) return null;
|
|
const value = std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.data.ptr + self.pos))).*);
|
|
self.pos += 4;
|
|
return value;
|
|
}
|
|
|
|
pub fn count(self: U32ArrayIterator) usize {
|
|
return self.data.len / 4;
|
|
}
|
|
};
|
|
|
|
pub const U64ArrayIterator = struct {
|
|
data: []const u8,
|
|
pos: usize = 0,
|
|
|
|
pub fn next(self: *U64ArrayIterator) ?u64 {
|
|
if (self.pos + 8 > self.data.len) return null;
|
|
const value = std.mem.bigToNative(u64, @as(*const u64, @alignCast(@ptrCast(self.data.ptr + self.pos))).*);
|
|
self.pos += 8;
|
|
return value;
|
|
}
|
|
|
|
pub fn count(self: U64ArrayIterator) usize {
|
|
return self.data.len / 8;
|
|
}
|
|
};
|
|
|
|
pub const Node = struct {
|
|
name: []const u8,
|
|
fdt: *const Fdt,
|
|
struct_offset: usize,
|
|
|
|
pub fn unitAddress(self: Node) ?[]const u8 {
|
|
for (self.name, 0..) |c, i| {
|
|
if (c == '@') return self.name[i + 1 ..];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn baseName(self: Node) []const u8 {
|
|
for (self.name, 0..) |c, i| {
|
|
if (c == '@') return self.name[0..i];
|
|
}
|
|
return self.name;
|
|
}
|
|
|
|
pub fn properties(self: Node) PropertyIterator {
|
|
return .{
|
|
.fdt = self.fdt,
|
|
.pos = self.struct_offset,
|
|
};
|
|
}
|
|
|
|
pub fn getProperty(self: Node, name: []const u8) ?Property {
|
|
var iter = self.properties();
|
|
while (iter.next()) |prop| {
|
|
if (std.mem.eql(u8, prop.name, name)) {
|
|
return prop;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn children(self: Node) ChildIterator {
|
|
// Skip to the end of properties to find children
|
|
var pos = self.struct_offset;
|
|
while (pos < self.fdt.struct_block.len) {
|
|
const token = self.fdt.readToken(pos) orelse break;
|
|
pos += 4;
|
|
|
|
switch (token) {
|
|
.prop => {
|
|
const len = self.fdt.readU32(pos) orelse break;
|
|
pos += 8; // skip len and nameoff
|
|
pos += alignUp(len, 4);
|
|
},
|
|
.nop => {},
|
|
.begin_node, .end_node, .end => {
|
|
pos -= 4; // back up to re-read this token
|
|
break;
|
|
},
|
|
}
|
|
}
|
|
|
|
return .{
|
|
.fdt = self.fdt,
|
|
.pos = pos,
|
|
.depth = 0,
|
|
};
|
|
}
|
|
|
|
pub fn getChild(self: Node, name: []const u8) ?Node {
|
|
var iter = self.children();
|
|
while (iter.next()) |child| {
|
|
if (std.mem.eql(u8, child.name, name)) {
|
|
return child;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn compatible(self: Node) ?StringListIterator {
|
|
const prop = self.getProperty("compatible") orelse return null;
|
|
return prop.asStringList();
|
|
}
|
|
|
|
pub fn isCompatible(self: Node, compat: []const u8) bool {
|
|
var iter = self.compatible() orelse return false;
|
|
while (iter.next()) |c| {
|
|
if (std.mem.eql(u8, c, compat)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
pub fn reg(self: Node) ?[]const u8 {
|
|
const prop = self.getProperty("reg") orelse return null;
|
|
return prop.data;
|
|
}
|
|
|
|
pub fn status(self: Node) ?[]const u8 {
|
|
const prop = self.getProperty("status") orelse return null;
|
|
return prop.asString();
|
|
}
|
|
|
|
pub fn isEnabled(self: Node) bool {
|
|
const s = self.status() orelse return true;
|
|
return std.mem.eql(u8, s, "okay") or std.mem.eql(u8, s, "ok");
|
|
}
|
|
|
|
pub fn addressCells(self: Node) u32 {
|
|
const prop = self.getProperty("#address-cells") orelse return 2;
|
|
return prop.asU32() orelse 2;
|
|
}
|
|
|
|
pub fn sizeCells(self: Node) u32 {
|
|
const prop = self.getProperty("#size-cells") orelse return 1;
|
|
return prop.asU32() orelse 1;
|
|
}
|
|
|
|
pub fn phandle(self: Node) ?u32 {
|
|
// Try both 'phandle' and legacy 'linux,phandle'
|
|
if (self.getProperty("phandle")) |prop| {
|
|
return prop.asU32();
|
|
}
|
|
if (self.getProperty("linux,phandle")) |prop| {
|
|
return prop.asU32();
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
pub const PropertyIterator = struct {
|
|
fdt: *const Fdt,
|
|
pos: usize,
|
|
|
|
pub fn next(self: *PropertyIterator) ?Property {
|
|
while (self.pos < self.fdt.struct_block.len) {
|
|
const token = self.fdt.readToken(self.pos) orelse return null;
|
|
self.pos += 4;
|
|
|
|
switch (token) {
|
|
.prop => {
|
|
const len = self.fdt.readU32(self.pos) orelse return null;
|
|
const nameoff = self.fdt.readU32(self.pos + 4) orelse return null;
|
|
self.pos += 8;
|
|
|
|
const name = self.fdt.getString(nameoff) orelse return null;
|
|
const data = if (len > 0 and self.pos + len <= self.fdt.struct_block.len)
|
|
self.fdt.struct_block[self.pos..][0..len]
|
|
else
|
|
&[_]u8{};
|
|
|
|
self.pos += alignUp(len, 4);
|
|
|
|
return Property{
|
|
.name = name,
|
|
.data = data,
|
|
};
|
|
},
|
|
.nop => continue,
|
|
.begin_node, .end_node, .end => {
|
|
self.pos -= 4;
|
|
return null;
|
|
},
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
pub const ChildIterator = struct {
|
|
fdt: *const Fdt,
|
|
pos: usize,
|
|
depth: i32,
|
|
|
|
pub fn next(self: *ChildIterator) ?Node {
|
|
while (self.pos < self.fdt.struct_block.len) {
|
|
const token = self.fdt.readToken(self.pos) orelse return null;
|
|
self.pos += 4;
|
|
|
|
switch (token) {
|
|
.begin_node => {
|
|
const name_start = self.pos;
|
|
// Find end of name
|
|
while (self.pos < self.fdt.struct_block.len and self.fdt.struct_block[self.pos] != 0) {
|
|
self.pos += 1;
|
|
}
|
|
const name = self.fdt.struct_block[name_start..self.pos];
|
|
self.pos += 1; // skip null
|
|
self.pos = alignUp(self.pos, 4);
|
|
|
|
if (self.depth == 0) {
|
|
self.depth = 1;
|
|
return Node{
|
|
.name = name,
|
|
.fdt = self.fdt,
|
|
.struct_offset = self.pos,
|
|
};
|
|
} else {
|
|
self.depth += 1;
|
|
}
|
|
},
|
|
.end_node => {
|
|
self.depth -= 1;
|
|
if (self.depth < 0) return null;
|
|
},
|
|
.prop => {
|
|
const len = self.fdt.readU32(self.pos) orelse return null;
|
|
self.pos += 8;
|
|
self.pos += alignUp(len, 4);
|
|
},
|
|
.nop => {},
|
|
.end => return null,
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
pub const NodeIterator = struct {
|
|
fdt: *const Fdt,
|
|
pos: usize,
|
|
depth: usize = 0,
|
|
include_root: bool = true,
|
|
root_seen: bool = false,
|
|
|
|
pub fn next(self: *NodeIterator) ?Node {
|
|
while (self.pos < self.fdt.struct_block.len) {
|
|
const token = self.fdt.readToken(self.pos) orelse return null;
|
|
self.pos += 4;
|
|
|
|
switch (token) {
|
|
.begin_node => {
|
|
const name_start = self.pos;
|
|
while (self.pos < self.fdt.struct_block.len and self.fdt.struct_block[self.pos] != 0) {
|
|
self.pos += 1;
|
|
}
|
|
const name = self.fdt.struct_block[name_start..self.pos];
|
|
self.pos += 1;
|
|
self.pos = alignUp(self.pos, 4);
|
|
self.depth += 1;
|
|
|
|
// Skip root node if requested
|
|
if (!self.root_seen) {
|
|
self.root_seen = true;
|
|
if (!self.include_root) continue;
|
|
}
|
|
|
|
return Node{
|
|
.name = name,
|
|
.fdt = self.fdt,
|
|
.struct_offset = self.pos,
|
|
};
|
|
},
|
|
.end_node => {
|
|
if (self.depth > 0) self.depth -= 1;
|
|
},
|
|
.prop => {
|
|
const len = self.fdt.readU32(self.pos) orelse return null;
|
|
self.pos += 8;
|
|
self.pos += alignUp(len, 4);
|
|
},
|
|
.nop => {},
|
|
.end => return null,
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
pub const RegIterator = struct {
|
|
data: []const u8,
|
|
address_cells: u32,
|
|
size_cells: u32,
|
|
pos: usize,
|
|
|
|
pub const Entry = struct {
|
|
address: u64,
|
|
size: u64,
|
|
};
|
|
|
|
pub fn next(self: *RegIterator) ?Entry {
|
|
const entry_size = (self.address_cells + self.size_cells) * 4;
|
|
if (self.pos + entry_size > self.data.len) return null;
|
|
|
|
var address: u64 = 0;
|
|
var i: usize = 0;
|
|
while (i < self.address_cells) : (i += 1) {
|
|
const val = std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.data.ptr + self.pos + i * 4))).*);
|
|
address = (address << 32) | val;
|
|
}
|
|
|
|
var size: u64 = 0;
|
|
i = 0;
|
|
while (i < self.size_cells) : (i += 1) {
|
|
const val = std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.data.ptr + self.pos + (self.address_cells + i) * 4))).*);
|
|
size = (size << 32) | val;
|
|
}
|
|
|
|
self.pos += entry_size;
|
|
|
|
return Entry{
|
|
.address = address,
|
|
.size = size,
|
|
};
|
|
}
|
|
};
|
|
|
|
base: [*]const u8,
|
|
totalsize: u32,
|
|
version: u32,
|
|
last_comp_version: u32,
|
|
boot_cpuid_phys: u32,
|
|
struct_block: []const u8,
|
|
strings_block: []const u8,
|
|
mem_rsv_block: []const u8,
|
|
|
|
pub const Error = error{
|
|
InvalidMagic,
|
|
UnsupportedVersion,
|
|
InvalidStructure,
|
|
};
|
|
|
|
pub fn parse(ptr: *const anyopaque) Error!Fdt {
|
|
const base: [*]const u8 = @ptrCast(ptr);
|
|
const header: *const RawHeader = @alignCast(@ptrCast(ptr));
|
|
|
|
// Validate magic
|
|
const magic = std.mem.bigToNative(u32, header.magic);
|
|
if (magic != MAGIC) {
|
|
return Error.InvalidMagic;
|
|
}
|
|
|
|
const version = std.mem.bigToNative(u32, header.version);
|
|
const last_comp_version = std.mem.bigToNative(u32, header.last_comp_version);
|
|
|
|
// We support FDT version 17 (current standard)
|
|
if (last_comp_version > 17) {
|
|
return Error.UnsupportedVersion;
|
|
}
|
|
|
|
const totalsize = std.mem.bigToNative(u32, header.totalsize);
|
|
const off_dt_struct = std.mem.bigToNative(u32, header.off_dt_struct);
|
|
const size_dt_struct = std.mem.bigToNative(u32, header.size_dt_struct);
|
|
const off_dt_strings = std.mem.bigToNative(u32, header.off_dt_strings);
|
|
const size_dt_strings = std.mem.bigToNative(u32, header.size_dt_strings);
|
|
const off_mem_rsvmap = std.mem.bigToNative(u32, header.off_mem_rsvmap);
|
|
|
|
return Fdt{
|
|
.base = base,
|
|
.totalsize = totalsize,
|
|
.version = version,
|
|
.last_comp_version = last_comp_version,
|
|
.boot_cpuid_phys = std.mem.bigToNative(u32, header.boot_cpuid_phys),
|
|
.struct_block = base[off_dt_struct..][0..size_dt_struct],
|
|
.strings_block = base[off_dt_strings..][0..size_dt_strings],
|
|
.mem_rsv_block = base[off_mem_rsvmap..totalsize],
|
|
};
|
|
}
|
|
|
|
pub fn root(self: *const Fdt) ?Node {
|
|
if (self.struct_block.len < 4) return null;
|
|
|
|
const token = self.readToken(0) orelse return null;
|
|
if (token != .begin_node) return null;
|
|
|
|
var pos: usize = 4;
|
|
// Skip root node name (usually empty)
|
|
while (pos < self.struct_block.len and self.struct_block[pos] != 0) {
|
|
pos += 1;
|
|
}
|
|
pos += 1;
|
|
pos = alignUp(pos, 4);
|
|
|
|
return Node{
|
|
.name = "",
|
|
.fdt = self,
|
|
.struct_offset = pos,
|
|
};
|
|
}
|
|
|
|
pub fn nodes(self: *const Fdt) NodeIterator {
|
|
return .{
|
|
.fdt = self,
|
|
.pos = 0,
|
|
.include_root = true,
|
|
};
|
|
}
|
|
|
|
pub fn nodesWithoutRoot(self: *const Fdt) NodeIterator {
|
|
return .{
|
|
.fdt = self,
|
|
.pos = 0,
|
|
.include_root = false,
|
|
};
|
|
}
|
|
|
|
pub fn findNode(self: *const Fdt, path: []const u8) ?Node {
|
|
if (path.len == 0 or path[0] != '/') return null;
|
|
|
|
var current = self.root() orelse return null;
|
|
|
|
// Handle root path
|
|
if (path.len == 1) return current;
|
|
|
|
// Parse path components
|
|
var remaining = path[1..]; // skip leading '/'
|
|
while (remaining.len > 0) {
|
|
// Find next component
|
|
var end: usize = 0;
|
|
while (end < remaining.len and remaining[end] != '/') {
|
|
end += 1;
|
|
}
|
|
|
|
const component = remaining[0..end];
|
|
if (component.len == 0) break;
|
|
|
|
// Find child with this name
|
|
current = current.getChild(component) orelse return null;
|
|
|
|
// Move past this component and any '/'
|
|
remaining = if (end < remaining.len) remaining[end + 1 ..] else &[_]u8{};
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
pub fn findByPhandle(self: *const Fdt, handle: u32) ?Node {
|
|
var iter = self.nodes();
|
|
while (iter.next()) |node| {
|
|
if (node.phandle()) |ph| {
|
|
if (ph == handle) return node;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn findCompatible(self: *const Fdt, compat: []const u8) CompatibleIterator {
|
|
return .{
|
|
.inner = self.nodesWithoutRoot(),
|
|
.compat = compat,
|
|
};
|
|
}
|
|
|
|
pub fn findFirstCompatible(self: *const Fdt, compat: []const u8) ?Node {
|
|
var iter = self.findCompatible(compat);
|
|
return iter.next();
|
|
}
|
|
|
|
pub fn memoryReservations(self: *const Fdt) MemoryReservationIterator {
|
|
return .{
|
|
.data = self.mem_rsv_block,
|
|
.pos = 0,
|
|
};
|
|
}
|
|
|
|
pub fn chosen(self: *const Fdt) ?Node {
|
|
return self.findNode("/chosen");
|
|
}
|
|
|
|
pub fn memory(self: *const Fdt) ?Node {
|
|
return self.findNode("/memory") orelse self.findFirstCompatible("memory");
|
|
}
|
|
|
|
pub fn cpus(self: *const Fdt) ?Node {
|
|
return self.findNode("/cpus");
|
|
}
|
|
|
|
// Internal helpers
|
|
fn readToken(self: *const Fdt, offset: usize) ?Token {
|
|
if (offset + 4 > self.struct_block.len) return null;
|
|
const val = std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.struct_block.ptr + offset))).*);
|
|
return @as(Token, @enumFromInt(val));
|
|
}
|
|
|
|
fn readU32(self: *const Fdt, offset: usize) ?u32 {
|
|
if (offset + 4 > self.struct_block.len) return null;
|
|
return std.mem.bigToNative(u32, @as(*const u32, @alignCast(@ptrCast(self.struct_block.ptr + offset))).*);
|
|
}
|
|
|
|
fn getString(self: *const Fdt, offset: u32) ?[]const u8 {
|
|
if (offset >= self.strings_block.len) return null;
|
|
const start = self.strings_block[offset..];
|
|
for (start, 0..) |c, i| {
|
|
if (c == 0) return start[0..i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub const CompatibleIterator = struct {
|
|
inner: NodeIterator,
|
|
compat: []const u8,
|
|
|
|
pub fn next(self: *CompatibleIterator) ?Node {
|
|
while (self.inner.next()) |node| {
|
|
if (node.isCompatible(self.compat)) {
|
|
return node;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
pub const MemoryReservationIterator = struct {
|
|
data: []const u8,
|
|
pos: usize,
|
|
|
|
pub fn next(self: *MemoryReservationIterator) ?struct { address: u64, size: u64 } {
|
|
if (self.pos + 16 > self.data.len) return null;
|
|
|
|
const entry: *const ReserveEntry = @alignCast(@ptrCast(self.data.ptr + self.pos));
|
|
const result = entry.toNative();
|
|
self.pos += 16;
|
|
|
|
// End marker is all zeros
|
|
if (result.address == 0 and result.size == 0) return null;
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
fn alignUp(value: anytype, alignment: @TypeOf(value)) @TypeOf(value) {
|
|
return (value + alignment - 1) & ~(alignment - 1);
|
|
}
|
|
|
|
pub fn parseReg(data: []const u8, address_cells: u32, size_cells: u32) RegIterator {
|
|
return .{
|
|
.data = data,
|
|
.address_cells = address_cells,
|
|
.size_cells = size_cells,
|
|
.pos = 0,
|
|
};
|
|
}
|
|
|
|
|