From 22029648dbaf7b329b48db7cbda905ed784c5ee2 Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Tue, 7 Nov 2023 09:57:44 +0100 Subject: [PATCH] freebsd-netgraph: FreeBSD port that accesses ng_hci node --- port/freebsd-netgraph/CMakeLists.txt | 206 +++++++++++ port/freebsd-netgraph/README.md | 44 +++ port/freebsd-netgraph/btstack_config.h | 67 ++++ .../freebsd-netgraph/hci_transport_netgraph.c | 350 ++++++++++++++++++ .../freebsd-netgraph/hci_transport_netgraph.h | 61 +++ port/freebsd-netgraph/main.c | 275 ++++++++++++++ 6 files changed, 1003 insertions(+) create mode 100644 port/freebsd-netgraph/CMakeLists.txt create mode 100644 port/freebsd-netgraph/README.md create mode 100644 port/freebsd-netgraph/btstack_config.h create mode 100644 port/freebsd-netgraph/hci_transport_netgraph.c create mode 100644 port/freebsd-netgraph/hci_transport_netgraph.h create mode 100644 port/freebsd-netgraph/main.c diff --git a/port/freebsd-netgraph/CMakeLists.txt b/port/freebsd-netgraph/CMakeLists.txt new file mode 100644 index 000000000..67b3fd058 --- /dev/null +++ b/port/freebsd-netgraph/CMakeLists.txt @@ -0,0 +1,206 @@ +cmake_minimum_required (VERSION 3.18) + +project(BTstack-posix-h4) + +SET(BTSTACK_ROOT ${CMAKE_SOURCE_DIR}/../..) + +# extra compiler warnings +if ("${CMAKE_C_COMPILER_ID}" MATCHES ".*Clang.*") + # using Clang + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wunused-variable -Wswitch-default -Werror") +elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + # using GCC + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wunused-but-set-variable -Wunused-variable -Wswitch-default -Werror") +elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "Intel") + # using Intel C++ +elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") + # using Visual Studio C++ +endif() + + +# to generate .h from .gatt files +find_package (Python REQUIRED COMPONENTS Interpreter) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +# local dir for btstack_config.h after build dir to avoid using .h from Makefile +include_directories(.) + + +include_directories(${BTSTACK_ROOT}/3rd-party/micro-ecc) +include_directories(${BTSTACK_ROOT}/3rd-party/bluedroid/decoder/include) +include_directories(${BTSTACK_ROOT}/3rd-party/bluedroid/encoder/include) +include_directories(${BTSTACK_ROOT}/3rd-party/lc3-google/include) +include_directories(${BTSTACK_ROOT}/3rd-party/md5) +include_directories(${BTSTACK_ROOT}/3rd-party/hxcmod-player) +include_directories(${BTSTACK_ROOT}/3rd-party/hxcmod-player/mod) +include_directories(${BTSTACK_ROOT}/3rd-party/lwip/core/src/include) +include_directories(${BTSTACK_ROOT}/3rd-party/lwip/dhcp-server) +include_directories(${BTSTACK_ROOT}/3rd-party/rijndael) +include_directories(${BTSTACK_ROOT}/3rd-party/yxml) +include_directories(${BTSTACK_ROOT}/3rd-party/tinydir) +include_directories(${BTSTACK_ROOT}/src) +include_directories(${BTSTACK_ROOT}/chipset/bcm) +include_directories(${BTSTACK_ROOT}/chipset/csr) +include_directories(${BTSTACK_ROOT}/chipset/cc256x) +include_directories(${BTSTACK_ROOT}/chipset/em9301) +include_directories(${BTSTACK_ROOT}/chipset/realtek) +include_directories(${BTSTACK_ROOT}/chipset/tc3566x) +include_directories(${BTSTACK_ROOT}/chipset/stlc2500d) +include_directories(${BTSTACK_ROOT}/chipset/zephyr) +include_directories(${BTSTACK_ROOT}/platform/embedded) +include_directories(${BTSTACK_ROOT}/platform/lwip) +include_directories(${BTSTACK_ROOT}/platform/lwip/port) +include_directories(${BTSTACK_ROOT}/platform/posix) + +file(GLOB SOURCES_SRC "${BTSTACK_ROOT}/src/*.c" "${BTSTACK_ROOT}/example/sco_demo_util.c") +file(GLOB SOURCES_BLE "${BTSTACK_ROOT}/src/ble/*.c") +file(GLOB SOURCES_BLUEDROID "${BTSTACK_ROOT}/3rd-party/bluedroid/encoder/srce/*.c" "${BTSTACK_ROOT}/3rd-party/bluedroid/decoder/srce/*.c") +file(GLOB SOURCES_CLASSIC "${BTSTACK_ROOT}/src/classic/*.c") +file(GLOB SOURCES_MESH "${BTSTACK_ROOT}/src/mesh/*.c") +file(GLOB SOURCES_GATT "${BTSTACK_ROOT}/src/ble/gatt-service/*.c") +file(GLOB SOURCES_UECC "${BTSTACK_ROOT}/3rd-party/micro-ecc/uECC.c") +file(GLOB SOURCES_HXCMOD "${BTSTACK_ROOT}/3rd-party/hxcmod-player/*.c" "${BTSTACK_ROOT}/3rd-party/hxcmod-player/mods/*.c") +file(GLOB SOURCES_MD5 "${BTSTACK_ROOT}/3rd-party/md5/md5.c") +file(GLOB SOURCES_RIJNDAEL "${BTSTACK_ROOT}/3rd-party/rijndael/rijndael.c") +file(GLOB SOURCES_YXML "${BTSTACK_ROOT}/3rd-party/yxml/yxml.c") +file(GLOB SOURCES_POSIX "${BTSTACK_ROOT}/platform/posix/*.c") +file(GLOB SOURCES_BCM "${BTSTACK_ROOT}/chipset/bcm/*.c") +file(GLOB SOURCES_CSR "${BTSTACK_ROOT}/chipset/csr/*.c") +file(GLOB SOURCES_EM9301 "${BTSTACK_ROOT}/chipset/em9301/*.c") +file(GLOB SOURCES_STLC2500D "${BTSTACK_ROOT}/chipset/stlc2500d/*.c") +file(GLOB SOURCES_TC2566X "${BTSTACK_ROOT}/chipset/tc3566x/*.c") +file(GLOB SOURCES_REALTEK "${BTSTACK_ROOT}/chipset/realtek/*.c") +file(GLOB SOURCES_ZEPHYR "${BTSTACK_ROOT}/chipset/zephyr/*.c") +file(GLOB SOURCES_LC3_GOOGLE "${BTSTACK_ROOT}/3rd-party/lc3-google/src/*.c") +file(GLOB SOURCES_PORT "*.c") + +set(LWIP_CORE_SRC + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/def.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/inet_chksum.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/init.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/ip.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/mem.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/memp.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/netif.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/pbuf.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/tcp.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/tcp_in.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/tcp_out.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/timeouts.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/udp.c + ) +set (LWIP_IPV4_SRC + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/ipv4/acd.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/ipv4/dhcp.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/ipv4/etharp.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/ipv4/icmp.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/ipv4/ip4.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/ipv4/ip4_addr.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/core/ipv4/ip4_frag.c + ) +set (LWIP_NETIF_SRC + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/netif/ethernet.c + ) +set (LWIP_HTTPD + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/apps/http/altcp_proxyconnect.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/apps/http/fs.c + ${BTSTACK_ROOT}/3rd-party/lwip/core/src/apps/http/httpd.c + ) +set (LWIP_DHCPD + ${BTSTACK_ROOT}/3rd-party/lwip/dhcp-server/dhserver.c + ) +set (LWIP_PORT + ${BTSTACK_ROOT}/platform/lwip/port/sys_arch.c + ${BTSTACK_ROOT}//platform/lwip/bnep_lwip.c + ) +set (SOURCES_LWIP ${LWIP_CORE_SRC} ${LWIP_IPV4_SRC} ${LWIP_NETIF_SRC} ${LWIP_HTTPD} ${LWIP_DHCPD} ${LWIP_PORT}) + +file(GLOB SOURCES_BLE_OFF "${BTSTACK_ROOT}/src/ble/le_device_db_memory.c") +list(REMOVE_ITEM SOURCES_BLE ${SOURCES_BLE_OFF}) + +file(GLOB SOURCES_POSIX_OFF "${BTSTACK_ROOT}/platform/posix/le_device_db_fs.c") +list(REMOVE_ITEM SOURCES_POSIX ${SOURCES_POSIX_OFF}) + +set(SOURCES + ${SOURCES_BLE} + ${SOURCES_BCM} + ${SOURCES_BLUEDROID} + ${SOURCES_CLASSIC} + ${SOURCES_CSR} + ${SOURCES_EM9301} + ${SOURCES_GATT} + ${SOURCES_HXCMOD} + ${SOURCES_HXCMOD} + ${SOURCES_LIBUSB} + ${SOURCES_MD5} + ${SOURCES_MESH} + ${SOURCES_PORT} + ${SOURCES_REALTEK} + ${SOURCES_RIJNDAEL} + ${SOURCES_SRC} + ${SOURCES_CSR} + ${SOURCES_STLC2500D} + ${SOURCES_TC2566X} + ${SOURCES_UECC} + ${SOURCES_POSIX} + ${SOURCES_YXML} + ${SOURCES_ZEPHYR} +) +list(SORT SOURCES) + +# create static lib +add_library(btstack STATIC ${SOURCES}) + +# pkgconfig +find_package(PkgConfig) + +# portaudio +if (PkgConfig_FOUND) + pkg_check_modules(PORTAUDIO portaudio-2.0) + if(PORTAUDIO_FOUND) + message("HAVE_PORTAUDIO") + include_directories(${PORTAUDIO_INCLUDE_DIRS}) + link_directories(${PORTAUDIO_LIBRARY_DIRS}) + link_libraries(${PORTAUDIO_LIBRARIES}) + add_compile_definitions(HAVE_PORTAUDIO) + endif() +endif() + +# pthread +find_package(Threads) +link_libraries(${CMAKE_THREAD_LIBS_INIT}) + +# Add CC256x Support and specify init script +# set (CC256X_INIT_SCRIPT bluetooth_init_cc2564C_1.5.c) +# include(${BTSTACK_ROOT}/chipset/cc256x/cc256x.cmake) + +# Add BCM Support and download PatchRAM files +# include(${BTSTACK_ROOT}/chipset/bcm/bcm.cmake) + +# get list of examples, skipping mesh_node_demo +include(../../example/CMakeLists.txt) +set (EXAMPLES ${EXAMPLES_GENERAL} ${EXAMPLES_CLASSIC_ONLY} ${EXAMPLES_LE_ONLY} ${EXAMPLES_DUAL_MODE}) +list(REMOVE_DUPLICATES EXAMPLES) +list(REMOVE_ITEM EXAMPLES "mesh_node_demo") + +# create targets +foreach(EXAMPLE ${EXAMPLES}) + # get c file + set (SOURCES_EXAMPLE ${BTSTACK_ROOT}/example/${EXAMPLE}.c) + + # add GATT DB creation + if ( "${EXAMPLES_GATT_FILES}" MATCHES ${EXAMPLE} ) + message("example ${EXAMPLE} -- with GATT DB") + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${EXAMPLE}.h + DEPENDS ${BTSTACK_ROOT}/example/${EXAMPLE}.gatt + COMMAND ${Python_EXECUTABLE} + ARGS ${BTSTACK_ROOT}/tool/compile_gatt.py ${BTSTACK_ROOT}/example/${EXAMPLE}.gatt ${CMAKE_CURRENT_BINARY_DIR}/${EXAMPLE}.h + ) + list(APPEND SOURCES_EXAMPLE ${CMAKE_CURRENT_BINARY_DIR}/${EXAMPLE}.h) + else() + message("example ${EXAMPLE}") + endif() + add_executable(${EXAMPLE} ${SOURCES_EXAMPLE}) + target_link_libraries(${EXAMPLE} btstack pthread netgraph) +endforeach(EXAMPLE) diff --git a/port/freebsd-netgraph/README.md b/port/freebsd-netgraph/README.md new file mode 100644 index 000000000..296e6dbd7 --- /dev/null +++ b/port/freebsd-netgraph/README.md @@ -0,0 +1,44 @@ +# BTstack Port for FreeBSD Systems + +## Overview +This port assumes that FreeBSD provides an ng_hci netgraph node for a connected Bluetooth Controller. +In most cases, these are Bluetooth USB dongles or built-in Bluetooth Controller connected via USB. + +For Bluetooth Controllers connected via UART, the POSX-H4 port might be a better option als + +## Implementation details +In FreeBSD 13.2, the hci node is connected to a l2cap node and a btsock_hci_raw node. In order to take control, this +port create a custom netgraph ng_socket node and connect to the 'acl' and 'raw' hooks of the hci node. The OS Bluetooth +functionality will be interrupted. + +## Compilation + +BTstack's FeeeBSD port does not have additional dependencies. To compile the cmake project with make + + mkdir build + cd build + cmake .. + make + +or using Ninja: + + mkdir ninja + cd ninja + cmake .. + ninja + +## Running the examples + +As the port needs to reconfigure the Bluetooth netgraph node, it needs to run with root privileges. +It tries to connect to 'ubt0hci' by default. If your Bluetooth Controller is different, you can select it with '-u node' +On start, BTstack prints the path to the packet log and prints the information on the detected Buetooth Controller. + + $ sudo gatt_counter + Packet Log: /tmp/hci_dump.pklg + BTstack counter 0001 + BTstack up and running on 00:1A:7D:DA:71:13. + +## ToDO +- drop privileges after startup +- auto-detect ng_hci node +- support for profiles that require SCO: HFP & HSP diff --git a/port/freebsd-netgraph/btstack_config.h b/port/freebsd-netgraph/btstack_config.h new file mode 100644 index 000000000..96f08690f --- /dev/null +++ b/port/freebsd-netgraph/btstack_config.h @@ -0,0 +1,67 @@ +// +// btstack_config.h for generic POSIX H4 port +// +// Documentation: https://bluekitchen-gmbh.com/btstack/#how_to/ +// + +#ifndef BTSTACK_CONFIG_H +#define BTSTACK_CONFIG_H + +// Port related features +#define HAVE_ASSERT +#define HAVE_BTSTACK_STDIN +#define HAVE_EM9304_PATCH_CONTAINER +#define HAVE_MALLOC +#define HAVE_POSIX_FILE_IO +#define HAVE_POSIX_TIME + +// BTstack features that can be enabled +#define ENABLE_ATT_DELAYED_RESPONSE +#define ENABLE_AVRCP_COVER_ART +#define ENABLE_BLE +#define ENABLE_CLASSIC +#define ENABLE_CROSS_TRANSPORT_KEY_DERIVATION +#define ENABLE_GOEP_L2CAP +#define ENABLE_HFP_WIDE_BAND_SPEECH +#define ENABLE_L2CAP_ENHANCED_RETRANSMISSION_MODE +#define ENABLE_L2CAP_LE_CREDIT_BASED_FLOW_CONTROL_MODE +#define ENABLE_LE_CENTRAL +#define ENABLE_LE_DATA_LENGTH_EXTENSION +#define ENABLE_LE_PERIPHERAL +#define ENABLE_LE_PRIVACY_ADDRESS_RESOLUTION +#define ENABLE_LE_SECURE_CONNECTIONS +#define ENABLE_LOG_ERROR +#define ENABLE_LOG_INFO +#define ENABLE_MICRO_ECC_FOR_LE_SECURE_CONNECTIONS +#define ENABLE_PRINTF_HEXDUMP +#define ENABLE_SCO_OVER_HCI +#define ENABLE_SDP_DES_DUMP +#define ENABLE_SOFTWARE_AES128 + +// BTstack configuration. buffers, sizes, ... +#define HCI_ACL_PAYLOAD_SIZE (1691 + 4) +#define HCI_INCOMING_PRE_BUFFER_SIZE 14 // sizeof benep heade, avoid memcpy + +#define NVM_NUM_DEVICE_DB_ENTRIES 16 +#define NVM_NUM_LINK_KEYS 16 + +// Mesh Configuration +#define ENABLE_MESH +#define ENABLE_MESH_ADV_BEARER +#define ENABLE_MESH_GATT_BEARER +#define ENABLE_MESH_PB_ADV +#define ENABLE_MESH_PB_GATT +#define ENABLE_MESH_PROVISIONER +#define ENABLE_MESH_PROXY_SERVER + +#define MAX_NR_MESH_SUBNETS 2 +#define MAX_NR_MESH_TRANSPORT_KEYS 16 +#define MAX_NR_MESH_VIRTUAL_ADDRESSES 16 + +// allow for one NetKey update +#define MAX_NR_MESH_NETWORK_KEYS (MAX_NR_MESH_SUBNETS+1) + +#define HCI_RESET_RESEND_TIMEOUT_MS 1000 + +#endif + diff --git a/port/freebsd-netgraph/hci_transport_netgraph.c b/port/freebsd-netgraph/hci_transport_netgraph.c new file mode 100644 index 000000000..a0c6ab5ca --- /dev/null +++ b/port/freebsd-netgraph/hci_transport_netgraph.c @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2023 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#define BTSTACK_FILE__ "hci_transport_netgraph.c" + +#include "hci_transport_netgraph.h" + +#include "btstack_bool.h" +#include "btstack_config.h" +#include "btstack_debug.h" +#include "btstack_event.h" +#include "btstack_run_loop.h" +#include "hci_transport.h" +#include "hci.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// avoid warning by /usr/include/netgraph/bluetooth/include/ng_btsocket.h +#define L2CAP_SOCKET_CHECKED +#include +#include +#include +#include +#include + +// hci packet handler +static void (*hci_transport_netgraph_packet_handler)(uint8_t packet_type, uint8_t *packet, uint16_t size); + +// data source for integration with BTstack Runloop +static btstack_data_source_t hci_transport_netgraph_data_source_hci_raw; + +// block write +static uint8_t hci_transport_netgraph_write_packet_type; +static int hci_transport_netgraph_write_bytes_len; +static const uint8_t * hci_transport_netgraph_write_bytes_data; + +// assert pre-buffer for packet type is available +#if !defined(HCI_OUTGOING_PRE_BUFFER_SIZE) || (HCI_OUTGOING_PRE_BUFFER_SIZE == 0) +#error HCI_OUTGOING_PRE_BUFFER_SIZE not defined. Please update hci.h +#endif + +typedef enum { + TX_OFF, + TX_IDLE, + TX_W4_PACKET_SENT, +} TX_STATE; + +// write state +static TX_STATE hci_transport_netgraph_tx_state; + +// incoming packet buffer +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) +static uint8_t * hci_packet = &hci_packet_with_pre_buffer[HCI_INCOMING_PRE_BUFFER_SIZE]; + +// Netgraph node +const char * hci_node_name = "ubt0hci"; + +// Control and Data socket for BTstack Netgraph node +static int hci_transport_netgraph_hci_raw_control_socket; +static int hci_transport_netgraph_hci_raw_data_socket; + +// Track HCI Netgraph Initialization +#define HCI_TODO_READ_BD_ADDR 1 +#define HCI_TODO_READ_LOCAL_SUPPORTED_FEATURES 2 +#define HCI_TODO_READ_BUFFER_SIZE 4 + +static uint8_t hci_transport_netgraph_hci_todo_init; + +static void hci_transport_netgraph_process_write(btstack_data_source_t *ds) { + + if (hci_transport_netgraph_write_bytes_len == 0) return; + + const char * hook = NULL; + switch (hci_transport_netgraph_write_packet_type){ + case HCI_COMMAND_DATA_PACKET: + hook = "raw"; + break; + case HCI_ACL_DATA_PACKET: + hook = "acl"; + break; + default: + btstack_unreachable(); + break; + } + + int res = NgSendData(ds->source.fd, hook, hci_transport_netgraph_write_bytes_data, hci_transport_netgraph_write_bytes_len); + + if (res < 0){ + log_error("send data via hook %s returned error %d", hook, errno); + btstack_run_loop_enable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_WRITE); + return; + } + + btstack_run_loop_disable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_WRITE); + + hci_transport_netgraph_tx_state = TX_IDLE; + hci_transport_netgraph_write_bytes_len = 0; + hci_transport_netgraph_write_bytes_data = NULL; + + // notify upper stack that it can send again + static const uint8_t packet_sent_event[] = { HCI_EVENT_TRANSPORT_PACKET_SENT, 0}; + hci_transport_netgraph_packet_handler(HCI_EVENT_PACKET, (uint8_t *) &packet_sent_event[0], sizeof(packet_sent_event)); +} + +static void hci_transport_netgraph_process_read(btstack_data_source_t * ds) { + // read complete HCI packet + char hook[NG_NODESIZ]; + int bytes_read = NgRecvData(ds->source.fd, (u_char *) hci_packet, HCI_INCOMING_PACKET_BUFFER_SIZE + 1, hook); + + if (bytes_read < 0){ + log_error("Read failed %d", (int) bytes_read); + } else { + + // ignore 'echo' - only accept hci events from 'raw' hook and acl packet from 'acl' hook + // - HCI CMDs that are received over 'raw' hook are sent back via 'raw' hook + if (strcmp(hook, "raw") == 0){ + if (hci_packet[0] != HCI_EVENT_PACKET){ + return; + } + } + // - ACL Packets that are received from driver are also sent via 'raw' hook + if (strcmp(hook, "acl") == 0){ + if (hci_packet[0] != HCI_ACL_DATA_PACKET){ + return; + } + } + + // track HCI Event Complete for init commands + if (hci_packet[0] == HCI_EVENT_PACKET){ + if (hci_event_packet_get_type(&hci_packet[1]) == HCI_EVENT_COMMAND_COMPLETE){ + uint8_t todos_before = hci_transport_netgraph_hci_todo_init; + switch (hci_event_command_complete_get_command_opcode(&hci_packet[1])){ + case HCI_OPCODE_HCI_RESET: + hci_transport_netgraph_hci_todo_init = HCI_TODO_READ_BD_ADDR | HCI_TODO_READ_LOCAL_SUPPORTED_FEATURES | HCI_TODO_READ_BUFFER_SIZE; + break; + case HCI_OPCODE_HCI_READ_BD_ADDR: + hci_transport_netgraph_hci_todo_init &= ~HCI_TODO_READ_BD_ADDR; + break; + case HCI_OPCODE_HCI_READ_LOCAL_SUPPORTED_FEATURES: + hci_transport_netgraph_hci_todo_init &= ~HCI_TODO_READ_LOCAL_SUPPORTED_FEATURES; + break; + case HCI_OPCODE_HCI_READ_BUFFER_SIZE: + hci_transport_netgraph_hci_todo_init &= ~HCI_TODO_READ_BUFFER_SIZE; + break; + default: + break; + } + if ((todos_before != 0) && (hci_transport_netgraph_hci_todo_init == 0)){ + // tell ubt0hci to disconnect acl hook to ubt0l2cap + char path[NG_NODESIZ]; + snprintf(path, sizeof(path), "%s:", hci_node_name); + if (NgSendAsciiMsg(hci_transport_netgraph_hci_raw_control_socket, path, "%s", "init") < 0) { + log_error("Failed to send init to %s", path); + } + } + } + } + + uint16_t packet_len = bytes_read-1u; + hci_transport_netgraph_packet_handler(hci_packet[0], &hci_packet[1], packet_len); + } +} + +static void hci_transport_netgraph_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type) { + if (ds->source.fd < 0) return; + switch (callback_type){ + case DATA_SOURCE_CALLBACK_READ: + hci_transport_netgraph_process_read(ds); + break; + case DATA_SOURCE_CALLBACK_WRITE: + hci_transport_netgraph_process_write(ds); + break; + default: + break; + } +} + +// TODO: could pass device name +static void hci_transport_netgraph_init (const void *transport_config){ + log_info("init"); + + btstack_assert(transport_config != NULL); + + // extract netgraph device name + hci_transport_config_netgraph_t * hci_transport_config_netgraph = (hci_transport_config_netgraph_t*) transport_config; + hci_node_name = hci_transport_config_netgraph->device_name; + + // set state to off + hci_transport_netgraph_tx_state = TX_OFF; +} + +static int hci_transport_netgraph_open(void) { + + log_info("open(%s)", hci_node_name); + + int res; + struct ngm_rmhook rmh; + struct ngm_connect con; + char path[NG_NODESIZ]; + char btstack_node_hci_raw_name[NG_NODESIZ]; + + // create hci_raw netgraph node + snprintf(btstack_node_hci_raw_name, sizeof(btstack_node_hci_raw_name), "btstack"); + if (NgMkSockNode(btstack_node_hci_raw_name, &hci_transport_netgraph_hci_raw_control_socket, &hci_transport_netgraph_hci_raw_data_socket) < 0){ + log_error("Cannot create netgraph node '%s'", btstack_node_hci_raw_name); + return -1; + } + + // tell hci node to disconnect raw hook to btsock_hci_raw (ignore result) + snprintf(path, sizeof(path), "%s:", hci_node_name); + strncpy(rmh.ourhook, "raw", sizeof(rmh.ourhook)); + res = NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE, + NGM_RMHOOK, &rmh, sizeof(rmh)); + log_info("Remove HCI RAW Hook: %d", res); + + // tell hci node to disconnect acl hook to ubt0l2cap (ignore result) + snprintf(path, sizeof(path), "%s:", hci_node_name); + strncpy(rmh.ourhook, "acl", sizeof(rmh.ourhook)); + res = NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE, + NGM_RMHOOK, &rmh, sizeof(rmh)); + log_info("Remove ACL Hook: %d", res); + + // connect ubth0hci/raw hook + snprintf(path, sizeof(path), "%s:", btstack_node_hci_raw_name); + snprintf(con.path, sizeof(con.path), "%s:", hci_node_name); + strncpy(con.ourhook, "raw", sizeof(con.ourhook)); + strncpy(con.peerhook, "raw", sizeof(con.peerhook)); + if (NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE, + NGM_CONNECT, &con, sizeof(con)) < 0) { + log_error("Cannot connect %s%s to %s%s", path, con.ourhook, con.path, con.peerhook); + return -1; + } + + // connect ubth0hci/acl hook + snprintf(path, sizeof(path), "%s:", btstack_node_hci_raw_name); + snprintf(con.path, sizeof(con.path), "%s:", hci_node_name); + strncpy(con.ourhook, "acl", sizeof(con.ourhook)); + strncpy(con.peerhook, "acl", sizeof(con.peerhook)); + if (NgSendMsg(hci_transport_netgraph_hci_raw_control_socket, path, NGM_GENERIC_COOKIE, + NGM_CONNECT, &con, sizeof(con)) < 0) { + log_error("Cannot connect %s%s to %s%s", path, con.ourhook, con.path, con.peerhook); + return -1; + } + + // set up HCI RAW data_source + btstack_run_loop_set_data_source_fd(&hci_transport_netgraph_data_source_hci_raw, hci_transport_netgraph_hci_raw_data_socket); + btstack_run_loop_set_data_source_handler(&hci_transport_netgraph_data_source_hci_raw, &hci_transport_netgraph_process); + btstack_run_loop_add_data_source(&hci_transport_netgraph_data_source_hci_raw); + btstack_run_loop_enable_data_source_callbacks(&hci_transport_netgraph_data_source_hci_raw, DATA_SOURCE_CALLBACK_READ); + + // init tx state machines + hci_transport_netgraph_tx_state = TX_IDLE; + + return 0; +} + +static int hci_transport_netgraph_close(void){ + // first remove run loop handler + btstack_run_loop_remove_data_source(&hci_transport_netgraph_data_source_hci_raw); + + // then close device + close(hci_transport_netgraph_data_source_hci_raw.source.fd); + hci_transport_netgraph_data_source_hci_raw.source.fd = -1; + return 0; +} + +static void hci_transport_netgraph_register_packet_handler(void (*handler)(uint8_t packet_type, uint8_t *packet, uint16_t size)){ + hci_transport_netgraph_packet_handler = handler; +} + +static int hci_transport_netgraph_can_send_now(uint8_t packet_type){ + UNUSED(packet_type); + return hci_transport_netgraph_tx_state == TX_IDLE; +} + +static int hci_transport_netgraph_send_packet(uint8_t packet_type, uint8_t * packet, int size) { + btstack_assert(hci_transport_netgraph_write_bytes_len == 0); + + // store packet type before actual data and increase size + uint8_t * buffer = &packet[-1]; + uint32_t buffer_size = size + 1; + buffer[0] = packet_type; + + // setup async write + hci_transport_netgraph_write_bytes_data = buffer; + hci_transport_netgraph_write_bytes_len = buffer_size; + hci_transport_netgraph_write_packet_type = packet_type; + hci_transport_netgraph_tx_state = TX_W4_PACKET_SENT; + + btstack_run_loop_enable_data_source_callbacks(&hci_transport_netgraph_data_source_hci_raw, DATA_SOURCE_CALLBACK_WRITE); + return 0; +} + +static const hci_transport_t hci_transport_netgraph = { + .name = "Netgraph", + .init = &hci_transport_netgraph_init, + .open = &hci_transport_netgraph_open, + .close = &hci_transport_netgraph_close, + .register_packet_handler = &hci_transport_netgraph_register_packet_handler, + .can_send_packet_now = &hci_transport_netgraph_can_send_now, + .send_packet = &hci_transport_netgraph_send_packet +}; + +const hci_transport_t * hci_transport_netgraph_instance(void) { + return &hci_transport_netgraph; +} diff --git a/port/freebsd-netgraph/hci_transport_netgraph.h b/port/freebsd-netgraph/hci_transport_netgraph.h new file mode 100644 index 000000000..fc8b98221 --- /dev/null +++ b/port/freebsd-netgraph/hci_transport_netgraph.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +/** + * @brief TODO + */ + +#ifndef HCI_TRANSPORT_NETGRAPH_H +#define HCI_TRANSPORT_NETGRAPH_H + +#include "hci_transport.h" + +#if defined __cplusplus +extern "C" { +#endif + +typedef struct { + hci_transport_config_type_t type; + const char *device_name; +} hci_transport_config_netgraph_t; + +const hci_transport_t * hci_transport_netgraph_instance(void); + +#if defined __cplusplus +} +#endif +#endif // HCI_TRANSPORT_NETGRAPH_H diff --git a/port/freebsd-netgraph/main.c b/port/freebsd-netgraph/main.c new file mode 100644 index 000000000..ca79beab5 --- /dev/null +++ b/port/freebsd-netgraph/main.c @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN + * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#define BTSTACK_FILE__ "main.c" + +// ***************************************************************************** +// +// minimal setup for HCI code +// +// ***************************************************************************** + +#include +#include +#include +#include +#include +#include +#include + +#include "btstack_config.h" + +#include "ble/le_device_db_tlv.h" +#include "bluetooth_company_id.h" +#include "btstack_audio.h" +#include "btstack_debug.h" +#include "btstack_event.h" +#include "btstack_memory.h" +#include "btstack_run_loop.h" +#include "btstack_run_loop_posix.h" +#include "btstack_signal.h" +#include "btstack_stdin.h" +#include "btstack_tlv_posix.h" +#include "btstack_uart.h" +#include "classic/btstack_link_key_db_tlv.h" +#include "hci.h" +#include "hci_dump.h" +#include "hci_dump_posix_fs.h" +#include "hci_dump_posix_stdout.h" +#include "hci_transport_netgraph.h" + + +#define TLV_DB_PATH_PREFIX "/tmp/btstack_" +#define TLV_DB_PATH_POSTFIX ".tlv" +static char tlv_db_path[100]; +static bool tlv_reset; +static const btstack_tlv_t * tlv_impl; +static btstack_tlv_posix_t tlv_context; +static bd_addr_t local_addr; +static hci_transport_config_netgraph_t config; + +// shutdown +static bool shutdown_triggered; + +int btstack_main(int argc, const char * argv[]); +static void local_version_information_handler(uint8_t * packet); + +static btstack_packet_callback_registration_t hci_event_callback_registration; + +static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + if (packet_type != HCI_EVENT_PACKET) return; + switch (hci_event_packet_get_type(packet)){ + case BTSTACK_EVENT_STATE: + switch(btstack_event_state_get_state(packet)){ + case HCI_STATE_WORKING: + gap_local_bd_addr(local_addr); + printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr)); + btstack_strcpy(tlv_db_path, sizeof(tlv_db_path), TLV_DB_PATH_PREFIX); + btstack_strcat(tlv_db_path, sizeof(tlv_db_path), bd_addr_to_str_with_delimiter(local_addr, '-')); + btstack_strcat(tlv_db_path, sizeof(tlv_db_path), TLV_DB_PATH_POSTFIX); + printf("TLV path: %s", tlv_db_path); + if (tlv_reset){ + int rc = unlink(tlv_db_path); + if (rc == 0) { + printf(", reset ok"); + } else { + printf(", reset failed with result = %d", rc); + } + } + printf("\n"); + tlv_impl = btstack_tlv_posix_init_instance(&tlv_context, tlv_db_path); + btstack_tlv_set_instance(tlv_impl, &tlv_context); +#ifdef ENABLE_CLASSIC + hci_set_link_key_db(btstack_link_key_db_tlv_get_instance(tlv_impl, &tlv_context)); +#endif +#ifdef ENABLE_BLE + le_device_db_tlv_configure(tlv_impl, &tlv_context); +#endif + break; + case HCI_STATE_OFF: + btstack_tlv_posix_deinit(&tlv_context); + if (!shutdown_triggered) break; + // reset stdin + btstack_stdin_reset(); + log_info("Good bye, see you.\n"); + exit(0); + break; + default: + break; + } + break; + case HCI_EVENT_COMMAND_COMPLETE: + if (hci_event_command_complete_get_command_opcode(packet) == HCI_OPCODE_HCI_READ_LOCAL_VERSION_INFORMATION){ + local_version_information_handler(packet); + } + break; + default: + break; + } +} + +static void trigger_shutdown(void){ + printf("CTRL-C - SIGINT received, shutting down..\n"); + log_info("sigint_handler: shutting down"); + shutdown_triggered = true; + hci_power_control(HCI_POWER_OFF); +} + +static int led_state = 0; +void hal_led_toggle(void){ + led_state = 1 - led_state; + printf("LED State %u\n", led_state); +} + +static void local_version_information_handler(uint8_t * packet){ + printf("Local version information:\n"); + uint16_t hci_version = packet[6]; + uint16_t hci_revision = little_endian_read_16(packet, 7); + uint16_t lmp_version = packet[9]; + uint16_t manufacturer = little_endian_read_16(packet, 10); + uint16_t lmp_subversion = little_endian_read_16(packet, 12); + printf("- HCI Version 0x%04x\n", hci_version); + printf("- HCI Revision 0x%04x\n", hci_revision); + printf("- LMP Version 0x%04x\n", lmp_version); + printf("- LMP Subversion 0x%04x\n", lmp_subversion); + printf("- Manufacturer 0x%04x\n", manufacturer); +} + +static char short_options[] = "hu:l:r"; + +static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"logfile", required_argument, NULL, 'l'}, + {"reset-tlv", no_argument, NULL, 'r'}, + {"node", required_argument, NULL, 'u'}, + {0, 0, 0, 0} +}; + +static char *help_options[] = { + "print (this) help.", + "set file to store debug output and HCI trace.", + "reset bonding information stored in TLV.", + "set name of netgraph HCI node. Default: ubt0hci", +}; + +static char *option_arg_name[] = { + "", + "LOGFILE", + "", + "NODE", +}; + +static void usage(const char *name){ + unsigned int i; + printf( "usage:\n\t%s [options]\n", name ); + printf("valid options:\n"); + for( i=0; long_options[i].name != 0; i++) { + printf("--%-10s| -%c %-10s\t\t%s\n", long_options[i].name, long_options[i].val, option_arg_name[i], help_options[i] ); + } +} + +int main(int argc, const char * argv[]){ + + const char * log_file_path = NULL; + + // set default device path + config.device_name = "ubt0hci"; + + // parse command line parameters + while(true){ + int c = getopt_long( argc, (char* const *)argv, short_options, long_options, NULL ); + if (c < 0) { + break; + } + if (c == '?'){ + break; + } + switch (c) { + case 'l': + log_file_path = optarg; + break; + case 'u': + config.device_name = optarg; + break; + case 'r': + tlv_reset = true; + break; + case 'h': + default: + usage(argv[0]); + return EXIT_FAILURE; + } + } + /// GET STARTED with BTstack /// + btstack_memory_init(); + btstack_run_loop_init(btstack_run_loop_posix_get_instance()); + + // log into file using HCI_DUMP_PACKETLOGGER format + if (log_file_path == NULL){ + log_file_path = "/tmp/hci_dump.pklg"; + } + + hci_dump_posix_fs_open(log_file_path, HCI_DUMP_PACKETLOGGER); + const hci_dump_t * hci_dump_impl = hci_dump_posix_fs_get_instance(); + hci_dump_init(hci_dump_impl); + printf("Packet Log: %s\n", log_file_path); + + // init HCI + const hci_transport_t * transport = hci_transport_netgraph_instance(); + config.type = HCI_TRANSPORT_CONFIG_USB; + hci_init(transport, &config); + +#ifdef HAVE_PORTAUDIO + btstack_audio_sink_set_instance(btstack_audio_portaudio_sink_get_instance()); + btstack_audio_source_set_instance(btstack_audio_portaudio_source_get_instance()); +#endif + + // inform about BTstack state + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + + // register callback for CTRL-c + btstack_signal_register_callback(SIGINT, &trigger_shutdown); + + // setup app + btstack_main(argc, argv); + + // go + btstack_run_loop_execute(); + + return 0; +}