a9cf4e7a48785ed808036485f68faf3dc81f66a2
[fw/sdcc] / packihx / packihx.c
1 /*-----------------------------------------------------------------------
2  * packihx.c:
3  *
4  * utility to pack an Intel HEX format file by removing redundant 
5  * extended offset records and accumulating data records up to
6  * OUTPUT_CHUNK (currently 16) bytes.
7  *
8  * Released to the public domain 10/16/2000 Kevin Vigor.
9  */
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <ctype.h>
15 #include <assert.h>
16
17 #if defined(_MSC_VER) || defined(__BORLANDC__)
18
19 typedef unsigned char Uint8 ;
20 typedef unsigned Uint16 ;
21
22 #else
23
24 #include "config.h"
25 #endif
26
27 /* A cooked line of input. */
28 typedef struct _Line
29 {
30     Uint8               len;            /* length of data portion of record. */
31     Uint16      offset;
32     Uint8               type;
33     Uint8               *data;          
34     Uint8               checksum;
35 } Line;
36
37 /* Largest expected line of raw input. */
38 #define MAX_INPUT_RAW   128
39 /* Largest expected cooked data portion of an input line. */
40 #define MAX_INPUT_COOKED (MAX_INPUT_RAW / 2)
41
42 /* Globals: current input & output line numbers. */
43 int lineno = 0;
44 int outlineno = 0;
45
46 /* Convert hex digit to numeric value 0 - 15; assumes input is a 
47  * valid digit (i.e. passes isxdigit()).
48  */
49 static Uint8 hexDigit(const char c)
50 {
51     if (isdigit(c))
52     {
53         return (Uint8)(c - '0');
54     }
55     else
56     {
57         return (Uint8)((islower(c) ? toupper(c) : c)  - 'A' + 10);
58     }
59 }
60
61 /* Convert two hex digits from cp to a byte value. */
62 static int getHexByte(const char *cp, Uint8 *byte)
63 {
64     if (cp && cp[0] && isxdigit(cp[0]) && cp[1] && isxdigit(cp[1]))
65     {
66         *byte = (hexDigit(cp[0]) << 4) + hexDigit(cp[1]);
67     }
68     else
69     {
70         return -1;
71     }
72     return 0;
73 }
74
75 /* Convert four hex digits from cp to a 2 byte value. */
76 static int getHexWord(const char *cp, Uint16 *word)
77 {
78     Uint8 byte1, byte2;
79     
80     if (getHexByte(cp, &byte1) || getHexByte(cp + 2, &byte2))
81     {
82         return -1;
83     }
84     *word = (byte1 << 8) + byte2;
85     return 0;
86 }
87
88 /* Return a single cooked line of input from the passed file handle. */
89 Line *readLine(FILE *inFile)
90 {
91     static char buffer[MAX_INPUT_RAW];
92     const char  *bp;
93     Line        *line;
94     unsigned    i;
95
96     line = (Line *)malloc(sizeof(Line));
97     if (!line)
98     {
99         fprintf(stderr, "packihx: no memory!\n");
100         return NULL;
101     }
102
103     do
104     {
105         if (!fgets(buffer, MAX_INPUT_RAW, inFile))
106         {
107             return NULL;
108         }
109         ++lineno;
110     
111         if (!buffer[0] || buffer[0] == '\r' || buffer[0] == '\n')
112         {
113             /* Empty input line. */
114             return NULL;
115         }
116     } while (buffer[0] != ':');
117
118     bp = buffer;
119     bp++;       /* Skip leading : */
120      
121     if (getHexByte(bp, &line->len))
122     {
123         fprintf(stderr, "packihx: can't read line length @ line %d\n", 
124                 lineno);
125         free(line);
126         return NULL;
127     }
128     bp += 2;    /* Two digits consumed. */
129
130     if (line->len > MAX_INPUT_COOKED)
131     {
132         fprintf(stderr, "packihx: line length %X too long @ line %d\n",
133                 (int)line->len, lineno);
134         free(line);
135         return NULL;
136     }
137     
138     if (getHexWord(bp, &line->offset))
139     {
140         fprintf(stderr, "packihx: can't read line offset @ line %d\n",
141                 lineno);
142         free(line);
143         return NULL;        
144     }
145     bp += 4; /* Four digits consumed. */
146     
147     if (getHexByte(bp, &line->type))
148     {
149         fprintf(stderr, "packihx: can't read record type @ line %d\n", 
150                 lineno);
151         free(line);
152         return NULL;
153     }
154     bp += 2;    /* Two digits consumed. */    
155    
156     /* Hack - always allocate something, even if len is zero.
157      * Avoids special case for len == 0. */
158     line->data = (Uint8 *)malloc(line->len ? line->len : 1);
159     if (!line->data)
160     {
161         free(line);
162         fprintf(stderr, "packihx: no memory!\n");
163         return NULL;
164     }
165     
166     for (i = 0; i < (unsigned)line->len; i++)
167     {
168         if (getHexByte(bp, &(line->data[i])))
169         {
170             fprintf(stderr, 
171                     "packihx: can't read data byte %u of %u @ line %d\n", 
172                     i, (unsigned) line->len, lineno);
173             free(line->data);
174             free(line);
175             return NULL; 
176         }
177         bp += 2; /* Two digits consumed. */
178     }
179     
180     if (getHexByte(bp, &line->checksum))
181     {
182         fprintf(stderr, "packihx: can't read checksum @ line %d\n", 
183                 lineno);
184         free(line->data);
185         free(line);
186         return NULL;
187     }
188     /* bp += 2; */    /* Two digits consumed. */
189         
190     return line;
191 }
192
193 /* Compute the checksum of a line. */
194 Uint16 lineChecksum(unsigned len, unsigned offset, unsigned type, 
195                     const Uint8 *data)
196 {
197     Uint16  checksum;
198     unsigned    i;
199      
200     checksum = len + type + (offset >> 8) + 
201                (offset & 0xff);
202                 
203     for (i = 0; i < len; i++)
204     {
205         checksum += data[i];       
206     }
207     
208     checksum &= 0xff;
209     if (checksum)
210     {
211        checksum = 0x100 - checksum;
212     }
213     return checksum;
214 }
215
216 /* Ensure that the checksum of a line matches the expected value. */
217 int validateChecksum(Line *line)
218 {
219     Uint16 checksum;
220     
221     checksum = lineChecksum(line->len, line->offset, line->type, line->data);
222     
223     if (checksum != line->checksum)
224     {
225         fprintf(stderr, "packihx: invalid checksum %X (want %X) @ line %d\n", 
226                 (unsigned)(line->checksum), (unsigned)checksum, 
227                 lineno);
228         return -1;      
229     }
230     
231     return 0;
232 }
233
234 /* Write a single record line. */
235 int writeRecord(unsigned len, unsigned offset, unsigned type,
236                 const Uint8 *data)
237 {
238     unsigned i;
239     
240     if (printf(":%02X%04X%02X", len, offset, type) == EOF)
241     {
242         return -1;
243     }
244     
245     for (i = 0; i < len; i++)
246     {
247         if (printf("%02X", data[i]) == EOF)
248         {
249             return -1;
250         }
251     }
252     
253     if (printf("%02X\n", lineChecksum(len, offset, type, data)) == EOF)
254     {
255         return -1;
256     }
257     outlineno++;
258     return 0;
259 }
260
261 #define OUTPUT_CHUNK 16         
262 static unsigned pendingLen = 0;
263 static unsigned pendingOffset = 0;
264 static char     pending[MAX_INPUT_COOKED + OUTPUT_CHUNK];
265
266 /* Buffer up a data record. */
267 int bufferOutput(Line *line)
268 {
269    unsigned offset = 0;
270    int      rc = 0;
271    
272    /* Stick the data onto any pending data. */
273    assert(pendingLen < OUTPUT_CHUNK);
274    memcpy(&pending[pendingLen], line->data, line->len);
275    pendingLen += line->len;
276    
277    /* Write it out untill we have less than an OUTPUT_CHUNK left. */
278    while (!rc && pendingLen >= OUTPUT_CHUNK)
279    {
280        rc = writeRecord(OUTPUT_CHUNK, pendingOffset, 0, &pending[offset]);
281        offset += OUTPUT_CHUNK;
282        pendingOffset += OUTPUT_CHUNK;
283        pendingLen -= OUTPUT_CHUNK;
284    }
285    
286    /* Copy any remaining bits back to the beginning of the buffer. */
287    if (pendingLen)
288    {
289         memmove(pending, &pending[offset], pendingLen);
290    }
291    return rc;
292 }
293
294 /* Write out any pending data. */
295 int flushPendingData(void)
296 {
297     int rc = 0;
298     
299     assert(pendingLen < OUTPUT_CHUNK);
300     
301     if (pendingLen)
302     {
303         rc =  writeRecord(pendingLen, pendingOffset, 0, pending);
304         pendingLen = pendingOffset = 0;
305     }
306     return rc;
307 }
308
309 /* Write an arbitrary line of output (buffering if possible) */
310 int writeLine(Line *line)
311 {
312     static Uint16 lastExtendedOffset = 0;
313     int       rc;
314     
315     if (line->type)
316     {
317         /* Not a data record. */
318         if (line->type == 4)
319         {
320             Uint16 offset;
321             
322             /* Extended offset record. */
323             if (line->len != 2)
324             {
325                 fprintf(stderr, 
326                         "packihx: invalid extended offset record @ line %d\n",
327                         lineno);
328                 return -1;
329             }
330             
331             offset = (line->data[0] << 8) + line->data[1];
332             
333             if (offset == lastExtendedOffset)
334             {
335                 /* We can simply skip this line. */
336                 return 0;
337             }
338             else
339             {
340                 lastExtendedOffset = offset;
341             }
342         }
343         
344         if (flushPendingData())
345         {
346             return -1;
347         }
348         
349         /* Write the line as is. */
350         rc = writeRecord(line->len, line->offset, line->type, line->data);
351     }
352     else
353     {
354         if (pendingOffset + pendingLen != (unsigned)line->offset)
355         {
356             /* This line is not contigous with the last one. Dump pending. */
357             if (flushPendingData())
358             {
359                 return -1;
360             }
361             pendingOffset = line->offset;
362         }
363         rc = bufferOutput(line);
364     }
365     return rc;
366 }
367
368 int main(int argc, char *argv[])
369 {
370     FILE *inFile;
371     Line *line;
372     int  closeFile;
373     int  rc = 0;
374     
375     if (argc > 1)
376     {
377         inFile = fopen(argv[1], "rt");
378         if (!inFile)
379         {
380             fprintf(stderr, "packihx: cannot open %s\n", 
381                      argv[1]);
382             return 1;
383         }
384         closeFile = 1;
385     }
386     else
387     {
388         inFile = stdin;
389         closeFile = 0;
390     }
391     
392     while (!rc && ((line = readLine(inFile)) != NULL))
393     {
394         rc = validateChecksum(line);
395         
396         if (!rc)
397         {
398             rc = writeLine(line);
399         }
400         
401         free(line->data);
402         free(line);
403     }
404     
405     if (!rc && !feof(inFile))
406     {
407         /* readLine must have failed for some reason. */
408         fprintf(stderr, "packihx: aborting after %d lines.\n", lineno);
409         rc = 1;
410     }
411     
412     if (!rc)
413     {
414         /* Just in case there's something still pending. */
415         rc = flushPendingData();
416     }
417     
418     if (!rc)
419     {
420         fprintf(stderr, "packihx: read %d lines, wrote %d: OK.\n", lineno, outlineno);
421     }
422
423     if (closeFile)
424     {
425         fclose(inFile);
426     }    
427     return rc;
428 }