-
Notifications
You must be signed in to change notification settings - Fork 2
/
BootLoader.c
2266 lines (1964 loc) · 74.6 KB
/
BootLoader.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
The MIT License (MIT)
Copyright (c) 2015 Hypnocube, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Code written by Chris Lomont, 2015
*/
// code for a boot loader for PIC32 (and other?) MCUs
// Chris Lomont May 2015
#include <stdint.h> // basic types like uint16_t, etc.
#include <stdbool.h> // type bool
#include <xc.h> // standard part port definitions
#include "BootLoader.h"
/************************** Overview *******************************************
*
* This is a bootloader for PIC32 MCUs, written by Chris Lomont for Hypnocube.
* It allows flashing PIC32s from (optionally) encrypted images for upgrading
* PIC applications in the field. Read the Usage section to see how to use it.
* www.hypnocube.com
* May 2015 - Version 0.5 - initial release
*
******************************************************************************/
/************************** Usage: *********************************************
*
* 1. Add this file (BootLoader.c) to your program.
* This makes keeping the config bits in sync; the bootloader and main
* program need to share configuration bits.
* 2. The bootloader needs to reside in a fixed location in FLASH. It uses the
* lowest address user FLASH (not the boot flash - see the notes section).
* To position it in memory, you must add and modify the linker script:
*
* This is in Microchip directories, has the same name as your PIC with a
* *.ld extension. For example, if you're using a PIC32MX150F128B, find the
* file named p32MX150F128B.ld, and make a copy in your project directory.
*
* Add this file to your project; now the linker will use your copy instead
* of the default one.
*
* To reserve a space for the bootloader, edit your copy of the linker
* script as follows: near the file beginning, in the MEMORY section,
* find the kseg0_program_mem line:
* kseg0_program_mem (rx) : ORIGIN = 0x9D000000, LENGTH = 0x1F000
*
* Subtract 0x1800 from the LENGTH, add it to the ORIGIN, and insert
* another entry like so. This split the memory section into two. Depending
* on compiler options, you may have to increase this 0x1800 as needed.
*
* hypnocube_bootcode (rx) : ORIGIN = 0x9D000000, LENGTH = 0x1800
* kseg0_program_mem (rx) : ORIGIN = 0x9D000000 + LENGTH(hypnocube_bootcode),
* LENGTH = 0x1F000 - LENGTH(hypnocube_bootcode)
*
* Also reserve one word in RAM by similarly splitting the line
*
* kseg1_data_mem (w!x) : ORIGIN = 0xA0000000, LENGTH = 0x8000
*
* into two
*
* hypnocube_bootram (w!x) : ORIGIN = 0xA0000000, LENGTH = 0x4
* kseg1_data_mem (w!x) : ORIGIN = 0xA0000000 + LENGTH(hypnocube_bootram),
* LENGTH = 0x8000-LENGTH(hypnocube_bootram)
*
* These two changes made room in flash and ram for our uses. Right after
* the closing brace in the memory section, add this line
*
* _HCBOOT_LD_SIZE_ = LENGTH(hypnocube_bootcode);
*
* which defines a symbol used in the code to determine the bootloader
* code area to protect.
*
* Now we have to tell the linker what to put in these locations using
* SECTIONs. So, before the .text: section definition, add these sections
* to map the code to this memory section:
*
* .hcbcode :
* {
* *(.hcbcode.*)
* *(.hcbcode)
* KEEP (*(.hcbcode))
* KEEP (*(.hcbcode.*))
* . = ALIGN(4) ;
* } >hypnocube_bootcode
* .hcbram :
* {
* *(.hcbram.*)
* *(.hcbram)
* KEEP (*(.hcbram))
* KEEP (*(.hcbram.*))
* . = ALIGN(4) ;
* } >hypnocube_bootram
*
* Now the bootloader code and single variable will be located correctly.
*
*
* 3. To make the bootloader called on power up, you need to modify the C
* runtime startup code.
*
* Locate the crt0.S file in the Microchip distribution, and copy it to
* your project. Rename it to BootLoader_crt0.S. The capital S is important.
* Change the project linker settings to not link the default startup code
* (Properties -> XC32 -> xc32-ld -> libraries -> Do not link startup code)
*
* Change the compiler to emit sections for each function and variable
* by changing two options under g++ (Isolate each function in its own
* section AND Place data in its own section).
*
* Edit it to call the bootloader before (almost) anything else is done.
* Right after the _startup: symbol, insert these lines (do not insert a
* second startup symbol):
*
* _startup:
*
* ##################################################################
* # Bootloader jump added by Chris Lomont from Hypnocube. If this is
* # changed, the check code in the bootloader must be changed, since
* # exactly this code sequence must appear early in the reset area
* ##################################################################
* la sp,0xA0000000 + 8*1024 # all PIC32s have >= 16K RAM, use 8
* la t0,BootloaderEntry # boot address, always same location
* jalr t0 # jump and link so we can return here
* nop # required branch delay slot, executed
*
* It is important these lines compile to reside very near the beginnging
* of the reset entry point, because the bootloader will not allow
* overwriting of this location without the same code snippet occurring.
*
* 4. Edit parameters in the user defines section. The SYS_CLOCK needs to match
* your device on power up, and DESIRED_BAUDRATE and uart used need to match
* your design.
*
* You may need to implement code for other UART ports or PICs than are here
* already. Please submit fixes and changes back to Chris Lomont so they
* can be incorporated.
*
* 5. Compile with the small, optimized code setting, otherwise the bootloader
* will not fit in the 0x1000 bytes from above. If you don't have that
* compiler, increase the sizes above to 0x2000 or whatever you need to get
* it to fit.
*
* The size must be an integral number of flash pages. Check your page
* sizes.
*
* 6. Read the Notes section for more information.
*
******************************************************************************/
/************************** User defines **************************************/
// You must set the clock speed the same as the boot up settings for the device
// this clock is used for timing and setting the UART baud rate
#define SYS_CLOCK 48000000L // system clock, ticks go half this rate
#define TICKS_PER_MILLISECOND ((SYS_CLOCK)/2000)
#define TICKS_PER_MICROSECOND ((SYS_CLOCK)/2000000)
// define this to allow flashing the BOOT flash section
// this is recommended - the bootloader still protects its own
// code which is stored at the lowest FLASH addresses
#define ALLOW_BOOTFLASH_OVERWRITE
// UART baud rate
#define DESIRED_BAUDRATE 1000000
// Define a UART (UART1, UART2, etc) to use. You must add code cases as needed
#define HC_UART1
// #define HC_UART2
// Number of milliseconds to look for flashing tool at boot.
#define BOOT_WAIT_MS 1000
// define this to use encrypted images, else unencrypted
#define USE_CRYPTO
#ifdef USE_CRYPTO
// If encrypted, you need a 32 byte key, stored here as eight 4 byte values
// These get written as the key, word 0 first (lowest address), each stored
// big endian into a byte array
#define PASSWORD_WORD0 0x12345678
#define PASSWORD_WORD1 0x12345678
#define PASSWORD_WORD2 0x12345678
#define PASSWORD_WORD3 0x12345678
#define PASSWORD_WORD4 0x12345678
#define PASSWORD_WORD5 0x12345678
#define PASSWORD_WORD6 0x12345678
#define PASSWORD_WORD7 0x12345678
#endif
// To have some blinking LED feedback, define these for your device
// otherwise leave blank
//#define LED_INIT()
//#define LED_ON()
//#define LED_OFF()
//#define LED_TOGGLE()
// example, if PORTA, pin RA1, is tied to a LED:
#define LED_INIT() LATACLR = 1<<1; TRISACLR = 1<<1
#define LED_ON() PORTAbits.RA1=1
#define LED_OFF() PORTAbits.RA1=0
#define LED_TOGGLE() PORTAbits.RA1^=1
// needed defines for each PIC type - todo - group them nicely
#if defined(__32MX150F128C__) || defined(__32MX150F128B__)
#define FLASH_PAGE_SIZE 1024 // bytes
#define FLASH_ROW_SIZE 128 // bytes
#else
error! need defines for your chip
#endif
// todo - clean and organize these better
// memory regions, end is one past usable end
// all values are PHYSICAL addresses, not logical
// most (all) are the same across chips
#define RAM_START 0x00000000
#define RAM_END ((RAM_START) + (BMXDRMSZ))
#define FLASH_START 0x1D000000
#define FLASH_SIZE (BMXPFMSZ)
#define FLASH_END ((FLASH_START) + (FLASH_SIZE))
#define PERIPHERAL_START 0x1F800000
#define PERIPHERAL_END 0x1F900000
#define BOOT_START 0x1FC00000
#define BOOT_SIZE (BMXBOOTSZ)
#define BOOT_END ((BOOT_START) + (BOOT_SIZE))
// note config may be in the boot flash region (always is?)
#define CONFIGURATION_START 0x1FC00BF0
#define CONFIGURATION_END 0x1FC00C00
// logical address for flash regions
#define FLASH_START_LOGICAL 0xBD000000
#define BOOT_START_LOGICAL 0xBFC00000
/*********************** Theory of operation ***********************************
*
* To understand how this works, first you must understand that the PIC32 uses
* memory mapping from physical addresses to logical addresses, and that the
* CPU sees logical addresses while flash erasing and writing use physical
* addresses. The notes section explains details. Physical addresses are always
* logical addresses masked with 0x1FFFFFFF. The other mapping is more complex.
*
* This bootloader operates as follows. There is boot flash (small) and user
* flash (large), and an ideal bootloader would sit in the boot flash, but this
* turns out to be very messy, as explained in the notes section.
*
* Boot flash starts at logical address 0xBD000000 and has length 3K or 12K
* (perhaps other values someday) depending on PIC. The hardware maps a few
* exception vectors into here that cannot be moved.
*
* On power up, PIC32 jumps to the start of boot flash, which usually contains
* the C runtime startup code, several exception handlers, and the debug
* executive for debugging (which is the main reason this bootloader is loaded
* elsewhere). This bootloader installs a small jump into the start of this C
* runtime to jump to an address where the bootloader will always reside, the
* start of user flash at logical address 0xBD000000, where it uses about 6K of
* flash. The jump stub and the boot code itself are protected by the bootloader
* from being overwritten.
*
* On anything other than a power on reset, the bootloader simply returns back
* to the C runtime startup. On a power on reset, the bootloader waits a compile
* time amount of time for a control byte from the flash utility. If this is
* detected, the bootloader then enters a command loop under the control of the
* flash utility.
*
* The flash utility issues commands to obtain for boot loader information,
* erase all flash (except the pages the bootloader protects), and then sends
* packets of data to be written into flash.
*
* There are some nuances to this, with some more details in the notes section.
*
* Flashing Protocol:
* 1. Flasher application sends repeating ACK command over the serial port
* Note there may be no serial port, since the device is not powered up,
* so the flasher will poll the port.
* 2. Power up Device to be flashed. It waits some amount of time (1 sec? 2 secs?)
* looking for an ACK command from the Flasher.
* 3. If Device sees ACK, sends back ACK, enters command loop
* 4. If Device does not see ACK within timeout, returns into user code.
* Command Loop - this is a loop, in client/server mode:
* Flasher sends command, Device responds.
*
* Commands: (each ASCII byte, some have additional data)
* 'I' (0x49) = Identify. No data. Respond ? bytes info
* Useful fields: bootloader version, product version, PIC version?
* memory locations
* Responds with DATA CRC
* 'C' (0x43) = CRC. Compute CRC32K over all flash, and output text, then ACK.
* 'E' (0x45) = Erase. Send 'E' Address CRC, responds ACK CRC
* 'W' (0x57) = Write. Send 'W' Address Length CRC, returns ACK CRC or NACK CRC
* 'Q' (0x51) = Quit. Send 'Q'CRC. Return ACK then Device exits boot loader.
*
******************************************************************************/
/*************************** Notes *********************************************
*
*
* The original idea to make a boot flash bootloader is fraught with problems:
* 1. Boot flash is small, 3K = 0xD00, on many PICs
* 2. Boot flash has a debug executive in it, and several fixed addresses
* that need exception vectors (Reset, boot exception, etc.)
* 3. Debug executive takes significant space (0x760)
* 4. These vectors are set to point to code in the CRT in non-boot flash at
* link time, so any recompile of the use app may move these vectors
* 5. Could fiddle with linker script, and crt enough to make all work, ....
* 6. But this is hard to maintain over time as crt changes....
* 7. So - we make boot code at a fixed, protected address, and the user
* program should call it first thing. Then boot loader allows flashing the
* boot area itself to allow user changes to crt, etc.
* 8. Downside - some chance of the boot loader wiping the code that is called
* before it.
* 9. All this would be ok on 12K boot flash PICs. Make version for there?
*
* BOOT FLASH layout (3K version)
* 0x0000 - 0x037F : 0x380 space, execution starts here
* 0x0380 - 0x047F : space? boot exception vector
* 0x0480 - 0x048F : 0x10 space, debug exception vector jumps into normal FLASH
* 0x0490 - 0x0BDF : 0x760 space, debug code executive, needed to debug
* 0x0BF0 - 0x0BFF configuration registers
*
* Misc notes:
*
* All variables are stored on the stack except one return code to minimize
* RAM usage. Most variables are all in one struct, which is cleared at the end,
* to minimize info leakage to user programs (even though now the C startup code
* should zero memory). As a result, the code uses a decent chunk of RAM, so
* should not be called later from the C strtup.
*
* All functions start with "Boot" to prevent accidentally calling outside
* functions and all use the BOOT_FUNC macro to locate them properly in flash.
*
* The bootloader should be compiled in the application so they share the same
* configuration bits. It is currently not possible to have the bootloader
* change the configuration bits.
*
* The boot page is not erased during entire device flash, but is erased when it
* is about to be overwritten, but only if the jump to bootloader code is
* present in the image about to be flashed. Thus the device can be bricked at
* this brief moment if the page is erased and power fails before the write, or
* if the erased page cannot be written for some reason. I let this page be
* updateable in this special case to allow some crt0 changes in the field.
*
* However, the config page cannot be changed at all - there is no way to do it
* from the device (unless the cfg bits are very luckily in a special config
* that allows flashing them without hanging the machine - check if this is
* possible)
*
* Configuration settings are in boot flash
* - erasing this page erases config bits. Cannot do this!
* Thus we block all access to this page (0x800-0xBFF).
* This may break the debug executive, but should allow changes
* to be made to the exception vectors which is more important.
*
*
* DEVCFG0 bits
* CP : prevent flash reading/modification from external device 0=enabled, 1=disabled
* BWP : prevent boot flash from being modified during code execution 0=not writeable, 1=writeable
* PWP : prevent selected flash pages form being modified during code execution
* 111111111 = disabled
* 111111110 = address below 0x0400 protected
* 111111101 = address below 0x0800 protected
* ....
* 011111111 = address below 0x40000 (256K) protected
* ...
* 000000000 = all possible memory protected
* ICESEL : ICD pins select PGECx/PGEDx x = 1,2,3,4 for bits 11,10,01,00
* JTAGEN, DEBUG,
* DEVCFG1 bits : lots of watchdog timer stuff, clock oscillator stuff
* DEVCFG2 bits : more clock stuff, USB stuff,
* DEVCFG4 bits : USB stuff, peripheral pin stuff, USERID
*
*
* _RESET_ADDR, _BEV_EXCPT_ADDR, _DBG_EXCPT_ADDR and _DBG_CODE_ADDR are all
* determined by the chip's hardware, cannot change them.
*
* _RESET_ADDR -- Reset Vector
* _BEV_EXCPT_ADDR -- Boot exception Vector
* _DBG_EXCPT_ADDR -- In-circuit Debugging Exception Vector
* _DBG_CODE_ADDR -- In-circuit Debug Executive address
* _DBG_CODE_SIZE -- In-circuit Debug Executive size
*
* _RESET_ADDR = 0xBFC00000;
* _BEV_EXCPT_ADDR = 0xBFC00380;
* _DBG_EXCPT_ADDR = 0xBFC00480;
* _DBG_CODE_ADDR = 0x9FC00490; // NOTE this is in boot flash
* _DBG_CODE_SIZE = 0x760;
*
* Logical addresses
* KSEG0 0x80000000 - 0x9FFFFFFF is the same (overlaps) as
* KSEG1 0xA0000000 - 0xBFFFFFFF
* RAM 0x80000000 & 0xA0000000
* FLASH 0x9D000000 & 0xBD000000
* Peripherals 0xBF8000000 ONLY
*
* BOOT 0x9FC00000 & 0xBFC00000, LENGTH = 0xD00 (3K) or 0x3000 (12K)
* CONFIG 0x9FC00BF0 & 0xBFC00BF0, LENGTH = 0x010 NOTE: overlaps boot flash!
*
* Can use xc32-objdump -x Bootloader.o to see where items went:
* "\Program Files (x86)\Microchip\xc32\v1.30\bin\xc32-objdump.exe" -S
* BootLoader.o | more
*
* "How to Get the Least out of your PIC32 C compiler"
* http://www.microchip.com/stellent/groups/SiteComm_sg/documents/DeviceDoc/en557154.pdf
*
* http://microchip.wikidot.com/32bit:mx-arch-exceptions-entry-points
* The Reset, Soft Reset, and NMI exceptions are always vectored to location
* 0xBFC00000 (uncached, start-up safe KSEG1 region).
*
* The original list of desired features is here for posterity. Many features
* were implemented; some were cut due to time or technical reasons.
*
* Features needed/desired
* 1. UART (serial) support, possible Ethernet, SD card, SPI, etc?
* 2. Can identify device to make sure not loading wrong items
* 3. Protected from overwriting itself
* 4. Setting of baud rate at compile time
* 5. Encryption
* 6. Compression
* 7. Blinking LED when available
* 8. Small RAM footprint (small enough for all PICs)
* 9. Page size set by PIC type
* 10. Error checking/re-transmission
* 11. Fires on boot, waits, then if nothing, does normal main
* 12. Small, works on all(?) PIC32?
* 13. how to identify boot loading?
* 14. verify code mode?
* 15. CRC32K throughout
* 16. can also load unencrypted -
* 17. bootloader Version #
* 18. error messages/bytes/stats
* 19. NO: did not need
* - modified crt0.s must modify linker script
* - possibly can make same file with defines used here
* and included in linker script?
* 20. explain how to check linker/load sections
* 21. looked at symmetric and public key methods
* - if either decryption key lifted from device,
* made PKI irrelevant, so pick symmetric:
* - chacha?
* - XXTEA seems good enough even though much weaker than AES.
* It's also very small
* - use ciphertext stealing to keep data short
* - use some block chaining format
* 22. Send data in blocks with CRC allowing resends?
* Or one large block with CRC and verify?
*
*
******************************************************************************/
/*************************** TODO **********************************************
* Things for future versions. X marks done
* 0. check all TODOs :)
* 1. Bootloader version at fixed address to allow outside reference
* 2. Special code to allow erasing all pages, moving bootloader,
* and overwriting the bootloader itself under proper control
* 3. Compression
* 4. Ability to put in boot section for 12K boot flash sizes
* 5. Better/nicer sync between boot utility and flashing code
* X 6. switch ACK in some cases to ACK,reason,CRC?
* useful for counting progress on erase, etc.
* X 7. When erasing: send to the PC the number of pages to be erased,
* then send ACK(BTLDR_ERASE) for each erased page (in order to use
* progress bar in PC software). When finished send BTLDR_OK.
* 8. Sometimes takes a few times to connect under Windows. Some connects are
* very fast, some take seconds. Not sure why.
* 9. Make sure no strings in here that are not in the boot flash
* 10. See if the config bits can be changed - this implies they can
* http://www.microchip.com/forums/m583991.aspx
* The idea is that the device runs from a backup copy, the bootloader
* erases and writes new ones, then upon reset the new ones take effect.
* Is this possible? Would have to the new bits still allow boots.
*
******************************************************************************/
/*********************** defines, types, storage ******************************/
// define this to get state and other debugging messages
// moves the bootloader and does some other things
// makes image larger, so linker script may need modified to make room
// #define DEBUG_BOOTLOADER
// define this to cause flash operations to be ignored
// useful for testing when you don't want flash being changed
// #define IGNORE_FLASH_OPS
// from linker, used to tell the bootloader its size
extern const unsigned int _HCBOOT_LD_SIZE_;
// addresses to protect the bootloader
#define BOOTLOADER_SIZE ((uint32_t)(&_HCBOOT_LD_SIZE_))// must match linker script
#define BOOT_PHYSICAL_ADDRESS 0x1D000000 // note PHYSICAL address
#define BOOT_LOGICAL_ADDRESS 0x9D000000 // note LOGICAL address
// max number of 32-bit instructions scanned to detect bootloader
#define BOOT_INSTRUCTION_SEEK 12
// number of 32-bit instructions matched to detect bootloader
#define BOOT_INSTRUCTION_COUNT 6
// space needed for buffer overhead when loading write packets
#define BUFFER_OVERHEAD 20
// internal ram buffer size used for temp storage
#define BUFFER_SIZE (FLASH_PAGE_SIZE+BUFFER_OVERHEAD)
// used to put code items into the boot rom section we defined in the linker script
#define BOOT_CODE __attribute__((section(".hcbcode")))
// used to put entry point into the boot rom section we defined in the linker script
// needs an extra section extension, else placed incorrectly in the section
#define BOOT_ENTRY __attribute__((section(".hcbcode.entry")))
// used to put data items into the boot rom section we defined in the linker script
// the ',r' part marks the data with a readonly attribute, for linker use
// use x for executable, b for BSS, r for read only, and d for writeable data
#define BOOT_DATA __attribute__((section(".hcbcode")))
// used to put items into the boot ram section we defined in the linker script
#define BOOT_RAM __attribute__((section(".hcbram")))
// Any strings needed must be defined using this, to ensure the storage
// is in the proper flash section, otherwise the linker may put the
// text elsewhere
#define BOOTSTRING(name,text) BOOT_DATA static const char name[] = text
// use to set an item address. Requires logical address
#define FIX_ADDRESS(addr) __attribute__((address(addr)))
// times to retry a write before giving up
#define WRITE_RETY_MAX 5
// how to map logical addresses to physical addresses
#define LOGICAL_TO_PHYSICAL_ADDRESS(addr) ((addr)&0x1FFFFFFF)
// bootloader code version
BOOTSTRING(bootloaderVersion,"0.5");
// Send \r\n to UART
#define ENDLINE() {BootUARTWriteByte('\r');BootUARTWriteByte('\n'); }
// write a single character with the high bit set, useful for debugging
// note that ASCII `,'a'-'z and {|}~ will overlap NACK and ACK codes, so
// don't use them
#define ERROR(ch) BootUARTWriteByte((128+(ch)))
// write a single character, useful
#define WRITE(ch) BootUARTWriteByte((ch))
#define CRYPTO_ROUNDS 20 // for 20 rounds of Salsa20
// ACK is a byte, starts with 0xF0, has 16 lower nibbles
#define ACK(reason) BootUARTWriteByte(reason)
// ACK reasons
enum {
ACK_PAGE_ERASED = 0xF0,
ACK_PAGE_PROTECTED = 0xF1,
ACK_ERASE_DONE = 0xF2,
// reserved for ACK
// the byte that signals a positive outcome to the flashing utility
// has nice property that becomes different values at nearby baud rates
ACK_OK = 0xFC
};
// NACK is a byte, starts with 0xE0, has 16 lower nibbles
#define NACK(reason) BootUARTWriteByte(reason)
// NACK reasons
enum {
// write problems
NACK_CRC_MISMATCH = 0xE0,
NACK_PACKET_SIZE_TOO_LARGE = 0xE1,
NACK_WRITE_WITHOUT_ERASE = 0xE2,
NACK_WRITE_SIZE_ERROR = 0xE3,
NACK_WRITE_MISALIGNED_ERROR = 0xE4,
NACK_WRITE_WRAPS_ERROR = 0xE5,
NACK_WRITE_OUT_OF_BOUNDS = 0xE6,
NACK_WRITE_OVER_CONFIGURATION = 0xE7,
NACK_WRITE_BOOT_MISSING = 0xE8,
NACK_WRITE_FLASH_FAILED = 0xE9,
NACK_COMPARE_FAILED = 0xEA,
NACK_WRITES_FAILED = 0xEB,
// system problems
NACK_UNKNOWN_COMMAND = 0xEC,
// erase problems
NACK_ERASE_FAILED = 0xED,
// currently unused
NACK_UNUSED1 = 0xEE,
NACK_UNUSED2 = 0xEF
};
// outcomes of the bootloader code, for querying
// by the application
enum {
BOOT_RESULT_SKIPPED = 0,
BOOT_RESULT_SUCCESSFUL = 1,
BOOT_RESULT_POWER_EXIT = 2,
BOOT_RESULT_STARTED = 3,
BOOT_SET_HARDWARE_FAILED = -1,
BOOT_RESULT_ASSUMPTIONS_FAILED = -2,
};
// one return variable at bottom of ram
// to access from main code, use :
// extern __attribute__((section(".hcram"))) uint8_t bootResult;
// this variable needs to persist past crt0, so uses the
// "persistent" attribute, which has linker script support
// by default from microchip. Otherwise this result will not be seen in the
// user code. See
// http://www.microchip.com/forums/m594242.aspx
// http://www.microchip.com/forums/m434737.aspx#434882
BOOT_RAM uint8_t bootResult __attribute__ ((persistent));
// this define must match the address of the boot result
#define BOOT_RESULT_VIRTUAL_ADDRESS 0xA0000000
// structure for tracking a timer
typedef struct
{
// for timing
uint32_t timerLast, timerNext, timerExcess;
uint32_t timerCount;// counts ms or us depending on the timer mode
uint32_t ticksPerCount; // can count many things,
} Timer_t;
#ifdef USE_CRYPTO
// crypto state
typedef struct
{
int i;
uint32_t state[16]; // crypto state
uint32_t x[16]; // crypto temp
uint8_t output[64];
int32_t bytes,d64;
} Crypto_t;
#endif
// this is all the local storage the boot loader needs
typedef struct
{
// used to see if flash erased command has completed
// must be done before writing allowed
bool flashErased;
// used to track timeouts and delays
Timer_t timeoutTimerMs;
// used for delays while writing flash
Timer_t nvmTimerUs;
// buffer for receiving pacekt of data from the flash utility
uint8_t buffer[BUFFER_SIZE];
// where in buffer to put next byte read
int readPos;
// number of bytes in packet when finished
int readMax;
// place to store CRC calculations
uint32_t transmittedCrc,computedCrc;
// temp address for iterating over memory
uint32_t curAddress;
// number of pages attempted to be erased
uint32_t pageEraseAttemptCount;
// number of pages failing to erase
uint32_t pageEraseFailureCount;
// address and size of data to write from the buffer into flash
uint32_t writeSize, writeAddress;
// number of writes that failed
// could be rows or single words
uint32_t writeFailureCount;
// count packets since last erase
uint32_t packetCounter;
// set to true on last write packet seen
bool writesFinished;
// counter used to retry writes a few times when flashing
uint32_t writeRetryCounter;
// space for result of a flash write
uint32_t flashWriteResult;
#ifdef USE_CRYPTO
Crypto_t crypto;
#endif
} Boot_t;
/**************************** UART section ***********************************/
#if 0
// if there is any UART error, return 1, else return 0
// todo - clear errors?
BOOT_CODE static int BootUARTError()
{
int err =
U1STAbits.OERR | // overrun error
U1STAbits.FERR | // framing error
U1STAbits.PERR ; // parity error
err |=
U2STAbits.OERR | // overrun error
U2STAbits.FERR | // framing error
U2STAbits.PERR ; // parity error
return err!=0?1:0;
}
#endif
// read byte if one is ready.
// if exists, return true and byte
// if return false, byte = 0 is none avail, else
// byte != 0 means error
BOOT_CODE static bool BootUARTReadByte(uint8_t * byte)
{
// if data ready, get it
if (U1STAbits.URXDA != 0)
{
*byte = U1RXREG;
return true;
}
*byte = 0;
return false;
} // UARTReadByte
// blocking call to write byte to UART
BOOT_CODE static void BootUARTWriteByte(char byte)
{
#ifdef HC_UART1
while (!U1STAbits.TRMT); // wait till bit clear, then ready to transmit
U1TXREG = byte; // write a byte
#else
while (!U2STAbits.TRMT); // wait till bit clear, then ready to transmit
U2TXREG = byte; // write a byte
#endif
}
// print the message to the serial port.
BOOT_CODE static void BootPrintSerial(const char * message)
{
while (*message != 0)
{
BootUARTWriteByte(*message);
message++;
}
}
// print the integer to the serial port.
BOOT_CODE static void BootPrintSerialInt(uint32_t value)
{
uint32_t pow10 = 1; // stack, ends on 0, so ok
// want value/10 < pow10 < value
while (pow10 <= value/10)
pow10 *= 10;
do
{
uint32_t digit = value/pow10;
BootUARTWriteByte(digit+'0');
value -= digit*pow10;
pow10 /=10;
} while (pow10 > 0);
}
// print the integer to the serial port as a n byte hex value
BOOT_CODE static void BootPrintSerialHexN(uint32_t value, int n)
{
int i;
for (i = n; i > 0; --i)
{
int val = (value>>(n*4-4))&15;
if (val < 10)
BootUARTWriteByte(val+'0');
else
BootUARTWriteByte(val+'A'-10);
value<<=4;
}
}
// print the integer to the serial port as a 4 byte hex value
BOOT_CODE static void BootPrintSerialHex(uint32_t value)
{
WRITE('0');
WRITE('x');
BootPrintSerialHexN(value, 8);
}
// initialize the serial port
BOOT_CODE static void BootUARTInit()
{
#if defined(__32MX150F128B__)
#ifdef HC_UART1
// UART1
// U1RX on RPA4
// U1TX on RPA0
// clear PORTA bits
LATACLR = (unsigned int)((1<<0) | (1<<4)); // BIT 0 and 4
// set PORTA input pins
TRISASET = (unsigned int)(1<<4);
ANSELACLR = (unsigned int)(1<<4);
// set PORTA output pins
TRISACLR = (unsigned int)(1<<0);
ANSELACLR = (unsigned int)(1<<0);
U1RXR = 2; // RPB2 = U1RX
RPA0R = 1; // PIN RPA0 = U1TX
// clock divider
U1BRG = SYS_CLOCK /(4*DESIRED_BAUDRATE) - 1;
// mode
U1MODE =
(1<<15) | // module enable
(1<< 3) | // 4x clock speed, high speed
0;
// TX/RX features
U1STA =
(1<<12) | // RX_ENABLE
(1<<10) | // TX_ENABLE
0;
#else
todo - UART2
#endif
#else
#pragma error "Unknown PIC version"
todo - error!
#endif
}
// print debug messages if the debugging define is set, else ignore them
#ifdef DEBUG_BOOTLOADER
#define BootDebugPrint(message) BootPrintSerial(message)
#define BootDebugPrintE(message) {BootPrintSerial(message); ENDLINE();}
#else
#define BootDebugPrint(message)
#define BootDebugPrintE(message)
#endif
#ifdef DEBUG_BOOTLOADER
// helper function that dumps memory over serial as text
BOOT_CODE static void BootPrintMemory(const char * msg, uint32_t address, int length)
{
// some memory dump to help debugging
BootDebugPrintE(msg);
int i;
for (i = 0; i < length; ++i)
{
uint32_t addr = address + i;
if ((i&7)==0)
{
BootPrintSerialHex(addr);
BootPrintSerial(" : ");
}
BootPrintSerialHexN((uint8_t)(*((uint8_t*)addr)),2);
BootPrintSerial(" ");
if ((i&7)==7)
{
ENDLINE();
}
}
}
#else
#define BootPrintMemory(msg,address,length)
#endif
/*************************** Utility section **********************************/
// compute overlap of intervals [a0,a1) and [b0,b1)
BOOT_CODE static uint32_t BootOverlap(
uint32_t a0, uint32_t a1,
uint32_t b0, uint32_t b1)
{
// trim [a0,a1) to the interval
a0 = a0>b0?a0:b0; // max, lower bound of intersection interval, inclusive
a1 = a1<b1?a1:b1; // min, upper bound of intersection interval, exclusive
return a1<=a0?0:a1-a0; //if empty, return 0, else positive length
}
// set the core tick counter
BOOT_CODE static void BootWriteTimer(uint32_t time)
{
asm volatile("mtc0 %0, $9": "+r"(time));
}
// read the core tick counter
BOOT_CODE static uint32_t BootReadTimer()
{
uint32_t time;
asm volatile("mfc0 %0, $9" : "=r"(time));
return time;
}
// initialize the timer fields
// call with TICKS_PER_MILLISECOND or TICKS_PER_MICROSECOND
BOOT_CODE static void BootStartTimer(Timer_t * timer, uint32_t ticksPerCount)
{
timer->timerLast = BootReadTimer();
timer->timerCount = 0;
timer->timerExcess = 0;
timer->ticksPerCount = ticksPerCount;
}
// initialize the timer fields
// update the timer fields, and return current time since start in
// whatever tick sizes were selected at timer start
BOOT_CODE static uint32_t BootUpdateTimer(Timer_t * timer)
{
timer->timerNext = BootReadTimer();
if (timer->timerNext - timer->timerLast + timer->timerExcess >= timer->ticksPerCount)
{
timer->timerExcess = timer->timerNext - timer->timerLast + timer->timerExcess - timer->ticksPerCount;
timer->timerCount++;
}
else
timer->timerExcess += timer->timerNext - timer->timerLast;
timer->timerLast = timer->timerNext;
return timer->timerCount;
}
// wait the given number of counts based on the ticks per count parameter
// uses the Boot timer
BOOT_CODE static void BootDelay(Timer_t * timer, uint32_t ticksPerCount, int counts)
{
BootStartTimer(timer, ticksPerCount);
while (BootUpdateTimer(timer)<counts)
{
// do nothing
}
}
BOOT_CODE static void BootReadBigEndian(uint32_t * answer, uint8_t * buffer, int32_t bytes)
{
*answer = 0;
while (bytes>0)
{
*answer <<= 8;
*answer += *buffer;
buffer++;
bytes--;
}
}
/*************************** CRC32K section ***********************************/
// Compute the CRC32K of the given data.
// done without tables to save space in bootloader
// The initial crc value should be 0, and this can be chained across calls.
// returns the new CRC
BOOT_CODE static uint32_t BootCrc32AddByteBitwise(uint8_t datum, uint32_t crc32)
{
#define poly 0x741B8CD7U
crc32 ^= (uint32_t)(datum << 24);
#if 1
// 1 bit per line
crc32 = (crc32 & 0x80000000U) == 0 ? (crc32<<1) : (crc32<<1)^poly;
crc32 = (crc32 & 0x80000000U) == 0 ? (crc32<<1) : (crc32<<1)^poly;
crc32 = (crc32 & 0x80000000U) == 0 ? (crc32<<1) : (crc32<<1)^poly;
crc32 = (crc32 & 0x80000000U) == 0 ? (crc32<<1) : (crc32<<1)^poly;
crc32 = (crc32 & 0x80000000U) == 0 ? (crc32<<1) : (crc32<<1)^poly;
crc32 = (crc32 & 0x80000000U) == 0 ? (crc32<<1) : (crc32<<1)^poly;