| ... | @@ -159,7 +159,147 @@ fn writeBinaryElfSection(elf_file: File, out_file: File, section: *BinaryElfSect | ... | @@ -159,7 +159,147 @@ fn writeBinaryElfSection(elf_file: File, out_file: File, section: *BinaryElfSect |
| 159 | }); | 159 | }); |
| 160 | } | 160 | } |
| 161 | | 161 | |
| 162 | fn emitRaw(allocator: *Allocator, elf_path: []const u8, raw_path: []const u8) !void { | 162 | const HexWriter = struct { |
| | 163 | prev_addr: ?u32 = null, |
| | 164 | out_file: File, |
| | 165 | |
| | 166 | /// Max data bytes per line of output |
| | 167 | const MAX_PAYLOAD_LEN: u8 = 16; |
| | 168 | |
| | 169 | fn addressParts(address: u16) [2]u8 { |
| | 170 | const msb = @truncate(u8, address >> 8); |
| | 171 | const lsb = @truncate(u8, address); |
| | 172 | return [2]u8{ msb, lsb }; |
| | 173 | } |
| | 174 | |
| | 175 | const Record = struct { |
| | 176 | const Type = enum(u8) { |
| | 177 | Data = 0, |
| | 178 | EOF = 1, |
| | 179 | ExtendedSegmentAddress = 2, |
| | 180 | ExtendedLinearAddress = 4, |
| | 181 | }; |
| | 182 | |
| | 183 | address: u16, |
| | 184 | payload: union(Type) { |
| | 185 | Data: []const u8, |
| | 186 | EOF: void, |
| | 187 | ExtendedSegmentAddress: [2]u8, |
| | 188 | ExtendedLinearAddress: [2]u8, |
| | 189 | }, |
| | 190 | |
| | 191 | fn EOF() Record { |
| | 192 | return Record{ |
| | 193 | .address = 0, |
| | 194 | .payload = .EOF, |
| | 195 | }; |
| | 196 | } |
| | 197 | |
| | 198 | fn Data(address: u32, data: []const u8) Record { |
| | 199 | return Record{ |
| | 200 | .address = @intCast(u16, address % 0x10000), |
| | 201 | .payload = .{ .Data = data }, |
| | 202 | }; |
| | 203 | } |
| | 204 | |
| | 205 | fn Address(address: u32) Record { |
| | 206 | std.debug.assert(address > 0xFFFF); |
| | 207 | const segment = @intCast(u16, address / 0x10000); |
| | 208 | if (address > 0xFFFFF) { |
| | 209 | return Record{ |
| | 210 | .address = 0, |
| | 211 | .payload = .{ .ExtendedLinearAddress = addressParts(segment) }, |
| | 212 | }; |
| | 213 | } else { |
| | 214 | return Record{ |
| | 215 | .address = 0, |
| | 216 | .payload = .{ .ExtendedSegmentAddress = addressParts(segment << 12) }, |
| | 217 | }; |
| | 218 | } |
| | 219 | } |
| | 220 | |
| | 221 | fn getPayloadBytes(self: Record) []const u8 { |
| | 222 | return switch (self.payload) { |
| | 223 | .Data => |d| d, |
| | 224 | .EOF => @as([]const u8, &.{}), |
| | 225 | .ExtendedSegmentAddress, .ExtendedLinearAddress => |*seg| seg, |
| | 226 | }; |
| | 227 | } |
| | 228 | |
| | 229 | fn checksum(self: Record) u8 { |
| | 230 | const payload_bytes = self.getPayloadBytes(); |
| | 231 | |
| | 232 | var sum: u8 = @intCast(u8, payload_bytes.len); |
| | 233 | const parts = addressParts(self.address); |
| | 234 | sum +%= parts[0]; |
| | 235 | sum +%= parts[1]; |
| | 236 | sum +%= @enumToInt(self.payload); |
| | 237 | for (payload_bytes) |byte| { |
| | 238 | sum +%= byte; |
| | 239 | } |
| | 240 | return (sum ^ 0xFF) +% 1; |
| | 241 | } |
| | 242 | |
| | 243 | fn write(self: Record, file: File) File.WriteError!void { |
| | 244 | const linesep = "\r\n"; |
| | 245 | // colon, (length, address, type, payload, checksum) as hex, CRLF |
| | 246 | const BUFSIZE = 1 + (1 + 2 + 1 + MAX_PAYLOAD_LEN + 1) * 2 + linesep.len; |
| | 247 | var outbuf: [BUFSIZE]u8 = undefined; |
| | 248 | const payload_bytes = self.getPayloadBytes(); |
| | 249 | std.debug.assert(payload_bytes.len <= MAX_PAYLOAD_LEN); |
| | 250 | |
| | 251 | const line = try std.fmt.bufPrint(&outbuf, ":{0X:0>2}{1X:0>4}{2X:0>2}{3s}{4X:0>2}" ++ linesep, .{ |
| | 252 | @intCast(u8, payload_bytes.len), |
| | 253 | self.address, |
| | 254 | @enumToInt(self.payload), |
| | 255 | std.fmt.fmtSliceHexUpper(payload_bytes), |
| | 256 | self.checksum(), |
| | 257 | }); |
| | 258 | try file.writeAll(line); |
| | 259 | } |
| | 260 | }; |
| | 261 | |
| | 262 | pub fn writeSegment(self: *HexWriter, segment: *const BinaryElfSegment, elf_file: File) !void { |
| | 263 | var buf: [MAX_PAYLOAD_LEN]u8 = undefined; |
| | 264 | var bytes_read: usize = 0; |
| | 265 | while (bytes_read < segment.fileSize) { |
| | 266 | const row_address = @intCast(u32, segment.physicalAddress + bytes_read); |
| | 267 | |
| | 268 | const remaining = segment.fileSize - bytes_read; |
| | 269 | const to_read = @minimum(remaining, MAX_PAYLOAD_LEN); |
| | 270 | const did_read = try elf_file.preadAll(buf[0..to_read], segment.elfOffset + bytes_read); |
| | 271 | if (did_read < to_read) return error.UnexpectedEOF; |
| | 272 | |
| | 273 | try self.writeDataRow(row_address, buf[0..did_read]); |
| | 274 | |
| | 275 | bytes_read += did_read; |
| | 276 | } |
| | 277 | } |
| | 278 | |
| | 279 | fn writeDataRow(self: *HexWriter, address: u32, data: []const u8) File.WriteError!void { |
| | 280 | const record = Record.Data(address, data); |
| | 281 | if (address > 0xFFFF and (self.prev_addr == null or record.address != self.prev_addr.?)) { |
| | 282 | try Record.Address(address).write(self.out_file); |
| | 283 | } |
| | 284 | try record.write(self.out_file); |
| | 285 | self.prev_addr = @intCast(u32, record.address + data.len); |
| | 286 | } |
| | 287 | |
| | 288 | fn writeEOF(self: HexWriter) File.WriteError!void { |
| | 289 | try Record.EOF().write(self.out_file); |
| | 290 | } |
| | 291 | }; |
| | 292 | |
| | 293 | fn containsValidAddressRange(segments: []*BinaryElfSegment) bool { |
| | 294 | const max_address = std.math.maxInt(u32); |
| | 295 | for (segments) |segment| { |
| | 296 | if (segment.fileSize > max_address or |
| | 297 | segment.physicalAddress > max_address - segment.fileSize) return false; |
| | 298 | } |
| | 299 | return true; |
| | 300 | } |
| | 301 | |
| | 302 | fn emitRaw(allocator: *Allocator, elf_path: []const u8, raw_path: []const u8, format: RawFormat) !void { |
| 163 | var elf_file = try fs.cwd().openFile(elf_path, .{}); | 303 | var elf_file = try fs.cwd().openFile(elf_path, .{}); |
| 164 | defer elf_file.close(); | 304 | defer elf_file.close(); |
| 165 | | 305 | |
| ... | @@ -169,8 +309,26 @@ fn emitRaw(allocator: *Allocator, elf_path: []const u8, raw_path: []const u8) !v | ... | @@ -169,8 +309,26 @@ fn emitRaw(allocator: *Allocator, elf_path: []const u8, raw_path: []const u8) !v |
| 169 | var binary_elf_output = try BinaryElfOutput.parse(allocator, elf_file); | 309 | var binary_elf_output = try BinaryElfOutput.parse(allocator, elf_file); |
| 170 | defer binary_elf_output.deinit(); | 310 | defer binary_elf_output.deinit(); |
| 171 | | 311 | |
| 172 | for (binary_elf_output.sections.items) |section| { | 312 | switch (format) { |
| 173 | try writeBinaryElfSection(elf_file, out_file, section); | 313 | .bin => { |
| | 314 | for (binary_elf_output.sections.items) |section| { |
| | 315 | try writeBinaryElfSection(elf_file, out_file, section); |
| | 316 | } |
| | 317 | }, |
| | 318 | .hex => { |
| | 319 | if (binary_elf_output.segments.items.len == 0) return; |
| | 320 | if (!containsValidAddressRange(binary_elf_output.segments.items)) { |
| | 321 | return error.InvalidHexfileAddressRange; |
| | 322 | } |
| | 323 | |
| | 324 | var hex_writer = HexWriter{ .out_file = out_file }; |
| | 325 | for (binary_elf_output.sections.items) |section| { |
| | 326 | if (section.segment) |segment| { |
| | 327 | try hex_writer.writeSegment(segment, elf_file); |
| | 328 | } |
| | 329 | } |
| | 330 | try hex_writer.writeEOF(); |
| | 331 | }, |
| 174 | } | 332 | } |
| 175 | } | 333 | } |
| 176 | | 334 | |
| ... | @@ -178,13 +336,26 @@ const InstallRawStep = @This(); | ... | @@ -178,13 +336,26 @@ const InstallRawStep = @This(); |
| 178 | | 336 | |
| 179 | pub const base_id = .install_raw; | 337 | pub const base_id = .install_raw; |
| 180 | | 338 | |
| | 339 | pub const RawFormat = enum { |
| | 340 | bin, |
| | 341 | hex, |
| | 342 | }; |
| | 343 | |
| 181 | step: Step, | 344 | step: Step, |
| 182 | builder: *Builder, | 345 | builder: *Builder, |
| 183 | artifact: *LibExeObjStep, | 346 | artifact: *LibExeObjStep, |
| 184 | dest_dir: InstallDir, | 347 | dest_dir: InstallDir, |
| 185 | dest_filename: []const u8, | 348 | dest_filename: []const u8, |
| | 349 | format: RawFormat, |
| | 350 | |
| | 351 | fn detectFormat(filename: []const u8) RawFormat { |
| | 352 | if (std.mem.endsWith(u8, filename, ".hex") or std.mem.endsWith(u8, filename, ".ihex")) { |
| | 353 | return .hex; |
| | 354 | } |
| | 355 | return .bin; |
| | 356 | } |
| 186 | | 357 | |
| 187 | pub fn create(builder: *Builder, artifact: *LibExeObjStep, dest_filename: []const u8) *InstallRawStep { | 358 | pub fn create(builder: *Builder, artifact: *LibExeObjStep, dest_filename: []const u8, format: ?RawFormat) *InstallRawStep { |
| 188 | const self = builder.allocator.create(InstallRawStep) catch unreachable; | 359 | const self = builder.allocator.create(InstallRawStep) catch unreachable; |
| 189 | self.* = InstallRawStep{ | 360 | self.* = InstallRawStep{ |
| 190 | .step = Step.init(.install_raw, builder.fmt("install raw binary {s}", .{artifact.step.name}), builder.allocator, make), | 361 | .step = Step.init(.install_raw, builder.fmt("install raw binary {s}", .{artifact.step.name}), builder.allocator, make), |
| ... | @@ -197,6 +368,7 @@ pub fn create(builder: *Builder, artifact: *LibExeObjStep, dest_filename: []cons | ... | @@ -197,6 +368,7 @@ pub fn create(builder: *Builder, artifact: *LibExeObjStep, dest_filename: []cons |
| 197 | .lib => unreachable, | 368 | .lib => unreachable, |
| 198 | }, | 369 | }, |
| 199 | .dest_filename = dest_filename, | 370 | .dest_filename = dest_filename, |
| | 371 | .format = format orelse detectFormat(dest_filename), |
| 200 | }; | 372 | }; |
| 201 | self.step.dependOn(&artifact.step); | 373 | self.step.dependOn(&artifact.step); |
| 202 | | 374 | |
| ... | @@ -217,9 +389,48 @@ fn make(step: *Step) !void { | ... | @@ -217,9 +389,48 @@ fn make(step: *Step) !void { |
| 217 | const full_dest_path = builder.getInstallPath(self.dest_dir, self.dest_filename); | 389 | const full_dest_path = builder.getInstallPath(self.dest_dir, self.dest_filename); |
| 218 | | 390 | |
| 219 | fs.cwd().makePath(builder.getInstallPath(self.dest_dir, "")) catch unreachable; | 391 | fs.cwd().makePath(builder.getInstallPath(self.dest_dir, "")) catch unreachable; |
| 220 | try emitRaw(builder.allocator, full_src_path, full_dest_path); | 392 | try emitRaw(builder.allocator, full_src_path, full_dest_path, self.format); |
| 221 | } | 393 | } |
| 222 | | 394 | |
| 223 | test { | 395 | test { |
| 224 | std.testing.refAllDecls(InstallRawStep); | 396 | std.testing.refAllDecls(InstallRawStep); |
| 225 | } | 397 | } |
| | 398 | |
| | 399 | test "Detect format from filename" { |
| | 400 | try std.testing.expectEqual(RawFormat.hex, detectFormat("foo.hex")); |
| | 401 | try std.testing.expectEqual(RawFormat.hex, detectFormat("foo.ihex")); |
| | 402 | try std.testing.expectEqual(RawFormat.bin, detectFormat("foo.bin")); |
| | 403 | try std.testing.expectEqual(RawFormat.bin, detectFormat("foo.bar")); |
| | 404 | try std.testing.expectEqual(RawFormat.bin, detectFormat("a")); |
| | 405 | } |
| | 406 | |
| | 407 | test "containsValidAddressRange" { |
| | 408 | var segment = BinaryElfSegment{ |
| | 409 | .physicalAddress = 0, |
| | 410 | .virtualAddress = 0, |
| | 411 | .elfOffset = 0, |
| | 412 | .binaryOffset = 0, |
| | 413 | .fileSize = 0, |
| | 414 | .firstSection = null, |
| | 415 | }; |
| | 416 | var buf: [1]*BinaryElfSegment = .{&segment}; |
| | 417 | |
| | 418 | // segment too big |
| | 419 | segment.fileSize = std.math.maxInt(u32) + 1; |
| | 420 | try std.testing.expect(!containsValidAddressRange(&buf)); |
| | 421 | |
| | 422 | // start address too big |
| | 423 | segment.physicalAddress = std.math.maxInt(u32) + 1; |
| | 424 | segment.fileSize = 2; |
| | 425 | try std.testing.expect(!containsValidAddressRange(&buf)); |
| | 426 | |
| | 427 | // max address too big |
| | 428 | segment.physicalAddress = std.math.maxInt(u32) - 1; |
| | 429 | segment.fileSize = 2; |
| | 430 | try std.testing.expect(!containsValidAddressRange(&buf)); |
| | 431 | |
| | 432 | // is ok |
| | 433 | segment.physicalAddress = std.math.maxInt(u32) - 1; |
| | 434 | segment.fileSize = 1; |
| | 435 | try std.testing.expect(containsValidAddressRange(&buf)); |
| | 436 | } |