Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
cpu_profile.hxx
1
35#ifndef _FREERTOS_DRIVERS_COMMON_CPU_PROFILE_HXX_
36#define _FREERTOS_DRIVERS_COMMON_CPU_PROFILE_HXX_
37
38#include <unwind.h>
39
40#define MAX_STRACE 20
41
42#ifndef TRACE_BUFFER_LENGTH_WORDS
43#define TRACE_BUFFER_LENGTH_WORDS 3000 // 12 kbytes
44#endif
45
46extern "C"
47{
48 extern void *stacktrace[MAX_STRACE];
49 extern int strace_len;
50 void call_unwind(void);
51}
52
56{
57public:
58 void *alloc(unsigned size)
59 {
60 size += 3;
61 size /= 4;
62 if (endOffset + size > TRACE_BUFFER_LENGTH_WORDS)
63 {
64 return nullptr;
65 }
66 void *ret = buffer + endOffset;
67 endOffset += size;
68 return ret;
69 }
70
71private:
73 unsigned buffer[TRACE_BUFFER_LENGTH_WORDS];
75 unsigned endOffset{0};
76
77public:
79 unsigned limitReached{0};
82 unsigned singleLenHack{0};
83} cpu_profile_allocator;
84
86struct trace
87{
89 unsigned hash : 24;
91 unsigned len : 8;
93 struct trace *next;
95 unsigned total_size;
96};
97
98struct trace *all_traces = nullptr;
99
104unsigned hash_trace(unsigned len, unsigned *buf)
105{
106 unsigned ret = 0;
107 for (unsigned i = 0; i < len; ++i)
108 {
109 ret *= (1 + 4 + 16);
110 ret ^= buf[i];
111 }
112 return ret & 0xFFFFFF;
113}
114
120struct trace *find_current_trace(unsigned hash)
121{
122 for (struct trace *t = all_traces; t; t = t->next)
123 {
124 if (t->hash != (hash & 0xFFFFFF))
125 continue;
126 if (t->len != strace_len)
127 continue;
128 unsigned *payload = (unsigned *)(t + 1);
129 if (memcmp(payload, stacktrace, strace_len * sizeof(stacktrace[0])) !=
130 0)
131 continue;
132 return t;
133 }
134 return nullptr;
135}
136
141struct trace *add_new_trace(unsigned hash)
142{
143 unsigned total_size =
144 sizeof(struct trace) + strace_len * sizeof(stacktrace[0]);
145 struct trace *t = (struct trace *)cpu_profile_allocator.alloc(total_size);
146 if (!t)
147 return nullptr;
148 memcpy(t + 1, stacktrace, strace_len * sizeof(stacktrace[0]));
149 t->hash = hash;
150 t->len = strace_len;
151 t->total_size = 0;
152 t->next = all_traces;
153 all_traces = t;
154 return t;
155}
156
158void *stacktrace[MAX_STRACE];
160int strace_len;
161
165{
166 unsigned r[16];
167};
168
171typedef struct
172{
173 unsigned demand_save_flags;
174 struct core_regs core;
175} phase2_vrs;
176
179phase2_vrs main_context;
181unsigned saved_lr;
182
185void fill_phase2_vrs(volatile unsigned *fault_args)
186{
187 main_context.demand_save_flags = 0;
188 main_context.core.r[0] = fault_args[0];
189 main_context.core.r[1] = fault_args[1];
190 main_context.core.r[2] = fault_args[2];
191 main_context.core.r[3] = fault_args[3];
192 main_context.core.r[12] = fault_args[4];
193 // We add +2 here because first thing libgcc does with the lr value is
194 // subtract two, presuming that lr points to after a branch
195 // instruction. However, exception entry's saved PC can point to the first
196 // instruction of a function and we don't want to have the backtrace end up
197 // showing the previous function.
198 main_context.core.r[14] = fault_args[6] + 2;
199 main_context.core.r[15] = fault_args[6];
200 saved_lr = fault_args[5];
201 main_context.core.r[13] = (unsigned)(fault_args + 8); // stack pointer
202}
203extern "C"
204{
205 _Unwind_Reason_Code __gnu_Unwind_Backtrace(
206 _Unwind_Trace_Fn trace, void *trace_argument, phase2_vrs *entry_vrs);
207}
208
210void *last_ip;
211
213_Unwind_Reason_Code trace_func(struct _Unwind_Context *context, void *arg)
214{
215 void *ip;
216 ip = (void *)_Unwind_GetIP(context);
217 if (strace_len == 0)
218 {
219 // stacktrace[strace_len++] = ip;
220 // By taking the beginning of the function for the immediate interrupt
221 // we will attempt to coalesce more traces.
222 // ip = (void *)_Unwind_GetRegionStart(context);
223 }
224 else if (last_ip == ip)
225 {
226 if (strace_len == 1 && saved_lr != _Unwind_GetGR(context, 14))
227 {
228 _Unwind_SetGR(context, 14, saved_lr);
229 cpu_profile_allocator.singleLenHack++;
230 return _URC_NO_REASON;
231 }
232 return _URC_END_OF_STACK;
233 }
234 if (strace_len >= MAX_STRACE - 1)
235 {
236 ++cpu_profile_allocator.limitReached;
237 return _URC_END_OF_STACK;
238 }
239 // stacktrace[strace_len++] = ip;
240 last_ip = ip;
241 ip = (void *)_Unwind_GetRegionStart(context);
242 stacktrace[strace_len++] = ip;
243 return _URC_NO_REASON;
244}
245
248void take_cpu_trace()
249{
250 memset(stacktrace, 0, sizeof(stacktrace));
251 strace_len = 0;
252 last_ip = nullptr;
253 phase2_vrs first_context = main_context;
254 __gnu_Unwind_Backtrace(&trace_func, 0, &first_context);
255 // This is a workaround for the case when the function in which we had the
256 // exception trigger does not have a stack saved LR. In this case the
257 // backtrace will fail after the first step. We manually append the second
258 // step to have at least some idea of what's going on.
259 if (strace_len == 0)
260 {
261 stacktrace[0] = (void*)main_context.core.r[15];
262 strace_len++;
263 }
264 if (strace_len == 1)
265 {
266 main_context.core.r[14] = saved_lr;
267 main_context.core.r[15] = saved_lr;
268 last_ip = nullptr;
269 __gnu_Unwind_Backtrace(&trace_func, 0, &main_context);
270 }
271 if (strace_len == 1)
272 {
273 stacktrace[1] = (void*)saved_lr;
274 strace_len++;
275 }
276 unsigned h = hash_trace(strace_len, (unsigned *)stacktrace);
277 struct trace *t = find_current_trace(h);
278 if (!t)
279 {
280 t = add_new_trace(h);
281 }
282 if (t)
283 {
284 t->total_size += 1;
285 }
286}
287
290bool enable_profiling = 0;
292volatile unsigned current_interrupt = 0;
293
296{
297public:
298 SetInterrupt(unsigned new_value)
299 {
300 old_value = current_interrupt;
301 current_interrupt = new_value;
302 }
303
305 {
306 current_interrupt = old_value;
307 }
308
309private:
310 unsigned old_value;
311};
312
318#define DEFINE_CPU_PROFILE_INTERRUPT_HANDLER(irq_handler_name, CLEAR_IRQ_FLAG) \
319 extern "C" \
320 { \
321 void __attribute__((__noinline__)) load_monitor_interrupt_handler( \
322 volatile unsigned *exception_args, unsigned exception_return_code) \
323 { \
324 if (enable_profiling) \
325 { \
326 fill_phase2_vrs(exception_args); \
327 take_cpu_trace(); \
328 } \
329 cpuload_tick(exception_return_code & 4 \
330 ? 0 \
331 : current_interrupt > 0 ? current_interrupt : 255); \
332 CLEAR_IRQ_FLAG; \
333 } \
334 void __attribute__((__naked__)) irq_handler_name(void) \
335 { \
336 __asm volatile("mov r0, %0 \n" \
337 "str r4, [r0, 4*4] \n" \
338 "str r5, [r0, 5*4] \n" \
339 "str r6, [r0, 6*4] \n" \
340 "str r7, [r0, 7*4] \n" \
341 "str r8, [r0, 8*4] \n" \
342 "str r9, [r0, 9*4] \n" \
343 "str r10, [r0, 10*4] \n" \
344 "str r11, [r0, 11*4] \n" \
345 "str r12, [r0, 12*4] \n" \
346 "str r13, [r0, 13*4] \n" \
347 "str r14, [r0, 14*4] \n" \
348 : \
349 : "r"(main_context.core.r) \
350 : "r0"); \
351 __asm volatile(" tst lr, #4 \n" \
352 " ite eq \n" \
353 " mrseq r0, msp \n" \
354 " mrsne r0, psp \n" \
355 " mov r1, lr \n" \
356 " ldr r2, =load_monitor_interrupt_handler \n" \
357 " bx r2 \n" \
358 : \
359 : \
360 : "r0", "r1", "r2"); \
361 } \
362 }
363
364#endif // _FREERTOS_DRIVERS_COMMON_CPU_PROFILE_HXX_
Helper class for keeping track of current interrupt number.
A custom unidirectional memory allocator so we can take traces from interrupts.
unsigned limitReached
how many times did we run into the MAX_STRACE limit.
unsigned buffer[TRACE_BUFFER_LENGTH_WORDS]
Storage of the trace buffer.
unsigned endOffset
index into buffer[] to denote first free element.
unsigned singleLenHack
How many times did we apply the backtrace hack to work around single-entry backtraces.
This struct definition mimics the internal structures of libgcc in arm-none-eabi binary.
This struct definition mimics the internal structures of libgcc in arm-none-eabi binary.
Linked list entry type for a call-stack backtrace.
unsigned hash
For quick comparison of traces.
unsigned total_size
total memory (bytes) allocated via this trace.
unsigned len
Number of entries in the trace.
struct trace * next
Link to the next trace entry.