LCM — CH32V003 Light Control Module
Light Control Module, I2C slave
Loading...
Searching...
No Matches
event_log.c
Go to the documentation of this file.
1/********************************** (C) COPYRIGHT *******************************
2 * File Name : event_log.c
3 * Description : Подробное логирование событий LCM на USART1 (PD6, 115200 8N1).
4 *
5 * USART1 TX — PD6 (GPIO_PartialRemap2_USART1), 115200, 8N1.
6 * События из ISR складываются в кольцевой буфер; форматирование — в main.
7 *********************************************************************************/
8
9#include "event_log.h"
10#include "lcm_module.h"
11#include "ch32v00x_conf.h"
12#include "core_riscv.h"
13#include <stdarg.h>
14#include <stdio.h>
15
16#define EVENT_LOG_BAUD 115200U
17#define EVENT_LOG_LINE_SIZE 96U
18#define EVENT_LOG_RING_SIZE 48U
19
20/*
21 * Все события (включая I2C CMD/READ) идут через это кольцо и печатаются
22 * только из EventLog_Process() в main. Важно: UART — общий ресурс; если
23 * писать в него и из ISR (напрямую), и из main одновременно, символы
24 * перемешиваются/бьются (напр. "0x" превращается в "0|"). Поэтому ISR
25 * только кладёт запись в кольцо (короткая парная disable/enable_irq),
26 * а реальная передача по UART — всегда из одного (main) контекста.
27 */
35
36/*
37 * Компактная запись одного события в кольце: 4 байта вместо строки —
38 * форматирование (snprintf, довольно «дорогое» по времени и стеку)
39 * откладывается до EventLog_Process() в главном цикле. id выбирает
40 * интерпретацию полей a/b/c — см. EventLog_Id и EventLog_FormatRecord().
41 */
42typedef struct {
43 uint8_t id;
44 uint8_t a;
45 uint8_t b;
46 uint8_t c;
48
49/*
50 * Кольцевой буфер: s_ring_head — индекс следующей свободной ячейки (пишет
51 * PushRaw), s_ring_tail — индекс следующей записи на вывод (двигает
52 * Process). Буфер пуст, когда head == tail; заполнен, когда добавление
53 * ещё одной записи сделало бы next(head) == tail — в этот момент PushRaw
54 * молча отбрасывает событие (переполнение лога не должно ронять систему).
55 * head/tail volatile, т.к. head пишется из ISR (через PushRaw), а tail —
56 * из main (через Process); сами перезаписи защищены __disable_irq()/
57 * __enable_irq() ниже.
58 */
59static volatile uint8_t s_ring_head;
60static volatile uint8_t s_ring_tail;
62static uint32_t s_seq; /* сквозной номер события для строки лога */
63static char s_line[EVENT_LOG_LINE_SIZE]; /* общий буфер форматирования (только из main) */
64
65#if EVENT_LOG_ENABLE
66
67/*********************************************************************
68 * @fn EventLog_UartPutChar
69 *
70 * @brief Отправить один байт по USART1 (блокирующе).
71 *
72 * @details Ждёт флаг TC (Transmission Complete) ПЕРЕД записью в DATAR,
73 * а не после — это гарантирует, что предыдущий байт (включая
74 * стоп-бит) уже полностью ушёл в линию до того, как мы положим
75 * новый байт в буфер данных USART. Вызывается только из главного
76 * цикла (через EventLog_UartWrite/Process/Printf) — прямой вывод
77 * из ISR запрещён (см. комментарий к структуре кольца выше и
78 * historical fix: конкурентная запись в USART1->DATAR из ISR и
79 * main одновременно ломала байты, напр. "0x" превращалось в "0|").
80 */
81static void EventLog_UartPutChar(char ch)
82{
83 while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {
84 }
85 USART_SendData(USART1, (uint16_t)(uint8_t)ch);
86}
87
88/*********************************************************************
89 * @fn EventLog_UartWrite
90 *
91 * @brief Отправить C-строку по USART1 побайтно (до '\0').
92 */
93static void EventLog_UartWrite(const char *s)
94{
95 while (*s != '\0') {
97 }
98}
99
100/*********************************************************************
101 * @fn EventLog_UartInit
102 *
103 * @brief USART1, PD6 TX, 115200 8N1.
104 *
105 * @details PD6 — не штатный вывод TX для USART1 (по умолчанию это PD5);
106 * используется альтернативная схема выводов PartialRemap2
107 * (GPIO_PinRemapConfig), поэтому AFIO-тактирование обязательно
108 * включается до вызова GPIO_PinRemapConfig(). Режим приёма (RX)
109 * не настраивается — USART_Mode_Tx, модуль только пишет в лог,
110 * обратный канал не нужен.
111 */
112static void EventLog_UartInit(void)
113{
114 GPIO_InitTypeDef gpio = {0};
115 USART_InitTypeDef usart = {0};
116
117 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1 | RCC_APB2Periph_AFIO, ENABLE);
118 GPIO_PinRemapConfig(GPIO_PartialRemap2_USART1, ENABLE);
119
120 gpio.GPIO_Pin = GPIO_Pin_6;
121 gpio.GPIO_Speed = GPIO_Speed_30MHz;
122 gpio.GPIO_Mode = GPIO_Mode_AF_PP; /* альтернативная функция, push-pull (не open-drain) */
123 GPIO_Init(GPIOD, &gpio);
124
125 USART_DeInit(USART1);
126 usart.USART_BaudRate = EVENT_LOG_BAUD;
127 usart.USART_WordLength = USART_WordLength_8b;
128 usart.USART_StopBits = USART_StopBits_1;
129 usart.USART_Parity = USART_Parity_No;
130 usart.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
131 usart.USART_Mode = USART_Mode_Tx;
132 USART_Init(USART1, &usart);
133 USART_Cmd(USART1, ENABLE);
134}
135
136/*********************************************************************
137 * @fn EventLog_PushRaw
138 *
139 * @brief Положить компактную запись события в кольцевой буфер.
140 *
141 * @param id, a, b, c — см. EventLog_Id и EventLog_FormatRecord()
142 * (интерпретация полей a/b/c зависит от id).
143 *
144 * @details Вызывается как из главного цикла, так и из I2C ISR (через
145 * EventLog_PushI2cCmd/PushI2cReadArm/PushLcmError и т.д.),
146 * поэтому доступ к s_ring_head защищён __disable_irq()/
147 * __enable_irq(). Это НЕ противоречит правилу «не звать
148 * disable/enable_irq в WCH-Interrupt-fast» (см. lcm_led.c) —
149 * там проблема была в НЕСБАЛАНСИРОВАННОМ вызове (disable в
150 * начале ISR без гарантированного enable перед выходом). Здесь
151 * пара вызовов всегда выполняется целиком внутри одной короткой
152 * функции — при любом пути выхода (переполнение или запись)
153 * __enable_irq() гарантированно вызывается до return, поэтому
154 * биты MIE/MPIE в mstatus восстанавливаются симметрично и mret
155 * после выхода из ISR не оставит прерывания выключенными навсегда.
156 *
157 * Если буфер заполнен (next(head) == tail), событие тихо
158 * отбрасывается — переполнение лога не должно блокировать или
159 * ронять основную логику (I2C/реле).
160 */
161static void EventLog_PushRaw(uint8_t id, uint8_t a, uint8_t b, uint8_t c)
162{
163 uint8_t head;
164 uint8_t next;
165
166 __disable_irq();
167 head = s_ring_head;
168 next = (uint8_t)((head + 1U) % EVENT_LOG_RING_SIZE);
169 if (next == s_ring_tail) {
170 __enable_irq();
171 return;
172 }
173
174 s_ring[head].id = id;
175 s_ring[head].a = a;
176 s_ring[head].b = b;
177 s_ring[head].c = c;
178 s_ring_head = next;
179 __enable_irq();
180}
181
182/*********************************************************************
183 * @fn EventLog_LcmErrName
184 *
185 * @brief Символьное имя кода ошибки LCM_ERR_* для строки лога.
186 *
187 * @param code — значение LCM_ERR_* (lcm_module.h).
188 *
189 * @return Короткое имя без префикса LCM_ERR_ (напр. "BAD_PARAM");
190 * "UNKNOWN" — если code не входит ни в один известный диапазон
191 * (защита от рассинхронизации со списком в lcm_module.h).
192 */
193static const char *EventLog_LcmErrName(uint8_t code)
194{
195 switch (code) {
196 case LCM_ERR_NONE: return "NONE";
197 case LCM_ERR_UNKNOWN_CMD: return "UNKNOWN_CMD";
198 case LCM_ERR_BAD_PARAM: return "BAD_PARAM";
199 case LCM_ERR_I2C_BERR: return "I2C_BERR";
200 case LCM_ERR_I2C_OVR: return "I2C_OVR";
201 case LCM_ERR_I2C_ARLO: return "I2C_ARLO";
202 case LCM_ERR_USER_DATA_COMMIT: return "UD_COMMIT";
203 case LCM_ERR_USER_DATA_PARAM: return "UD_PARAM";
204 case LCM_ERR_USER_DATA_RANGE: return "UD_RANGE";
205 case LCM_ERR_USER_DATA_FLASH: return "UD_FLASH";
206 case LCM_ERR_USER_DATA_BUSY: return "UD_BUSY";
207 case LCM_ERR_GPIO_INIT: return "GPIO_INIT";
208 case LCM_ERR_GPIO_SET: return "GPIO_SET";
209 case LCM_ERR_GPIO_GET: return "GPIO_GET";
210 case LCM_ERR_GPIO_CLEAR: return "GPIO_CLEAR";
211 case LCM_ERR_GPIO_SET_ALL: return "GPIO_SET_ALL";
212 case LCM_ERR_GPIO_CLEAR_ALL: return "GPIO_CLEAR_ALL";
213 default: return "UNKNOWN";
214 }
215}
216
217/*********************************************************************
218 * @fn EventLog_UserDataName
219 *
220 * @brief Символьное имя UserData_Status для строки лога NVM-событий.
221 *
222 * @param st — значение из перечисления UserData_Status (user_data.h),
223 * переданное как uint8_t (сами события в кольце хранят статус
224 * именно так — компактной записью в 1 байт).
225 */
226static const char *EventLog_UserDataName(uint8_t st)
227{
228 switch (st) {
229 case USER_DATA_OK: return "OK";
230 case USER_DATA_ERR_PARAM: return "PARAM";
231 case USER_DATA_ERR_RANGE: return "RANGE";
232 case USER_DATA_ERR_FLASH: return "FLASH";
233 case USER_DATA_ERR_BUSY: return "BUSY";
234 default: return "?";
235 }
236}
237
238/*********************************************************************
239 * @fn EventLog_FormatRecord
240 *
241 * @brief Развернуть компактную запись EventLog_Rec в текстовую строку
242 * и вывести её на UART.
243 *
244 * @details Вызывается только из EventLog_Process() (главный цикл) —
245 * использует общий static-буфер s_line и медленный snprintf,
246 * поэтому не годится для вызова из ISR.
247 *
248 * Числовое поле rec->a для EVT_NVM/EVT_SYS — это условный «tag»
249 * источника события (не входит в отдельный enum, т.к. кодируется
250 * прямо в вызовах EventLog_PushNvm/PushSystem из lcm_module.c):
251 * EVT_NVM: 1 — запись в RAM-кэш user_data (LCM_NvmWriteConfig/
252 * LCM_LoadConfig), rec->b = UserData_Status;
253 * 2 — результат UserData_Commit() (сама запись во
254 * flash), rec->b = UserData_Status;
255 * 3 — сброс кэша к заводским значениям при LCM_RESET,
256 * rec->b = UserData_Status.
257 * EVT_SYS: 1 — запрошена программная перезагрузка (перед
258 * NVIC_SystemReset(), после SET_ADDR/LCM_RESET);
259 * 2 — IWDG успешно взведён (~2 c) при старте.
260 * Любое иное значение tag печатается «как есть» (fallback-ветка
261 * "NVM event tag=… " / "SYS tag=…") — это защита от рассинхронизации,
262 * если в lcm_module.c/main.c добавят новый tag без обновления
263 * этой функции.
264 */
265static void EventLog_FormatRecord(const EventLog_Rec *rec)
266{
267 s_seq++;
268 switch (rec->id) {
269 case EVT_I2C_CMD:
270 if (rec->c) {
271 snprintf(s_line, sizeof(s_line),
272 "[%lu] I2C CMD 0x%02X param=0x%02X\r\n",
273 (unsigned long)s_seq, rec->a, rec->b);
274 } else {
275 snprintf(s_line, sizeof(s_line),
276 "[%lu] I2C CMD 0x%02X\r\n",
277 (unsigned long)s_seq, rec->a);
278 }
279 break;
280
281 case EVT_I2C_READ:
282 snprintf(s_line, sizeof(s_line),
283 "[%lu] I2C READ arm cmd=0x%02X\r\n",
284 (unsigned long)s_seq, rec->a);
285 break;
286
287 case EVT_LCM_ERR:
288 snprintf(s_line, sizeof(s_line),
289 "[%lu] ERROR %s (0x%02X)\r\n",
290 (unsigned long)s_seq, EventLog_LcmErrName(rec->a), rec->a);
291 break;
292
293 case EVT_NVM:
294 if (rec->a == 1U) {
295 snprintf(s_line, sizeof(s_line),
296 "[%lu] NVM cache_write %s\r\n",
297 (unsigned long)s_seq, EventLog_UserDataName(rec->b));
298 } else if (rec->a == 2U) {
299 snprintf(s_line, sizeof(s_line),
300 "[%lu] NVM commit %s\r\n",
301 (unsigned long)s_seq, EventLog_UserDataName(rec->b));
302 } else if (rec->a == 3U) {
303 snprintf(s_line, sizeof(s_line),
304 "[%lu] NVM factory_reset cache %s\r\n",
305 (unsigned long)s_seq, EventLog_UserDataName(rec->b));
306 } else {
307 snprintf(s_line, sizeof(s_line),
308 "[%lu] NVM event tag=%u st=%u\r\n",
309 (unsigned long)s_seq, rec->a, rec->b);
310 }
311 break;
312
313 case EVT_SYS:
314 if (rec->a == 1U) {
315 snprintf(s_line, sizeof(s_line),
316 "[%lu] SYS reboot requested\r\n", (unsigned long)s_seq);
317 } else if (rec->a == 2U) {
318 snprintf(s_line, sizeof(s_line),
319 "[%lu] SYS IWDG armed (~2 s)\r\n", (unsigned long)s_seq);
320 } else {
321 snprintf(s_line, sizeof(s_line),
322 "[%lu] SYS tag=%u\r\n", (unsigned long)s_seq, rec->a);
323 }
324 break;
325
326 default:
327 snprintf(s_line, sizeof(s_line),
328 "[%lu] EVT id=%u a=%u b=%u c=%u\r\n",
329 (unsigned long)s_seq, rec->id, rec->a, rec->b, rec->c);
330 break;
331 }
333}
334
336{
337 s_ring_head = 0U;
338 s_ring_tail = 0U;
339 s_seq = 0U;
341}
342
343/*
344 * Внешний while() читает s_ring_tail/head без блокировки прерываний —
345 * это лишь быстрая проверка «есть ли вообще что обрабатывать», не
346 * гарантирующая согласованность (ISR может добавить запись в любой
347 * момент). Настоящее извлечение элемента — под __disable_irq(), с
348 * повторной проверкой условия внутри (на случай, если ISR успела
349 * забрать последнее событие между проверкой снаружи и входом в секцию).
350 * EventLog_FormatRecord() (snprintf + побайтная отправка по UART,
351 * потенциально десятки микросекунд) выполняется УЖЕ ПОСЛЕ __enable_irq() —
352 * критическая секция защищает только сам доступ к индексам/массиву
353 * кольца, а не форматирование и вывод, чтобы не держать прерывания
354 * выключенными дольше необходимого (I2C ISR не должна ждать снаружи).
355 */
357{
358 EventLog_Rec rec;
359
360 while (s_ring_tail != s_ring_head) {
361 __disable_irq();
362 if (s_ring_tail == s_ring_head) {
363 __enable_irq();
364 break;
365 }
366 rec = s_ring[s_ring_tail];
367 s_ring_tail = (uint8_t)((s_ring_tail + 1U) % EVENT_LOG_RING_SIZE);
368 __enable_irq();
370 }
371}
372
373/*
374 * В отличие от EventLog_Push*(), пишет на UART немедленно и синхронно —
375 * поэтому вызывать только из main (см. @brief в event_log.h). vsnprintf
376 * возвращает отрицательное значение при внутренней ошибке форматирования
377 * (сюда не должно доходить при корректных fmt/аргументах) — в этом случае
378 * s_line не трогаем и ничего не выводим, чтобы не отправить в UART мусор
379 * из предыдущего вызова.
380 */
381void EventLog_Printf(const char *fmt, ...)
382{
383 va_list ap;
384 int n;
385
386 va_start(ap, fmt);
387 n = vsnprintf(s_line, sizeof(s_line), fmt, ap);
388 va_end(ap);
389
390 if (n <= 0) {
391 return;
392 }
394}
395
397{
399 "\r\n=== LCM event log USART1 PD6 %u 8N1 ===\r\n"
400 "MCU CH32V003F4P6 LCM_ID=0x%02X\r\n",
401 (unsigned)EVENT_LOG_BAUD, (unsigned)LCM_ID);
402}
403
405{
406 if (st == USER_DATA_OK) {
407 EventLog_Printf("[%lu] BOOT UserData_Init OK\r\n", (unsigned long)(++s_seq));
408 } else {
409 EventLog_Printf("[%lu] BOOT UserData_Init FAIL %s\r\n",
410 (unsigned long)(++s_seq), EventLog_UserDataName((uint8_t)st));
411 }
412}
413
414void EventLog_LcmConfig(uint8_t i2c_addr, const uint8_t group_mask[4], uint8_t relay_state)
415{
417 "[%lu] BOOT LCM addr=0x%02X groups=%02X %02X %02X %02X relay=0x%02X\r\n",
418 (unsigned long)(++s_seq),
419 (unsigned)i2c_addr,
420 (unsigned)group_mask[0], (unsigned)group_mask[1],
421 (unsigned)group_mask[2], (unsigned)group_mask[3],
422 (unsigned)relay_state);
423}
424
425void EventLog_PushI2cCmd(uint8_t cmd, uint8_t param, uint8_t has_param)
426{
427 EventLog_PushRaw(EVT_I2C_CMD, cmd, param, has_param);
428}
429
430void EventLog_PushI2cReadArm(uint8_t cmd)
431{
432 EventLog_PushRaw(EVT_I2C_READ, cmd, 0U, 0U);
433}
434
435void EventLog_PushLcmError(uint8_t lcm_err_code)
436{
437 EventLog_PushRaw(EVT_LCM_ERR, lcm_err_code, 0U, 0U);
438}
439
440void EventLog_PushNvm(uint8_t tag, uint8_t status)
441{
442 EventLog_PushRaw(EVT_NVM, tag, status, 0U);
443}
444
445void EventLog_PushSystem(uint8_t tag)
446{
447 EventLog_PushRaw(EVT_SYS, tag, 0U, 0U);
448}
449
450#else /* EVENT_LOG_ENABLE */
451
452void EventLog_Init(void) {}
453void EventLog_Process(void) {}
454void EventLog_Printf(const char *fmt, ...) { (void)fmt; }
455void EventLog_BootBanner(void) {}
456void EventLog_UserDataInit(UserData_Status st) { (void)st; }
457void EventLog_LcmConfig(uint8_t i2c_addr, const uint8_t *group_mask, uint8_t relay_state)
458{
459 (void)i2c_addr;
460 (void)group_mask;
461 (void)relay_state;
462}
463void EventLog_PushI2cCmd(uint8_t cmd, uint8_t param, uint8_t has_param)
464{
465 (void)cmd; (void)param; (void)has_param;
466}
467void EventLog_PushI2cReadArm(uint8_t cmd) { (void)cmd; }
468void EventLog_PushLcmError(uint8_t lcm_err_code) { (void)lcm_err_code; }
469void EventLog_PushNvm(uint8_t tag, uint8_t status) { (void)tag; (void)status; }
470void EventLog_PushSystem(uint8_t tag) { (void)tag; }
471
472#endif /* EVENT_LOG_ENABLE */
#define EVENT_LOG_RING_SIZE
Definition event_log.c:18
#define EVENT_LOG_LINE_SIZE
Definition event_log.c:17
void EventLog_PushI2cReadArm(uint8_t cmd)
Definition event_log.c:430
#define EVENT_LOG_BAUD
Definition event_log.c:16
static void EventLog_UartWrite(const char *s)
Definition event_log.c:93
void EventLog_Process(void)
Definition event_log.c:356
void EventLog_PushNvm(uint8_t tag, uint8_t status)
Definition event_log.c:440
void EventLog_PushSystem(uint8_t tag)
Definition event_log.c:445
static const char * EventLog_UserDataName(uint8_t st)
Definition event_log.c:226
static const char * EventLog_LcmErrName(uint8_t code)
Definition event_log.c:193
void EventLog_UserDataInit(UserData_Status st)
Definition event_log.c:404
static EventLog_Rec s_ring[EVENT_LOG_RING_SIZE]
Definition event_log.c:61
static char s_line[EVENT_LOG_LINE_SIZE]
Definition event_log.c:63
void EventLog_BootBanner(void)
Definition event_log.c:396
void EventLog_LcmConfig(uint8_t i2c_addr, const uint8_t group_mask[4], uint8_t relay_state)
Definition event_log.c:414
static void EventLog_FormatRecord(const EventLog_Rec *rec)
Definition event_log.c:265
static void EventLog_PushRaw(uint8_t id, uint8_t a, uint8_t b, uint8_t c)
Definition event_log.c:161
static uint32_t s_seq
Definition event_log.c:62
void EventLog_PushI2cCmd(uint8_t cmd, uint8_t param, uint8_t has_param)
Definition event_log.c:425
void EventLog_Printf(const char *fmt,...)
Definition event_log.c:381
static volatile uint8_t s_ring_head
Definition event_log.c:59
EventLog_Id
Definition event_log.c:28
@ EVT_SYS
Definition event_log.c:31
@ EVT_LCM_ERR
Definition event_log.c:29
@ EVT_NVM
Definition event_log.c:30
@ EVT_I2C_CMD
Definition event_log.c:32
@ EVT_I2C_READ
Definition event_log.c:33
static volatile uint8_t s_ring_tail
Definition event_log.c:60
void EventLog_PushLcmError(uint8_t lcm_err_code)
Definition event_log.c:435
static void EventLog_UartInit(void)
Definition event_log.c:112
void EventLog_Init(void)
Definition event_log.c:335
static void EventLog_UartPutChar(char ch)
Definition event_log.c:81
#define LCM_ERR_USER_DATA_RANGE
Definition lcm_module.h:186
#define LCM_ERR_BAD_PARAM
Definition lcm_module.h:176
#define LCM_ERR_I2C_OVR
Definition lcm_module.h:180
#define LCM_ERR_GPIO_INIT
Definition lcm_module.h:191
#define LCM_ERR_USER_DATA_BUSY
Definition lcm_module.h:188
#define LCM_ID
Definition lcm_module.h:65
#define LCM_ERR_GPIO_CLEAR_ALL
Definition lcm_module.h:196
#define LCM_ERR_I2C_ARLO
Definition lcm_module.h:181
#define LCM_ERR_NONE
Definition lcm_module.h:174
#define LCM_ERR_USER_DATA_PARAM
Definition lcm_module.h:185
#define LCM_ERR_UNKNOWN_CMD
Definition lcm_module.h:175
#define LCM_ERR_USER_DATA_COMMIT
Definition lcm_module.h:184
#define LCM_ERR_GPIO_SET
Definition lcm_module.h:192
#define LCM_ERR_USER_DATA_FLASH
Definition lcm_module.h:187
#define LCM_ERR_GPIO_CLEAR
Definition lcm_module.h:194
#define LCM_ERR_I2C_BERR
Definition lcm_module.h:179
#define LCM_ERR_GPIO_SET_ALL
Definition lcm_module.h:195
#define LCM_ERR_GPIO_GET
Definition lcm_module.h:193
uint8_t id
Definition event_log.c:43
uint8_t c
Definition event_log.c:46
uint8_t a
Definition event_log.c:44
uint8_t b
Definition event_log.c:45
UserData_Status
Definition user_data.h:44
@ USER_DATA_ERR_PARAM
Definition user_data.h:46
@ USER_DATA_OK
Definition user_data.h:45
@ USER_DATA_ERR_BUSY
Definition user_data.h:49
@ USER_DATA_ERR_RANGE
Definition user_data.h:47
@ USER_DATA_ERR_FLASH
Definition user_data.h:48