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, ¬ify_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