diff --git a/tools/regz/.buildkite/pipeline.yml b/tools/regz/.buildkite/pipeline.yml index f30b281..8f0e476 100644 --- a/tools/regz/.buildkite/pipeline.yml +++ b/tools/regz/.buildkite/pipeline.yml @@ -14,10 +14,10 @@ steps: - "aarch64-linux-gnu" - "aarch64-linux-musl" - "aarch64-macos" - - "i386-linux-gnu" - - "i386-linux-musl" + - "x86-linux-gnu" + - "x86-linux-musl" # TODO: when _tls_index is fixed - #- "i386-windows" + #- "x86-windows" - "x86_64-linux-gnu" - "x86_64-linux-musl" - "x86_64-macos" diff --git a/tools/regz/build.zig b/tools/regz/build.zig index 1580a9b..deeb967 100644 --- a/tools/regz/build.zig +++ b/tools/regz/build.zig @@ -108,6 +108,19 @@ pub fn build(b: *std.build.Builder) !void { const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); + const ndjson = b.addExecutable("ndjson", "src/ndjson.zig"); + ndjson.addPackagePath("xml", "src/xml.zig"); + regz.xml.link(ndjson); + + const ndjson_run = ndjson.run(); + ndjson_run.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + ndjson_run.addArgs(args); + } + + const ndjson_step = b.step("ndjson", "Run ndjson program"); + ndjson_step.dependOn(&ndjson_run.step); + const test_chip_file = regz.addGeneratedChipFile("tests/svd/cmsis-example.svd"); const tests = b.addTest("tests/main.zig"); diff --git a/tools/regz/src/Database.zig b/tools/regz/src/Database.zig index 4bfe18d..c7052a1 100644 --- a/tools/regz/src/Database.zig +++ b/tools/regz/src/Database.zig @@ -528,8 +528,11 @@ pub fn initFromAtdf(allocator: Allocator, doc: *xml.Doc) !Database { const root_element: *xml.Node = xml.docGetRootElement(doc) orelse return error.NoRoot; const tools_node = xml.findNode(root_element, "avr-tools-device-file") orelse return error.NoToolsNode; - var peripheral_instances = std.StringHashMap(void).init(allocator); - defer peripheral_instances.deinit(); + var register_groups = std.StringHashMap(struct { + reg_range: IndexRange(RegisterIndex), + description: ?[]const u8, + }).init(allocator); + defer register_groups.deinit(); var db = Database{ .gpa = allocator, @@ -539,107 +542,13 @@ pub fn initFromAtdf(allocator: Allocator, doc: *xml.Doc) !Database { }; errdefer db.deinit(); - if (xml.findNode(tools_node.children orelse return error.NoChildren, "devices")) |devices_node| { - var device_it: ?*xml.Node = xml.findNode(devices_node.children, "device"); - while (device_it != null) : (device_it = xml.findNode(device_it.?.next, "device")) { - if (db.device != null) { - std.log.err("multiple devices defined in this file. TODO: give user list of devices to choose from", .{}); - return error.Explained; - } - - // this name lowercased should line up with a zig target - const name: ?[]const u8 = if (xml.getAttribute(device_it, "name")) |n| - try db.arena.allocator().dupe(u8, n) - else - null; - const arch: ?[]const u8 = if (xml.getAttribute(device_it, "architecture")) |a| - try db.arena.allocator().dupe(u8, a) - else - null; - const family: ?[]const u8 = if (xml.getAttribute(device_it, "family")) |f| - try db.arena.allocator().dupe(u8, f) - else - null; - - db.device = .{ - .vendor = "Atmel", - .series = family, - .name = name, - .address_unit_bits = 16, - .width = 8, - .register_properties = .{}, - }; - - db.cpu = svd.Cpu{ - .name = arch, - .revision = "0.0.1", - .endian = .little, - .nvic_prio_bits = 0, - .vendor_systick_config = false, - .device_num_interrupts = null, - .vtor_present = false, - .mpu_present = false, - }; - - const device_nodes: *xml.Node = device_it.?.children orelse continue; - if (xml.findNode(device_nodes, "peripherals")) |peripherals_node| { - var module_it: ?*xml.Node = xml.findNode(peripherals_node.children.?, "module"); - while (module_it != null) : (module_it = xml.findNode(module_it.?.next, "module")) { - const module_nodes = module_it.?.children orelse continue; - var instance_it: ?*xml.Node = xml.findNode(module_nodes, "instance"); - while (instance_it != null) : (instance_it = xml.findNode(instance_it.?.next, "instance")) { - const instance_nodes = instance_it.?.children orelse continue; - const instance_name = xml.getAttribute(instance_it, "name") orelse return error.NoInstanceName; - if (xml.findNode(instance_nodes, "register-group")) |register_group| { - const group_name = xml.getAttribute(register_group, "name") orelse return error.NoRegisterGroupName; - const name_in_module = xml.getAttribute(register_group, "name-in-module") orelse return error.NoNameInModule; - - if (!std.mem.eql(u8, instance_name, group_name) or !std.mem.eql(u8, group_name, name_in_module)) { - std.log.warn("mismatching names for name-in-module: {s}, ignoring, if you see this please cut a ticket: https://github.com/ZigEmbeddedGroup/regz", .{ - name_in_module, - }); - continue; - } - - try peripheral_instances.put(try db.arena.allocator().dupe(u8, name_in_module), {}); - } - } - } - } - - if (xml.findNode(device_nodes, "interrupts")) |interrupts_node| { - var interrupt_it: ?*xml.Node = xml.findNode(interrupts_node.children.?, "interrupt"); - while (interrupt_it != null) : (interrupt_it = xml.findNode(interrupt_it.?.next, "interrupt")) { - const interrupt = svd.Interrupt{ - .name = if (xml.getAttribute(interrupt_it, "name")) |interrupt_name| - try db.arena.allocator().dupe(u8, interrupt_name) - else - return error.NoName, - .description = if (xml.getAttribute(interrupt_it, "caption")) |caption| - try db.arena.allocator().dupe(u8, caption) - else - return error.NoCaption, - .value = if (xml.getAttribute(interrupt_it, "index")) |index_str| - try std.fmt.parseInt(usize, index_str, 0) - else - return error.NoIndex, - }; - - // if the interrupt doesn't exist then do a sorted insert - if (std.sort.binarySearch(svd.Interrupt, interrupt, db.interrupts.items, {}, svd.Interrupt.compare) == null) { - try db.interrupts.append(db.gpa, interrupt); - std.sort.sort(svd.Interrupt, db.interrupts.items, {}, svd.Interrupt.lessThan); - } - } - } - } - } - if (xml.findNode(tools_node.children orelse return error.NoChildren, "modules")) |modules_node| { + std.log.debug("looking at modules", .{}); var module_it: ?*xml.Node = xml.findNode(modules_node.children.?, "module"); while (module_it != null) : (module_it = xml.findNode(module_it.?.next, "module")) { const module_nodes: *xml.Node = module_it.?.children orelse continue; + // value groups are enums var value_groups = std.StringHashMap(IndexRange(u32)).init(allocator); defer value_groups.deinit(); @@ -673,22 +582,25 @@ pub fn initFromAtdf(allocator: Allocator, doc: *xml.Doc) !Database { }); } + // register groups in this part of the ATDF are templates for + // peripherals. In `devices` peripherals are instantiated using a + // register group. + // + // TODO: determine if peripheral instantiation can contain multiple + // register groups var register_group_it: ?*xml.Node = xml.findNode(module_nodes, "register-group"); while (register_group_it != null) : (register_group_it = xml.findNode(register_group_it.?.next, "register-group")) { const register_group_nodes: *xml.Node = register_group_it.?.children orelse continue; const group_name = xml.getAttribute(register_group_it, "name") orelse continue; - if (!peripheral_instances.contains(group_name)) + if (register_groups.contains(group_name)) { + std.log.warn("register name collision: {s}", .{group_name}); continue; - - const register_group_offset = try xml.parseIntForKey(usize, db.gpa, register_group_nodes, "offset"); - - const peripheral_idx = @intCast(PeripheralIndex, db.peripherals.items.len); - try db.peripherals.append(db.gpa, try atdf.parsePeripheral(&db.arena, register_group_it.?)); + } const reg_begin_idx = @intCast(RegisterIndex, db.registers.items.len); var register_it: ?*xml.Node = xml.findNode(register_group_nodes, "register"); while (register_it != null) : (register_it = xml.findNode(register_it.?.next, "register")) { - const register = try atdf.parseRegister(&db.arena, register_it.?, register_group_offset, register_it.?.children != null); + const register = try atdf.parseRegister(&db.arena, register_it.?, null, register_it.?.children != null); const register_idx = @intCast(RegisterIndex, db.registers.items.len); try db.registers.append(db.gpa, register); @@ -730,14 +642,129 @@ pub fn initFromAtdf(allocator: Allocator, doc: *xml.Doc) !Database { }); } - try db.registers_in_peripherals.put(db.gpa, peripheral_idx, .{ - .begin = reg_begin_idx, - .end = @intCast(RegisterIndex, db.registers.items.len), + std.log.debug("found register group: {s}", .{group_name}); + try register_groups.put(try db.arena.allocator().dupe(u8, group_name), .{ + .description = if (xml.getAttribute(register_group_it, "caption")) |caption| + try db.arena.allocator().dupe(u8, caption) + else + null, + .reg_range = .{ + .begin = reg_begin_idx, + .end = @intCast(RegisterIndex, db.registers.items.len), + }, }); } } } + if (xml.findNode(tools_node.children orelse return error.NoChildren, "devices")) |devices_node| { + var device_it: ?*xml.Node = xml.findNode(devices_node.children, "device"); + while (device_it != null) : (device_it = xml.findNode(device_it.?.next, "device")) { + if (db.device != null) { + std.log.err("multiple devices defined in this file. TODO: give user list of devices to choose from", .{}); + return error.Explained; + } + + // this name lowercased should line up with a zig target + const name: ?[]const u8 = if (xml.getAttribute(device_it, "name")) |n| + try db.arena.allocator().dupe(u8, n) + else + null; + const arch: ?[]const u8 = if (xml.getAttribute(device_it, "architecture")) |a| + try db.arena.allocator().dupe(u8, a) + else + null; + const family: ?[]const u8 = if (xml.getAttribute(device_it, "family")) |f| + try db.arena.allocator().dupe(u8, f) + else + null; + + db.device = .{ + .vendor = "Atmel", + .series = family, + .name = name, + .address_unit_bits = 16, + .width = 8, + .register_properties = .{}, + }; + + db.cpu = svd.Cpu{ + .name = arch, + .revision = "0.0.1", + .endian = .little, + .nvic_prio_bits = 0, + .vendor_systick_config = false, + .device_num_interrupts = null, + .vtor_present = false, + .mpu_present = false, + }; + + const device_nodes: *xml.Node = device_it.?.children orelse continue; + if (xml.findNode(device_nodes, "peripherals")) |peripherals_node| { + var module_it: ?*xml.Node = xml.findNode(peripherals_node.children.?, "module"); + while (module_it != null) : (module_it = xml.findNode(module_it.?.next, "module")) { + const module_nodes = module_it.?.children orelse continue; + var instance_it: ?*xml.Node = xml.findNode(module_nodes, "instance"); + while (instance_it != null) : (instance_it = xml.findNode(instance_it.?.next, "instance")) { + const instance_nodes = instance_it.?.children orelse continue; + const instance_name = xml.getAttribute(instance_it, "name") orelse return error.NoInstanceName; + if (xml.findNode(instance_nodes, "register-group")) |register_group| { + const name_in_module = xml.getAttribute(register_group, "name-in-module") orelse return error.NoNameInModule; + const template_group = register_groups.get(name_in_module) orelse { + std.log.warn("failed to find register group '{s}' in for peripheral '{s}'", .{ + name_in_module, + instance_name, + }); + continue; + }; + + std.log.debug("creating peripheral instance: {s}", .{instance_name}); + const peripheral_idx = @intCast(PeripheralIndex, db.peripherals.items.len); + try db.peripherals.append(db.gpa, .{ + .name = try db.arena.allocator().dupe(u8, instance_name), + .version = null, + .description = template_group.description, + .base_addr = if (xml.getAttribute(register_group, "offset")) |reg_offset_str| + try std.fmt.parseInt(usize, reg_offset_str, 0) + else + return error.NoOffset, + }); + + try db.registers_in_peripherals.put(db.gpa, peripheral_idx, template_group.reg_range); + } + } + } + } + + // TODO: `module-instance` is used to relate an interrupt to a peripheral + if (xml.findNode(device_nodes, "interrupts")) |interrupts_node| { + var interrupt_it: ?*xml.Node = xml.findNode(interrupts_node.children.?, "interrupt"); + while (interrupt_it != null) : (interrupt_it = xml.findNode(interrupt_it.?.next, "interrupt")) { + const interrupt = svd.Interrupt{ + .name = if (xml.getAttribute(interrupt_it, "name")) |interrupt_name| + try db.arena.allocator().dupe(u8, interrupt_name) + else + return error.NoName, + .description = if (xml.getAttribute(interrupt_it, "caption")) |caption| + try db.arena.allocator().dupe(u8, caption) + else + null, + .value = if (xml.getAttribute(interrupt_it, "index")) |index_str| + try std.fmt.parseInt(usize, index_str, 0) + else + return error.NoIndex, + }; + + // if the interrupt doesn't exist then do a sorted insert + if (std.sort.binarySearch(svd.Interrupt, interrupt, db.interrupts.items, {}, svd.Interrupt.compare) == null) { + try db.interrupts.append(db.gpa, interrupt); + std.sort.sort(svd.Interrupt, db.interrupts.items, {}, svd.Interrupt.lessThan); + } + } + } + } + } + // there is also pinouts, however that's linked to IC package information, // not exactly sure if we're going to do anything with that @@ -1224,14 +1251,6 @@ fn genZigFields( if (std.mem.indexOf(u8, field.name, "%s") != null) return error.MissingDimension; - if (expected_bit > field.offset) { - std.log.err("found overlapping fields in register:", .{}); - for (fields) |f| { - std.log.err(" {s}: {}+{}", .{ f.name, f.offset, f.width }); - } - return error.Explained; - } - while (expected_bit < field.offset) : ({ expected_bit += 1; reserved_num += 1; diff --git a/tools/regz/src/main.zig b/tools/regz/src/main.zig index 1cec546..2025187 100644 --- a/tools/regz/src/main.zig +++ b/tools/regz/src/main.zig @@ -8,8 +8,6 @@ const ArenaAllocator = std.heap.ArenaAllocator; const Allocator = std.mem.Allocator; const assert = std.debug.assert; -pub const log_level: std.log.Level = .info; - const svd_schema = @embedFile("cmsis-svd.xsd"); const params = clap.parseParamsComptime( diff --git a/tools/regz/src/ndjson.zig b/tools/regz/src/ndjson.zig new file mode 100644 index 0000000..93d4075 --- /dev/null +++ b/tools/regz/src/ndjson.zig @@ -0,0 +1,142 @@ +//! This program takes nested xml objects and turns them into newline delimited JSON +const std = @import("std"); +const json = std.json; +const assert = std.debug.assert; + +const xml = @import("xml"); + +const ContextMap = std.StringHashMap(std.StringHashMapUnmanaged([]const u8)); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 10 }){}; + defer _ = gpa.deinit(); + + var args = std.process.args(); + _ = args.next(); + + const query = args.next() orelse return error.NoQuery; + const path = args.next() orelse return error.NoXmlPath; + if (args.next() != null) + return error.TooManyArgs; + + var components = std.ArrayList([]const u8).init(gpa.allocator()); + defer components.deinit(); + + { + var it = std.mem.split(u8, query, "."); + while (it.next()) |component| + try components.append(component); + } + + if (components.items.len == 0) + return error.NoComponents; + + const doc = xml.readFile(path.ptr, null, 0) orelse return error.ReadXmlFile; + const root_element: *xml.Node = xml.docGetRootElement(doc) orelse return error.NoRoot; + if (!std.mem.eql(u8, components.items[0], std.mem.span(root_element.name))) + return; + + var context = std.StringHashMap(std.StringHashMapUnmanaged([]const u8)).init(gpa.allocator()); + defer context.deinit(); + + const stdout = std.io.getStdOut().writer(); + try recursiveSearchAndPrint( + gpa.allocator(), + components.items, + context, + root_element, + stdout, + ); + + var children: ?*xml.Node = root_element.children; + while (children != null) : (children = children.?.next) { + if (1 != children.?.type) + continue; + } +} + +fn RecursiveSearchAndPrintError(comptime Writer: type) type { + return Writer.Error || error{OutOfMemory}; +} + +fn recursiveSearchAndPrint( + allocator: std.mem.Allocator, + components: []const []const u8, + context: ContextMap, + node: *xml.Node, + writer: anytype, +) RecursiveSearchAndPrintError(@TypeOf(writer))!void { + assert(components.len != 0); + + var attr_map = std.StringHashMapUnmanaged([]const u8){}; + defer attr_map.deinit(allocator); + + { + var attr_it: ?*xml.Attr = node.properties; + while (attr_it != null) : (attr_it = attr_it.?.next) + if (attr_it.?.name) |name| + if (@ptrCast(*xml.Node, attr_it.?.children).content) |content| + try attr_map.put(allocator, std.mem.span(name), std.mem.span(content)); + } + + var current_context = ContextMap.init(allocator); + defer current_context.deinit(); + + { + var it = context.iterator(); + while (it.next()) |entry| + try current_context.put(entry.key_ptr.*, entry.value_ptr.*); + } + + if (attr_map.count() > 0) + try current_context.put(components[0], attr_map); + + if (components.len == 1) { + // we're done, convert into json tree and write to writer. + var tree = json.ValueTree{ + .arena = std.heap.ArenaAllocator.init(allocator), + .root = json.Value{ .Object = json.ObjectMap.init(allocator) }, + }; + defer { + var it = tree.root.Object.iterator(); + while (it.next()) |entry| + entry.value_ptr.Object.deinit(); + + tree.root.Object.deinit(); + tree.deinit(); + } + + var it = current_context.iterator(); + while (it.next()) |entry| { + var obj = json.Value{ .Object = json.ObjectMap.init(allocator) }; + + var attr_it = entry.value_ptr.iterator(); + while (attr_it.next()) |attr_entry| { + try obj.Object.put(attr_entry.key_ptr.*, json.Value{ + .String = attr_entry.value_ptr.*, + }); + } + + try tree.root.Object.put(entry.key_ptr.*, obj); + } + + try tree.root.jsonStringify(.{}, writer); + try writer.writeByte('\n'); + } else { + // pass it down to the children + var child_it: ?*xml.Node = node.children; + while (child_it != null) : (child_it = child_it.?.next) { + if (1 != child_it.?.type) + continue; + + if (std.mem.eql(u8, components[1], std.mem.span(child_it.?.name))) + try recursiveSearchAndPrint( + allocator, + components[1..], + current_context, + child_it.?, + writer, + ); + } + } +} diff --git a/tools/regz/src/xml.zig b/tools/regz/src/xml.zig index 2b86c7d..d60daa8 100644 --- a/tools/regz/src/xml.zig +++ b/tools/regz/src/xml.zig @@ -10,6 +10,7 @@ const Allocator = std.mem.Allocator; pub const Node = c.xmlNode; pub const Doc = c.xmlDoc; +pub const Attr = c.xmlAttr; pub const readFile = c.xmlReadFile; pub const readIo = c.xmlReadIO; pub const cleanupParser = c.xmlCleanupParser;