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