1 /*
2  * Copyright (c) 2018 Nordic Semiconductor ASA
3  * Copyright (c) 2023 Meta
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <zephyr/logging/log_backend.h>
9 #include <zephyr/logging/log_core.h>
10 #include <zephyr/logging/log_output.h>
11 #include <zephyr/logging/log_output_dict.h>
12 #include <zephyr/logging/log_backend_std.h>
13 #include <zephyr/logging/log.h>
14 #include <zephyr/device.h>
15 #include <zephyr/drivers/uart.h>
16 #include <zephyr/sys/util_macro.h>
17 #include <zephyr/sys/__assert.h>
18 #include <zephyr/pm/device.h>
19 #include <zephyr/pm/device_runtime.h>
20 LOG_MODULE_REGISTER(log_uart);
21 
22 struct lbu_data {
23 	struct k_sem sem;
24 	uint32_t log_format_current;
25 	volatile bool in_panic;
26 	bool use_async;
27 };
28 
29 struct lbu_cb_ctx {
30 	const struct log_output *output;
31 #if DT_HAS_CHOSEN(zephyr_log_uart)
32 	const struct device *uart_dev;
33 #endif
34 	struct lbu_data *data;
35 };
36 
37 #define LBU_UART_DEV(ctx)                                                                          \
38 	COND_CODE_1(DT_HAS_CHOSEN(zephyr_log_uart), (ctx->uart_dev),                               \
39 		    (DEVICE_DT_GET(DT_CHOSEN(zephyr_console))))
40 
41 /* Fixed size to avoid auto-added trailing '\0'.
42  * Used if CONFIG_LOG_BACKEND_UART_OUTPUT_DICTIONARY_HEX.
43  */
44 static const char LOG_HEX_SEP[10] = "##ZLOGV1##";
45 
uart_callback(const struct device * dev,struct uart_event * evt,void * user_data)46 static void uart_callback(const struct device *dev,
47 			  struct uart_event *evt,
48 			  void *user_data)
49 {
50 	const struct lbu_cb_ctx *ctx = user_data;
51 	struct lbu_data *data = ctx->data;
52 
53 	ARG_UNUSED(dev);
54 
55 	switch (evt->type) {
56 	case UART_TX_DONE:
57 		k_sem_give(&data->sem);
58 		break;
59 	default:
60 		break;
61 	}
62 }
63 
dict_char_out_hex(const struct device * uart_dev,uint8_t * data,size_t length)64 static void dict_char_out_hex(const struct device *uart_dev, uint8_t *data, size_t length)
65 {
66 	for (size_t i = 0; i < length; i++) {
67 		char c;
68 		uint8_t x;
69 
70 		/* upper 8-bit */
71 		x = data[i] >> 4;
72 		(void)hex2char(x, &c);
73 		uart_poll_out(uart_dev, c);
74 
75 		/* lower 8-bit */
76 		x = data[i] & 0x0FU;
77 		(void)hex2char(x, &c);
78 		uart_poll_out(uart_dev, c);
79 	}
80 }
81 
char_out(uint8_t * data,size_t length,void * ctx)82 static int char_out(uint8_t *data, size_t length, void *ctx)
83 {
84 	int err;
85 	const struct lbu_cb_ctx *cb_ctx = ctx;
86 	struct lbu_data *lb_data = cb_ctx->data;
87 	const struct device *uart_dev = LBU_UART_DEV(cb_ctx);
88 
89 	if (pm_device_runtime_get(uart_dev) < 0) {
90 		/* Enabling the UART instance has failed but this
91 		 * function MUST return the number of bytes consumed.
92 		 */
93 		return length;
94 	}
95 
96 	if (IS_ENABLED(CONFIG_LOG_BACKEND_UART_OUTPUT_DICTIONARY_HEX)) {
97 		dict_char_out_hex(uart_dev, data, length);
98 		goto cleanup;
99 	}
100 
101 	if (!IS_ENABLED(CONFIG_LOG_BACKEND_UART_ASYNC) || lb_data->in_panic ||
102 	    !lb_data->use_async) {
103 		for (size_t i = 0; i < length; i++) {
104 			uart_poll_out(uart_dev, data[i]);
105 		}
106 		goto cleanup;
107 	}
108 
109 	err = uart_tx(uart_dev, data, length, SYS_FOREVER_US);
110 	__ASSERT_NO_MSG(err == 0);
111 
112 	err = k_sem_take(&lb_data->sem, K_FOREVER);
113 	__ASSERT_NO_MSG(err == 0);
114 
115 	(void)err;
116 cleanup:
117 	/* Use async put to avoid useless device suspension/resumption
118 	 * when tranmiting chain of chars.
119 	 * As errors cannot be returned, ignore the return value
120 	 */
121 	(void)pm_device_runtime_put_async(uart_dev, K_MSEC(1));
122 
123 	return length;
124 }
125 
process(const struct log_backend * const backend,union log_msg_generic * msg)126 static void process(const struct log_backend *const backend,
127 		union log_msg_generic *msg)
128 {
129 	const struct lbu_cb_ctx *ctx = backend->cb->ctx;
130 	struct lbu_data *data = ctx->data;
131 	uint32_t flags = log_backend_std_get_flags();
132 	log_format_func_t log_output_func = log_format_func_t_get(data->log_format_current);
133 
134 	log_output_func(ctx->output, &msg->log, flags);
135 }
136 
format_set(const struct log_backend * const backend,uint32_t log_type)137 static int format_set(const struct log_backend *const backend, uint32_t log_type)
138 {
139 	const struct lbu_cb_ctx *ctx = backend->cb->ctx;
140 	struct lbu_data *data = ctx->data;
141 
142 	data->log_format_current = log_type;
143 
144 	return 0;
145 }
146 
log_backend_uart_init(struct log_backend const * const backend)147 static void log_backend_uart_init(struct log_backend const *const backend)
148 {
149 	const struct lbu_cb_ctx *ctx = backend->cb->ctx;
150 	const struct device *uart_dev = LBU_UART_DEV(ctx);
151 	struct lbu_data *data = ctx->data;
152 
153 	__ASSERT_NO_MSG(device_is_ready(uart_dev));
154 
155 	log_output_ctx_set(ctx->output, (void *)ctx);
156 
157 	if (IS_ENABLED(CONFIG_LOG_BACKEND_UART_OUTPUT_DICTIONARY_HEX)) {
158 		/* Print a separator so the output can be fed into
159 		 * log parser directly. This is useful when capturing
160 		 * from UART directly where there might be other output
161 		 * (e.g. bootloader).
162 		 */
163 		for (int i = 0; i < sizeof(LOG_HEX_SEP); i++) {
164 			uart_poll_out(uart_dev, LOG_HEX_SEP[i]);
165 		}
166 
167 		return;
168 	}
169 
170 	if (IS_ENABLED(CONFIG_LOG_BACKEND_UART_ASYNC)) {
171 		int err = uart_callback_set(uart_dev, uart_callback, (void *)ctx);
172 
173 		if (err == 0) {
174 			data->use_async = true;
175 			k_sem_init(&data->sem, 0, 1);
176 		} else {
177 			LOG_WRN("Failed to initialize asynchronous mode (err:%d). "
178 				"Fallback to polling.",
179 				err);
180 		}
181 	}
182 }
183 
panic(struct log_backend const * const backend)184 static void panic(struct log_backend const *const backend)
185 {
186 	const struct lbu_cb_ctx *ctx = backend->cb->ctx;
187 	struct lbu_data *data = ctx->data;
188 	const struct device *uart_dev = LBU_UART_DEV(ctx);
189 
190 	/* Ensure that the UART device is in active mode */
191 #if defined(CONFIG_PM_DEVICE_RUNTIME)
192 	(void)pm_device_runtime_get(uart_dev);
193 #elif defined(CONFIG_PM_DEVICE)
194 	enum pm_device_state pm_state;
195 	int rc;
196 
197 	rc = pm_device_state_get(uart_dev, &pm_state);
198 	if ((rc == 0) && (pm_state == PM_DEVICE_STATE_SUSPENDED)) {
199 		pm_device_action_run(uart_dev, PM_DEVICE_ACTION_RESUME);
200 	}
201 #else
202 	ARG_UNUSED(uart_dev);
203 #endif /* CONFIG_PM_DEVICE */
204 
205 	data->in_panic = true;
206 	log_backend_std_panic(ctx->output);
207 }
208 
dropped(const struct log_backend * const backend,uint32_t cnt)209 static void dropped(const struct log_backend *const backend, uint32_t cnt)
210 {
211 	const struct lbu_cb_ctx *ctx = backend->cb->ctx;
212 
213 	if (IS_ENABLED(CONFIG_LOG_BACKEND_UART_OUTPUT_DICTIONARY)) {
214 		log_dict_output_dropped_process(ctx->output, cnt);
215 	} else {
216 		log_backend_std_dropped(ctx->output, cnt);
217 	}
218 }
219 
220 const struct log_backend_api log_backend_uart_api = {
221 	.process = process,
222 	.panic = panic,
223 	.init = log_backend_uart_init,
224 	.dropped = IS_ENABLED(CONFIG_LOG_MODE_IMMEDIATE) ? NULL : dropped,
225 	.format_set = format_set,
226 };
227 
228 #define LBU_DEFINE(node_id, ...)                                                                   \
229 	static uint8_t lbu_buffer##__VA_ARGS__[CONFIG_LOG_BACKEND_UART_BUFFER_SIZE];               \
230 	LOG_OUTPUT_DEFINE(lbu_output##__VA_ARGS__, char_out, lbu_buffer##__VA_ARGS__,              \
231 			  CONFIG_LOG_BACKEND_UART_BUFFER_SIZE);                                    \
232                                                                                                    \
233 	static struct lbu_data lbu_data##__VA_ARGS__ = {                                           \
234 		.log_format_current = CONFIG_LOG_BACKEND_UART_OUTPUT_DEFAULT,                      \
235 	};                                                                                         \
236                                                                                                    \
237 	static const struct lbu_cb_ctx lbu_cb_ctx##__VA_ARGS__ = {                                 \
238 		.output = &lbu_output##__VA_ARGS__,                                                \
239 		COND_CODE_0(NUM_VA_ARGS_LESS_1(_, ##__VA_ARGS__), (),                              \
240 				(.uart_dev = DEVICE_DT_GET(node_id),))                             \
241 		.data = &lbu_data##__VA_ARGS__,                                                    \
242 	};                                                                                         \
243                                                                                                    \
244 	LOG_BACKEND_DEFINE(log_backend_uart##__VA_ARGS__, log_backend_uart_api,                    \
245 			   IS_ENABLED(CONFIG_LOG_BACKEND_UART_AUTOSTART),                          \
246 			   (void *)&lbu_cb_ctx##__VA_ARGS__);
247 
248 #if DT_HAS_CHOSEN(zephyr_log_uart)
249 #define LBU_PHA_FN(node_id, prop, idx) LBU_DEFINE(DT_PHANDLE_BY_IDX(node_id, prop, idx), idx)
250 DT_FOREACH_PROP_ELEM_SEP(DT_CHOSEN(zephyr_log_uart), uarts, LBU_PHA_FN, ());
251 #else
252 LBU_DEFINE(DT_CHOSEN(zephyr_console));
253 #endif
254