Runtime integer sizes?

I have been working on devicetree stuff for my kernel recently.
Currently I am trying to handle the properties of a devicetree memory node.
For anyone that wants to know more about that, feel free to take a look at my devicetree branch and at the devicetree spec.

So, there is the reg property which is an array consisting of (address, length) pairs. address and length for each item are big-endian integers that have a size specified in the address-cells and size-cells properties (they specify how many u32 cells one address or size/length property takes).
The problem is that those sizes are runtime-known and, as the sizes are a 32-bit unsigned integer, the properties can take very much space (in theory).
As I am discovering the available memory using the devicetree, I can’t use an allocator safely.

I thought of two approaches to handle this problem:

  1. Use an union for integer types and a function that returns matching tags for that union
  2. Just reading until a normal usize is full (because an address with more bits than usize would be unnecessary)

The problem with (1) would be that the union could get very long (a zig integer has up to 65536 bit length possibilities as seen in the standard library and we can only use each 32nd length, so there are 65536/32=2048 different tags for the enum behind the union).
The problem with (2) would be that it isn’t fully conformant.

Any further approaches / ideas about my approaches?

1 Like

I’m not familiar with kernels or the devicetree spec, but from a glance at the docs I’d go with a pair of slices that can be converted to fixed size integers when the context demands it:

const std = @import("std");

/// Convert literal to big endian
fn be(T: type, n: comptime_int) T {
    return switch (@import("builtin").cpu.arch.endian()) {
        .big => @as(T, n),
        .little => @byteSwap(@as(T, n)),
    };
}

const Resource = struct {
    address: []const u32,
    length: []const u32,

    /// Read a Resource pair from a buffer
    pub fn init(address_cells: u32, size_cells: u32, buf: []const u32) Resource {
        std.debug.assert(buf.len == address_cells + size_cells);
        return .{
            .address = buf[0..address_cells],
            .length = buf[address_cells .. address_cells + size_cells],
        };
    }

    /// Convert a slice to a fixed size integer
    pub fn asInt(T: type, buf: []const u32) T {
        std.debug.assert(buf.len * 4 == @sizeOf(T));
        return std.mem.readInt(T, @ptrCast(buf.ptr), .big);
    }
};

pub fn main() void {
    // 32-bit address, 32-bit size
    {
        const address_cells = 1;
        const size_cells = 1;
        const resource_cells = address_cells + size_cells;

        // < (0x3000 0x20) (0xFE00 0x100) >
        const reg = [4]u32{ be(u32, 0x3000), be(u32, 0x20), be(u32, 0xFE00), be(u32, 0x100) };
        std.debug.assert(reg.len % resource_cells == 0);

        var cursor: usize = 0;
        while (cursor < reg.len) : (cursor += resource_cells) {
            const res = Resource.init(address_cells, size_cells, reg[cursor .. cursor + resource_cells]);
            std.debug.print("(0x{x} 0x{x})\n", .{ Resource.asInt(u32, res.address), Resource.asInt(u32, res.length) });
        }
    }

    // 64-bit address, 32-bit size
    {
        const address_cells = 2;
        const size_cells = 1;
        const resource_cells = address_cells + size_cells;

        // < (0x0 0x3000 0x20) (0x0 0xFE00 0x100) >
        const reg = @as([2]u32, @bitCast([1]u64{be(u64, 0x3000)})) ++ [1]u32{be(u32, 0x20)} ++ @as([2]u32, @bitCast([1]u64{be(u64, 0xFE00)})) ++ [1]u32{be(u32, 0x100)};
        std.debug.assert(reg.len % resource_cells == 0);

        var cursor: usize = 0;
        while (cursor < reg.len) : (cursor += resource_cells) {
            const res = Resource.init(address_cells, size_cells, reg[cursor .. cursor + resource_cells]);
            std.debug.print("(0x{x} 0x{x})\n", .{ Resource.asInt(u64, res.address), Resource.asInt(u32, res.length) });
        }
    }
}

Slices also allow you to compare dynamic sized addresses or lengths without generating code paths for every possible integer size.

For example, in this example, if you wanted to look up the region mapping of the slice [1, 0] you could just use std.mem.eq instead of reading every region child address to a fixed size u64