diff options
author | Andrey Smirnov <andrew.smirnov@gmail.com> | 2018-10-17 14:27:18 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-11-11 15:58:27 -0500 |
commit | 1e5106031f298af4ad29c2eb866780d9a21e9ab4 (patch) | |
tree | 9bc1380a216ac0fc6b10b4855d68337f8a02ef25 | |
parent | 4fcba7802c3e15a6e56e255871d6c72f829b9dd8 (diff) |
tools: Add 'firmware' category and add ihex2fw tool
Commit 5620a0d1aacd ("firmware: delete in-kernel firmware") removed
ihex2fw tool together with the rest of the contents of firmware/
folder. Since that tool is quite useful for doing .ihex -> .fw
converstion, restore its original source code to tools/firmware
Suggested-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Kyle McMartin <kyle@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Masahiro Yamada <yamada.masahiro@socionext.com>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-kernel <linux-kernel@vger.kernel.org>
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | tools/Makefile | 7 | ||||
-rw-r--r-- | tools/firmware/Makefile | 13 | ||||
-rw-r--r-- | tools/firmware/ihex2fw.c | 281 |
3 files changed, 298 insertions, 3 deletions
diff --git a/tools/Makefile b/tools/Makefile index abb358a70ad0..77f1aee8ea01 100644 --- a/tools/Makefile +++ b/tools/Makefile | |||
@@ -13,6 +13,7 @@ help: | |||
13 | @echo ' cgroup - cgroup tools' | 13 | @echo ' cgroup - cgroup tools' |
14 | @echo ' cpupower - a tool for all things x86 CPU power' | 14 | @echo ' cpupower - a tool for all things x86 CPU power' |
15 | @echo ' firewire - the userspace part of nosy, an IEEE-1394 traffic sniffer' | 15 | @echo ' firewire - the userspace part of nosy, an IEEE-1394 traffic sniffer' |
16 | @echo ' firmware - Firmware tools' | ||
16 | @echo ' freefall - laptop accelerometer program for disk protection' | 17 | @echo ' freefall - laptop accelerometer program for disk protection' |
17 | @echo ' gpio - GPIO tools' | 18 | @echo ' gpio - GPIO tools' |
18 | @echo ' hv - tools used when in Hyper-V clients' | 19 | @echo ' hv - tools used when in Hyper-V clients' |
@@ -60,7 +61,7 @@ acpi: FORCE | |||
60 | cpupower: FORCE | 61 | cpupower: FORCE |
61 | $(call descend,power/$@) | 62 | $(call descend,power/$@) |
62 | 63 | ||
63 | cgroup firewire hv guest spi usb virtio vm bpf iio gpio objtool leds wmi pci: FORCE | 64 | cgroup firewire hv guest spi usb virtio vm bpf iio gpio objtool leds wmi pci firmware: FORCE |
64 | $(call descend,$@) | 65 | $(call descend,$@) |
65 | 66 | ||
66 | liblockdep: FORCE | 67 | liblockdep: FORCE |
@@ -137,7 +138,7 @@ acpi_clean: | |||
137 | cpupower_clean: | 138 | cpupower_clean: |
138 | $(call descend,power/cpupower,clean) | 139 | $(call descend,power/cpupower,clean) |
139 | 140 | ||
140 | cgroup_clean hv_clean firewire_clean spi_clean usb_clean virtio_clean vm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean: | 141 | cgroup_clean hv_clean firewire_clean spi_clean usb_clean virtio_clean vm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean: |
141 | $(call descend,$(@:_clean=),clean) | 142 | $(call descend,$(@:_clean=),clean) |
142 | 143 | ||
143 | liblockdep_clean: | 144 | liblockdep_clean: |
@@ -175,6 +176,6 @@ clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean \ | |||
175 | perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \ | 176 | perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \ |
176 | vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ | 177 | vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ |
177 | freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \ | 178 | freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \ |
178 | gpio_clean objtool_clean leds_clean wmi_clean pci_clean | 179 | gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean |
179 | 180 | ||
180 | .PHONY: FORCE | 181 | .PHONY: FORCE |
diff --git a/tools/firmware/Makefile b/tools/firmware/Makefile new file mode 100644 index 000000000000..d329825aa31b --- /dev/null +++ b/tools/firmware/Makefile | |||
@@ -0,0 +1,13 @@ | |||
1 | # SPDX-License-Identifier: GPL-2.0 | ||
2 | # Makefile for firmware tools | ||
3 | |||
4 | CFLAGS = -Wall -Wextra -g | ||
5 | |||
6 | all: ihex2fw | ||
7 | %: %.c | ||
8 | $(CC) $(CFLAGS) -o $@ $^ | ||
9 | |||
10 | clean: | ||
11 | $(RM) ihex2fw | ||
12 | |||
13 | .PHONY: all clean \ No newline at end of file | ||
diff --git a/tools/firmware/ihex2fw.c b/tools/firmware/ihex2fw.c new file mode 100644 index 000000000000..b58dd061e978 --- /dev/null +++ b/tools/firmware/ihex2fw.c | |||
@@ -0,0 +1,281 @@ | |||
1 | /* | ||
2 | * Parser/loader for IHEX formatted data. | ||
3 | * | ||
4 | * Copyright © 2008 David Woodhouse <dwmw2@infradead.org> | ||
5 | * Copyright © 2005 Jan Harkes <jaharkes@cs.cmu.edu> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License version 2 as | ||
9 | * published by the Free Software Foundation. | ||
10 | */ | ||
11 | |||
12 | #include <stdint.h> | ||
13 | #include <arpa/inet.h> | ||
14 | #include <stdio.h> | ||
15 | #include <errno.h> | ||
16 | #include <sys/types.h> | ||
17 | #include <sys/stat.h> | ||
18 | #include <sys/mman.h> | ||
19 | #include <fcntl.h> | ||
20 | #include <string.h> | ||
21 | #include <unistd.h> | ||
22 | #include <stdlib.h> | ||
23 | #define _GNU_SOURCE | ||
24 | #include <getopt.h> | ||
25 | |||
26 | |||
27 | struct ihex_binrec { | ||
28 | struct ihex_binrec *next; /* not part of the real data structure */ | ||
29 | uint32_t addr; | ||
30 | uint16_t len; | ||
31 | uint8_t data[]; | ||
32 | }; | ||
33 | |||
34 | /** | ||
35 | * nybble/hex are little helpers to parse hexadecimal numbers to a byte value | ||
36 | **/ | ||
37 | static uint8_t nybble(const uint8_t n) | ||
38 | { | ||
39 | if (n >= '0' && n <= '9') return n - '0'; | ||
40 | else if (n >= 'A' && n <= 'F') return n - ('A' - 10); | ||
41 | else if (n >= 'a' && n <= 'f') return n - ('a' - 10); | ||
42 | return 0; | ||
43 | } | ||
44 | |||
45 | static uint8_t hex(const uint8_t *data, uint8_t *crc) | ||
46 | { | ||
47 | uint8_t val = (nybble(data[0]) << 4) | nybble(data[1]); | ||
48 | *crc += val; | ||
49 | return val; | ||
50 | } | ||
51 | |||
52 | static int process_ihex(uint8_t *data, ssize_t size); | ||
53 | static void file_record(struct ihex_binrec *record); | ||
54 | static int output_records(int outfd); | ||
55 | |||
56 | static int sort_records = 0; | ||
57 | static int wide_records = 0; | ||
58 | static int include_jump = 0; | ||
59 | |||
60 | static int usage(void) | ||
61 | { | ||
62 | fprintf(stderr, "ihex2fw: Convert ihex files into binary " | ||
63 | "representation for use by Linux kernel\n"); | ||
64 | fprintf(stderr, "usage: ihex2fw [<options>] <src.HEX> <dst.fw>\n"); | ||
65 | fprintf(stderr, " -w: wide records (16-bit length)\n"); | ||
66 | fprintf(stderr, " -s: sort records by address\n"); | ||
67 | fprintf(stderr, " -j: include records for CS:IP/EIP address\n"); | ||
68 | return 1; | ||
69 | } | ||
70 | |||
71 | int main(int argc, char **argv) | ||
72 | { | ||
73 | int infd, outfd; | ||
74 | struct stat st; | ||
75 | uint8_t *data; | ||
76 | int opt; | ||
77 | |||
78 | while ((opt = getopt(argc, argv, "wsj")) != -1) { | ||
79 | switch (opt) { | ||
80 | case 'w': | ||
81 | wide_records = 1; | ||
82 | break; | ||
83 | case 's': | ||
84 | sort_records = 1; | ||
85 | break; | ||
86 | case 'j': | ||
87 | include_jump = 1; | ||
88 | break; | ||
89 | default: | ||
90 | return usage(); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | if (optind + 2 != argc) | ||
95 | return usage(); | ||
96 | |||
97 | if (!strcmp(argv[optind], "-")) | ||
98 | infd = 0; | ||
99 | else | ||
100 | infd = open(argv[optind], O_RDONLY); | ||
101 | if (infd == -1) { | ||
102 | fprintf(stderr, "Failed to open source file: %s", | ||
103 | strerror(errno)); | ||
104 | return usage(); | ||
105 | } | ||
106 | if (fstat(infd, &st)) { | ||
107 | perror("stat"); | ||
108 | return 1; | ||
109 | } | ||
110 | data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, infd, 0); | ||
111 | if (data == MAP_FAILED) { | ||
112 | perror("mmap"); | ||
113 | return 1; | ||
114 | } | ||
115 | |||
116 | if (!strcmp(argv[optind+1], "-")) | ||
117 | outfd = 1; | ||
118 | else | ||
119 | outfd = open(argv[optind+1], O_TRUNC|O_CREAT|O_WRONLY, 0644); | ||
120 | if (outfd == -1) { | ||
121 | fprintf(stderr, "Failed to open destination file: %s", | ||
122 | strerror(errno)); | ||
123 | return usage(); | ||
124 | } | ||
125 | if (process_ihex(data, st.st_size)) | ||
126 | return 1; | ||
127 | |||
128 | return output_records(outfd); | ||
129 | } | ||
130 | |||
131 | static int process_ihex(uint8_t *data, ssize_t size) | ||
132 | { | ||
133 | struct ihex_binrec *record; | ||
134 | uint32_t offset = 0; | ||
135 | uint32_t data32; | ||
136 | uint8_t type, crc = 0, crcbyte = 0; | ||
137 | int i, j; | ||
138 | int line = 1; | ||
139 | int len; | ||
140 | |||
141 | i = 0; | ||
142 | next_record: | ||
143 | /* search for the start of record character */ | ||
144 | while (i < size) { | ||
145 | if (data[i] == '\n') line++; | ||
146 | if (data[i++] == ':') break; | ||
147 | } | ||
148 | |||
149 | /* Minimum record length would be about 10 characters */ | ||
150 | if (i + 10 > size) { | ||
151 | fprintf(stderr, "Can't find valid record at line %d\n", line); | ||
152 | return -EINVAL; | ||
153 | } | ||
154 | |||
155 | len = hex(data + i, &crc); i += 2; | ||
156 | if (wide_records) { | ||
157 | len <<= 8; | ||
158 | len += hex(data + i, &crc); i += 2; | ||
159 | } | ||
160 | record = malloc((sizeof (*record) + len + 3) & ~3); | ||
161 | if (!record) { | ||
162 | fprintf(stderr, "out of memory for records\n"); | ||
163 | return -ENOMEM; | ||
164 | } | ||
165 | memset(record, 0, (sizeof(*record) + len + 3) & ~3); | ||
166 | record->len = len; | ||
167 | |||
168 | /* now check if we have enough data to read everything */ | ||
169 | if (i + 8 + (record->len * 2) > size) { | ||
170 | fprintf(stderr, "Not enough data to read complete record at line %d\n", | ||
171 | line); | ||
172 | return -EINVAL; | ||
173 | } | ||
174 | |||
175 | record->addr = hex(data + i, &crc) << 8; i += 2; | ||
176 | record->addr |= hex(data + i, &crc); i += 2; | ||
177 | type = hex(data + i, &crc); i += 2; | ||
178 | |||
179 | for (j = 0; j < record->len; j++, i += 2) | ||
180 | record->data[j] = hex(data + i, &crc); | ||
181 | |||
182 | /* check CRC */ | ||
183 | crcbyte = hex(data + i, &crc); i += 2; | ||
184 | if (crc != 0) { | ||
185 | fprintf(stderr, "CRC failure at line %d: got 0x%X, expected 0x%X\n", | ||
186 | line, crcbyte, (unsigned char)(crcbyte-crc)); | ||
187 | return -EINVAL; | ||
188 | } | ||
189 | |||
190 | /* Done reading the record */ | ||
191 | switch (type) { | ||
192 | case 0: | ||
193 | /* old style EOF record? */ | ||
194 | if (!record->len) | ||
195 | break; | ||
196 | |||
197 | record->addr += offset; | ||
198 | file_record(record); | ||
199 | goto next_record; | ||
200 | |||
201 | case 1: /* End-Of-File Record */ | ||
202 | if (record->addr || record->len) { | ||
203 | fprintf(stderr, "Bad EOF record (type 01) format at line %d", | ||
204 | line); | ||
205 | return -EINVAL; | ||
206 | } | ||
207 | break; | ||
208 | |||
209 | case 2: /* Extended Segment Address Record (HEX86) */ | ||
210 | case 4: /* Extended Linear Address Record (HEX386) */ | ||
211 | if (record->addr || record->len != 2) { | ||
212 | fprintf(stderr, "Bad HEX86/HEX386 record (type %02X) at line %d\n", | ||
213 | type, line); | ||
214 | return -EINVAL; | ||
215 | } | ||
216 | |||
217 | /* We shouldn't really be using the offset for HEX86 because | ||
218 | * the wraparound case is specified quite differently. */ | ||
219 | offset = record->data[0] << 8 | record->data[1]; | ||
220 | offset <<= (type == 2 ? 4 : 16); | ||
221 | goto next_record; | ||
222 | |||
223 | case 3: /* Start Segment Address Record */ | ||
224 | case 5: /* Start Linear Address Record */ | ||
225 | if (record->addr || record->len != 4) { | ||
226 | fprintf(stderr, "Bad Start Address record (type %02X) at line %d\n", | ||
227 | type, line); | ||
228 | return -EINVAL; | ||
229 | } | ||
230 | |||
231 | memcpy(&data32, &record->data[0], sizeof(data32)); | ||
232 | data32 = htonl(data32); | ||
233 | memcpy(&record->data[0], &data32, sizeof(data32)); | ||
234 | |||
235 | /* These records contain the CS/IP or EIP where execution | ||
236 | * starts. If requested output this as a record. */ | ||
237 | if (include_jump) | ||
238 | file_record(record); | ||
239 | goto next_record; | ||
240 | |||
241 | default: | ||
242 | fprintf(stderr, "Unknown record (type %02X)\n", type); | ||
243 | return -EINVAL; | ||
244 | } | ||
245 | |||
246 | return 0; | ||
247 | } | ||
248 | |||
249 | static struct ihex_binrec *records; | ||
250 | |||
251 | static void file_record(struct ihex_binrec *record) | ||
252 | { | ||
253 | struct ihex_binrec **p = &records; | ||
254 | |||
255 | while ((*p) && (!sort_records || (*p)->addr < record->addr)) | ||
256 | p = &((*p)->next); | ||
257 | |||
258 | record->next = *p; | ||
259 | *p = record; | ||
260 | } | ||
261 | |||
262 | static int output_records(int outfd) | ||
263 | { | ||
264 | unsigned char zeroes[6] = {0, 0, 0, 0, 0, 0}; | ||
265 | struct ihex_binrec *p = records; | ||
266 | |||
267 | while (p) { | ||
268 | uint16_t writelen = (p->len + 9) & ~3; | ||
269 | |||
270 | p->addr = htonl(p->addr); | ||
271 | p->len = htons(p->len); | ||
272 | if (write(outfd, &p->addr, writelen) != writelen) | ||
273 | return 1; | ||
274 | p = p->next; | ||
275 | } | ||
276 | /* EOF record is zero length, since we don't bother to represent | ||
277 | the type field in the binary version */ | ||
278 | if (write(outfd, zeroes, 6) != 6) | ||
279 | return 1; | ||
280 | return 0; | ||
281 | } | ||