forked from esp8266/Arduino
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhwdt_app_entry.cpp
1207 lines (1091 loc) · 44.6 KB
/
hwdt_app_entry.cpp
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
/*
* Copyright 2020 Michael Hightower
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* As far as I know, there is no way to get called for a Hardware WDT. I assume
* it generates a form of reset that occurs at a low level that cannot be
* trapped. Debugging an HWDT can be quite challenging.
*
* This module writes a stack dump to the serial port after a Hardware Watchdog
* Timer has struck, and a new boot cycle has begun. By making adjustments to the
* stack, we can avoid crash stack data being overwritten by this tool,
* the Boot ROM, and the bootloader.
*
* We are using the method defined for `core_esp8266_app_entry_noextra4k.cpp` to
* load an alternate `app_entry_redefinable()`. For details on this method, see
* comments in `core_esp8266_main.cpp's app_entry()`.
*
* Using this tool's alternate `app_entry_redefinable()`, we can gain control
* before the SDK is started. And dump the contents of the "sys" and "cont"
* stacks.
*
* By making some adjustments to start of the stack pointer, at the entry to
* `app_entry_redefinable()`, and also to the stack pointer passed to the SDK,
* we can preserve the stack during an HWDT event.
*
* To use this tool, select HWDT or HWDT_NOEXTRA4K from the Arduino IDE menu
* "Tools->Debug Level" before building your sketch. Note, 'Tools->Debug port'
* selection is not needed or referenced for printing the HWDT stack dump.
*
* To enable in other build environments, add DEBUG_ESP_HWDT_NOEXTRA4K or
* DEBUG_ESP_HWDT global defines to your build.
*
* This tool prints to the serial port at the default serial port speed set by
* the Boot ROM. On a Hardware WDT reset that port speed is 115200 bps. If your
* needs differ, see the DEBUG_ESP_HWDT_UART_SPEED option below.
*
* More on crystal differences and data rates:
* When the ESP8266 restarts because of a Hardware WDT reset, the port speed
* defaults to 115200. This will be the speed, even if you have a 26MHz or
* 40MHz Crystal. If you would like to use a different data rate, use the
* option DEBUG_ESP_HWDT_UART_SPEED described below.
*
* The Boot ROM initially sets the UART clock divisor to support a data rate
* of 115200 bps with the assumption that it has a 40MHz crystal. When a
* 26MHz crystal is used instead, the resulting error gives us a real rate
* of 74880 bps and printing is garbled at first, until the CPU clock's PLL
* is adjusted by the NONOS SDK. While CH_PD and EST_RST bring the CPU back
* to this state of underclocking with a 26MHz crystal, the Hardware WDT
* reset does not appear to do the same. The UART continues to be clocked at
* a rate to support a device at 115200 bps. Thus, allowing us to see the
* old cryptic WDT message along with our new stack dump.
*
*
* When you get a stack dump, copy-paste it into the "ESP Exception Decoder".
* Since we don't have an SP, we see a lot more stuff in the report, compared to
* what you would see with a postmortem report. Start at the bottom and work
* your way up. At this time, I have not had a lot of practice using this tool.
* TODO: Update description with more details when available.
*
* SYS Stack Issue with Extra 4K Heap option:
* During WiFi Connect, Reconnect, and about every hour a block of memory
* 0x3FFFEA80 - 0x3FFFEB30 (176 bytes) is zeroed by the Boot ROM function
* aes_decrypt_init. All other painted memory in the area was untouched
* after starting WiFi. See `core/esp8266/aes_unwrap.cpp` for more details.
*
*
* Possible Issues/Thoughts/Improvements:
*
* On reboot after an OTA download, eboot requires a lot of stack and DRAM
* space. On the other hand, for routine loads from flash, the stack and DRAM
* usage is small, leaving us valid data to print a stack dump.
*
* If a problem should arise with some data elements being corrupted during
* reboot, would it be possible to move their DRAM location higher in memory?
*
* Also, DRAM being valid across reset events other than power-on and deep
* sleep, suggest that a variable in the .noinit section could be used instead
* of the more limited RTC Memory for sketches that don't do deep sleep.
* However, DRAM should be considered invalid after an upload serial or OTA.
* These operations use a lot of DRAM.
*
* With this module active, postmortem stack dumps will be a little longer than
* they need to be. The sys stack now ends at 0x3FFFFC00 instead of 0x3FFFFB0.
*
* Maybe an in/out ref count would be nice for bearssl and cont stacks.
*/
/*____________________________________________________________________________*/
/* */
/* Configuration Options */
/*____________________________________________________________________________*/
/*
* DEBUG_ESP_HWDT
*
* Enables this debug tool for printing a Hardware WDT stack dump at restart.
*
* This option is now managed from the Arduino IDE menu 'Tools->Debug Level'
#define DEBUG_ESP_HWDT
*/
/*
* DEBUG_ESP_HWDT_NOEXTRA4K
*
* This option will leave more of the system stack available for the stack dump.
* A problem we have with the "4K extra" option, is it pushes the system stack
* up into the ROM's BSS area which gets zeroed at reboot by the Boot ROM.
*
* Using this option has the effect of taking 4K of DRAM away from the heap,
* which gets used for the "cont" stack. Leaving an extra 4K on the "sys" stack,
* that is clear of the ROM's BSS area. This allows for a more complete "sys"
* stack dump. The choice here can depend on where you are crashing.
*
* Because we don't know where the crash occurs, this option prints two stack
* dumps, one for "cont" (user stack) and one for "sys" (NONOS SDK).
*
* In contrast, if the hang is happening on the "cont" stack, you don't need a
* complete stack dump of the "sys" stack. You can omit this define and have an
* extra 4K in the heap.
*
* This option is now managed from the Arduinoo IDE menu 'Tools->Debug Level'
#define DEBUG_ESP_HWDT_NOEXTRA4K
*/
/*
* DEBUG_ESP_HWDT_UART_SPEED
*
* This option alters the UART serial speed used for printing the Hardware WDT
* reset stack dump. Without this option on an HWDT reset, the existing default
* speed of 115200 bps will be used. If you are using this default speed, you
* can skip this option and save on the IRAM space. Note this option only
* changes the speed while this module is printing.
*
* For more confusion on the serial port speed, see "More on crystal differences
* and data rates" in the comments at the top.
*
*/
// #define DEBUG_ESP_HWDT_UART_SPEED (19200)
// #define DEBUG_ESP_HWDT_UART_SPEED (74880)
// #define DEBUG_ESP_HWDT_UART_SPEED (115200)
// #define DEBUG_ESP_HWDT_UART_SPEED (230400)
/*
* DEBUG_ESP_HWDT_PRINT_GREETING
*
* Prints a simple introduction to let you know this tool is active and in the
* build. At power-on, this may not be viewable on some devices. The crystal has
* to be 40Mhz for this to work w/o using the DEBUG_ESP_HWDT_UART_SPEED option
* above. May not be worth the cost in IRAM.
*
* EDIT: There is something different in the UART setup after a flash upload. I
* am unable to print using the same code that works for Power-on and an EXT_RST
* at any other time. After the SDK has run a 2nd EXT_RST will show the greeting
* message.
*
* EDIT2: Seems to work better now. Placed delays around calls to
* uart_div_modify(). Leave these comments until I have more experience with
* this change.
*
* EDIT3: The delay before the uart_div_modify() has been replaced with a wait
* till FIFO empty loop. I now believe the lost greeting message after an
* esptool firmware update, has to do with the transition period between the
* tool performing hardware reset and exiting, then the serial monitor
* re-engaging. This is not an issue that needs to be addressed here.
*/
#ifndef DEBUG_ESP_HWDT_PRINT_GREETING
#define DEBUG_ESP_HWDT_PRINT_GREETING (1)
#endif
/*
* DEBUG_ESP_HWDT_ROM_STACK_SIZE
*
* There are four sections of code starting just before 0x40000000, that share
* the same stack space.
* 1) The Boot ROM (uses around 640 bytes)
* 2) The Bootloader, eboot.elf (uses around 720 bytes.)
* 3) `app_entry_redefinable()` just before it starts the SDK.
* 4) The NONOS SDK and optionally the Core when the extra 4K option is selected.
*
* To preserve the sketch stack data for a stack dump, I define three separate
* stacks:
* 1) Boot ROM and eboot
* 2) this stack dump code
* 3) SDK, Core, and Sketch
*
* For the "NO extra 4K Heap" case, we use a ROM stack size of 1024. However,
* without `aes_unwrap.cpp`, 1024 is not safe for the "extra 4K of heap" case.
* Bad crashes happen with the 1024 and the "extra 4K of Heap". For now, leave
* this case with 720 bytes for ROM Stack since it also gives more SYS stack for
* dumping. See comment in aes_unwrap.cpp for AES buffer clash with SYS stack
* space description.
*
* If or when eboot.elf uses more than 720 there will be a little over-writing
* of the cont stack that we report. Then 720 can be increased as long as the
* replacement aes_unwrap is used.
*
* If possible, use the no-extra 4K heap option. This is the optimum choice for
* debugging HWDT crashes. It has the content of SYS stack fully exposed.
*
*/
#ifndef DEBUG_ESP_HWDT_ROM_STACK_SIZE
#ifdef DEBUG_ESP_HWDT_NOEXTRA4K
#define DEBUG_ESP_HWDT_ROM_STACK_SIZE (1024UL)
#else
#define DEBUG_ESP_HWDT_ROM_STACK_SIZE (720UL)
#endif
#endif
/*
* DEBUG_ESP_HWDT_INFO
*
* Gather some information on ROM and bootloader combined, sys, and cont stack
* usage. If you are missing the include file for this structure, you can
* copy-paste from the embedded version of the .h below.
*
*/
// #define DEBUG_ESP_HWDT_INFO
/*
* ROM_STACK_DUMP
*
* Dump the stack contents of the ROM Stack area. This gives us a visual of the
* stack usage. Probably not of value, beyond developing this tool.
*
* To see printing, you may need to use this option with DEBUG_ESP_HWDT_UART_SPEED.
*/
// #define ROM_STACK_DUMP
/*
* HWDT_IF_METHOD_RESET_REASON
*
* "If" statement or "switch" method to implement, the reset reason logic. Both
* can be made smaller by removing confirmation checks.
*
* Checks are performed when DEBUG_ESP_HWDT_DEV_DEBUG_RESET_REASON has been defined.
*
* EDIT: I should settle on one or the other; however, new issues continue to
* pop up on determining reset reason. I'll wait until later and pick one.
*
#define DEBUG_ESP_HWDT_DEV_DEBUG_RESET_REASON
*/
#define HWDT_IF_METHOD_RESET_REASON
/*____________________________________________________________________________*/
/* */
/* End of Configuration Options */
/*____________________________________________________________________________*/
#if defined(DEBUG_ESP_HWDT) || defined(DEBUG_ESP_HWDT_NOEXTRA4K)
#include <c_types.h>
#include "cont.h"
#include "coredecls.h"
#include <core_esp8266_features.h>
#include <esp8266_undocumented.h>
#include <esp8266_peri.h>
#include <uart.h>
#include <pgmspace.h>
#include "umm_malloc/umm_malloc.h"
#include "mmu_iram.h"
extern "C" {
#include <user_interface.h>
extern void call_user_start();
extern uint32_t rtc_get_reset_reason(void);
uint32_t __zero_return() {
return 0;
}
// extern void stack_thunk_dump_stack();
extern uint32_t stack_thunk_get_refcnt() __attribute__((weak, alias("__zero_return")));
extern uint32_t stack_thunk_get_stack_top() __attribute__((weak, alias("__zero_return")));
extern uint32_t stack_thunk_get_stack_bot() __attribute__((weak, alias("__zero_return")));
}
// #define DEBUG_ESP_HWDT_DEV_DEBUG
// #define USE_IRAM
//C To the maintainers: Since the Cache_Read_Enable logic seems to work, do you
//C see a need or would you like to keep the IRAM build path?
//C If you see no need for it the USE_IRAM build path could be deleted.
#ifdef USE_IRAM
#undef USE_IRAM
#define USE_IRAM 1
#define IRAM_MAYBE IRAM_ATTR
#else
#undef USE_IRAM
#define IRAM_MAYBE
#endif
#define STATIC static __attribute__((noinline))
// #define STATIC
#ifdef DEBUG_ESP_HWDT_DEV_DEBUG
/*
* We have two copies of hwdt_info_t. Verify internal and external structures
* match.
*
* This duplication is done so that in most cases, a simple/quick add one file
* to a sketch folder is enough to debug.
*
* Only if additional internal information is needed, would this include be
* added. Since we have two copies, a static_assert is used to verify that at
* least the the size of the two structures are the same.
*/
#include "hwdt_app_entry.h"
#endif
/*
* Verify that the internal and external structure definitions match.
*/
#ifdef HWDT_STACK_DUMP_H
#define hwdt_info_t LOCAL_HWDT_INFO_T
#define hwdt_info_ LOCAL_HWDT_INFO_
#define hwdt_info LOCAL_HWDT_INFO
#define HWDT_VERIFY_HWDT_INFO
#endif
/*
* If you are using the hwdt_info_t structure, and are missing the include file.
* Copy-paste the include block below into its respective filename.
*/
/*____________________________________________________________________________*/
/* */
/* Start of copy-paste block to create "hwdt_app_entry.h" */
/*____________________________________________________________________________*/
#if !defined(HWDT_STACK_DUMP_H) || defined(HWDT_VERIFY_HWDT_INFO)
#define HWDT_STACK_DUMP_H
typedef struct hwdt_info_ {
uint32_t rom;
uint32_t sys;
uint32_t cont;
uint32_t bearssl;
uint32_t rom_api_reason;
uint32_t rtc_sys_reason;
uint32_t reset_reason;
uint32_t cont_integrity;
bool g_pcont_valid;
} hwdt_info_t;
extern "C" void debug_hwdt_init(void);
extern uint32_t *g_rom_stack;
extern hwdt_info_t hwdt_info;
#endif
/*____________________________________________________________________________*/
/* */
/* End of copy-paste block for creating "hwdt_app_entry.h" */
/*____________________________________________________________________________*/
#ifdef HWDT_VERIFY_HWDT_INFO
#undef hwdt_info_t
#undef hwdt_info_
#undef hwdt_info
#undef HWDT_VERIFY_HWDT_INFO
static_assert(sizeof(hwdt_info_t) == sizeof(LOCAL_HWDT_INFO_T), "Local and include version of hwdt_info_t do not match.");
#endif
#define MK_ALIGN16_SZ(a) (((a) + 0x0FUL) & ~0x0FUL)
#define ALIGN_UP(a, s) ((decltype(a))((((uintptr_t)(a)) + (s-1)) & ~(s-1)))
#define ALIGN_DOWN(a, s) ((decltype(a))(((uintptr_t)(a)) & ~(s-1)))
#ifndef CONT_STACKGUARD
#define CONT_STACKGUARD 0xfeefeffe
#endif
#define RTC_SYS ((volatile uint32_t*)0x60001100UL)
/*
*
#define DRAM_START ((uint32_t *)0x3FFE8000UL)
#define DRAM_END ((uint32_t *)0x40000000UL)
*
* The space between 0x3fffe000 up to 0x3fffeb30 is a ROM BSS area that is later
* claimed by the SDK for stack space. This is a problem area for this tool,
* because the ROM BSS gets zeroed as part of ROM init on reboot. Any part of
* the "sys" stack residing there is lost. On the other hand, it becomes a prime
* candidate for DRAM address space to handle the needs of this tool.
*
#define SYS_STACK_E000 ((uint32_t *)0x3fffe000UL)
*/
#define ROM_STACK_FIRST ((uint32_t *)0x40000000UL)
#define SYS_STACK ((uint32_t *)0x3fffeb30UL)
// Map out who will live where.
#define ROM_STACK_A16_SZ (MK_ALIGN16_SZ(DEBUG_ESP_HWDT_ROM_STACK_SIZE))
#define CONT_STACK_A16_SZ (MK_ALIGN16_SZ(sizeof(cont_t)))
/*
* For WPS support, cont stack comes out of the user's heap address space.
* The NONOS-SDK stack address is initialized before the reserved ROM stack
* space. In this configuration there is no extra 4K in the heap.
* Memory map: 0x3FFE8000, ..., (CONT_STACK), ..., (SYS), (ROM_STACK), 0x4000000
*
* sys_stack_first <= ROM_STACK
*/
#define ROM_STACK ((uint32_t *) ((uintptr_t)ROM_STACK_FIRST - ROM_STACK_A16_SZ))
#define CONT_STACK_FIRST ROM_STACK // only for computation
/*
* For extra 4K of heap space, the continuation stack (user's stack) is created
* in the SYS stack address space. The NONOS-SDK stack starts before the cont
* stack.
* Memory map: 0x3FFE8000, ..., (SYS), (CONT_STACK), (ROM_STACK), 0x4000000
*
* sys_stack_first <= CONT_STACK
*/
#define CONT_STACK ((cont_t *)((uintptr_t)CONT_STACK_FIRST - CONT_STACK_A16_SZ))
uint32_t *g_rom_stack __attribute__((section(".noinit")));
uint32_t *sys_stack_first __attribute__((section(".noinit")));
size_t g_rom_stack_A16_sz __attribute__((section(".noinit")));
hwdt_info_t hwdt_info __attribute__((section(".noinit")));
extern "C" {
extern cont_t * get_noextra4k_g_pcont(void);
cont_t * IRAM_ATTR get_noextra4k_g_pcont(void) __attribute__((weak));
cont_t * IRAM_ATTR get_noextra4k_g_pcont(void) {
return NULL;
}
static void IRAM_MAYBE set__sys_stack_first(void) {
if (get_noextra4k_g_pcont()) {
sys_stack_first = ROM_STACK;
} else {
sys_stack_first = (uint32_t *)CONT_STACK;
}
}
#if USE_IRAM
#define ETS_PRINTF ets_uart_printf
#else
/*
* This function is already in umm_malloc for some debug options.
* Define here in case they are not enabled.
*/
int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) __attribute__((weak));
int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) {
char ram_buf[strlen_P(fmt) + 1];
strcpy_P(ram_buf, fmt);
va_list argPtr;
va_start(argPtr, fmt);
int result = ets_vprintf(ets_uart_putc1, ram_buf, argPtr);
va_end(argPtr);
return result;
}
#define ETS_PRINTF(fmt, ...) umm_info_safe_printf_P(PSTR(fmt), ##__VA_ARGS__)
#endif
#define ETS_FLUSH(uart_no) while((USS(uart_no) >> USTXC) & 0xff) {}
enum PRINT_STACK {
CONT = 1,
SYS = 2,
ROM = 4,
BEARSSL = 8
};
STATIC void IRAM_MAYBE print_stack(const uintptr_t start, const uintptr_t end, const uint32_t chunk) {
ETS_PRINTF("\n\n>>>stack>>>\n\nctx: ");
if (chunk & PRINT_STACK::CONT) {
ETS_PRINTF("cont");
} else
if (chunk & PRINT_STACK::SYS) {
ETS_PRINTF("sys");
} else
if (chunk & PRINT_STACK::ROM) {
ETS_PRINTF("ROM");
} else
if (chunk & PRINT_STACK::BEARSSL) {
ETS_PRINTF("bearssl");
}
ETS_PRINTF("\nsp: %08x end: %08x offset: %04x\n", start, end, 0);
const size_t this_mutch = end - start;
if (this_mutch >= 0x10) {
for (size_t pos = 0; pos < this_mutch; pos += 0x10) {
const uint32_t *value = (uint32_t *)(start + pos);
/* rough indicator: stack frames usually have SP saved as the second word */
bool looksLikeStackFrame = (value[2] == (start + pos + 0x10));
ETS_PRINTF("%08x: %08x %08x %08x %08x %c\n", (uint32_t)&value[0],
value[0], value[1], value[2], value[3],
(looksLikeStackFrame)?'<':' ');
}
}
ETS_PRINTF("<<<stack<<<\n");
}
STATIC const uint32_t * IRAM_MAYBE skip_stackguard(const uint32_t *start, const uint32_t *end, const uint32_t pattern) {
// Find the end of SYS stack activity
const uint32_t *uptr = start;
size_t this_mutch = (uintptr_t)end - (uintptr_t)start;
this_mutch /= sizeof(uint32_t);
size_t i = 0;
for (; i < this_mutch; i++) {
if (pattern != uptr[i]) {
i &= ~3U;
uptr = &uptr[i];
break;
}
}
if (i == this_mutch) {
uptr = &uptr[i];
}
return uptr;
}
bool IRAM_MAYBE hwdt_check_g_pcont_validity(void) {
/*
* DRAM appears to remain valid after most resets. There is more on this in
* handle_hwdt().
*
* Testing of vital pointers for validity could also aid as a partial
* indicator of power-on. Not needed for that purpose at this time.
*/
cont_t *noextra4k_g_pcont = get_noextra4k_g_pcont();
if (g_rom_stack == ROM_STACK &&
g_rom_stack_A16_sz == ROM_STACK_A16_SZ &&
g_pcont == ((noextra4k_g_pcont) ? noextra4k_g_pcont : CONT_STACK)
) {
hwdt_info.g_pcont_valid = true;
} else {
hwdt_info.g_pcont_valid = false;
g_rom_stack = ROM_STACK;
g_rom_stack_A16_sz = ROM_STACK_A16_SZ;
}
return hwdt_info.g_pcont_valid;
}
#if defined(DEBUG_ESP_HWDT_DEV_DEBUG) || defined(DEBUG_ESP_HWDT_DEV_DEBUG_RESET_REASON)
#ifndef DEBUG_ESP_HWDT_DEV_DEBUG_RESET_REASON
#define DEBUG_ESP_HWDT_DEV_DEBUG_RESET_REASON
#endif
#define debug__confirm_rom_reason(a) ((a) == rom_api_reason)
#else
#define debug__confirm_rom_reason(a) (true)
#endif
typedef enum ROM_RST_REASON { /* Comments on the right are from RTOS SDK */
NO_MEAN = 0, /* Undefined */
POWERON_RESET = 1, /* Power on boot *//**<1, Vbat power on reset */
EXT_RESET = 2, /* External reset or wake-up from Deep-sleep */
/**<2, external system reset */
SW_RESET = 3, /* *//**<3, Software reset digital core */
OWDT_RESET = 4, /* Hardware WDT reset *//**<4, Legacy watch dog reset digital core */
DEEPSLEEP_RESET = 5, /* *//**<5, Deep Sleep reset digital core */
SDIO_RESET = 6, /* *//**<6, Reset by SLC module, reset digital core*/
} ROM_RST_REASON_t;
STATIC uint32_t IRAM_MAYBE get_reset_reason(bool* power_on, bool* hwdt_reset) {
/*
* Detecting a Hardware WDT (HWDT) reset is a little complicated at boot
* before the SDK is started.
*
* While the ROM API will report an HWDT, it does not change the status
* after a software restart. And the SDK has not been started so its API is
* not available.
*
* There is a value in System RTC memory that appears to store, at restart,
* the reset reason for the SDK. I assume it is set by SDK before the
* restart. Of course, this value is invalid at power on before the SDK
* runs. It is set to 0 by the SDK after init and later is changed to a 1
* and remains 1 during the operation of the sketch. So during normal
* execution the reason is preset to indicate a Hardware WDT reset.
*
* These cases represent some examples. The list is not meant to be
* comprehensive.
*
* Case 1: At power-on boot the ROM API result is valid; however, the SDK
* value in RTC Memory has not been set at this time.
*
* Case 2: A HWDT reset has occurred, which is later followed with a
* restart by the SDK. At boot, the ROM API result still reports the HWDT
* reason.
*
* Case 3: It is pssible to see a value of 0 or 1 (power-on or HWDT) for the
* SDK reset reason for both EXT_RST and Hardware WDT resets. For either of
* these values, the ROM API does hold the valid reset reason.
*
* I need to know if this is the 1st boot at power on. Combining these
* indicators has been tricky, I think I now have it.
*
* Maybe a TODO: This logic is for the general case of detecting the true
* reset cause before the SDK is started. It may be possible to reduce to
* a specific HWDT test. Leaving for now.
*/
hwdt_info.rtc_sys_reason = RTC_SYS[0];
const uint32_t rtc_sys_reason = hwdt_info.rtc_sys_reason;
hwdt_info.rom_api_reason = rtc_get_reset_reason();
const ROM_RST_REASON_t rom_api_reason = (ROM_RST_REASON_t)hwdt_info.rom_api_reason;
//C To maintainers: Cleanup: Of these two methods is there one you prefer?
//C The other could be deleted.
//C I did it two ways looking to see if one was shorter than the other. I think
//C they ended out being about the same. Also coding it twice, helped check my
//C logic.
#ifdef HWDT_IF_METHOD_RESET_REASON
*hwdt_reset = false;
*power_on = false;
/*
* This logic takes the reason left in memory by the SDK as an initial
* estimate and improves on it.
*/
hwdt_info.reset_reason = rtc_sys_reason;
if (REASON_DEFAULT_RST == rtc_sys_reason ||
REASON_WDT_RST == rtc_sys_reason) {
/*
* 1) The 0 value (REASON_DEFAULT_RST) shows up with multiple EXT_RSTs
* quickly. The 1 value (REASON_WDT_RST), previous if, shows up if
* you wait a while before the EXT_RST.
* 2) The 0 value also shows up if a HWDT reset occurs too quickly after
* the system starts. Note the current SDK will get this one wrong
* and indicate power-on reset.
*/
if (OWDT_RESET == rom_api_reason) {
*hwdt_reset = true;
hwdt_info.reset_reason = REASON_WDT_RST;
} else {
hwdt_info.reset_reason = REASON_EXT_SYS_RST;
if (!debug__confirm_rom_reason(EXT_RESET)) {
hwdt_info.reset_reason = ~0;
}
}
} else if (REASON_EXT_SYS_RST < rtc_sys_reason) {
/*
* We only want to indicate power-on, if the ROM API reason confirms it.
* A reliable power-on indicator is need for set_uart_speed() to work
* properly.
*/
*power_on = true;
hwdt_info.reset_reason = REASON_DEFAULT_RST;
if (!debug__confirm_rom_reason(POWERON_RESET)) {
hwdt_info.reset_reason = ~0;
*power_on = false;
}
} else {
/*
* REASON_EXT_SYS_RST is not expected at reboot, let it fall through
* for confirmation in debug option.
*/
if (REASON_EXT_SYS_RST == rtc_sys_reason) {
if (!debug__confirm_rom_reason(EXT_RESET)) {
hwdt_info.reset_reason = ~0;
}
}
}
#else
// New reset reason logic test
*hwdt_reset = false;
*power_on = false;
switch(rtc_sys_reason) {
case REASON_DEFAULT_RST:
/*
* This can be present for REASON_EXT_SYS_RST or REASON_WDT_RST
* The rtc_sys_reason starts off at 0 and is set to 1 later,
* if crash occurs before set, then it is still 0.
*/
case REASON_WDT_RST:
/*
* This may be present for REASON_EXT_SYS_RST or REASON_WDT_RST,
* use rom_api_reason to confirm.
*/
if (OWDT_RESET == rom_api_reason) {
hwdt_info.reset_reason = REASON_WDT_RST;
*hwdt_reset = true;
} else {
hwdt_info.reset_reason = REASON_EXT_SYS_RST;
if (!debug__confirm_rom_reason(EXT_RESET)) {
hwdt_info.reset_reason = ~0;
}
}
break;
/* These should be correct as is */
case REASON_EXCEPTION_RST:
case REASON_SOFT_WDT_RST:
case REASON_SOFT_RESTART:
case REASON_DEEP_SLEEP_AWAKE:
hwdt_info.reset_reason = rtc_sys_reason;
break;
/*
* REASON_EXT_SYS_RST is not expected at reboot, let it fall through to
* default for confirmation.
*/
case REASON_EXT_SYS_RST:
default:
/*
* Out of range value, this could be a REASON_DEFAULT_RST,
* use rom_api_reason to confirm.
*/
if (POWERON_RESET == rom_api_reason) {
hwdt_info.reset_reason = REASON_DEFAULT_RST;
*power_on = true;
} else {
hwdt_info.reset_reason = REASON_EXT_SYS_RST;
if (!debug__confirm_rom_reason(EXT_RESET)) {
hwdt_info.reset_reason = ~0;
}
}
break;
}
#endif
return hwdt_info.reset_reason;
}
#ifdef DEBUG_ESP_HWDT_UART_SPEED
/*
* Here we use uart_div_modify in the Boot ROM. Note the Boot ROM version does
* not do any input validation.
*
* We cannot use the SDKs replacement. It is not in IRAM.
*/
#ifndef ROM_uart_div_modify
#define ROM_uart_div_modify 0x400039d8
#endif
typedef void (*fp_uart_div_modify_t)(uint32_t uart_no, uint32 DivLatchValue);
// const fp_uart_div_modify_t real_uart_div_modify = (fp_uart_div_modify_t)ROM_uart_div_modify;
#define real_uart_div_modify ((fp_uart_div_modify_t)ROM_uart_div_modify)
#define UART_CLKDIV_MASK 0x000FFFFFUL
void adjust_uart_speed(uint32_t uart_divisor) {
/*
* Take care to let serial data in the FIFO finish before changing UART
* speed. Then after changing speeds, let the uart clock generator
* stablilize, and let the remote receiver come to an idle state before
* continuing.
*
* Load a Rubout character for the final character shifting out to stop
* the last character from getting crunched during the speed change.
*
* The thinking is if the speed changes while shifting out, as long as the
* start bit gets out before the change. The change will not be noticed
* since all ones will look like the serial line idle state. And for text
* displaying, received rubout characters should be discarded. At least that
* was true 40 years ago.
*
* EDIT: In today's world, it appears the rubout character is no longer
* discarded. However, I think we are better off using it since it allows
* for creating a deterministic last character that will not clear the
* screen or something worse. And it is simpler than the other option of
* calculating and waiting for a character time to pass.
*
* These adjustments appear to resolve the lost data problem that occurs
* when printing after a flash upload using esptool.
*/
ets_putc('\xFF');
ETS_FLUSH(0);
real_uart_div_modify(0, uart_divisor);
ets_delay_us(150);
}
STATIC uint32_t IRAM_MAYBE set_uart_speed(const uint32_t uart_no, const uint32_t new_speed) {
uint32_t uart_divisor = USD(uart_no) & UART_CLKDIV_MASK;
/*
* No adjustments are needed on most reboots, etc.
*
* The UART clock is independent of the CPU Speed. (ie. 80MHz, 160MHz)
* UART_CLK_FREQ is used in user_init, and ESP8266_CLOCK is used in
* uart.h. Both are defined to be 80000000.
*/
#if (1 < F_CRYSTAL)
constexpr uint32_t crystal_freq = F_CRYSTAL;
#else
constexpr uint32_t crystal_freq = 26000000;
#endif
uint32_t master_freq = UART_CLK_FREQ;
if (REASON_DEFAULT_RST == hwdt_info.reset_reason ||
REASON_EXT_SYS_RST == hwdt_info.reset_reason ||
REASON_DEEP_SLEEP_AWAKE == hwdt_info.reset_reason) {
/*
* At this time, with power on boot or EXT_RST, the CPU Frequency
* calibration has not happened. Thus for a 26MHz Xtal, the CPU clock is
* running at 52MHz. Tweak UART speed here, so printing works. To avoid
* confusion on exit, we later restore the divisor.
*/
master_freq = crystal_freq * 2;
}
uint32_t new_uart_divisor = master_freq / new_speed;
if (UART_CLKDIV_MASK < new_uart_divisor ||
2 > new_uart_divisor ||
new_uart_divisor == uart_divisor) {
uart_divisor = 0; // used to indicate no change
} else {
adjust_uart_speed(new_uart_divisor);
}
#if defined(DEBUG_ESP_HWDT_DEV_DEBUG)
ETS_PRINTF("\n\nreal_uart_div_modify(0, %u / %u);\n", master_freq, new_speed);
ETS_PRINTF("F_CRYSTAL = %u\n", crystal_freq);
ETS_PRINTF("old uart_divisor = %u\n", uart_divisor);
ETS_PRINTF("new uart_divisor = %u\n", new_uart_divisor);
ETS_PRINTF("master_freq = %u\n", master_freq);
#endif
return uart_divisor;
}
#endif
/*
* When g_pcont is valid, we expect these checks to be valid. I am not sure
* what to do when they are not. An error that could lead to a crash is
* corrected. We currently continue and print the stack dump. This assumes
* something is better than nothing.
*
* Make global so postmortem can take advange of this check.
*/
uint32_t IRAM_MAYBE hwdt_cont_integrity_check() {
uint32_t cont_integrity = 0;
if (g_pcont->stack_guard1 != CONT_STACKGUARD) {
cont_integrity |= 0x0001;
}
if (g_pcont->stack_guard2 != CONT_STACKGUARD) {
cont_integrity |= 0x0020;
}
if (g_pcont->stack_end != (g_pcont->stack + (sizeof(g_pcont->stack) / 4))) {
cont_integrity |= 0x0300;
// Fix ending so we don't crash
g_pcont->stack_end = (g_pcont->stack + (sizeof(g_pcont->stack) / 4));
}
if (g_pcont->struct_start != (unsigned*)g_pcont) {
cont_integrity |= 0x4000;
g_pcont->struct_start = (unsigned*)g_pcont;
}
hwdt_info.cont_integrity = cont_integrity;
return cont_integrity;
}
/*
* Determine if we have a HWDT reboot and dump stack traces if so.
*/
STATIC void IRAM_MAYBE handle_hwdt(void) __attribute__((used));
STATIC void IRAM_MAYBE handle_hwdt(void) {
#ifdef DEBUG_ESP_HWDT_NOEXTRA4K
disable_extra4k_at_link_time();
#endif
set__sys_stack_first();
ets_memset(&hwdt_info, 0, sizeof(hwdt_info));
hwdt_check_g_pcont_validity();
bool power_on = false;
bool hwdt_reset = false;
get_reset_reason(&power_on, &hwdt_reset);
#ifdef DEBUG_ESP_HWDT_UART_SPEED
const uint32_t uart_divisor = set_uart_speed(0, DEBUG_ESP_HWDT_UART_SPEED);
#endif
#if defined(DEBUG_ESP_HWDT_DEV_DEBUG)
ETS_PRINTF("Basic boot reason: %s\n", (power_on) ? "Power-on" : "Reboot");
ETS_PRINTF("RTC_SYS Reset Reason = %u\n", hwdt_info.rtc_sys_reason);
ETS_PRINTF("ROM API Reset Reason = %u\n", hwdt_info.rom_api_reason);
ETS_PRINTF("HWDT Reset Reason = %u\n\n", hwdt_info.reset_reason);
#endif
#if defined(DEBUG_ESP_HWDT_DEV_DEBUG_RESET_REASON)
if (REASON_EXT_SYS_RST < hwdt_info.reset_reason) {
ETS_PRINTF("Reset reason confirmation failed!\n");
ETS_PRINTF(" RTC_SYS Reset Reason = %u\n", hwdt_info.rtc_sys_reason);
ETS_PRINTF(" ROM API Reset Reason = %u\n", hwdt_info.rom_api_reason);
}
#endif
/*
* With a few exceptions, DRAM data remains valid after a reset.
*
* Check for "cont" stack consistency.
* The contents of DRAM are not expected to be valid after a:
* 1) flash update (OTA or serial)
* 2) power-on
* 3) deep sleep
* Additionally, g_pcont is expected to be invalid after these events.
*
*/
if (!power_on && hwdt_info.g_pcont_valid) {
// Checks and fixes incorrect cont_t structure values that might
// otherwise cause us to crash.
hwdt_cont_integrity_check();
const uint32_t *ctx_cont_ptr = NULL;
#if !defined(DEBUG_ESP_HWDT_INFO)
if (get_noextra4k_g_pcont())
#endif
{
ctx_cont_ptr = skip_stackguard(g_pcont->stack, g_pcont->stack_end, CONT_STACKGUARD);
hwdt_info.cont = (uintptr_t)g_pcont->stack_end - (uintptr_t)ctx_cont_ptr;
}
const uint32_t *ctx_sys_ptr = skip_stackguard(SYS_STACK, ROM_STACK, CONT_STACKGUARD);
hwdt_info.sys = (uintptr_t)ROM_STACK - (uintptr_t)ctx_sys_ptr;
#ifndef USE_IRAM
const uint32_t *bearssl_stack_top = NULL;
const uint32_t *ctx_bearssl_ptr = NULL;
if (stack_thunk_get_refcnt()) {
bearssl_stack_top = (const uint32_t *)stack_thunk_get_stack_top();
ctx_bearssl_ptr = skip_stackguard((const uint32_t *)stack_thunk_get_stack_bot(), bearssl_stack_top, 0xdeadbeef);
hwdt_info.bearssl = (uintptr_t)bearssl_stack_top - (uintptr_t)ctx_bearssl_ptr;
}
#endif
if (hwdt_reset) {
ETS_PRINTF("\n\nHardware WDT reset\n");
#ifndef USE_IRAM
if (bearssl_stack_top) {
/* Print context bearssl */
print_stack((uintptr_t)ctx_bearssl_ptr, (uintptr_t)bearssl_stack_top, PRINT_STACK::BEARSSL);
}
#endif
/* Print context SYS */
print_stack((uintptr_t)ctx_sys_ptr, (uintptr_t)ROM_STACK, PRINT_STACK::SYS);
if (get_noextra4k_g_pcont()) {
/* Print separate ctx: cont stack */
/* Check if cont stack is yielding to SYS */
if (0 == hwdt_info.cont_integrity && 0 != g_pcont->pc_suspend) {
ctx_cont_ptr = (const uint32_t *)((uintptr_t)g_pcont->sp_suspend - 8u);
}
print_stack((uintptr_t)ctx_cont_ptr, (uintptr_t)g_pcont->stack_end, PRINT_STACK::CONT);
} else {
if (0 == hwdt_info.cont_integrity && 0 != g_pcont->pc_suspend) {
ETS_PRINTF("\nCont stack is yielding. Active stack starts at 0x%08X.\n", (uint32_t)g_pcont->sp_suspend - 8u);
}
}
if (hwdt_info.cont_integrity) {
ETS_PRINTF("\nCaution, the stack is possibly corrupt integrity checks did not pass.\n\n");
}
}
}
/*
* Fill the SDK stack area with CONT_STACKGUARD so we can detect and
* skip the unused section of the stack when printing a Stack Dump.
*/
{
size_t this_mutch = (uintptr_t)ROM_STACK - (uintptr_t)SYS_STACK;
this_mutch /= sizeof(uint32_t);
for (size_t i = 0; i < this_mutch; i++) {
SYS_STACK[i] = CONT_STACKGUARD;
}
}
#if defined(DEBUG_ESP_HWDT_INFO) || defined(ROM_STACK_DUMP)
/*
* Reports on ROM_STACK usage by ROM and eboot.
* Used to confirm DEBUG_ESP_HWDT_ROM_STACK_SIZE is large enough.
*/
{
const uint32_t *ctx_rom_ptr = skip_stackguard(ROM_STACK, ROM_STACK_FIRST, CONT_STACKGUARD);
hwdt_info.rom = (uintptr_t)ROM_STACK_FIRST - (uintptr_t)ctx_rom_ptr;
#if defined(ROM_STACK_DUMP)
print_stack((uintptr_t)ctx_rom_ptr, (uintptr_t)ROM_STACK_FIRST, PRINT_STACK::ROM);
#endif
}
#endif