xref: /btstack/port/freebsd-netgraph/hci_transport_netgraph.c (revision db88441f671cf9b797d1a7638cc0e38d13db6ac0)
1 /*
2  * Copyright (C) 2023 BlueKitchen GmbH
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the copyright holders nor the names of
14  *    contributors may be used to endorse or promote products derived
15  *    from this software without specific prior written permission.
16  * 4. Any redistribution, use, or modification is done solely for
17  *    personal benefit and not for any commercial purpose or for
18  *    monetary gain.
19  *
20  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
24  * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * Please inquire about commercial licensing options at
34  * [email protected]
35  *
36  */
37 
38 #define BTSTACK_FILE__ "hci_transport_netgraph.c"
39 
40 #include "hci_transport_netgraph.h"
41 
42 #include "btstack_bool.h"
43 #include "btstack_config.h"
44 #include "btstack_debug.h"
45 #include "btstack_event.h"
46 #include "btstack_run_loop.h"
47 #include "hci_transport.h"
48 #include "hci.h"
49 
50 #include <err.h>
51 #include <errno.h>
52 #include <stdint.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 
58 #include <sys/bitstring.h>
59 #include <sys/types.h>
60 #include <sys/socket.h>
61 
62 // avoid warning by /usr/include/netgraph/bluetooth/include/ng_btsocket.h
63 #define L2CAP_SOCKET_CHECKED
64 #include <bluetooth.h>
65 #include <netgraph.h>
66 #include <netgraph/bluetooth/include/ng_hci.h>
67 #include <netgraph/bluetooth/include/ng_l2cap.h>
68 #include <netgraph/bluetooth/include/ng_btsocket.h>
69 
70 // hci packet handler
71 static void (*hci_transport_netgraph_packet_handler)(uint8_t packet_type, uint8_t *packet, uint16_t size);
72 
73 // data source for integration with BTstack Runloop
74 static btstack_data_source_t hci_transport_netgraph_data_source_hci_raw;
75 
76 // block write
77 static uint8_t         hci_transport_netgraph_write_packet_type;
78 static int             hci_transport_netgraph_write_bytes_len;
79 static const uint8_t * hci_transport_netgraph_write_bytes_data;
80 
81 // assert pre-buffer for packet type is available
82 #if !defined(HCI_OUTGOING_PRE_BUFFER_SIZE) || (HCI_OUTGOING_PRE_BUFFER_SIZE == 0)
83 #error HCI_OUTGOING_PRE_BUFFER_SIZE not defined. Please update hci.h
84 #endif
85 
86 typedef enum {
87     TX_OFF,
88     TX_IDLE,
89     TX_W4_PACKET_SENT,
90 } TX_STATE;
91 
92 // write state
93 static TX_STATE hci_transport_netgraph_tx_state;
94 
95 // incoming packet buffer
96 static uint8_t hci_packet_with_pre_buffer[HCI_INCOMING_PRE_BUFFER_SIZE + HCI_INCOMING_PACKET_BUFFER_SIZE + 1]; // packet type + max(acl header + acl payload, event header + event data)
97 static uint8_t * hci_packet = &hci_packet_with_pre_buffer[HCI_INCOMING_PRE_BUFFER_SIZE];
98 
99 // Netgraph node
100 const char * hci_node_name = "ubt0hci";
101 
102 // Control and Data socket for BTstack Netgraph node
103 static int hci_transport_netgraph_hci_raw_control_socket;
104 static int hci_transport_netgraph_hci_raw_data_socket;
105 
106 // Track HCI Netgraph Initialization
107 #define HCI_TODO_READ_BD_ADDR                   1
108 #define HCI_TODO_READ_LOCAL_SUPPORTED_FEATURES  2
109 #define HCI_TODO_READ_BUFFER_SIZE               4
110 
111 static uint8_t hci_transport_netgraph_hci_todo_init;
112 
113 static void hci_transport_netgraph_process_write(btstack_data_source_t *ds) {
114 
115     if (hci_transport_netgraph_write_bytes_len == 0) return;
116 
117     const char * hook = NULL;
118     switch (hci_transport_netgraph_write_packet_type){
119         case HCI_COMMAND_DATA_PACKET:
120             hook = "raw";
121             break;
122         case HCI_ACL_DATA_PACKET:
123             hook = "acl";
124             break;
125         default:
126             btstack_unreachable();
127             break;
128     }
129 
130     int res = NgSendData(ds->source.fd, hook, hci_transport_netgraph_write_bytes_data, hci_transport_netgraph_write_bytes_len);
131 
132     if (res < 0){
133         log_error("send data via hook %s returned error %d", hook, errno);
134         btstack_run_loop_enable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_WRITE);
135         return;
136     }
137 
138     btstack_run_loop_disable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_WRITE);
139 
140     hci_transport_netgraph_tx_state = TX_IDLE;
141     hci_transport_netgraph_write_bytes_len = 0;
142     hci_transport_netgraph_write_bytes_data = NULL;
143 
144     // notify upper stack that it can send again
145     static const uint8_t packet_sent_event[] = { HCI_EVENT_TRANSPORT_PACKET_SENT, 0};
146     hci_transport_netgraph_packet_handler(HCI_EVENT_PACKET, (uint8_t *) &packet_sent_event[0], sizeof(packet_sent_event));
147 }
148 
149 static void hci_transport_netgraph_process_read(btstack_data_source_t * ds) {
150     // read complete HCI packet
151     char hook[NG_NODESIZ];
152     int bytes_read = NgRecvData(ds->source.fd, (u_char *)  hci_packet, HCI_INCOMING_PACKET_BUFFER_SIZE + 1, hook);
153 
154     if (bytes_read < 0){
155         log_error("Read failed %d", (int) bytes_read);
156     } else {
157 
158         // ignore 'echo' - only accept hci events from 'raw' hook and acl packet from 'acl' hook
159         // - HCI CMDs that are received over 'raw' hook are sent back via 'raw' hook
160         if (strcmp(hook, "raw") == 0){
161             if (hci_packet[0] != HCI_EVENT_PACKET){
162                 return;
163             }
164         }
165         // - ACL Packets that are received from driver are also sent via 'raw' hook
166         if (strcmp(hook, "acl") == 0){
167             if (hci_packet[0] != HCI_ACL_DATA_PACKET){
168                 return;
169             }
170         }
171 
172         // track HCI Event Complete for init commands
173         if (hci_packet[0] == HCI_EVENT_PACKET){
174             if (hci_event_packet_get_type(&hci_packet[1]) == HCI_EVENT_COMMAND_COMPLETE){
175                 uint8_t todos_before = hci_transport_netgraph_hci_todo_init;
176                 switch (hci_event_command_complete_get_command_opcode(&hci_packet[1])){
177                     case HCI_OPCODE_HCI_RESET:
178                         hci_transport_netgraph_hci_todo_init = HCI_TODO_READ_BD_ADDR | HCI_TODO_READ_LOCAL_SUPPORTED_FEATURES | HCI_TODO_READ_BUFFER_SIZE;
179                         break;
180                     case HCI_OPCODE_HCI_READ_BD_ADDR:
181                         hci_transport_netgraph_hci_todo_init &= ~HCI_TODO_READ_BD_ADDR;
182                         break;
183                     case HCI_OPCODE_HCI_READ_LOCAL_SUPPORTED_FEATURES:
184                         hci_transport_netgraph_hci_todo_init &= ~HCI_TODO_READ_LOCAL_SUPPORTED_FEATURES;
185                         break;
186                     case HCI_OPCODE_HCI_READ_BUFFER_SIZE:
187                         hci_transport_netgraph_hci_todo_init &= ~HCI_TODO_READ_BUFFER_SIZE;
188                         break;
189                     default:
190                         break;
191                 }
192                 if ((todos_before != 0) && (hci_transport_netgraph_hci_todo_init == 0)){
193                     // tell ubt0hci to disconnect acl hook to ubt0l2cap
194                     char path[NG_NODESIZ];
195                     snprintf(path, sizeof(path), "%s:", hci_node_name);
196                     if (NgSendAsciiMsg(hci_transport_netgraph_hci_raw_control_socket, path, "%s", "init") < 0) {
197                         log_error("Failed to send init to %s", path);
198                     }
199                 }
200             }
201         }
202 
203         uint16_t packet_len = bytes_read-1u;
204         hci_transport_netgraph_packet_handler(hci_packet[0], &hci_packet[1], packet_len);
205     }
206 }
207 
208 static void hci_transport_netgraph_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type) {
209     if (ds->source.fd < 0) return;
210     switch (callback_type){
211         case DATA_SOURCE_CALLBACK_READ:
212             hci_transport_netgraph_process_read(ds);
213             break;
214         case DATA_SOURCE_CALLBACK_WRITE:
215             hci_transport_netgraph_process_write(ds);
216             break;
217         default:
218             break;
219     }
220 }
221 
222 // TODO: could pass device name
223 static void hci_transport_netgraph_init (const void *transport_config){
224     log_info("init");
225 
226     btstack_assert(transport_config != NULL);
227 
228     // extract netgraph device name
229     hci_transport_config_netgraph_t * hci_transport_config_netgraph = (hci_transport_config_netgraph_t*) transport_config;
230     hci_node_name = hci_transport_config_netgraph->device_name;
231 
232     // set state to off
233     hci_transport_netgraph_tx_state = TX_OFF;
234 }
235 
236 static int hci_transport_netgraph_open(void) {
237 
238     log_info("open(%s)", hci_node_name);
239 
240     int res;
241     struct ngm_rmhook rmh;
242     struct ngm_connect con;
243     char path[NG_NODESIZ];
244     char btstack_node_hci_raw_name[NG_NODESIZ];
245 
246     // create hci_raw netgraph node
247     snprintf(btstack_node_hci_raw_name, sizeof(btstack_node_hci_raw_name), "btstack");
248     if (NgMkSockNode(btstack_node_hci_raw_name, &hci_transport_netgraph_hci_raw_control_socket, &hci_transport_netgraph_hci_raw_data_socket) < 0){
249         log_error("Cannot create netgraph node '%s'", btstack_node_hci_raw_name);
250         return -1;
251     }
252 
253     // tell hci node to disconnect raw hook to btsock_hci_raw (ignore result)
254     snprintf(path, sizeof(path), "%s:", hci_node_name);
255     strncpy(rmh.ourhook, "raw", sizeof(rmh.ourhook));
256     res = NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE,
257                         NGM_RMHOOK, &rmh, sizeof(rmh));
258     log_info("Remove HCI RAW Hook: %d", res);
259 
260     // tell hci node to disconnect acl hook to ubt0l2cap (ignore result)
261     snprintf(path, sizeof(path), "%s:", hci_node_name);
262     strncpy(rmh.ourhook, "acl", sizeof(rmh.ourhook));
263     res = NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE,
264                     NGM_RMHOOK, &rmh, sizeof(rmh));
265     log_info("Remove ACL Hook: %d", res);
266 
267     // connect ubth0hci/raw hook
268     snprintf(path, sizeof(path), "%s:",         btstack_node_hci_raw_name);
269     snprintf(con.path, sizeof(con.path), "%s:", hci_node_name);
270     strncpy(con.ourhook,  "raw", sizeof(con.ourhook));
271     strncpy(con.peerhook, "raw", sizeof(con.peerhook));
272     if (NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE,
273                   NGM_CONNECT, &con, sizeof(con)) < 0) {
274         log_error("Cannot connect %s%s to %s%s", path, con.ourhook, con.path, con.peerhook);
275         return -1;
276     }
277 
278     // connect ubth0hci/acl hook
279     snprintf(path, sizeof(path), "%s:",         btstack_node_hci_raw_name);
280     snprintf(con.path, sizeof(con.path), "%s:", hci_node_name);
281     strncpy(con.ourhook,  "acl", sizeof(con.ourhook));
282     strncpy(con.peerhook, "acl", sizeof(con.peerhook));
283     if (NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE,
284                   NGM_CONNECT, &con, sizeof(con)) < 0) {
285         log_error("Cannot connect %s%s to %s%s", path, con.ourhook, con.path, con.peerhook);
286         return -1;
287     }
288 
289     // set up HCI RAW data_source
290     btstack_run_loop_set_data_source_fd(&hci_transport_netgraph_data_source_hci_raw, hci_transport_netgraph_hci_raw_data_socket);
291     btstack_run_loop_set_data_source_handler(&hci_transport_netgraph_data_source_hci_raw, &hci_transport_netgraph_process);
292     btstack_run_loop_add_data_source(&hci_transport_netgraph_data_source_hci_raw);
293     btstack_run_loop_enable_data_source_callbacks(&hci_transport_netgraph_data_source_hci_raw, DATA_SOURCE_CALLBACK_READ);
294 
295     // init tx state machines
296     hci_transport_netgraph_tx_state = TX_IDLE;
297 
298     return 0;
299 }
300 
301 static int hci_transport_netgraph_close(void){
302     // first remove run loop handler
303     btstack_run_loop_remove_data_source(&hci_transport_netgraph_data_source_hci_raw);
304 
305     // then close device
306     close(hci_transport_netgraph_data_source_hci_raw.source.fd);
307     hci_transport_netgraph_data_source_hci_raw.source.fd = -1;
308     return 0;
309 }
310 
311 static void hci_transport_netgraph_register_packet_handler(void (*handler)(uint8_t packet_type, uint8_t *packet, uint16_t size)){
312     hci_transport_netgraph_packet_handler = handler;
313 }
314 
315 static int hci_transport_netgraph_can_send_now(uint8_t packet_type){
316     UNUSED(packet_type);
317     return hci_transport_netgraph_tx_state == TX_IDLE;
318 }
319 
320 static int hci_transport_netgraph_send_packet(uint8_t packet_type, uint8_t * packet, int size) {
321     btstack_assert(hci_transport_netgraph_write_bytes_len == 0);
322 
323     // store packet type before actual data and increase size
324     uint8_t * buffer = &packet[-1];
325     uint32_t  buffer_size = size + 1;
326     buffer[0] = packet_type;
327 
328     // setup async write
329     hci_transport_netgraph_write_bytes_data = buffer;
330     hci_transport_netgraph_write_bytes_len  = buffer_size;
331     hci_transport_netgraph_write_packet_type = packet_type;
332     hci_transport_netgraph_tx_state = TX_W4_PACKET_SENT;
333 
334     btstack_run_loop_enable_data_source_callbacks(&hci_transport_netgraph_data_source_hci_raw, DATA_SOURCE_CALLBACK_WRITE);
335     return 0;
336 }
337 
338 static const hci_transport_t hci_transport_netgraph = {
339     .name = "Netgraph",
340     .init = &hci_transport_netgraph_init,
341     .open = &hci_transport_netgraph_open,
342     .close = &hci_transport_netgraph_close,
343     .register_packet_handler = &hci_transport_netgraph_register_packet_handler,
344     .can_send_packet_now = &hci_transport_netgraph_can_send_now,
345     .send_packet = &hci_transport_netgraph_send_packet
346 };
347 
348 const hci_transport_t * hci_transport_netgraph_instance(void) {
349     return &hci_transport_netgraph;
350 }
351