From 6bbe16601a7477bbed0066cb1c7acd759e5ccbd5 Mon Sep 17 00:00:00 2001 From: Hauke Petersen Date: Fri, 17 May 2019 11:15:49 +0200 Subject: [PATCH] pkg/nimble: add netif wrapper --- pkg/nimble/Makefile | 3 + pkg/nimble/Makefile.dep | 19 + pkg/nimble/Makefile.include | 12 + pkg/nimble/contrib/nimble_riot.c | 5 + pkg/nimble/netif/Makefile | 3 + pkg/nimble/netif/include/nimble_netif.h | 232 +++++++ pkg/nimble/netif/include/nimble_netif_conn.h | 223 +++++++ pkg/nimble/netif/nimble_netif.c | 630 +++++++++++++++++++ pkg/nimble/netif/nimble_netif_conn.c | 202 ++++++ 9 files changed, 1329 insertions(+) create mode 100644 pkg/nimble/netif/Makefile create mode 100644 pkg/nimble/netif/include/nimble_netif.h create mode 100644 pkg/nimble/netif/include/nimble_netif_conn.h create mode 100644 pkg/nimble/netif/nimble_netif.c create mode 100644 pkg/nimble/netif/nimble_netif_conn.c diff --git a/pkg/nimble/Makefile b/pkg/nimble/Makefile index 9003bff94f..286e2696de 100644 --- a/pkg/nimble/Makefile +++ b/pkg/nimble/Makefile @@ -71,6 +71,9 @@ nimble_drivers_nrf5x: nimble_addr: "$(MAKE)" -C $(TDIR)/addr/ +nimble_netif: + "$(MAKE)" -C $(TDIR)/netif/ + nimble_scanlist: "$(MAKE)" -C $(TDIR)/scanlist diff --git a/pkg/nimble/Makefile.dep b/pkg/nimble/Makefile.dep index c917a37926..10e9d27d3b 100644 --- a/pkg/nimble/Makefile.dep +++ b/pkg/nimble/Makefile.dep @@ -34,6 +34,7 @@ ifneq (,$(filter nimble_controller,$(USEMODULE))) endif endif +# RIOT specific submodule dependencies ifneq (,$(filter nimble_addr,$(USEMODULE))) USEMODULE += bluetil_addr endif @@ -42,3 +43,21 @@ ifneq (,$(filter nimble_scanlist,$(USEMODULE))) USEMODULE += nimble_addr USEMODULE += bluetil_ad endif + +ifneq (,$(filter nimble_netif,$(USEMODULE))) + USEMODULE += l2util + USEMODULE += bluetil_addr + ifneq (,$(filter gnrc_ipv6_%,$(USEMODULE))) + USEMODULE += nimble_svc_ipss + endif + ifneq (,$(filter gnrc_ipv6_router_default,$(USEMODULE))) + USEMODULE += gnrc_ipv6_nib_6lr + USEMODULE += gnrc_sixlowpan + USEMODULE += gnrc_sixlowpan_iphc + endif + ifneq (,$(filter gnrc_ipv6_default,$(USEMODULE))) + USEMODULE += gnrc_ipv6_nib_6ln + USEMODULE += gnrc_sixlowpan + USEMODULE += gnrc_sixlowpan_iphc + endif +endif diff --git a/pkg/nimble/Makefile.include b/pkg/nimble/Makefile.include index ade682ca6b..7c536a3e3e 100644 --- a/pkg/nimble/Makefile.include +++ b/pkg/nimble/Makefile.include @@ -76,6 +76,18 @@ endif ifneq (,$(filter nimble_addr,$(USEMODULE))) INCLUDES += -I$(RIOTPKG)/nimble/addr/include endif +ifneq (,$(filter nimble_netif,$(USEMODULE))) + INCLUDES += -I$(RIOTPKG)/nimble/netif/include + + # configure NimBLE's internals + NIMBLE_MAX_CONN ?= 3 + CFLAGS += -DMYNEWT_VAL_MSYS_1_BLOCK_SIZE=264 + CFLAGS += -DMYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM=$(NIMBLE_MAX_CONN) + CFLAGS += -DMYNEWT_VAL_BLE_MAX_CONNECTIONS=$(NIMBLE_MAX_CONN) + # NimBLEs internal buffer need to hold one IPv6 MTU per connection + # for the internal MTU of 256 byte, we need 10 mbufs per connection... + CFLAGS += -DMYNEWT_VAL_MSYS_1_BLOCK_COUNT=35 +endif ifneq (,$(filter nimble_scanlist,$(USEMODULE))) INCLUDES += -I$(RIOTPKG)/nimble/scanlist/include endif diff --git a/pkg/nimble/contrib/nimble_riot.c b/pkg/nimble/contrib/nimble_riot.c index 31126059b3..968df5fd23 100644 --- a/pkg/nimble/contrib/nimble_riot.c +++ b/pkg/nimble/contrib/nimble_riot.c @@ -100,6 +100,11 @@ void nimble_riot_init(void) assert(res == 0); (void)res; +#ifdef MODULE_NIMBLE_NETIF + extern void nimble_netif_init(void); + nimble_netif_init(); +#endif + /* initialize the configured, build-in services */ #ifdef MODULE_NIMBLE_SVC_GAP ble_svc_gap_init(); diff --git a/pkg/nimble/netif/Makefile b/pkg/nimble/netif/Makefile new file mode 100644 index 0000000000..30ca7894f3 --- /dev/null +++ b/pkg/nimble/netif/Makefile @@ -0,0 +1,3 @@ +MODULE = nimble_netif + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/nimble/netif/include/nimble_netif.h b/pkg/nimble/netif/include/nimble_netif.h new file mode 100644 index 0000000000..b9182f1bf5 --- /dev/null +++ b/pkg/nimble/netif/include/nimble_netif.h @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2018-2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup pkg_nimble_netif GNRC netif Implementation + * @ingroup ble_nimble + * @brief GNRC netif implementation for NimBLE, enabling the integration + * of NimBLE into GNRC + * + * # About + * This NimBLE submodule provides a GNRC netif wrapper for integrating NimBLE + * with GNRC and other network stacks using netif (e.g. CCNlite). + * + * # Concept + * According to the IPv6-over-BLE standards (RFC7668 and IPSP), this module + * exposes a (configurable) number of point-to-point BLE connections as a single + * network device to BLE. Unicast traffic is only send using the corresponding + * BLE connection. Multicast and Broadcast packets are duplicated and send via + * each open BLE connection. + * + * # Structure + * The netif implementation is able to handle multiple connections + * simultaneously. The maximum number of concurrent connections is configured + * during compile time, using NimBLEs MYNEWT_VAL_BLE_MAX_CONNECTIONS option. + * Dependent on this value, the netif implementation takes care of allocation + * all the memory needed. The API of this submodule uses simply integer values + * to reference the used connection context (like file descriptors in linux). + * + * Like any other GNRC network device, the NimBLE netif wrapper runs in its own + * thread. This thread is started and configured by the common netif code. All + * send and get/set operations are handled by this thread. For efficiency + * reasons, receiving of data is however handled completely in the NimBLE host + * thread, from where the received data is directly passed on to the + * corresponding GNRC thread. + * + * Although the wrapper hooks into GNRC using the netif interface, it does need + * to implement parts of the netdev interface as well. This is done where + * needed. + * + * # Usage + * This submodule is designed to work fully asynchronous, in the same way as the + * NimBLE interfaces are designed. All functions in this submodule will only + * trigger the intended action. Once this action is complete, the module will + * report the result asynchronously using the configured callback. + * + * So before using this module, make sure to register a callback using the + * @ref nimble_netif_eventcb() function. + * + * After this, this module provides functions for managing BLE connections to + * other devices. Once these connections are established, this module takes care + * of mapping IP packets to the corresponding connections. + * + * @{ + * + * @file + * @brief GNRC netif implementation for NimBLE + * + * @author Hauke Petersen + */ + +#ifndef NIMBLE_NETIF_H +#define NIMBLE_NETIF_H + +#include + +#include "net/ble.h" + +#include "host/ble_hs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Default L2CAP channel ID to use + */ +#ifndef NIMBLE_NETIF_CID +#define NIMBLE_NETIF_CID (BLE_L2CAP_CID_IPSP) +#endif + +/** + * @brief Default MTU size supported by the NimBLE netif wrapper + */ +/* NOTE: We do not use the @ref IPV6_MIN_MTU define here, as the iov6.h header + pulls in some other RIOT headers that clash with NimBLE header (e.g. + * byteorder.h vs. endian.h) */ +#ifndef NIMBLE_NETIF_MTU +#define NIMBLE_NETIF_MTU (1280U) +#endif + +/** + * @brief Return codes used by the NimBLE netif module + */ +enum { + NIMBLE_NETIF_OK = 0, /**< everything went fine */ + NIMBLE_NETIF_NOTCONN = -1, /**< not connected */ + NIMBLE_NETIF_DEVERR = -2, /**< internal BLE stack error */ + NIMBLE_NETIF_BUSY = -3, /**< network device is busy */ + NIMBLE_NETIF_NOMEM = -4, /**< insufficient memory */ + NIMBLE_NETIF_NOTADV = -5, /**< not advertising */ + NIMBLE_NETIF_NOTFOUND = -6, /**< no fitting entry found */ +}; + +/** + * @brief Event types triggered by the NimBLE netif module + */ +typedef enum { + NIMBLE_NETIF_CONNECTED_MASTER, /**< connection established as master */ + NIMBLE_NETIF_CONNECTED_SLAVE, /**< connection established as slave */ + NIMBLE_NETIF_CLOSED_MASTER, /**< connection closed (we were master) */ + NIMBLE_NETIF_CLOSED_SLAVE, /**< connection closed (we were slave) */ + NIMBLE_NETIF_CONNECT_ABORT, /**< connection establishment aborted */ + NIMBLE_NETIF_CONN_UPDATED, /**< connection parameter update done */ +} nimble_netif_event_t; + +/** + * @brief Flags describing the state of a single connection context + */ +enum { + NIMBLE_NETIF_L2CAP_CLIENT = 0x0001, /**< L2CAP client */ + NIMBLE_NETIF_L2CAP_SERVER = 0x0002, /**< L2CAP server */ + NIMBLE_NETIF_L2CAP_CONNECTED = 0x0003, /**< L2CAP is connected */ + NIMBLE_NETIF_GAP_MASTER = 0x0010, /**< GAP master */ + NIMBLE_NETIF_GAP_SLAVE = 0x0020, /**< GAP slave */ + NIMBLE_NETIF_GAP_CONNECTED = 0x0030, /**< GAP is connected */ + NIMBLE_NETIF_ADV = 0x0100, /**< currently advertising */ + NIMBLE_NETIF_CONNECTING = 0x4000, /**< connection in progress */ + NIMBLE_NETIF_UNUSED = 0x8000, /**< context unused */ + NIMBLE_NETIF_ANY = 0xffff, /**< match any state */ +}; + +/** + * @brief Event callback signature used for asynchronous event signaling + * + * @note The event callback is always executed in NimBLE's host thread + * + * @param[in] handle handle to the connection that triggered the event + * @param[in] event type of the event + */ +typedef void(*nimble_netif_eventcb_t)(int handle, nimble_netif_event_t event); + +/** + * @brief Initialize the netif implementation, spawns the netif thread + * + * This function is meant to be called once during system initialization, i.e. + * auto-init. + */ +void nimble_netif_init(void); + +/** + * @brief Register a global event callback, servicing all NimBLE connections + * + * @note The event callback is always executed in NimBLE's host thread + * + * @param[in] cb event callback to register, may be NULL + */ +void nimble_netif_eventcb(nimble_netif_eventcb_t cb); + +/** + * @brief Open a BLE connection as BLE master + * + * @param[in] addr address of the advertising BLE slave, in the NimBLE + * addr format (little endian) + * @param[in] conn_params connection (timing) parameters + * @param[in] timeout connect timeout + * + * @return the used connection handle on success + * @return NIMBLE_NETIF_BUSY if already connected to the given address or if + * a connection setup procedure is in progress + * @return NIMBLE_NETIF_NOMEM if no connection context memory is available + */ +int nimble_netif_connect(const ble_addr_t *addr, + const struct ble_gap_conn_params *conn_params, + uint32_t timeout); + +/** + * @brief Close the connection with the given handle + * + * @param[in] handle handle for the connection to be closed + * + * @return NIMBLE_NETIF_OK on success + * @return NIMBLE_NETIF_NOTFOUND if the handle is invalid + * @return NIMBLE_NETIF_NOTCONN if context for given handle is not connected + */ +int nimble_netif_close(int handle); + +/** + * @brief Accept incoming connections by starting to advertise this node + * + * @param[in] ad advertising data (in BLE AD format) + * @param[in] ad_len length of @p ad in bytes + * @param[in] adv_params advertising (timing) parameters to use + * + * @return NIMBLE_NETIF_OK on success + * @return NIMBLE_NETIF_BUSY if already advertising + * @return NIMBLE_NETIF_NOMEM on insufficient connection memory + */ +int nimble_netif_accept(const uint8_t *ad, size_t ad_len, + const struct ble_gap_adv_params *adv_params); + +/** + * @brief Stop accepting incoming connections (stop advertising) + * * + * @return NIMBLE_NETIF_OK on success + * @return NIMBLE_NETIF_NOTADV if not currently advertising + */ +int nimble_netif_accept_stop(void); + +/** + * @brief Update the connection parameters for the given connection + * + * @param[in] handle connection handle + * @param[in] conn_params new connection parameters to apply + * + * @return NIMBLE_NETIF_OK on success + * @return NIMBLE_NETIF_NOTCONN if handle does not point to a connection + * @return NIMBLE_NETIF_DEVERR if applying the given parameters failed + */ +int nimble_netif_update(int handle, + const struct ble_gap_upd_params *conn_params); + +#ifdef __cplusplus +} +#endif + +#endif /* NIMBLE_NETIF_H */ +/** @} */ diff --git a/pkg/nimble/netif/include/nimble_netif_conn.h b/pkg/nimble/netif/include/nimble_netif_conn.h new file mode 100644 index 0000000000..2a7d41dbc1 --- /dev/null +++ b/pkg/nimble/netif/include/nimble_netif_conn.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2018-2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup pkg_nimble_netif_conn Connection State Management for netif + * @ingroup pkg_nimble_netif + * @brief Helper module for managing the memory needed to store the + * BLE connection state for the netif wrapper + * @{ + * + * @file + * @brief Connection allocation and maintenance for NimBLE netif + * + * @author Hauke Petersen + */ + +#ifndef NIMBLE_NETIF_CONN_H +#define NIMBLE_NETIF_CONN_H + +#include + +#include "nimble_netif.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Value for marking a handle invalid + */ +#define NIMBLE_NETIF_CONN_INVALID (-1) + +/** + * @brief Memory layout for holding the relevant connection information + */ +typedef struct { + struct ble_l2cap_chan *coc; /**< l2cap context as exposed by NimBLE */ + uint16_t gaphandle; /**< GAP handle exposed by NimBLE */ + uint16_t state; /**< the current state of the context */ + uint8_t addr[BLE_ADDR_LEN]; /**< BLE address of connected peer + (in network byte order) */ +} nimble_netif_conn_t; + +/** + * @brief Iterator function signature used by @ref nimble_netif_conn_foreach() + * + * @param[in] conn connection context of the current entry + * @param[in] handle handle of the current entry + * @param[in] arg user supplied argument + * + * @return 0 to continue + * @return != 0 to stop iterating + */ +typedef int (*nimble_netif_conn_iter_t)(nimble_netif_conn_t *conn, + int handle, void *arg); + +/** + * @brief Initialize the connection state manager + * + * This functions is typically called by @ref nimble_netif_init(). + */ +void nimble_netif_conn_init(void); + +/** + * @brief Get the connection context corresponding to the given handle + * + * @param[in] handle handle to a connection context + * + * @return pointer to the corresponding connection context + * @return NULL if handle in invalid + */ +nimble_netif_conn_t *nimble_netif_conn_get(int handle); + +/** + * @brief Get the handle to the context that is currently advertising + * + * @return handle to the currently advertising context + * @return NIMBLE_NETIF_CONN_INVALID if not advertising + */ +int nimble_netif_conn_get_adv(void); + +/** + * @brief Get the handle to the context that is busy connecting + * + * @return handle to the busy context + * @return NIMBLE_NETIF_CONN_INVALID if not busy connecting + */ +int nimble_netif_conn_get_connecting(void); + +/** + * @brief Find the connection to the peer with the given BLE address + * + * @param[in] addr BLE address, in network byte order + * + * @return handle to the matching connection context + * @return NIMBLE_NETIF_CONN_INVALID if no matching connection was found + */ +int nimble_netif_conn_get_by_addr(const uint8_t *addr); + +/** + * @brief Find the connection using the given NimBLE GAP handle + * + * @param[in] gaphandle GAP handle as exposed by NimBLE + * + * @return handle to the matching connection context + * @return NIMBLE_NETIF_CONN_INVALID if no matching connection was found + */ +int nimble_netif_conn_get_by_gaphandle(uint16_t gaphandle); + + +/** + * @brief Iterate over all connection contexts that match the filter condition + * + * @warning Do not call any other nimble_netif_conn function from within the + * callback, this will lead to a deadlock! + * + * @param[in] filter filter mask + * @param[in] cb callback called on each filtered entry + * @param[in] arg user argument + */ +void nimble_netif_conn_foreach(uint16_t filter, + nimble_netif_conn_iter_t cb, void *arg); + +/** + * @brief Count the number of connections contexts for which the given filter + * applies + * + * @param[in] filter filter mask + * + * @return number of contexts for which the filter applied + */ + +unsigned nimble_netif_conn_count(uint16_t filter); + +/** + * @brief Allocate an unused context for starting a connection + * + * @param[in] addr the BLE address of the peer node, in network byte + * order + * + * @return handle used for the new connection + */ +int nimble_netif_conn_start_connection(const uint8_t *addr); + +/** + * @brief Reserve a unused context for the purpose of accepting a new + * connection + * + * @return handle of the reserved context + * @return NIMBLE_NETIF_CONN_INVALID if no unused context was available + */ +int nimble_netif_conn_start_adv(void); + +/** + * @brief Free the connection context with the given handle + */ +void nimble_netif_conn_free(int handle); + +/** + * @brief Find the connection context with a given GAP handle and return a + * pointer to it + * + * @param[in] gh GAP handle used by NimBLE + * + * @return Pointer to the selected context + * @return NULL if no fitting context was found + */ +static inline +nimble_netif_conn_t *nimble_netif_conn_from_gaphandle(uint16_t gh) +{ + return nimble_netif_conn_get(nimble_netif_conn_get_by_gaphandle(gh)); +} + +/** + * @brief Convenience function to check if any context is currently in the + * connecting state (@ref NIMBLE_NETIF_CONNECTING) + * + * @return != 0 if true + * @return 0 if false + */ +static inline int nimble_netif_conn_connecting(void) +{ + return (nimble_netif_conn_get_connecting() != NIMBLE_NETIF_CONN_INVALID); +} + +/** + * @brief Convenience function to check if we are currently connected to a + * peer with the given address + * + * @param[in] addr BLE address, in network byte order + * + * @return != 0 if true + * @return 0 if false + */ +static inline int nimble_netif_conn_connected(const uint8_t *addr) +{ + return (nimble_netif_conn_get_by_addr(addr) != NIMBLE_NETIF_CONN_INVALID); +} + + +/** + * @brief Convenience function to check if any context is currently in the + * advertising state (@ref NIMBLE_NETIF_ADV) + * + * @return != 0 if true + * @return 0 if false + */ +static inline int nimble_netif_conn_is_adv(void) +{ + return (nimble_netif_conn_get_adv() != NIMBLE_NETIF_CONN_INVALID); +} + +#ifdef __cplusplus +} +#endif + +#endif /* NIMBLE_NETIF_CONN_H */ +/** @} */ diff --git a/pkg/nimble/netif/nimble_netif.c b/pkg/nimble/netif/nimble_netif.c new file mode 100644 index 0000000000..4e7190bc56 --- /dev/null +++ b/pkg/nimble/netif/nimble_netif.c @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2018-2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup pkg_nimble_netif + * @{ + * + * @file + * @brief GNRC netif wrapper for NimBLE + * + * @author Hauke Petersen + * + * @} + */ + +#include +#include + +#include "assert.h" +#include "thread.h" +#include "thread_flags.h" + +#include "net/ble.h" +#include "net/bluetil/addr.h" +#include "net/gnrc/netif.h" +#include "net/gnrc/netif/hdr.h" +#include "net/gnrc/netreg.h" +#include "net/gnrc/pktbuf.h" +#include "net/gnrc/nettype.h" + +#include "nimble_netif.h" +#include "nimble_netif_conn.h" +#include "nimble_riot.h" +#include "host/ble_gap.h" +#include "host/util/util.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#ifdef MODULE_GNRC_SIXLOWPAN +#define NETTYPE GNRC_NETTYPE_SIXLOWPAN +#elif defined(MODULE_GNRC_IPV6) +#define NETTYPE GNRC_NETTYPE_IPV6 +#else +#define NETTYPE GNRC_NETTYPE_UNDEF +#endif + +/* buffer configuration + * - we need one RX and one TX buffer per connection */ +#define MTU_SIZE (NIMBLE_NETIF_MTU) +#define MBUF_OVHD (sizeof(struct os_mbuf) + \ + sizeof(struct os_mbuf_pkthdr)) +#define MBUF_SIZE (MBUF_OVHD + MYNEWT_VAL_BLE_L2CAP_COC_MPS) +#define MBUF_CNT (MYNEWT_VAL_BLE_MAX_CONNECTIONS * 2 * \ + ((MTU_SIZE + (MBUF_SIZE - 1)) / MBUF_SIZE)) + +/* thread flag used for signaling transmit readiness */ +#define FLAG_TX_UNSTALLED (1u << 13) + +/* allocate a stack for the netif device */ +static char _stack[THREAD_STACKSIZE_DEFAULT]; +static thread_t *_netif_thread; + +/* keep the actual device state */ +static gnrc_netif_t *_nimble_netif = NULL; +static gnrc_nettype_t _nettype = NETTYPE; + +/* keep a reference to the event callback */ +static nimble_netif_eventcb_t _eventcb; + +/* allocation of memory for buffering IP packets when handing them to NimBLE */ +static os_membuf_t _mem[OS_MEMPOOL_SIZE(MBUF_CNT, MBUF_SIZE)]; +static struct os_mempool _mem_pool; +static struct os_mbuf_pool _mbuf_pool; + +/* notify the user about state changes for a connection context */ +static void _notify(int handle, nimble_netif_event_t event) +{ + if (_eventcb) { + _eventcb(handle, event); + } +} + +static void _netif_init(gnrc_netif_t *netif) +{ + (void)netif; + + /* save the threads context pointer, so we can set its flags */ + _netif_thread = (thread_t *)thread_get(thread_getpid()); + +#ifdef MODULE_GNRC_SIXLOWPAN + /* we disable fragmentation for this device, as the L2CAP layer takes care + * of this */ + _nimble_netif->sixlo.max_frag_size = 0; +#endif +} + +static int _send_pkt(nimble_netif_conn_t *conn, gnrc_pktsnip_t *pkt) +{ + int res; + int num_bytes = 0; + + if (conn == NULL || conn->coc == NULL) { + return -ENOTCONN; + } + + /* copy the data into a newly allocated mbuf */ + struct os_mbuf *sdu = os_mbuf_get_pkthdr(&_mbuf_pool, 0); + if (sdu == NULL) { + return -ENOBUFS; + } + while (pkt) { + res = os_mbuf_append(sdu, pkt->data, pkt->size); + if (res != 0) { + os_mbuf_free_chain(sdu); + return -ENOBUFS; + } + num_bytes += (int)pkt->size; + pkt = pkt->next; + } + + /* send packet via the given L2CAP COC */ + do { + res = ble_l2cap_send(conn->coc, sdu); + if (res == BLE_HS_EBUSY) { + thread_flags_wait_all(FLAG_TX_UNSTALLED); + } + } while (res == BLE_HS_EBUSY); + + if ((res != 0) && (res != BLE_HS_ESTALLED)) { + os_mbuf_free_chain(sdu); + return -ENOBUFS; + } + + return num_bytes; +} + +static int _netif_send_iter(nimble_netif_conn_t *conn, + int handle, void *arg) +{ + (void)handle; + _send_pkt(conn, (gnrc_pktsnip_t *)arg); + return 0; +} + +static int _netif_send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt) +{ + assert(pkt->type == GNRC_NETTYPE_NETIF); + + (void)netif; + int res; + + gnrc_netif_hdr_t *hdr = (gnrc_netif_hdr_t *)pkt->data; + /* if packet is bcast or mcast, we send it to every connected node */ + if (hdr->flags & + (GNRC_NETIF_HDR_FLAGS_BROADCAST | GNRC_NETIF_HDR_FLAGS_MULTICAST)) { + nimble_netif_conn_foreach(NIMBLE_NETIF_L2CAP_CONNECTED, + _netif_send_iter, pkt->next); + res = (int)gnrc_pkt_len(pkt->next); + } + /* send unicast */ + else { + int handle = nimble_netif_conn_get_by_addr( + gnrc_netif_hdr_get_dst_addr(hdr)); + nimble_netif_conn_t *conn = nimble_netif_conn_get(handle); + res = _send_pkt(conn, pkt->next); + } + + /* release the packet in GNRC's packet buffer */ + gnrc_pktbuf_release(pkt); + return res; +} + +/* not used, we pass incoming data to GNRC directly from the NimBLE thread */ +static gnrc_pktsnip_t *_netif_recv(gnrc_netif_t *netif) +{ + (void)netif; + return NULL; +} + +static const gnrc_netif_ops_t _nimble_netif_ops = { + .init = _netif_init, + .send = _netif_send, + .recv = _netif_recv, + .get = gnrc_netif_get_from_netdev, + .set = gnrc_netif_set_from_netdev, + .msg_handler = NULL, +}; + +static inline int _netdev_init(netdev_t *dev) +{ + _nimble_netif = dev->context; + + /* get our own address from the controller */ + uint8_t tmp[6]; + int res = ble_hs_id_copy_addr(nimble_riot_own_addr_type, tmp, NULL); + assert(res == 0); + (void)res; + + bluetil_addr_swapped_cp(tmp, _nimble_netif->l2addr); + return 0; +} + +static inline int _netdev_get(netdev_t *dev, netopt_t opt, + void *value, size_t max_len) +{ + (void)dev; + int res = -ENOTSUP; + + switch (opt) { + case NETOPT_ADDRESS: + assert(max_len >= BLE_ADDR_LEN); + memcpy(value, _nimble_netif->l2addr, BLE_ADDR_LEN); + res = BLE_ADDR_LEN; + break; + case NETOPT_ADDR_LEN: + case NETOPT_SRC_LEN: + assert(max_len == sizeof(uint16_t)); + *((uint16_t *)value) = BLE_ADDR_LEN; + res = sizeof(uint16_t); + break; + case NETOPT_MAX_PACKET_SIZE: + assert(max_len >= sizeof(uint16_t)); + *((uint16_t *)value) = MTU_SIZE; + res = sizeof(uint16_t); + break; + case NETOPT_PROTO: + assert(max_len == sizeof(gnrc_nettype_t)); + *((gnrc_nettype_t *)value) = _nettype; + res = sizeof(gnrc_nettype_t); + break; + case NETOPT_DEVICE_TYPE: + assert(max_len == sizeof(uint16_t)); + *((uint16_t *)value) = NETDEV_TYPE_BLE; + res = sizeof(uint16_t); + break; + default: + break; + } + + return res; +} + +static inline int _netdev_set(netdev_t *dev, netopt_t opt, + const void *value, size_t val_len) +{ + (void)dev; + int res = -ENOTSUP; + + switch (opt) { + case NETOPT_PROTO: + assert(val_len == sizeof(_nettype)); + memcpy(&_nettype, value, sizeof(_nettype)); + res = sizeof(_nettype); + break; + default: + break; + } + + return res; +} + +static const netdev_driver_t _nimble_netdev_driver = { + .send = NULL, + .recv = NULL, + .init = _netdev_init, + .isr = NULL, + .get = _netdev_get, + .set = _netdev_set, +}; + +static netdev_t _nimble_netdev_dummy = { + .driver = &_nimble_netdev_driver, +}; + +static void _on_data(nimble_netif_conn_t *conn, struct ble_l2cap_event *event) +{ + struct os_mbuf *rxb = event->receive.sdu_rx; + size_t rx_len = (size_t)OS_MBUF_PKTLEN(rxb); + + /* allocate netif header */ + gnrc_pktsnip_t *if_snip = gnrc_netif_hdr_build(conn->addr, BLE_ADDR_LEN, + _nimble_netif->l2addr, + BLE_ADDR_LEN); + if (if_snip == NULL) { + goto end; + } + + /* we need to add the device PID to the netif header */ + gnrc_netif_hdr_t *netif_hdr = (gnrc_netif_hdr_t *)if_snip->data; + netif_hdr->if_pid = _nimble_netif->pid; + + /* allocate space in the pktbuf to store the packet */ + gnrc_pktsnip_t *payload = gnrc_pktbuf_add(if_snip, NULL, rx_len, _nettype); + if (payload == NULL) { + gnrc_pktbuf_release(if_snip); + goto end; + } + + /* copy payload from mbuf into pktbuffer */ + int res = os_mbuf_copydata(rxb, 0, rx_len, payload->data); + if (res != 0) { + gnrc_pktbuf_release(payload); + goto end; + } + + /* finally dispatch the receive packet to GNRC */ + if (!gnrc_netapi_dispatch_receive(payload->type, GNRC_NETREG_DEMUX_CTX_ALL, + payload)) { + gnrc_pktbuf_release(payload); + } + +end: + /* free the mbuf and allocate a new one for receiving new data */ + os_mbuf_free_chain(rxb); + rxb = os_mbuf_get_pkthdr(&_mbuf_pool, 0); + /* due to buffer provisioning, there should always be enough space */ + assert(rxb != NULL); + ble_l2cap_recv_ready(event->receive.chan, rxb); +} + +static int _on_l2cap_client_evt(struct ble_l2cap_event *event, void *arg) +{ + int handle = (int)arg; + nimble_netif_conn_t *conn = nimble_netif_conn_get(handle); + assert(conn && (conn->state & NIMBLE_NETIF_GAP_MASTER)); + + switch (event->type) { + case BLE_L2CAP_EVENT_COC_CONNECTED: + conn->coc = event->connect.chan; + conn->state |= NIMBLE_NETIF_L2CAP_CLIENT; + conn->state &= ~NIMBLE_NETIF_CONNECTING; + _notify(handle, NIMBLE_NETIF_CONNECTED_MASTER); + break; + case BLE_L2CAP_EVENT_COC_DISCONNECTED: + assert(conn->coc); + conn->coc = NULL; + conn->state &= ~NIMBLE_NETIF_L2CAP_CONNECTED; + break; + case BLE_L2CAP_EVENT_COC_ACCEPT: + /* this event should never be triggered for the L2CAP client */ + assert(0); + break; + case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: + _on_data(conn, event); + break; + case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: + thread_flags_set(_netif_thread, FLAG_TX_UNSTALLED); + break; + default: + break; + } + + return 0; +} + +static int _on_l2cap_server_evt(struct ble_l2cap_event *event, void *arg) +{ + (void)arg; + int handle; + nimble_netif_conn_t *conn; + + switch (event->type) { + case BLE_L2CAP_EVENT_COC_CONNECTED: + handle = nimble_netif_conn_get_by_gaphandle(event->connect.conn_handle); + conn = nimble_netif_conn_get(handle); + assert(conn); + conn->coc = event->connect.chan; + conn->state |= NIMBLE_NETIF_L2CAP_SERVER; + conn->state &= ~(NIMBLE_NETIF_ADV | NIMBLE_NETIF_CONNECTING); + _notify(handle, NIMBLE_NETIF_CONNECTED_SLAVE); + break; + case BLE_L2CAP_EVENT_COC_DISCONNECTED: + conn = nimble_netif_conn_from_gaphandle(event->disconnect.conn_handle); + assert(conn && conn->coc); + conn->coc = NULL; + conn->state &= ~NIMBLE_NETIF_L2CAP_CONNECTED; + break; + case BLE_L2CAP_EVENT_COC_ACCEPT: { + struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&_mbuf_pool, 0); + /* there should always be enough buffer space */ + assert(sdu_rx != NULL); + ble_l2cap_recv_ready(event->accept.chan, sdu_rx); + break; + } + case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: + conn = nimble_netif_conn_from_gaphandle(event->receive.conn_handle); + assert(conn); + _on_data(conn, event); + break; + case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: + thread_flags_set(_netif_thread, FLAG_TX_UNSTALLED); + break; + default: + break; + } + + return 0; +} + +static void _on_gap_connected(nimble_netif_conn_t *conn, uint16_t conn_handle) +{ + struct ble_gap_conn_desc desc; + int res = ble_gap_conn_find(conn_handle, &desc); + assert(res == 0); + (void)res; + + conn->gaphandle = conn_handle; + bluetil_addr_swapped_cp(desc.peer_id_addr.val, conn->addr); +} + +static int _on_gap_master_evt(struct ble_gap_event *event, void *arg) +{ + int res = 0; + int handle = (int)arg; + nimble_netif_conn_t *conn = nimble_netif_conn_get(handle); + assert(conn); + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: { + if (event->connect.status != 0) { + nimble_netif_conn_free(handle); + _notify(handle, NIMBLE_NETIF_CONNECT_ABORT); + return 0; + } + _on_gap_connected(conn, event->connect.conn_handle); + conn->state |= NIMBLE_NETIF_GAP_MASTER; + + struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&_mbuf_pool, 0); + /* we should never run out of buffer space... */ + assert(sdu_rx != NULL); + res = ble_l2cap_connect(event->connect.conn_handle, + NIMBLE_NETIF_CID, MTU_SIZE, sdu_rx, + _on_l2cap_client_evt, (void *)handle); + /* should always success as well */ + assert(res == 0); + break; + } + case BLE_GAP_EVENT_DISCONNECT: + nimble_netif_conn_free(handle); + _notify(handle, NIMBLE_NETIF_CLOSED_MASTER); + break; + case BLE_GAP_EVENT_CONN_UPDATE: + _notify(handle, NIMBLE_NETIF_CONN_UPDATED); + break; + case BLE_GAP_EVENT_CONN_UPDATE_REQ: + case BLE_GAP_EVENT_MTU: + /* nothing to do here */ + break; + default: + break; + } + + return res; +} + +static int _on_gap_slave_evt(struct ble_gap_event *event, void *arg) +{ + int handle = (int)arg; + nimble_netif_conn_t *conn = nimble_netif_conn_get(handle); + assert(conn); + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: { + if (event->connect.status != 0) { + nimble_netif_conn_free(handle); + _notify(handle, NIMBLE_NETIF_CONNECT_ABORT); + break; + } + _on_gap_connected(conn, event->connect.conn_handle); + assert(conn->state == NIMBLE_NETIF_ADV); + conn->state = NIMBLE_NETIF_GAP_SLAVE; + break; + } + case BLE_GAP_EVENT_DISCONNECT: + nimble_netif_conn_free(handle); + _notify(handle, NIMBLE_NETIF_CLOSED_SLAVE); + break; + case BLE_GAP_EVENT_CONN_UPDATE: + _notify(handle, NIMBLE_NETIF_CONN_UPDATED); + break; + case BLE_GAP_EVENT_CONN_UPDATE_REQ: + /* nothing to do here */ + break; + default: + break; + } + + return 0; +} + +void nimble_netif_init(void) +{ + int res; + (void)res; + + /* setup the connection context table */ + nimble_netif_conn_init(); + + /* initialize of BLE related buffers */ + res = os_mempool_init(&_mem_pool, MBUF_CNT, MBUF_SIZE, _mem, "nim_gnrc"); + assert(res == 0); + res = os_mbuf_pool_init(&_mbuf_pool, &_mem_pool, MBUF_SIZE, MBUF_CNT); + assert(res == 0); + + res = ble_l2cap_create_server(NIMBLE_NETIF_CID, MTU_SIZE, + _on_l2cap_server_evt, NULL); + assert(res == 0); + (void)res; + + gnrc_netif_create(_stack, sizeof(_stack), GNRC_NETIF_PRIO, + "nimble_netif", &_nimble_netdev_dummy, &_nimble_netif_ops); +} + +void nimble_netif_eventcb(nimble_netif_eventcb_t cb) +{ + _eventcb = cb; +} + +int nimble_netif_connect(const ble_addr_t *addr, + const struct ble_gap_conn_params *conn_params, + uint32_t timeout) +{ + assert(addr); + assert(_eventcb); + + /* the netif_conn module expects addresses in network byte order */ + uint8_t addrn[BLE_ADDR_LEN]; + bluetil_addr_swapped_cp(addr->val, addrn); + + /* check that there is no open connection with the given address */ + if (nimble_netif_conn_connected(addrn) || + nimble_netif_conn_connecting()) { + return NIMBLE_NETIF_BUSY; + } + + /* get empty connection context */ + int handle = nimble_netif_conn_start_connection(addrn); + if (handle == NIMBLE_NETIF_CONN_INVALID) { + return NIMBLE_NETIF_NOMEM; + } + + int res = ble_gap_connect(nimble_riot_own_addr_type, addr, timeout, + conn_params, _on_gap_master_evt, (void *)handle); + assert(res == 0); + (void)res; + + return handle; +} + +int nimble_netif_close(int handle) +{ + nimble_netif_conn_t *conn = nimble_netif_conn_get(handle); + if (conn == NULL) { + return NIMBLE_NETIF_NOTFOUND; + } + else if (!(conn->state & NIMBLE_NETIF_L2CAP_CONNECTED)) { + return NIMBLE_NETIF_NOTCONN; + } + + int res = ble_gap_terminate(ble_l2cap_get_conn_handle(conn->coc), + BLE_ERR_REM_USER_CONN_TERM); + assert(res == 0); + (void)res; + + return NIMBLE_NETIF_OK; +} + +int nimble_netif_accept(const uint8_t *ad, size_t ad_len, + const struct ble_gap_adv_params *adv_params) +{ + assert(ad); + assert(adv_params); + + int handle; + int res; + (void)res; + + /* allocate a connection context for incoming connections */ + handle = nimble_netif_conn_start_adv(); + if (handle < 0) { + return handle; + } + + /* set advertisement data */ + res = ble_gap_adv_set_data(ad, (int)ad_len); + assert(res == 0); + /* remember context and start advertising */ + res = ble_gap_adv_start(nimble_riot_own_addr_type, NULL, BLE_HS_FOREVER, + adv_params, _on_gap_slave_evt, (void *)handle); + assert(res == 0); + + return NIMBLE_NETIF_OK; +} + +int nimble_netif_accept_stop(void) +{ + int handle = nimble_netif_conn_get_adv(); + if (handle == NIMBLE_NETIF_CONN_INVALID) { + return NIMBLE_NETIF_NOTADV; + } + + int res = ble_gap_adv_stop(); + assert(res == 0); + (void)res; + nimble_netif_conn_free(handle); + + return NIMBLE_NETIF_OK; +} + +int nimble_netif_update(int handle, + const struct ble_gap_upd_params *conn_params) +{ + nimble_netif_conn_t *conn = nimble_netif_conn_get(handle); + if (conn == NULL) { + return NIMBLE_NETIF_NOTCONN; + } + + int res = ble_gap_update_params(conn->gaphandle, conn_params); + if (res != 0) { + return NIMBLE_NETIF_DEVERR; + } + + return NIMBLE_NETIF_OK; +} diff --git a/pkg/nimble/netif/nimble_netif_conn.c b/pkg/nimble/netif/nimble_netif_conn.c new file mode 100644 index 0000000000..14ef49cb79 --- /dev/null +++ b/pkg/nimble/netif/nimble_netif_conn.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2018-2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup pkg_nimble_netif_conn + * @{ + * + * @file + * @brief Connection context handling for NimBLE netif + * + * @author Hauke Petersen + * + * @} + */ + +#include "nimble_netif_conn.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define CONN_CNT (MYNEWT_VAL_BLE_MAX_CONNECTIONS) + +static mutex_t _lock = MUTEX_INIT; +static nimble_netif_conn_t _conn[CONN_CNT]; + +static int _find_by_state(uint16_t filter) +{ + for (unsigned i = 0; i < CONN_CNT; i++) { + if (_conn[i].state & filter) { + return (int)i; + } + } + return NIMBLE_NETIF_CONN_INVALID; +} + +void nimble_netif_conn_init(void) +{ + DEBUG("conn_init\n"); + memset(_conn, 0, sizeof(_conn)); + for (unsigned i = 0; i < CONN_CNT; i++) { + _conn[i].state = NIMBLE_NETIF_UNUSED; + } +} + +nimble_netif_conn_t *nimble_netif_conn_get(int handle) +{ + if ((handle < 0) || (handle >= CONN_CNT)) { + return NULL; + } + return &_conn[handle]; +} + +int nimble_netif_conn_get_adv(void) +{ + int handle; + DEBUG("nimble_netif_conn_get_adv\n"); + mutex_lock(&_lock); + handle = _find_by_state(NIMBLE_NETIF_ADV); + mutex_unlock(&_lock); + return handle; +} + +int nimble_netif_conn_get_connecting(void) +{ + int handle; + DEBUG("nimble_netif_conn_get_connecting\n"); + mutex_lock(&_lock); + handle = _find_by_state(NIMBLE_NETIF_CONNECTING); + mutex_unlock(&_lock); + DEBUG("nimble_netif_conn_get_connecting - handle %i\n", handle); + return handle; +} + +int nimble_netif_conn_get_by_addr(const uint8_t *addr) +{ + assert(addr); + int handle = NIMBLE_NETIF_CONN_INVALID; + + DEBUG("nimble_netif_conn_get_by_addr %02x\n", (int)addr[5]); + mutex_lock(&_lock); + for (unsigned i = 0; i < CONN_CNT; i++) { + if ((_conn[i].state & NIMBLE_NETIF_L2CAP_CONNECTED) && + memcmp(_conn[i].addr, addr, BLE_ADDR_LEN) == 0) { + handle = (int)i; + break; + } + } + mutex_unlock(&_lock); + DEBUG("nimble_netif_conn_get_by_addr - found: %i\n", handle); + + return handle; +} + +int nimble_netif_conn_get_by_gaphandle(uint16_t gaphandle) +{ + int handle = NIMBLE_NETIF_CONN_INVALID; + + DEBUG("nimble_netif_conn_get_by_gaphandle %i\n", (int)gaphandle); + mutex_lock(&_lock); + for (unsigned i = 0; i < CONN_CNT; i++) { + if (_conn[i].gaphandle == gaphandle) { + handle = (int)i; + break; + } + } + mutex_unlock(&_lock); + DEBUG("nimble_netif_conn_get_by_gaphandle - found %i\n", handle); + + return handle; +} + +int nimble_netif_conn_start_connection(const uint8_t *addr) +{ + int handle; + + DEBUG("nimble_netif_conn_start_connection, addr %02x\n", (int)addr[5]); + mutex_lock(&_lock); + handle = _find_by_state(NIMBLE_NETIF_UNUSED); + if (handle != NIMBLE_NETIF_CONN_INVALID) { + _conn[handle].state = NIMBLE_NETIF_CONNECTING; + memcpy(_conn[handle].addr, addr, BLE_ADDR_LEN); + } + + mutex_unlock(&_lock); + + return handle; +} + +int nimble_netif_conn_start_adv(void) +{ + int handle; + + DEBUG("nimble_netif_conn_start_adv\n"); + mutex_lock(&_lock); + handle = _find_by_state(NIMBLE_NETIF_ADV); + if (handle != NIMBLE_NETIF_CONN_INVALID) { + handle = NIMBLE_NETIF_BUSY; + } + else { + handle = _find_by_state(NIMBLE_NETIF_UNUSED); + if (handle != NIMBLE_NETIF_CONN_INVALID) { + _conn[handle].state = NIMBLE_NETIF_ADV; + } + else { + handle = NIMBLE_NETIF_NOMEM; + } + + } + mutex_unlock(&_lock); + + return handle; +} + +void nimble_netif_conn_free(int handle) +{ + assert((handle >= 0) && (handle < CONN_CNT)); + + DEBUG("nimble_netif_conn_free, handle %i\n", handle); + mutex_lock(&_lock); + memset(&_conn[handle], 0, sizeof(nimble_netif_conn_t)); + _conn[handle].state = NIMBLE_NETIF_UNUSED; + mutex_unlock(&_lock); +} + +void nimble_netif_conn_foreach(uint16_t filter, + nimble_netif_conn_iter_t cb, void *arg) +{ + assert(cb); + + DEBUG("nimble_netif_conn_foreach 0x%04x\n", (int)filter); + mutex_lock(&_lock); + for (unsigned i = 0; i < CONN_CNT; i++) { + if (_conn[i].state & filter) { + int res = cb(&_conn[i], (int)i, arg); + if (res != 0) { + break; + } + } + } + mutex_unlock(&_lock); +} + +unsigned nimble_netif_conn_count(uint16_t filter) +{ + unsigned cnt = 0; + + DEBUG("nimble_netif_conn_count, filter 0x%04x\n", (int)filter); + mutex_lock(&_lock); + for (unsigned i = 0; i < CONN_CNT; i++) { + if (_conn[i].state & filter) { + ++cnt; + } + } + mutex_unlock(&_lock); + + return cnt; +}