diff --git a/include/nuttx/wireless/bt_ioctl.h b/include/nuttx/wireless/bt_ioctl.h index 02f2b92375..7e4ac97a14 100644 --- a/include/nuttx/wireless/bt_ioctl.h +++ b/include/nuttx/wireless/bt_ioctl.h @@ -49,6 +49,7 @@ #include <nuttx/wireless/wireless.h> #include <nuttx/wireless/bt_core.h> #include <nuttx/wireless/bt_hci.h> +#include <nuttx/wireless/bt_gatt.h> /**************************************************************************** * Pre-processor Definitions @@ -59,7 +60,7 @@ /* Bluetooth network device IOCTL commands. */ -#if !defined(WL_BLUETOOTHCMDS) || WL_BLUETOOTHCMDS != 16 +#if !defined(WL_BLUETOOTHCMDS) || WL_BLUETOOTHCMDS != 18 # error Incorrect setting for number of Bluetooth IOCTL commands #endif @@ -162,6 +163,18 @@ #define SIOCBTSECURITY _WLIOC(WL_BLUETOOTHFIRST + 15) +/* GATT + * + * SIOCBTDISCOVER + * Starts GATT discovery + * SIOCBTDISCGET + * Return discovery results buffered since the call time that the + * SIOCBTDISCGET command was invoked. + */ + +#define SIOCBTDISCOVER _WLIOC(WL_BLUETOOTHFIRST + 16) +#define SIOCBTDISCGET _WLIOC(WL_BLUETOOTHFIRST + 17) + /* Definitions associated with struct btreg_s *******************************/ /* struct btreq_s union field accessors */ @@ -193,6 +206,15 @@ #define btr_secaddr btru.btrse.btrse_secaddr #define btr_seclevel btru.btrse.btrse_seclevel +#define btr_dtype btru.btrds.btrds_dtype +#define btr_dpeer btru.btrds.btrds_dpeer +#define btr_duuid16 btru.btrds.btrds_duuid16 +#define btr_dstart btru.btrds.btrds_dstart +#define btr_dend btru.btrds.btrds_dend + +#define btr_gnrsp btru.btrdg.btrdg_gnrsp +#define btr_grsp btru.btrdg.btrdg_grsp + #define btr_stats btru.btrs /* btr_flags */ @@ -213,6 +235,15 @@ * Public Types ****************************************************************************/ +/* Type of GATT discovery command */ + +enum bt_gatt_discover_e +{ + GATT_DISCOVER = 0, /* Discover */ + GATT_DISCOVER_DESC, /* Discover descriptor */ + GATT_DISCOVER_CHAR, /* Discover characteristic */ +}; + /* Write-able data that accompanies the SIOCBTSCANGET IOCTL command */ struct bt_scanresponse_s @@ -224,6 +255,14 @@ struct bt_scanresponse_s uint8_t sr_data[CONFIG_BLUETOOTH_MAXSCANDATA]; }; +/* Write-able data that accompanies the SIOCBTDISCGET IOCTL command */ + +struct bt_discresonse_s +{ + uint16_t dr_handle; /* Discovered handled */ + uint8_t dr_perm; /* Permissions */ +}; + /* Bluetooth statistics */ struct bt_stats_s @@ -323,6 +362,32 @@ struct btreq_s enum bt_security_e btrse_seclevel; /* Security level */ } btrse; + /* Read-only data that accompanies SIOCBTDISCOVER command */ + + struct + { + uint8_t btrds_dtype; /* Discovery type (see enum + * bt_gatt_discover_e) */ + bt_addr_le_t btrds_dpeer; /* Peer address */ + uint16_t btrds_duuid16; /* Discover UUID type */ + uint16_t btrds_dstart; /* Discover start handle */ + uint16_t btrds_dend; /* Discover end handle */ + } btrds; + + /* Write-able structure that accompanies SIOCBTDISCGET command. */ + + struct + { + uint8_t btrdg_gnrsp; /* Input: Max number of responses + * Return: Actual number of responses */ + + /* Reference to a beginning of an array in user memory in which to + * return the discovered data. The size of the array is btrdg_gnrsp. + */ + + FAR struct bt_discresonse_s *btrdg_grsp; + } btrdg; + struct bt_stats_s btrs; /* Unit statistics */ } btru; }; diff --git a/include/nuttx/wireless/wireless.h b/include/nuttx/wireless/wireless.h index f4a0454a3e..922d255e3c 100644 --- a/include/nuttx/wireless/wireless.h +++ b/include/nuttx/wireless/wireless.h @@ -160,7 +160,7 @@ /* Reserved for Bluetooth network devices (see bt_ioctls.h) */ #define WL_BLUETOOTHFIRST (WL_NETFIRST + WL_NNETCMDS) -#define WL_BLUETOOTHCMDS (16) +#define WL_BLUETOOTHCMDS (18) #define WL_IBLUETOOTHCMD(cmd) (_WLIOCVALID(cmd) && \ _IOC_NR(cmd) >= WL_BLUETOOTHFIRST && \ _IOC_NR(cmd) < (WL_BLUETOOTHFIRST + WL_BLUETOOTHCMDS)) diff --git a/wireless/bluetooth/Kconfig b/wireless/bluetooth/Kconfig index 83527b6fa2..7b0b0ba136 100644 --- a/wireless/bluetooth/Kconfig +++ b/wireless/bluetooth/Kconfig @@ -98,6 +98,15 @@ config BLUETOOTH_MAXSCANRESULT This contributes to a static memory allocation that will be greater than CONFIG_BLUETOOTH_MAXSCANDATA * CONFIG_BLUETOOTH_MAXSCANRESULT +config BLUETOOTH_MAXDISCOVER + int "Max GATT discovery results" + default 8 + range 1 255 + ---help--- + GATT discovery results will be buffered in memory until the user + requests the results. This parameter specifies the maximum results + that can be buffered before discovery results are lost. + config BLUETOOTH_BUFFER_PREALLOC int "Number of pre-allocated buffer structures" default 20 diff --git a/wireless/bluetooth/bt_gatt.c b/wireless/bluetooth/bt_gatt.c index 6f466bf7a4..3437b89869 100644 --- a/wireless/bluetooth/bt_gatt.c +++ b/wireless/bluetooth/bt_gatt.c @@ -702,6 +702,15 @@ done: } } +/**************************************************************************** + * Name: bt_gatt_discover + * + * Description: + * This function implements the SIOCBTDISCOVER ioctl command for the + * GATT discovery. + * + ****************************************************************************/ + int bt_gatt_discover(FAR struct bt_conn_s *conn, FAR struct bt_gatt_discover_params_s *params) { @@ -867,6 +876,15 @@ done: } } +/**************************************************************************** + * Name: bt_gatt_discover_characteristic + * + * Description: + * This function implements the SIOCBTDISCOVER ioctl command for the + * GATT discover characteristics type. + * + ****************************************************************************/ + int bt_gatt_discover_characteristic(FAR struct bt_conn_s *conn, FAR struct bt_gatt_discover_params_s *params) { @@ -1014,6 +1032,15 @@ done: } } +/**************************************************************************** + * Name: bt_gatt_discover_descriptor + * + * Description: + * This function implements the SIOCBTDISCOVER ioctl command for the + * GATT discover descriptor type. + * + ****************************************************************************/ + int bt_gatt_discover_descriptor(FAR struct bt_conn_s *conn, FAR struct bt_gatt_discover_params_s *params) { diff --git a/wireless/bluetooth/bt_ioctl.c b/wireless/bluetooth/bt_ioctl.c index f3011f377d..2f4fb0c6af 100644 --- a/wireless/bluetooth/bt_ioctl.c +++ b/wireless/bluetooth/bt_ioctl.c @@ -62,7 +62,7 @@ * Public Types ****************************************************************************/ -/* This structure encapsulates all globals used by the IOCTL logic */ +/* These structures encapsulate all globals used by the IOCTL logic. */ struct btnet_scanstate_s { @@ -74,15 +74,28 @@ struct btnet_scanstate_s struct bt_scanresponse_s bs_rsp[CONFIG_BLUETOOTH_MAXSCANRESULT]; }; +struct btnet_discoverstate_s +{ + sem_t bd_exclsem; /* Manages exclusive access */ + bool bd_discovering; /* True: Discovery in progress */ + uint8_t bd_head; /* Head of circular list (for removal) */ + uint8_t bd_tail; /* Tail of circular list (for addition) */ + + struct bt_discresonse_s bd_rsp[CONFIG_BLUETOOTH_MAXDISCOVER]; +}; + /**************************************************************************** * Private Data ****************************************************************************/ /* At present only a single Bluetooth device is supported. So we can simply - * maintain the scan state as a global. + * maintain the scan and the discovery state as globals. + * NOTE: This limits to one concurrent scan action and one concurrent + * discovery action. */ -static struct btnet_scanstate_s g_scanstate; +static struct btnet_scanstate_s g_scanstate; +static struct btnet_discoverstate_s g_discoverstate; /**************************************************************************** * Private Functions @@ -179,9 +192,8 @@ static void btnet_scan_callback(FAR const bt_addr_le_t *addr, int8_t rssi, * Name: btnet_scan_result * * Description: - * This is an HCI callback function. HCI provides scan result data via - * this callback function. The scan result data will be added to the - * cached scan results. + * This function implements the SIOCBTSCANGET IOCTL command. It returns + * the current, buffered discovered handles. * * Input Parameters: * result - Location to return the scan result data @@ -214,8 +226,8 @@ static int btnet_scan_result(FAR struct bt_scanresponse_s *result, /* Copy all available results */ - head = g_scanstate.bs_head; - tail = g_scanstate.bs_tail; + head = g_scanstate.bs_head; + tail = g_scanstate.bs_tail; for (nrsp = 0; nrsp < maxrsp && head != tail; nrsp++) { @@ -241,6 +253,177 @@ static int btnet_scan_result(FAR struct bt_scanresponse_s *result, return nrsp; } +/**************************************************************************** + * Name: bt_discover_func + * + * Description: + * GATT discovery callback. This function is called when a new handle is + * discovered + * + * Input Parameters: + * attr - The discovered attributes + * arg - The original discovery parameters + * + * Returned Value: + * BT_GATT_ITER_CONTINUE meaning to continue the iteration. + * + ****************************************************************************/ + +static uint8_t bt_discover_func(FAR const struct bt_gatt_attr_s *attr, + FAR void *arg) +{ + uint8_t nexttail; + uint8_t head; + uint8_t tail; + int ret; + + wlinfo("Discovered handle %u\n", attr->handle); + + if (!g_discoverstate.bd_discovering) + { + wlerr("ERROR: Results received while not discovering\n"); + return BT_GATT_ITER_STOP; + } + + /* Get exclusive access to the discovered data */ + + while ((ret = nxsem_wait(&g_discoverstate.bd_exclsem)) < 0) + { + DEBUGASSERT(ret == -EINTR || ret == -ECANCELED); + if (ret != -EINTR) + { + return BT_GATT_ITER_STOP; + } + } + + /* Add the discovered data to the cache */ + + tail = g_discoverstate.bd_tail; + nexttail = tail + 1; + + if (nexttail >= CONFIG_BLUETOOTH_MAXSCANRESULT) + { + nexttail = 0; + } + + /* Is the circular buffer full? */ + + head = g_discoverstate.bd_head; + if (nexttail == head) + { + wlerr("ERROR: Too many handles discovered. Data lost.\n"); + + if (++head >= CONFIG_BLUETOOTH_MAXSCANRESULT) + { + head = 0; + } + + g_discoverstate.bd_head = head; + } + + /* Save the newly discovered handle */ + + g_discoverstate.bd_rsp[tail].dr_handle = attr->handle; + g_discoverstate.bd_rsp[tail].dr_perm = attr->perm; + g_discoverstate.bd_tail = nexttail; + + nxsem_post(&g_discoverstate.bd_exclsem); + return BT_GATT_ITER_CONTINUE; +} + +/**************************************************************************** + * Name: bt_discover_destroy + * + * Description: + * GATT destroy callback. This function is called when a discovery + * completes. + * + * Input Parameters: + * arg - The original discovery parameters + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void bt_discover_destroy(FAR void *arg) +{ + FAR struct bt_gatt_discover_params *params = arg; + + /* There is nothing that needs to be down here. The parameters were + * allocated on the stack and are long gone. + */ + + wlinfo("Discover destroy. params %p\n", params); + DEBUGASSERT(params != NULL); + UNUSED(params); + + DEBUGASSERT(g_discoverstate.bd_discovering); + nxsem_destroy(&g_discoverstate.bd_exclsem); + g_discoverstate.bd_discovering = false; +} + +/**************************************************************************** + * Name: btnet_discover_result + * + * Description: + * This function implements the SIOCBTDISCGET IOCTL command. It returns + * the current, buffered discovered handles. + * + * Input Parameters: + * result - Location to return the discovery result data + * maxrsp - The maximum number of responses that can be returned. + * + * Returned Value: + * On success, the actual number of discovery results obtain is returned. A + * negated errno value is returned on any failure. + * + ****************************************************************************/ + +static int btnet_discover_result(FAR struct bt_discresonse_s *result, + uint8_t maxrsp) +{ + uint8_t head; + uint8_t tail; + uint8_t nrsp; + int ret; + + wlinfo("Discovering? %s\n", g_discoverstate.bd_discovering ? "YES" : "NO"); + + /* Get exclusive access to the discovery data */ + + ret = nxsem_wait(&g_discoverstate.bd_exclsem); + if (ret < 0) + { + DEBUGASSERT(ret == -EINTR || ret == -ECANCELED); + return ret; + } + + /* Copy all available results */ + + head = g_discoverstate.bd_head; + tail = g_discoverstate.bd_tail; + + for (nrsp = 0; nrsp < maxrsp && head != tail; nrsp++) + { + /* Copy data from the head index into the user buffer */ + + result[nrsp].dr_handle = g_discoverstate.bd_rsp[head].dr_handle; + result[nrsp].dr_perm = g_discoverstate.bd_rsp[head].dr_perm; + + /* Increment the head index */ + + if (++head >= CONFIG_BLUETOOTH_MAXDISCOVER) + { + head = 0; + } + } + + g_discoverstate.bd_head = head; + nxsem_post(&g_discoverstate.bd_exclsem); + return nrsp; +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -363,6 +546,7 @@ int btnet_ioctl(FAR struct net_driver_s *netdev, int cmd, unsigned long arg) if (g_scanstate.bs_scanning) { + wlwarn("WARNING: Already scanning\n"); ret = -EBUSY; } else @@ -445,6 +629,103 @@ int btnet_ioctl(FAR struct net_driver_s *netdev, int cmd, unsigned long arg) } break; + /* SIOCBTDISCOVER: Starts GATT discovery */ + + case SIOCBTDISCOVER: + { + FAR struct bt_conn_s *conn; + + /* Check if discovery is already in progress */ + + if (g_discoverstate.bd_discovering) + { + wlwarn("WARNING: Discovery is already in progress\n"); + ret = -EBUSY; + } + else + { + /* Get the connection associated with the provided LE address */ + + conn = bt_conn_lookup_addr_le(&btreq->btr_dpeer); + if (conn == NULL) + { + wlwarn("WARNING: Peer not connected\n"); + ret = -ENOTCONN; + } + else + { + struct bt_gatt_discover_params_s params; + struct bt_uuid_s uuid; + + + /* Set up the query */ + + nxsem_init(&g_discoverstate.bd_exclsem, 0, 1); + g_discoverstate.bd_discovering = true; + g_discoverstate.bd_head = 0; + g_discoverstate.bd_tail = 0; + + /* Start the query */ + + uuid.type = BT_UUID_16; + uuid.u.u16 = btreq->btr_duuid16; + + params.uuid = &uuid; + params.func = bt_discover_func; + params.destroy = bt_discover_destroy; + params.start_handle = btreq->btr_dstart; + params.end_handle = btreq->btr_dend; + + switch (btreq->btr_dtype) + { + case GATT_DISCOVER: + ret = bt_gatt_discover(conn, ¶ms); + break; + + case GATT_DISCOVER_DESC: + ret = bt_gatt_discover_descriptor(conn, ¶ms); + break; + + case GATT_DISCOVER_CHAR: + ret = bt_gatt_discover_characteristic(conn, ¶ms); + break; + + default: + wlerr("ERROR: Unrecognized GATT discover type: %u\n", + btreq->btr_dtype); + ret = -EINVAL; + } + + if (ret < 0) + { + wlerr("ERROR: Failed to start discovery: %d\n", ret); + nxsem_destroy(&g_discoverstate.bd_exclsem); + g_discoverstate.bd_discovering = false; + } + + bt_conn_release(conn); + } + } + } + break; + + /* SIOCBTDISCGET: Return discovered results buffered since the call time + * that the SIOCBTDISCGET command was invoked. + */ + + case SIOCBTDISCGET: + { + ret = btnet_discover_result(btreq->btr_grsp, btreq->btr_gnrsp); + wlinfo("Get discovery results: %d\n", ret); + + if (ret >= 0) + { + btreq->btr_nrsp = ret; + ret = OK; + } + } + break; + default: wlwarn("WARNING: Unrecognized IOCTL command: %02x\n", cmd); ret = -ENOTTY;