1 //
2 // Copyright 2015 Ettus Research LLC
3 // Copyright 2018 Ettus Research, a National Instruments Company
4 //
5 // SPDX-License-Identifier: GPL-3.0-or-later
6 //
7
8 #include <uhd/exception.hpp>
9 #include <uhdlib/utils/ihex.hpp>
10 #include <boost/format.hpp>
11 #include <fstream>
12 #include <functional>
13 #include <memory>
14 #include <sstream>
15
16 using namespace uhd;
17
18 /*!
19 * Verify checksum of a Intel HEX record
20 * \param record a line from an Intel HEX file
21 * \return true if record is valid, false otherwise
22 */
checksum(const std::string & record)23 static bool checksum(const std::string& record)
24 {
25 size_t len = record.length();
26 unsigned char sum = 0;
27 unsigned int val;
28
29 for (size_t i = 1; i < len; i += 2) {
30 std::istringstream(record.substr(i, 2)) >> std::hex >> val;
31 sum += val;
32 }
33
34 if (sum == 0)
35 return true;
36 else
37 return false;
38 }
39
40
41 /*!
42 * Parse Intel HEX record
43 *
44 * \param record a line from an Intel HEX file
45 * \param len output length of record
46 * \param addr output address
47 * \param type output type
48 * \param data output data
49 * \return true if record is sucessfully read, false on error
50 */
parse_record(const std::string & record,uint16_t & len,uint16_t & addr,uint16_t & type,unsigned char * data)51 static bool parse_record(const std::string& record,
52 uint16_t& len,
53 uint16_t& addr,
54 uint16_t& type,
55 unsigned char* data)
56 {
57 unsigned int i;
58 unsigned int val;
59
60 if (record.substr(0, 1) != ":")
61 return false;
62
63 std::istringstream(record.substr(1, 2)) >> std::hex >> len;
64 std::istringstream(record.substr(3, 4)) >> std::hex >> addr;
65 std::istringstream(record.substr(7, 2)) >> std::hex >> type;
66
67 if (len > (2 * (record.length() - 9))) // sanity check to prevent buffer overrun
68 return false;
69
70 for (i = 0; i < len; i++) {
71 std::istringstream(record.substr(9 + 2 * i, 2)) >> std::hex >> val;
72 data[i] = (unsigned char)val;
73 }
74
75 return true;
76 }
77
78
ihex_reader(const std::string & ihex_filename)79 ihex_reader::ihex_reader(const std::string& ihex_filename) : _ihex_filename(ihex_filename)
80 {
81 // nop
82 }
83
84
read(ihex_reader::record_handle_type record_handler)85 void ihex_reader::read(ihex_reader::record_handle_type record_handler)
86 {
87 const char* filename = _ihex_filename.c_str();
88
89 /* Fields used in every record. */
90 uint16_t len = 0;
91 uint16_t type = 0;
92 uint16_t lower_address_bits = 0x0000;
93 static const int MAX_RECORD_LENGTH = 255;
94 unsigned char data[MAX_RECORD_LENGTH];
95
96 /* Can be set by the Intel HEX record 0x04, used for all 0x00 records
97 * thereafter. Note this field takes the place of the 'index' parameter in
98 * libusb calls, and is necessary for FX3's 32-bit addressing. */
99 uint16_t upper_address_bits = 0x0000;
100
101 std::ifstream file;
102 file.open(filename, std::ifstream::in);
103
104 if (!file.good()) {
105 throw uhd::io_error("ihex_reader::read(): cannot open firmware input file");
106 }
107
108 while (!file.eof()) {
109 int32_t ret = 0;
110 std::string record;
111 file >> record;
112
113 if (!(record.length() > 0))
114 continue;
115
116 /* Check for valid Intel HEX record. */
117 if (!checksum(record)
118 || !parse_record(record, len, lower_address_bits, type, data)) {
119 throw uhd::io_error("ihex_reader::read(): bad intel hex record checksum");
120 }
121
122 /* Type 0x00: Data. */
123 if (type == 0x00) {
124 ret = record_handler(lower_address_bits, upper_address_bits, data, len);
125
126 if (ret < 0) {
127 throw uhd::io_error(
128 "ihex_reader::read(): record hander returned failure code");
129 }
130 }
131
132 /* Type 0x01: EOF. */
133 else if (type == 0x01) {
134 if (lower_address_bits != 0x0000 || len != 0) {
135 throw uhd::io_error("ihex_reader::read(): For EOF record, address must "
136 "be 0, length must be 0.");
137 }
138
139 /* Successful termination! */
140 file.close();
141 return;
142 }
143
144 /* Type 0x04: Extended Linear Address Record. */
145 else if (type == 0x04) {
146 if (lower_address_bits != 0x0000 || len != 2) {
147 throw uhd::io_error("ihex_reader::read(): For ELA record, address must "
148 "be 0, length must be 2.");
149 }
150
151 upper_address_bits =
152 ((uint16_t)((data[0] & 0x00FF) << 8)) + ((uint16_t)(data[1] & 0x00FF));
153 }
154
155 /* Type 0x05: Start Linear Address Record. */
156 else if (type == 0x05) {
157 if (lower_address_bits != 0x0000 || len != 4) {
158 throw uhd::io_error("ihex_reader::read(): For SLA record, address must "
159 "be 0, length must be 4.");
160 }
161
162 /* The firmware load is complete. We now need to tell the CPU
163 * to jump to an execution address start point, now contained within
164 * the data field. Parse these address bits out, and then push the
165 * instruction. */
166 upper_address_bits =
167 ((uint16_t)((data[0] & 0x00FF) << 8)) + ((uint16_t)(data[1] & 0x00FF));
168 lower_address_bits =
169 ((uint16_t)((data[2] & 0x00FF) << 8)) + ((uint16_t)(data[3] & 0x00FF));
170
171 record_handler(lower_address_bits, upper_address_bits, 0, 0);
172 }
173
174 /* If we receive an unknown record type, error out. */
175 else {
176 throw uhd::io_error(
177 str(boost::format("ihex_reader::read(): unsupported record type: %X.")
178 % type));
179 }
180 }
181
182 /* There was no valid EOF. */
183 throw uhd::io_error("ihex_reader::read(): No EOF record found.");
184 }
185
186 // We need a functor for the cast, a lambda would be perfect...
_file_writer_callback(std::shared_ptr<std::ofstream> output_file,unsigned char * buff,uint16_t len)187 int _file_writer_callback(
188 std::shared_ptr<std::ofstream> output_file, unsigned char* buff, uint16_t len)
189 {
190 output_file->write((const char*)buff, len);
191 return 0;
192 }
193
to_bin_file(const std::string & bin_filename)194 void ihex_reader::to_bin_file(const std::string& bin_filename)
195 {
196 std::shared_ptr<std::ofstream> output_file(std::make_shared<std::ofstream>());
197 output_file->open(bin_filename.c_str(), std::ios::out | std::ios::binary);
198 if (not output_file->is_open()) {
199 throw uhd::io_error(
200 str(boost::format("Could not open file for writing: %s") % bin_filename));
201 }
202
203 this->read(std::bind(&_file_writer_callback,
204 output_file,
205 std::placeholders::_3,
206 std::placeholders::_4));
207
208 output_file->close();
209 }
210
211 // We need a functor for the cast, a lambda would be perfect...
_vector_writer_callback(std::vector<uint8_t> & vector,unsigned char * buff,uint16_t len)212 int _vector_writer_callback(
213 std::vector<uint8_t>& vector, unsigned char* buff, uint16_t len)
214 {
215 for (size_t i = 0; i < len; i++) {
216 vector.push_back(buff[i]);
217 }
218 return 0;
219 }
220
221 #define DEFAULT_SIZE_ESTIMATE 8000000
to_vector(const size_t size_estimate)222 std::vector<uint8_t> ihex_reader::to_vector(const size_t size_estimate)
223 {
224 std::vector<uint8_t> buf;
225 buf.reserve(size_estimate == 0 ? DEFAULT_SIZE_ESTIMATE : size_estimate);
226
227 this->read(std::bind(&_vector_writer_callback,
228 std::ref(buf),
229 std::placeholders::_3,
230 std::placeholders::_4));
231
232 return buf;
233 }
234