diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index f30b281..8f0e476 100644 --- a/.buildkite/pipeline.yml +++ b/.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/build.zig b/build.zig index 1580a9b..deeb967 100644 --- a/build.zig +++ b/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/src/Database.zig b/src/Database.zig index 4bfe18d..c7052a1 100644 --- a/src/Database.zig +++ b/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,6 +542,121 @@ pub fn initFromAtdf(allocator: Allocator, doc: *xml.Doc) !Database { }; errdefer db.deinit(); + 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(); + + var value_group_it: ?*xml.Node = xml.findNode(module_nodes, "value-group"); + while (value_group_it != null) : (value_group_it = xml.findNode(value_group_it.?.next, "value-group")) { + const value_group_nodes: *xml.Node = value_group_it.?.children orelse continue; + const value_group_name = if (xml.getAttribute(value_group_it, "name")) |name| + try db.arena.allocator().dupe(u8, name) + else + continue; + + const first_enum_idx = @intCast(EnumIndex, db.enumerations.items.len); + var value_it: ?*xml.Node = xml.findNode(value_group_nodes, "value"); + while (value_it != null) : (value_it = xml.findNode(value_it.?.next, "value")) { + try db.enumerations.append(db.gpa, .{ + .value = if (xml.getAttribute(value_it, "value")) |value_str| + try std.fmt.parseInt(usize, value_str, 0) + else + continue, + .description = if (xml.getAttribute(value_it, "caption")) |caption| + try db.arena.allocator().dupe(u8, caption) + else + null, + }); + } + + std.sort.sort(Enumeration, db.enumerations.items[first_enum_idx..], {}, Enumeration.lessThan); + try value_groups.put(value_group_name, .{ + .begin = first_enum_idx, + .end = @intCast(EnumIndex, db.enumerations.items.len), + }); + } + + // 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 (register_groups.contains(group_name)) { + std.log.warn("register name collision: {s}", .{group_name}); + continue; + } + + 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.?, null, register_it.?.children != null); + + const register_idx = @intCast(RegisterIndex, db.registers.items.len); + try db.registers.append(db.gpa, register); + + if (register.size) |size| + try db.register_properties.register.size.put(db.gpa, register_idx, size); + + const register_nodes: *xml.Node = register_it.?.children orelse continue; + const field_begin_idx = @intCast(FieldIndex, db.fields.items.len); + var bitfield_it: ?*xml.Node = xml.findNode(register_nodes, "bitfield"); + while (bitfield_it != null) : (bitfield_it = xml.findNode(bitfield_it.?.next, "bitfield")) { + try db.fields.append(db.gpa, atdf.parseField(&db.arena, bitfield_it.?) catch |err| switch (err) { + error.InvalidMask => continue, + else => return err, + }); + } + + // we expect fields to be sorted by offset + std.sort.sort(Field, db.fields.items[field_begin_idx..], {}, Field.lessThan); + + // go back through bitfields and get the enumerations + bitfield_it = xml.findNode(register_nodes, "bitfield"); + while (bitfield_it != null) : (bitfield_it = xml.findNode(bitfield_it.?.next, "bitfield")) { + if (xml.getAttribute(bitfield_it, "values")) |value_group_name| { + const field_name = xml.getAttribute(bitfield_it, "name") orelse continue; + const field_idx = for (db.fields.items[field_begin_idx..]) |field, offset| { + if (std.mem.eql(u8, field_name, field.name)) + break field_begin_idx + @intCast(FieldIndex, offset); + } else continue; + + if (value_groups.get(value_group_name)) |enum_range| + try db.enumerations_in_fields.put(db.gpa, field_idx, enum_range); + } + } + + try db.fields_in_registers.put(db.gpa, register_idx, .{ + .begin = field_begin_idx, + .end = @intCast(FieldIndex, db.fields.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")) { @@ -591,22 +709,34 @@ pub fn initFromAtdf(allocator: Allocator, doc: *xml.Doc) !Database { 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", .{ + 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; - } + }; - try peripheral_instances.put(try db.arena.allocator().dupe(u8, name_in_module), {}); + 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")) { @@ -618,7 +748,7 @@ pub fn initFromAtdf(allocator: Allocator, doc: *xml.Doc) !Database { .description = if (xml.getAttribute(interrupt_it, "caption")) |caption| try db.arena.allocator().dupe(u8, caption) else - return error.NoCaption, + null, .value = if (xml.getAttribute(interrupt_it, "index")) |index_str| try std.fmt.parseInt(usize, index_str, 0) else @@ -635,109 +765,6 @@ pub fn initFromAtdf(allocator: Allocator, doc: *xml.Doc) !Database { } } - if (xml.findNode(tools_node.children orelse return error.NoChildren, "modules")) |modules_node| { - 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; - - var value_groups = std.StringHashMap(IndexRange(u32)).init(allocator); - defer value_groups.deinit(); - - var value_group_it: ?*xml.Node = xml.findNode(module_nodes, "value-group"); - while (value_group_it != null) : (value_group_it = xml.findNode(value_group_it.?.next, "value-group")) { - const value_group_nodes: *xml.Node = value_group_it.?.children orelse continue; - const value_group_name = if (xml.getAttribute(value_group_it, "name")) |name| - try db.arena.allocator().dupe(u8, name) - else - continue; - - const first_enum_idx = @intCast(EnumIndex, db.enumerations.items.len); - var value_it: ?*xml.Node = xml.findNode(value_group_nodes, "value"); - while (value_it != null) : (value_it = xml.findNode(value_it.?.next, "value")) { - try db.enumerations.append(db.gpa, .{ - .value = if (xml.getAttribute(value_it, "value")) |value_str| - try std.fmt.parseInt(usize, value_str, 0) - else - continue, - .description = if (xml.getAttribute(value_it, "caption")) |caption| - try db.arena.allocator().dupe(u8, caption) - else - null, - }); - } - - std.sort.sort(Enumeration, db.enumerations.items[first_enum_idx..], {}, Enumeration.lessThan); - try value_groups.put(value_group_name, .{ - .begin = first_enum_idx, - .end = @intCast(EnumIndex, db.enumerations.items.len), - }); - } - - 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)) - 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_idx = @intCast(RegisterIndex, db.registers.items.len); - try db.registers.append(db.gpa, register); - - if (register.size) |size| - try db.register_properties.register.size.put(db.gpa, register_idx, size); - - const register_nodes: *xml.Node = register_it.?.children orelse continue; - const field_begin_idx = @intCast(FieldIndex, db.fields.items.len); - var bitfield_it: ?*xml.Node = xml.findNode(register_nodes, "bitfield"); - while (bitfield_it != null) : (bitfield_it = xml.findNode(bitfield_it.?.next, "bitfield")) { - try db.fields.append(db.gpa, atdf.parseField(&db.arena, bitfield_it.?) catch |err| switch (err) { - error.InvalidMask => continue, - else => return err, - }); - } - - // we expect fields to be sorted by offset - std.sort.sort(Field, db.fields.items[field_begin_idx..], {}, Field.lessThan); - - // go back through bitfields and get the enumerations - bitfield_it = xml.findNode(register_nodes, "bitfield"); - while (bitfield_it != null) : (bitfield_it = xml.findNode(bitfield_it.?.next, "bitfield")) { - if (xml.getAttribute(bitfield_it, "values")) |value_group_name| { - const field_name = xml.getAttribute(bitfield_it, "name") orelse continue; - const field_idx = for (db.fields.items[field_begin_idx..]) |field, offset| { - if (std.mem.eql(u8, field_name, field.name)) - break field_begin_idx + @intCast(FieldIndex, offset); - } else continue; - - if (value_groups.get(value_group_name)) |enum_range| - try db.enumerations_in_fields.put(db.gpa, field_idx, enum_range); - } - } - - try db.fields_in_registers.put(db.gpa, register_idx, .{ - .begin = field_begin_idx, - .end = @intCast(FieldIndex, db.fields.items.len), - }); - } - - try db.registers_in_peripherals.put(db.gpa, peripheral_idx, .{ - .begin = reg_begin_idx, - .end = @intCast(RegisterIndex, db.registers.items.len), - }); - } - } - } - // 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/src/main.zig b/src/main.zig index 1cec546..2025187 100644 --- a/src/main.zig +++ b/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/src/ndjson.zig b/src/ndjson.zig new file mode 100644 index 0000000..93d4075 --- /dev/null +++ b/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/src/xml.zig b/src/xml.zig index 2b86c7d..d60daa8 100644 --- a/src/xml.zig +++ b/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;