Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ include arch/$(ARCH)/build.mk
INC_DIRS += -I $(SRC_DIR)/include \
-I $(SRC_DIR)/include/lib

KERNEL_OBJS := timer.o mqueue.o pipe.o semaphore.o mutex.o logger.o error.o syscall.o task.o memprot.o main.o
KERNEL_OBJS := timer.o mqueue.o pipe.o semaphore.o mutex.o logger.o error.o syscall.o task.o memprot.o debug_trace.o main.o
KERNEL_OBJS := $(addprefix $(BUILD_KERNEL_DIR)/,$(KERNEL_OBJS))
deps += $(KERNEL_OBJS:%.o=%.o.d)

Expand Down
22 changes: 22 additions & 0 deletions config.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,25 @@
#ifndef CONFIG_STACK_PROTECTION
#define CONFIG_STACK_PROTECTION 1 /* Default: enabled for safety */
#endif

/* Kernel event tracing configuration */
#ifndef CONFIG_DEBUG_TRACE
#define CONFIG_DEBUG_TRACE 0 /* default: disabled */
#endif

/*
* Ring buffer slot count — power of 2 recommended for O(1) bitmask
* index calc instead of modulo.
*/
#ifndef DEBUG_EVENT_BUFFER_SIZE
#define DEBUG_EVENT_BUFFER_SIZE 256
#endif

/*
* Per-event slot stride in bytes — defines the fixed-size slot that
* every event occupies in the ring buffer. Must be a multiple of 4
* (sizeof(uint32_t)). Power of 2 recommended.
*/
#ifndef DEBUG_EVENT_STRUCT_SIZE
#define DEBUG_EVENT_STRUCT_SIZE 16
#endif
268 changes: 268 additions & 0 deletions include/sys/debug_trace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
#pragma once

#include <types.h>

/*
* Tracing system — 5-pass X-macro architecture
*
* trace_events.h is #include'd five times with different macro
* definitions to generate:
*
* Pass 1 -> debug_event_type_t enum
* Pass 2 -> event-class struct definitions
* Pass 3 -> always_inline write functions (_tr_do_EVENT_##name)
* Pass 4 -> per-event print functions (debug_trace.c)
* Pass 5 -> print-function pointer table (debug_trace.c)
*
* Users define events in trace_events.h using:
*
* DEFINE_EVENT_CLASS(name, __field(type, fieldname) ...)
* DEFINE_EVENT(name, class, prot, proto, pairs...)
*
* Pairs: _NO(field) skip, _SET(field,expr) store, TR_TID task id
*/

#if CONFIG_DEBUG_TRACE

/* ═══════════════════════════════════════════════════════════════════ *
* Pass 1: enum
* ═══════════════════════════════════════════════════════════════════ */

#define DEFINE_EVENT_CLASS(...) /* nothing */
#define __field(...) /* nothing */
#define DEFINE_EVENT(name, ...) EVENT_##name,

typedef enum {
#include <sys/trace_events.h>
EVENT_TYPE_MAX
} debug_event_type_t;

#undef DEFINE_EVENT_CLASS
#undef __field
#undef DEFINE_EVENT

/*
* Reader-side event struct — used by debug_trace_get().
* Each class has its own packed layout (DEBUG_EVENT_STRUCT_SIZE
* bytes); this struct provides a common projection for reading.
*/
typedef struct {
uint8_t event_type;
uint8_t reserved;
uint16_t task_id;
uint32_t timestamp;
uint32_t param1;
uint32_t param2;
} debug_event_t;
STATIC_ASSERT(sizeof(debug_event_t) == DEBUG_EVENT_STRUCT_SIZE,
"debug_event_t size mismatch");

#include <hal.h>
#include <sys/task.h>

#define DEBUG_TRACE_EVENT_WORDS ((DEBUG_EVENT_STRUCT_SIZE) / sizeof(uint32_t))
STATIC_ASSERT((DEBUG_EVENT_STRUCT_SIZE) % 4 == 0,
"DEBUG_EVENT_STRUCT_SIZE must be a multiple of 4");

extern uint32_t debug_trace_buffer[DEBUG_EVENT_BUFFER_SIZE * DEBUG_TRACE_EVENT_WORDS];
extern volatile uint32_t debug_trace_total;

#if (DEBUG_EVENT_BUFFER_SIZE & (DEBUG_EVENT_BUFFER_SIZE - 1)) == 0
#define DEBUG_TRACE_BUFFER_INDEX(sequence) \
((uint32_t) (sequence) & (DEBUG_EVENT_BUFFER_SIZE - 1U))
#else
#define DEBUG_TRACE_BUFFER_INDEX(sequence) \
((uint32_t) (sequence) % DEBUG_EVENT_BUFFER_SIZE)
#endif

#define DEBUG_TRACE_CURRENT_TASK_ID() \
(likely(kcb && kcb->task_current && kcb->task_current->data) \
? ((tcb_t *) kcb->task_current->data)->id \
: 0U)


/* ═══════════════════════════════════════════════════════════════════ *
* Pass 2: class struct definitions
*
* STATIC_ASSERT catches wrong field ordering at compile time:
* if fields are not declared in descending-size order (u32→u16→u8)
* the compiler inserts padding → struct exceeds size → compile error.
* aligned(4) ensures native lw/lh/lb access without packed overhead.
* ═══════════════════════════════════════════════════════════════════ */

#define DEFINE_EVENT(...) /* nothing */
#define __field(type, name_) type name_;

#define DEFINE_EVENT_CLASS(name, ...) \
struct debug_event_cls_##name { \
uint8_t event_type; \
uint8_t reserved; \
uint16_t task_id; \
uint32_t timestamp; \
union { \
struct { __VA_ARGS__ }; \
struct { uint32_t _param1; uint32_t _param2; }; \
}; \
} __attribute__((aligned(4))); \
STATIC_ASSERT(sizeof(struct debug_event_cls_##name) == DEBUG_EVENT_STRUCT_SIZE, \
"event class size mismatch");

#include <sys/trace_events.h>

#undef DEFINE_EVENT
#undef __field
#undef DEFINE_EVENT_CLASS

/* ═══════════════════════════════════════════════════════════════════ *
* Interrupt protection helpers
* ═══════════════════════════════════════════════════════════════════ */

/* csrrci/csrsi replace hal_interrupt_set — 1 CSR insn
* per direction instead of 3. Saves ~4-5 cycles per SAFE event. */
#define TR_SAFE_ENTER() \
uint32_t _tr_s; \
asm volatile("csrrci %0, mstatus, 8" : "=r"(_tr_s) : : "memory")
#define TR_SAFE_LEAVE() \
do { \
if ((_tr_s) & 8) \
asm volatile("csrsi mstatus, 8" ::: "memory"); \
} while (0)
#define TR_RAW_ENTER() do {} while (0)
#define TR_RAW_LEAVE() do {} while (0)

/* ═══════════════════════════════════════════════════════════════════ *
* Pair processing — up to 11 pairs per event
*
* Each pair is a single macro invocation:
* _NO(field) -> skip store
* _SET(field, expr) -> _ev->field = (expr)
* TR_TID -> store current task id (always at _ev->task_id)
*
* _TR_UNPACK_PAIR uses paste to dispatch on the pair token
* (_TR_PROCP_##_NO etc.) so arbitrary _SET expressions work.
*
* Pad‑to‑11 sentinels ensure any event works without counting.
* Extra arguments beyond 11 are silently discarded via the trailing `, ...`.
* ═══════════════════════════════════════════════════════════════════ */

#define _TR_SKIP_PAIR _NO(_TR_SKP)

#define _TR_FOR_EACH_PAIR(...) \
_TR_FOR_EACH_11(__VA_ARGS__, \
_TR_SKIP_PAIR, _TR_SKIP_PAIR, _TR_SKIP_PAIR, \
_TR_SKIP_PAIR, _TR_SKIP_PAIR, _TR_SKIP_PAIR, \
_TR_SKIP_PAIR, _TR_SKIP_PAIR, _TR_SKIP_PAIR, \
_TR_SKIP_PAIR, _TR_SKIP_PAIR)

/*
* With DEBUG_EVENT_STRUCT_SIZE = 16 bytes (4 words):
* 8 B framework fields (event_type, reserved, task_id, timestamp)
* 8 B remaining for user __field entries
*
* The 8 remaining bytes can hold up to 8 __field entries
* (e.g. eight uint8_t). A DEFINE_EVENT invocation then has at most:
* 8 _SET for the __field entries
* 1 _SET for the reserved byte (framework, optional store)
* 1 TR_TID (or _SET(task_id, ...)) (per-event store)
* 1 _PRINT (per-event print format)
* ---------
* 11 pairs total
*
* Extra arguments beyond 11 are silently discarded
* via the trailing `, ...`.
*
* If DEBUG_EVENT_STRUCT_SIZE grows (e.g. to 8 words),
* expand _TR_FOR_EACH_11 to a larger variant.
*/
#define _TR_FOR_EACH_11(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, ...) \
_TR_UNPACK_PAIR(p1) _TR_UNPACK_PAIR(p2) _TR_UNPACK_PAIR(p3) \
_TR_UNPACK_PAIR(p4) _TR_UNPACK_PAIR(p5) _TR_UNPACK_PAIR(p6) \
_TR_UNPACK_PAIR(p7) _TR_UNPACK_PAIR(p8) _TR_UNPACK_PAIR(p9) \
_TR_UNPACK_PAIR(p10) _TR_UNPACK_PAIR(p11)

/* _TR_UNPACK_PAIR(_NO(field)) -> _TR_PROCP_##_NO -> _TR_PROCP__NO(field) */
#define _TR_UNPACK_PAIR(pair) _TR_PROCP_##pair

#define _TR_PROCP__NO(f) /* skip */
#define _TR_PROCP__SET(f, e) _ev->f = (e);
#define _TR_PROCP_TR_TID _ev->task_id = (uint16_t)DEBUG_TRACE_CURRENT_TASK_ID();
#define _TR_PROCP__PRINT(...) /* ignored in write pass */

/* ═══════════════════════════════════════════════════════════════════ *
* Pass 3: per-event static inline write functions
*
* One function per event, always_inline. The proto argument
* provides the typed parameter list so callers pass arguments
* directly — no positional extraction needed.
* ═══════════════════════════════════════════════════════════════════ */

#define DEFINE_EVENT_CLASS(...) /* nothing */
#define __field(...) /* nothing */

#define DEFINE_EVENT(name, cls, prot, proto, ...) \
__attribute__((always_inline)) static inline void \
_tr_do_EVENT_##name proto \
{ \
struct debug_event_cls_##cls *_ev; \
uint32_t _total, _idx; \
TR_##prot##_ENTER(); \
_total = debug_trace_total; \
_idx = DEBUG_TRACE_BUFFER_INDEX(_total); \
_ev = (void *)&debug_trace_buffer[_idx * DEBUG_TRACE_EVENT_WORDS]; \
_ev->event_type = (uint8_t)(EVENT_##name); \
_TR_FOR_EACH_PAIR(__VA_ARGS__) \
_ev->timestamp = (uint32_t)(likely(kcb) \
? kcb->ticks : 0U); \
debug_trace_total = _total + 1; \
TR_##prot##_LEAVE(); \
}

#include <sys/trace_events.h>

#undef DEFINE_EVENT
#undef __field
#undef DEFINE_EVENT_CLASS

/* ═══════════════════════════════════════════════════════════════════ *
* Public API macro
*
* DEBUG_TRACE_EVENT(EVENT_xxx, args...)
*
* Args are forwarded directly — the per‑event function signature
* provides compile‑time type checking.
* ═══════════════════════════════════════════════════════════════════ */

#define DEBUG_TRACE_EVENT(ev, ...) \
_tr_do_##ev(__VA_ARGS__)


void debug_dump_events(void);
void debug_clear_events(void);
uint32_t debug_trace_count(void);
uint32_t debug_trace_overwrites(void);
int32_t debug_trace_get(uint32_t index, debug_event_t *event);

#else /* !CONFIG_DEBUG_TRACE */

#define DEBUG_TRACE_EVENT(...) do {} while (0)

static inline void debug_dump_events(void) {}

static inline void debug_clear_events(void) {}

static inline uint32_t debug_trace_count(void)
{
return 0U;
}

static inline uint32_t debug_trace_overwrites(void)
{
return 0U;
}

static inline int32_t debug_trace_get(uint32_t index, void *event)
{
return 0U;
}

#endif /* CONFIG_DEBUG_TRACE */
Loading