Skip to content

Commit ed68b97

Browse files
committed
Hex File Parsing
1 parent e45941c commit ed68b97

File tree

13 files changed

+441
-15
lines changed

13 files changed

+441
-15
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,5 @@ fabric.properties
7777
.idea/caches/build_file_checksums.ser
7878
build/
7979

80-
.DS_Store
80+
.DS_Store
81+
.cache/

CMakeLists.txt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ if(BLISP_USE_SYSTEM_LIBRARIES)
4848
target_link_libraries(libblisp_static PUBLIC Libserialport::Libserialport)
4949
target_include_directories(libblisp_obj PUBLIC ${Libserialport_INCLUDE_DIRS})
5050
else()
51-
if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
52-
target_sources(libblisp_obj PRIVATE
51+
if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
52+
target_sources(libblisp_obj PRIVATE
5353
${CMAKE_SOURCE_DIR}/vendor/libserialport/serialport.c
5454
${CMAKE_SOURCE_DIR}/vendor/libserialport/timing.c)
5555

56-
target_include_directories(libblisp_obj PRIVATE ${CMAKE_SOURCE_DIR}/vendor/libserialport)
57-
endif()
56+
target_include_directories(libblisp_obj PRIVATE ${CMAKE_SOURCE_DIR}/vendor/libserialport)
57+
endif()
5858

5959
if(WIN32)
6060
target_link_libraries(libblisp PRIVATE Setupapi.lib)
@@ -92,7 +92,7 @@ else()
9292
endif()
9393

9494
include(GNUInstallDirs)
95-
install(TARGETS libblisp libblisp_static
95+
install(TARGETS libblisp libblisp_static
9696
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
9797
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
9898
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
@@ -105,5 +105,6 @@ endif()
105105

106106

107107
if(COMPILE_TESTS)
108-
add_subdirectory(tools/blisp/src/cmd/dfu/tests)
108+
add_subdirectory(tools/blisp/src/file_parsers/dfu/tests)
109+
add_subdirectory(tools/blisp/src/file_parsers/hex/tests)
109110
endif(COMPILE_TESTS)

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![GitHub release](https://img.shields.io/github/v/release/pine64/blisp?color=5791ac)](https://github.com/pine64/blisp/releases/tag/v0.0.4)
55

66
<img src="./img/Gradient-white-blue-03.png" align="left" width="60" > <br clear="left" />
7-
# BLISP
7+
# BLISP
88

99
Bouffalo Labs ISP (in-system-programming) tool & library: an open source tool to flash Bouffalo RISC-V MCUs.
1010

@@ -16,13 +16,13 @@ Bouffalo Labs ISP (in-system-programming) tool & library: an open source tool to
1616
- [x] `bl70x` - BL702 / BL704 / BL706
1717
<br>
1818

19-
## Supported Devices
19+
## Supported Devices
2020
| System | <img width="15" src="img/win32.png" /> Windows | <img width="15" src="https://cdn.simpleicons.org/Apple/5791ac" /> MacOS| <img width="17" src="https://cdn.simpleicons.org/Linux/5791ac" /> Linux| <img width="15" src="https://cdn.simpleicons.org/Freebsd/5791ac" /> FreeBSD |
2121
| :-----: | :------: | :------: | :------: | :------: |
2222
| Pinecil V2 |<img width="22" src="https://cdn.simpleicons.org/cachet/5791ac" />|<img width="22" src="https://cdn.simpleicons.org/cachet/5791ac" />| <img width="22" src="https://cdn.simpleicons.org/cachet/5791ac" />| <img width="22" src="https://cdn.simpleicons.org/cachet/5791ac" /> |
2323
| Pinecone |<img width="22" src="https://cdn.simpleicons.org/cachet/5791ac" />|<img width="22" src="https://cdn.simpleicons.org/cachet/5791ac" />|<img width="22" src="https://cdn.simpleicons.org/cachet/5791ac" />| <img width="22" src="https://cdn.simpleicons.org/cachet/5791ac" /> |
2424
<br>
25-
25+
2626
## How to update Pinecil V2
2727

2828
Download the newest release of [Blisp updater here](https://github.com/pine64/blisp/releases/).
@@ -94,6 +94,15 @@ Because this is done at the lowest level of serial communication, the
9494
displays aren't packet-aware or know about the chip's command set or such.
9595
This is really only useful for debugging systems-level issues withing
9696
the device or blisp itself.
97+
## Running unit tests
98+
99+
```shell
100+
mkdir build && cd build
101+
cmake -DBLISP_BUILD_CLI=ON -DCOMPILE_TESTS=ON ..
102+
cmake --build .
103+
# Find all compiled unit test files; you can now run these directly
104+
find . -type f -executable -iname "*_test" -print
105+
```
97106

98107
## Troubleshooting
99108

tools/blisp/src/file_parsers/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
list(APPEND ADD_INCLUDE
22
"${CMAKE_CURRENT_SOURCE_DIR}/bin"
33
"${CMAKE_CURRENT_SOURCE_DIR}/dfu"
4+
"${CMAKE_CURRENT_SOURCE_DIR}/hex"
45
"${CMAKE_CURRENT_SOURCE_DIR}"
56
)
67

@@ -12,13 +13,15 @@ add_library(file_parsers STATIC
1213
"${CMAKE_CURRENT_SOURCE_DIR}/bin/bin_file.c"
1314
"${CMAKE_CURRENT_SOURCE_DIR}/dfu/dfu_file.c"
1415
"${CMAKE_CURRENT_SOURCE_DIR}/dfu/dfu_crc.c"
16+
"${CMAKE_CURRENT_SOURCE_DIR}/hex/hex_file.c"
1517
"${CMAKE_CURRENT_SOURCE_DIR}/parse_file.c"
1618
"${CMAKE_CURRENT_SOURCE_DIR}/get_file_contents.c"
1719
)
1820

1921
target_include_directories(file_parsers PUBLIC
2022
${CMAKE_CURRENT_SOURCE_DIR}/bin
2123
${CMAKE_CURRENT_SOURCE_DIR}/dfu
24+
${CMAKE_CURRENT_SOURCE_DIR}/hex
2225
${CMAKE_CURRENT_SOURCE_DIR}
2326

2427
)

tools/blisp/src/file_parsers/dfu/dfu_file.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ int dfu_file_parse(const char* file_path_on_disk,
7878
size_t* payload_address) {
7979
uint8_t* dfu_file_contents = NULL;
8080
ssize_t file_size = get_file_contents(file_path_on_disk, &dfu_file_contents);
81+
// Bubble up the result if it was an error instead of size (a negative value)
8182
if (file_size < 0) {
8283
return file_size;
8384
}

tools/blisp/src/file_parsers/dfu/tests/CMakeLists.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@ target_link_libraries(dfu_file_test
2020
PRIVATE
2121
GTest::GTest
2222
)
23-
include_directories(dfu_file_test PRIVATE ../)
23+
include_directories(dfu_file_test PRIVATE ../ )
2424
add_test(dfu_file_test_gtests dfu_file_test)
2525

2626
configure_file(Config.h.in ${CMAKE_BINARY_DIR}/Config.h)
2727
include_directories(${CMAKE_BINARY_DIR})
2828

2929
set(TEST_APP_NAME dfu_file_tests)
30-
31-
#add_custom_command(TARGET ${TEST_APP_NAME} COMMAND ./${TEST_APP_NAME} POST_BUILD)
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
#include "hex_file.h"
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
#include "parse_file.h"
6+
7+
// Convert ASCII hex character to integer
8+
static int hex_to_int(char c) {
9+
if (c >= '0' && c <= '9')
10+
return c - '0';
11+
if (c >= 'A' && c <= 'F')
12+
return c - 'A' + 10;
13+
if (c >= 'a' && c <= 'f')
14+
return c - 'a' + 10;
15+
return -1;
16+
}
17+
18+
// Convert 2 ASCII hex characters to a byte
19+
static int hex_byte_to_int(const char* str) {
20+
int high = hex_to_int(str[0]);
21+
int low = hex_to_int(str[1]);
22+
if (high < 0 || low < 0)
23+
return -1;
24+
return (high << 4) | low;
25+
}
26+
27+
// Parse a single Intel HEX line into the record type and data
28+
// Returns: Record Type on success, negative error code on failure
29+
static int parse_hex_line(const char* line,
30+
uint8_t* data_buffer,
31+
uint32_t* address,
32+
uint32_t* base_address,
33+
uint32_t* max_address,
34+
uint32_t min_address) {
35+
size_t len = strlen(line);
36+
37+
// Line must start with ':' and have at least 11 characters (:BBAAAATTCC)
38+
if (line[0] != ':' || len < 11) {
39+
return HEX_PARSE_ERROR_INVALID_FORMAT;
40+
}
41+
42+
// Extract record fields
43+
int byte_count = hex_byte_to_int(line + 1);
44+
if (byte_count < 0)
45+
return HEX_PARSE_ERROR_INVALID_FORMAT;
46+
47+
// Make sure line is long enough
48+
if (len < (size_t)(11 + byte_count * 2)) {
49+
return HEX_PARSE_ERROR_INVALID_FORMAT;
50+
}
51+
52+
int addr_high = hex_byte_to_int(line + 3);
53+
int addr_low = hex_byte_to_int(line + 5);
54+
if (addr_high < 0 || addr_low < 0)
55+
return HEX_PARSE_ERROR_INVALID_FORMAT;
56+
57+
uint16_t record_address = (addr_high << 8) | addr_low;
58+
59+
int record_type = hex_byte_to_int(line + 7);
60+
if (record_type < 0)
61+
return HEX_PARSE_ERROR_INVALID_FORMAT;
62+
63+
// Verify checksum
64+
uint8_t checksum = 0;
65+
for (int i = 1; i < 9 + byte_count * 2; i += 2) {
66+
int value = hex_byte_to_int(line + i);
67+
if (value < 0) {
68+
return HEX_PARSE_ERROR_INVALID_FORMAT;
69+
}
70+
checksum += value;
71+
}
72+
73+
int file_checksum = hex_byte_to_int(line + 9 + byte_count * 2);
74+
if (file_checksum < 0) {
75+
return HEX_PARSE_ERROR_INVALID_FORMAT;
76+
}
77+
checksum = ~checksum + 1; // Two's complement
78+
// Verify checksum
79+
if (checksum != file_checksum) {
80+
return HEX_PARSE_ERROR_CHECKSUM;
81+
}
82+
83+
// Process the record based on record type
84+
switch (record_type) {
85+
case HEX_RECORD_DATA: {
86+
uint32_t absolute_address = *base_address + record_address;
87+
*address = absolute_address;
88+
89+
// Update max address if this data extends beyond current max
90+
if (absolute_address + byte_count > *max_address) {
91+
*max_address = absolute_address + byte_count;
92+
}
93+
94+
// Parse data bytes
95+
for (int i = 0; i < byte_count; i++) {
96+
int value = hex_byte_to_int(line + 9 + i * 2);
97+
if (value < 0)
98+
return HEX_PARSE_ERROR_INVALID_FORMAT;
99+
100+
// Make sure we don't write outside our buffer
101+
if (data_buffer != NULL) {
102+
size_t buf_offset = absolute_address - min_address + i;
103+
data_buffer[buf_offset] = value;
104+
}
105+
}
106+
break;
107+
}
108+
109+
case HEX_RECORD_EOF:
110+
// End of file, nothing to do
111+
break;
112+
113+
case HEX_RECORD_EXTENDED_SEGMENT:
114+
// Set segment base address (offset = value * 16)
115+
if (byte_count != 2)
116+
return HEX_PARSE_ERROR_INVALID_FORMAT;
117+
int value_high = hex_byte_to_int(line + 9);
118+
int value_low = hex_byte_to_int(line + 11);
119+
if (value_high < 0 || value_low < 0)
120+
return HEX_PARSE_ERROR_INVALID_FORMAT;
121+
*base_address = ((value_high << 8) | value_low) << 4;
122+
break;
123+
124+
case HEX_RECORD_EXTENDED_LINEAR:
125+
// Set high-order 16 bits of address
126+
if (byte_count != 2)
127+
return HEX_PARSE_ERROR_INVALID_FORMAT;
128+
value_high = hex_byte_to_int(line + 9);
129+
value_low = hex_byte_to_int(line + 11);
130+
if (value_high < 0 || value_low < 0)
131+
return HEX_PARSE_ERROR_INVALID_FORMAT;
132+
*base_address = ((value_high << 8) | value_low) << 16;
133+
break;
134+
135+
case HEX_RECORD_START_LINEAR:
136+
// Start linear address - store as a potential entry point
137+
// but not crucial for firmware extraction
138+
break;
139+
140+
case HEX_RECORD_START_SEGMENT:
141+
// Start segment address - similar to above
142+
break;
143+
144+
default:
145+
return HEX_PARSE_ERROR_UNSUPPORTED_RECORD;
146+
}
147+
148+
return record_type;
149+
}
150+
151+
int hex_file_parse(const char* file_path_on_disk,
152+
uint8_t** payload,
153+
size_t* payload_length,
154+
size_t* payload_address) {
155+
FILE* file = fopen(file_path_on_disk, "r");
156+
if (!file) {
157+
fprintf(stderr, "Could not open file %s for reading\n", file_path_on_disk);
158+
return PARSED_ERROR_CANT_OPEN_FILE;
159+
}
160+
161+
// First pass: Find start and end addresses, and thus size of the payload
162+
char line[512];
163+
uint32_t base_address = 0;
164+
uint32_t address = 0;
165+
uint32_t min_address = 0xFFFFFFFF;
166+
uint32_t max_address = 0;
167+
bool found_data = false;
168+
169+
// First pass to determine memory range
170+
while (fgets(line, sizeof(line), file)) {
171+
size_t len = strlen(line);
172+
// Trim trailing newline and carriage return
173+
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
174+
line[--len] = 0;
175+
}
176+
if (len == 0 || line[0] != ':')
177+
continue;
178+
179+
int result =
180+
parse_hex_line(line, NULL, &address, &base_address, &max_address, 0);
181+
if (result < 0) {
182+
fclose(file);
183+
return result;
184+
}
185+
186+
// Check if this is a data record (type 0)
187+
if (result == HEX_RECORD_DATA) {
188+
found_data = true;
189+
if (address < min_address) {
190+
min_address = address;
191+
}
192+
}
193+
194+
// If we hit EOF record, we're done
195+
if (result == HEX_RECORD_EOF) {
196+
break;
197+
}
198+
}
199+
200+
// If no data was found, return error
201+
if (!found_data) {
202+
fclose(file);
203+
return HEX_PARSE_ERROR_INVALID_FORMAT;
204+
}
205+
206+
// Calculate payload size
207+
size_t size = max_address - min_address;
208+
if (size > (1024 * 1024 * 128)) { // Limit to 128 MB
209+
fclose(file);
210+
return HEX_PARSE_ERROR_TOO_LARGE;
211+
}
212+
// Allocate memory for the payload
213+
*payload = (uint8_t*)calloc(size, sizeof(uint8_t));
214+
if (!*payload) {
215+
fclose(file);
216+
return -1;
217+
}
218+
219+
// Clear the memory to ensure all bytes are initialized
220+
memset(*payload, 0, size);
221+
222+
// Second pass: actually parse the data and fill out the buffer with the data
223+
rewind(file);
224+
base_address = 0;
225+
226+
while (fgets(line, sizeof(line), file)) {
227+
size_t len = strlen(line);
228+
// Trim trailing newline and carriage return
229+
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
230+
line[--len] = 0;
231+
}
232+
if (len == 0 || line[0] != ':')
233+
continue;
234+
235+
// When parsing for real, data is written to the payload buffer
236+
// with addresses relative to min_address
237+
uint32_t dummy_max = 0;
238+
239+
int result = parse_hex_line(line, *payload, &address, &base_address,
240+
&dummy_max, min_address);
241+
if (result < 0) {
242+
free(*payload);
243+
*payload = NULL;
244+
fclose(file);
245+
return result;
246+
}
247+
248+
// If we hit EOF record, we're done
249+
if (result == HEX_RECORD_EOF) {
250+
break;
251+
}
252+
}
253+
254+
fclose(file);
255+
256+
// Set output parameters
257+
*payload_length = size;
258+
*payload_address = min_address;
259+
260+
return 0;
261+
}

0 commit comments

Comments
 (0)