1 /*
2  * Copyright (c) 2023 Victor Chavez
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include <zephyr/logging/log_backend.h>
7 #include <zephyr/logging/log_output.h>
8 #include <zephyr/logging/log_backend_ble.h>
9 #include <zephyr/bluetooth/gatt.h>
10 #include <zephyr/bluetooth/conn.h>
11 #include <zephyr/logging/log_ctrl.h>
12 
13 #define ATT_NOTIFY_SIZE 3
14 #define LOG_BACKEND_BLE_BUF_SIZE (CONFIG_BT_L2CAP_TX_MTU - ATT_NOTIFY_SIZE)
15 
16 static uint8_t output_buf[LOG_BACKEND_BLE_BUF_SIZE];
17 static bool panic_mode;
18 static uint32_t log_format_current = CONFIG_LOG_BACKEND_BLE_OUTPUT_DEFAULT;
19 static logger_backend_ble_hook user_hook;
20 static bool first_enable;
21 static void *user_ctx;
22 static struct bt_conn *ble_backend_conn;
23 
24 /* Forward declarations*/
25 static const struct log_backend *log_backend_ble_get(void);
26 static void log_backend_ble_connect(struct bt_conn *conn, uint8_t err);
27 static void log_backend_ble_disconnect(struct bt_conn *conn, uint8_t reason);
28 
29 /**
30  * @brief Callback for the subscription to the ble logger notification characteristic
31  * @details This callback enables/disables automatically the logger when the notification
32  *			is subscribed.
33  *  @param attr   The attribute that's changed value
34  *  @param value  New value
35  */
36 static void log_notify_changed(const struct bt_gatt_attr *attr, uint16_t value);
37 
38 /** BLE Logger based on the UUIDs for the NRF Connect SDK NUS service
39  *	https://developer.nordicsemi.com/nRF_Connect_SDK/doc/2.3.0/nrf/libraries/bluetooth_services/services/nus.html
40  */
41 #define NUS_SERVICE_UUID                                                                           \
42 	BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x6E400001, 0xB5A3, 0xF393, 0xE0A9, 0xE50E24DCCA9E))
43 
44 #define LOGGER_TX_SERVICE_UUID                                                                     \
45 	BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x6E400003, 0xB5A3, 0xF393, 0xE0A9, 0xE50E24DCCA9E))
46 
47 #define LOGGER_RX_SERVICE_UUID                                                                     \
48 	BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x6E400002, 0xB5A3, 0xF393, 0xE0A9, 0xE50E24DCCA9E))
49 
50 BT_CONN_CB_DEFINE(log_backend_ble) = {
51 	.connected = log_backend_ble_connect,
52 	.disconnected = log_backend_ble_disconnect,
53 	.le_param_req = NULL,
54 	.le_param_updated = NULL
55 };
56 
57 /**
58  * @brief BLE Service that represents this backend
59  * @note Only transmission characteristic is used. The RX characteristic
60  *       is added to make the backend usable with the NRF toolbox app
61  *       which expects both characteristics.
62  */
63 BT_GATT_SERVICE_DEFINE(ble_log_svc, BT_GATT_PRIMARY_SERVICE(NUS_SERVICE_UUID),
64 		       BT_GATT_CHARACTERISTIC(LOGGER_TX_SERVICE_UUID, BT_GATT_CHRC_NOTIFY,
65 					      BT_GATT_PERM_READ, NULL, NULL, NULL),
66 		       BT_GATT_CCC(log_notify_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
67 		       BT_GATT_CHARACTERISTIC(LOGGER_RX_SERVICE_UUID, BT_GATT_CHRC_WRITE, 0,
68 					      NULL, NULL, NULL),
69 
70 );
71 
72 /* Log characteristic attribute is defined after the first attribute (i.e. the service) */
73 const struct bt_gatt_attr *log_characteristic = &ble_log_svc.attrs[1];
74 
logger_backend_ble_set_hook(logger_backend_ble_hook hook,void * ctx)75 void logger_backend_ble_set_hook(logger_backend_ble_hook hook, void *ctx)
76 {
77 	user_hook = hook;
78 	user_ctx = ctx;
79 }
80 
log_backend_ble_connect(struct bt_conn * conn,uint8_t err)81 static void log_backend_ble_connect(struct bt_conn *conn, uint8_t err)
82 {
83 	if (err == 0) {
84 		ble_backend_conn = conn;
85 	}
86 }
87 
log_backend_ble_disconnect(struct bt_conn * conn,uint8_t reason)88 static void log_backend_ble_disconnect(struct bt_conn *conn, uint8_t reason)
89 {
90 	ARG_UNUSED(conn);
91 	ARG_UNUSED(reason);
92 	ble_backend_conn = NULL;
93 }
94 
log_notify_changed(const struct bt_gatt_attr * attr,uint16_t value)95 void log_notify_changed(const struct bt_gatt_attr *attr, uint16_t value)
96 {
97 	ARG_UNUSED(attr);
98 
99 	const bool notify_enabled = value == BT_GATT_CCC_NOTIFY;
100 
101 	if (notify_enabled) {
102 		if (first_enable == false) {
103 			first_enable = true;
104 			log_backend_enable(log_backend_ble_get(), NULL, CONFIG_LOG_MAX_LEVEL);
105 		} else {
106 			log_backend_activate(log_backend_ble_get(), NULL);
107 		}
108 	} else {
109 		log_backend_deactivate(log_backend_ble_get());
110 	}
111 	if (user_hook != NULL) {
112 		user_hook(notify_enabled, user_ctx);
113 	}
114 }
115 
line_out(uint8_t * data,size_t length,void * output_ctx)116 static int line_out(uint8_t *data, size_t length, void *output_ctx)
117 {
118 	ARG_UNUSED(output_ctx);
119 	const uint16_t mtu_size = bt_gatt_get_mtu(ble_backend_conn);
120 	const uint16_t attr_data_len = mtu_size - ATT_NOTIFY_SIZE;
121 	uint16_t notify_len;
122 
123 	if (attr_data_len < LOG_BACKEND_BLE_BUF_SIZE) {
124 		notify_len = attr_data_len;
125 	} else {
126 		notify_len = LOG_BACKEND_BLE_BUF_SIZE;
127 	}
128 
129 	struct bt_gatt_notify_params notify_param = {
130 		.uuid = NULL,
131 		.attr = log_characteristic,
132 		.data = data,
133 		.len = notify_len,
134 		.func = NULL,
135 		.user_data = NULL,
136 	#if defined(CONFIG_BT_EATT)
137 		.chan_opt = BT_ATT_CHAN_OPT_NONE
138 	#endif
139 	};
140 
141 	const int notify_res = bt_gatt_notify_cb(ble_backend_conn, &notify_param);
142 	/* ignore notification result and continue sending msg*/
143 	ARG_UNUSED(notify_res);
144 
145 	return length;
146 }
147 
148 LOG_OUTPUT_DEFINE(log_output_ble, line_out, output_buf, sizeof(output_buf));
149 
process(const struct log_backend * const backend,union log_msg_generic * msg)150 static void process(const struct log_backend *const backend, union log_msg_generic *msg)
151 {
152 	ARG_UNUSED(backend);
153 	uint32_t flags = LOG_OUTPUT_FLAG_FORMAT_SYSLOG | LOG_OUTPUT_FLAG_TIMESTAMP;
154 
155 	if (panic_mode) {
156 		return;
157 	}
158 
159 	log_format_func_t log_output_func = log_format_func_t_get(log_format_current);
160 
161 	log_output_func(&log_output_ble, &msg->log, flags);
162 }
163 
format_set(const struct log_backend * const backend,uint32_t log_type)164 static int format_set(const struct log_backend *const backend, uint32_t log_type)
165 {
166 	ARG_UNUSED(backend);
167 	log_format_current = log_type;
168 	return 0;
169 }
170 
init_ble(struct log_backend const * const backend)171 static void init_ble(struct log_backend const *const backend)
172 {
173 	ARG_UNUSED(backend);
174 	log_backend_deactivate(log_backend_ble_get());
175 }
176 
panic(struct log_backend const * const backend)177 static void panic(struct log_backend const *const backend)
178 {
179 	ARG_UNUSED(backend);
180 	panic_mode = true;
181 }
182 
183 /**
184  * @brief Backend ready function for ble logger
185  * @details After initialization of the logger, this function avoids
186  *          the logger subys to enable it. The logger is enabled automatically
187  *          via the notification changed callback.
188  * @param backend Logger backend
189  * @return Zephyr permission denied
190  */
backend_ready(const struct log_backend * const backend)191 static int backend_ready(const struct log_backend *const backend)
192 {
193 	ARG_UNUSED(backend);
194 	return -EACCES;
195 }
196 
197 const struct log_backend_api log_backend_ble_api = {.process = process,
198 						    .dropped = NULL,
199 						    .panic = panic,
200 						    .init = init_ble,
201 						    .is_ready = backend_ready,
202 						    .format_set = format_set,
203 						    .notify = NULL};
204 
205 LOG_BACKEND_DEFINE(log_backend_ble, log_backend_ble_api, true);
206 
log_backend_ble_get(void)207 const struct log_backend *log_backend_ble_get(void)
208 {
209 	return &log_backend_ble;
210 }
211