xref: /btstack/port/freebsd-netgraph/hci_transport_netgraph.c (revision de13ba803acdb54c41ebfbca4e795757f719a2dd)
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 
hci_transport_netgraph_process_write(btstack_data_source_t * ds)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 
hci_transport_netgraph_process_read(btstack_data_source_t * ds)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 
hci_transport_netgraph_process(btstack_data_source_t * ds,btstack_data_source_callback_type_t callback_type)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
hci_transport_netgraph_init(const void * transport_config)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     // reset sockets file descriptors
236     hci_transport_netgraph_hci_raw_control_socket = -1;
237     hci_transport_netgraph_hci_raw_data_socket    = -1;
238 }
239 
hci_transport_netgraph_open(void)240 static int hci_transport_netgraph_open(void) {
241 
242     log_info("open(%s)", hci_node_name);
243 
244     int res;
245     struct ngm_rmhook rmh;
246     struct ngm_connect con;
247     char path[NG_NODESIZ];
248     char btstack_node_hci_raw_name[NG_NODESIZ];
249 
250     // create hci_raw netgraph node
251     snprintf(btstack_node_hci_raw_name, sizeof(btstack_node_hci_raw_name), "btstack");
252     if (NgMkSockNode(btstack_node_hci_raw_name, &hci_transport_netgraph_hci_raw_control_socket, &hci_transport_netgraph_hci_raw_data_socket) < 0){
253         log_error("Cannot create netgraph node '%s'", btstack_node_hci_raw_name);
254         return -1;
255     }
256 
257     // tell hci node to disconnect raw hook to btsock_hci_raw (ignore result)
258     snprintf(path, sizeof(path), "%s:", hci_node_name);
259     strncpy(rmh.ourhook, "raw", sizeof(rmh.ourhook));
260     res = NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE,
261                         NGM_RMHOOK, &rmh, sizeof(rmh));
262     log_info("Remove HCI RAW Hook: %d", res);
263 
264     // tell hci node to disconnect acl hook to ubt0l2cap (ignore result)
265     snprintf(path, sizeof(path), "%s:", hci_node_name);
266     strncpy(rmh.ourhook, "acl", sizeof(rmh.ourhook));
267     res = NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE,
268                     NGM_RMHOOK, &rmh, sizeof(rmh));
269     log_info("Remove ACL Hook: %d", res);
270 
271     // connect ubth0hci/raw hook
272     snprintf(path, sizeof(path), "%s:",         btstack_node_hci_raw_name);
273     snprintf(con.path, sizeof(con.path), "%s:", hci_node_name);
274     strncpy(con.ourhook,  "raw", sizeof(con.ourhook));
275     strncpy(con.peerhook, "raw", sizeof(con.peerhook));
276     if (NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE,
277                   NGM_CONNECT, &con, sizeof(con)) < 0) {
278         log_error("Cannot connect %s%s to %s%s", path, con.ourhook, con.path, con.peerhook);
279         return -1;
280     }
281 
282     // connect ubth0hci/acl hook
283     snprintf(path, sizeof(path), "%s:",         btstack_node_hci_raw_name);
284     snprintf(con.path, sizeof(con.path), "%s:", hci_node_name);
285     strncpy(con.ourhook,  "acl", sizeof(con.ourhook));
286     strncpy(con.peerhook, "acl", sizeof(con.peerhook));
287     if (NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE,
288                   NGM_CONNECT, &con, sizeof(con)) < 0) {
289         log_error("Cannot connect %s%s to %s%s", path, con.ourhook, con.path, con.peerhook);
290         return -1;
291     }
292 
293     // set up HCI RAW data_source
294     btstack_run_loop_set_data_source_fd(&hci_transport_netgraph_data_source_hci_raw, hci_transport_netgraph_hci_raw_data_socket);
295     btstack_run_loop_set_data_source_handler(&hci_transport_netgraph_data_source_hci_raw, &hci_transport_netgraph_process);
296     btstack_run_loop_add_data_source(&hci_transport_netgraph_data_source_hci_raw);
297     btstack_run_loop_enable_data_source_callbacks(&hci_transport_netgraph_data_source_hci_raw, DATA_SOURCE_CALLBACK_READ);
298 
299     // init tx state machines
300     hci_transport_netgraph_tx_state = TX_IDLE;
301 
302     return 0;
303 }
304 
hci_transport_netgraph_close(void)305 static int hci_transport_netgraph_close(void){
306     // first remove data source
307     btstack_run_loop_remove_data_source(&hci_transport_netgraph_data_source_hci_raw);
308 
309     log_info("Close Netgraph sockets %u and %u", hci_transport_netgraph_hci_raw_control_socket, hci_transport_netgraph_hci_raw_data_socket);
310 
311     // then close device
312     close(hci_transport_netgraph_hci_raw_control_socket);
313     close(hci_transport_netgraph_hci_raw_data_socket);
314 
315     // and reset file descriptors
316     hci_transport_netgraph_hci_raw_control_socket = -1;
317     hci_transport_netgraph_hci_raw_data_socket    = -1;
318     return 0;
319 }
320 
hci_transport_netgraph_register_packet_handler(void (* handler)(uint8_t packet_type,uint8_t * packet,uint16_t size))321 static void hci_transport_netgraph_register_packet_handler(void (*handler)(uint8_t packet_type, uint8_t *packet, uint16_t size)){
322     hci_transport_netgraph_packet_handler = handler;
323 }
324 
hci_transport_netgraph_can_send_now(uint8_t packet_type)325 static int hci_transport_netgraph_can_send_now(uint8_t packet_type){
326     UNUSED(packet_type);
327     return hci_transport_netgraph_tx_state == TX_IDLE;
328 }
329 
hci_transport_netgraph_send_packet(uint8_t packet_type,uint8_t * packet,int size)330 static int hci_transport_netgraph_send_packet(uint8_t packet_type, uint8_t * packet, int size) {
331     btstack_assert(hci_transport_netgraph_write_bytes_len == 0);
332 
333     // store packet type before actual data and increase size
334     uint8_t * buffer = &packet[-1];
335     uint32_t  buffer_size = size + 1;
336     buffer[0] = packet_type;
337 
338     // setup async write
339     hci_transport_netgraph_write_bytes_data = buffer;
340     hci_transport_netgraph_write_bytes_len  = buffer_size;
341     hci_transport_netgraph_write_packet_type = packet_type;
342     hci_transport_netgraph_tx_state = TX_W4_PACKET_SENT;
343 
344     btstack_run_loop_enable_data_source_callbacks(&hci_transport_netgraph_data_source_hci_raw, DATA_SOURCE_CALLBACK_WRITE);
345     return 0;
346 }
347 
348 static const hci_transport_t hci_transport_netgraph = {
349     .name = "Netgraph",
350     .init = &hci_transport_netgraph_init,
351     .open = &hci_transport_netgraph_open,
352     .close = &hci_transport_netgraph_close,
353     .register_packet_handler = &hci_transport_netgraph_register_packet_handler,
354     .can_send_packet_now = &hci_transport_netgraph_can_send_now,
355     .send_packet = &hci_transport_netgraph_send_packet
356 };
357 
hci_transport_netgraph_instance(void)358 const hci_transport_t * hci_transport_netgraph_instance(void) {
359     return &hci_transport_netgraph;
360 }
361