| ... | ... | @@ -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 | 303 | var elf_file = try fs.cwd().openFile(elf_path, .{}); |
| 164 | 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 | 309 | var binary_elf_output = try BinaryElfOutput.parse(allocator, elf_file); |
| 170 | 310 | defer binary_elf_output.deinit(); |
| 171 | 311 | |
| 172 | | for (binary_elf_output.sections.items) |section| { |
| 173 | | try writeBinaryElfSection(elf_file, out_file, section); |
| 312 | switch (format) { |
| 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 | 336 | |
| 179 | 337 | pub const base_id = .install_raw; |
| 180 | 338 | |
| 339 | pub const RawFormat = enum { |
| 340 | bin, |
| 341 | hex, |
| 342 | }; |
| 343 | |
| 181 | 344 | step: Step, |
| 182 | 345 | builder: *Builder, |
| 183 | 346 | artifact: *LibExeObjStep, |
| 184 | 347 | dest_dir: InstallDir, |
| 185 | 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 | 359 | const self = builder.allocator.create(InstallRawStep) catch unreachable; |
| 189 | 360 | self.* = InstallRawStep{ |
| 190 | 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 | 368 | .lib => unreachable, |
| 198 | 369 | }, |
| 199 | 370 | .dest_filename = dest_filename, |
| 371 | .format = format orelse detectFormat(dest_filename), |
| 200 | 372 | }; |
| 201 | 373 | self.step.dependOn(&artifact.step); |
| 202 | 374 | |
| ... | ... | @@ -217,9 +389,48 @@ fn make(step: *Step) !void { |
| 217 | 389 | const full_dest_path = builder.getInstallPath(self.dest_dir, self.dest_filename); |
| 218 | 390 | |
| 219 | 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 | 395 | test { |
| 224 | 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 | } |