Merge pull request #7417 from pan-/improve-cordio-hci-doc

Cordio Documentation: Explain how to tests and what tools are available.
pull/7880/head
Martin Kojtal 2018-08-24 09:52:21 +02:00 committed by GitHub
commit 70814d6185
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 158 additions and 202 deletions

View File

@ -16,12 +16,12 @@ This can be summarized in the following diagram:
## CordioHCITransportDriver ## CordioHCITransportDriver
The single responsabilities of this a driver is to handle the communication with The single responsibility of this driver is to handle the communication with
the Bluetooth module. Basically, sending and reading bytes. the Bluetooth module. Basically, sending and reading bytes.
Given that the Bluetooth specification define standard transport interface, an Given that the Bluetooth specification defines standard transport interface, an
implementation of the H4 interface is bundled in this port. It might be extended implementation of the H4 interface is bundled in this port. It might be extended
in the future with an implementation of the H5 interface. However there is no in the future with an implementation of the H5 interface. However, there is no
plan to provide the SDIO implementation at the moment. plan to provide the SDIO implementation at the moment.
This interface is defined in the header file This interface is defined in the header file
@ -31,14 +31,14 @@ This interface is defined in the header file
The responsibilities of this driver are: The responsibilities of this driver are:
* Provide the memory which will used by the Bluetooth stack. * Provide the memory which will used by the Bluetooth stack.
* Initialize the bluetooth controller. * Initialize the Bluetooth controller.
* Handle the reset/startup sequence of the bluetooth controller. * Handle the reset/startup sequence of the Bluetooth controller.
This interface is defined in the header file This interface is defined in the header file
[CordioHCIDriver.h](../driver/CordioHCIDriver.h) [CordioHCIDriver.h](../driver/CordioHCIDriver.h)
A partial implementation is present in the file A partial implementation is present in the file
[CordioHCIDriver.cpp](../driver/CordioHCIDriver.cpp). It defines the function [CordioHCIDriver.cpp](../driver/CordioHCIDriver.cpp). It defines the function
delivering memory to the stack and a complete reset sequence. However it does delivering memory to the stack and a complete reset sequence. However, it does
not define any initialization for the Bluetooth controller, this part being not define any initialization for the Bluetooth controller, this part being
specific to the controller used. specific to the controller used.

View File

@ -1,24 +1,20 @@
# Porting guide ## Porting guide
Enabling the mbed BLE Cordio port for a given target is a two step process: There are two main steps to enable the Mbed BLE Cordio port:
* The target shall be configured to include Cordio BLE port and Cordio libraries
during the build process.
* An implementation of the `CordioHCIDriver` class targeting the bluetooth
controller used shall be provided.
## Target configuration 1. Configure your target to include Cordio BLE port and Cordio libraries during the build process.
This step happens in the file defining all mbed os targets. The file is located 1. Implement the `CordioHCIDriver` class targeting the Bluetooth controller.
in `targets/target.json`.
### Add BLE feature ### Configure the target
First of all support for BLE has to be added to the target. This is achieved by 1. Define all Mbed OS targets in the `targets/target.json` file:
adding the string `BLE` in the list of `features` of the target. Once this
value has been added the sources of BLE api are added to the list of sources
compiled for the target.
```json 1. Add BLE support to the target:
* Add the string `BLE` to the target's list of `features`. This adds the BLE API sources to the list of sources compiled for the target:
```
"TARGET_NAME": { "TARGET_NAME": {
"features": ["target features ...", "BLE"] "features": ["target features ...", "BLE"]
} }
@ -26,50 +22,41 @@ compiled for the target.
### Include Cordio BLE implementation ### Include Cordio BLE implementation
The target should also compile the sources of the BLE Cordio port. It is Compile the BLE Cordio port sources:
achieved by adding the string `CORDIO` in the list of the `extra_labels`
property of the target.
```json * Add the string `CORDIO` to the `extra_labels` property of the JSON file:
```
"TARGET_NAME": { "TARGET_NAME": {
"extra_labels": ["target extra labels ...", "CORDIO"], "extra_labels": ["target extra labels ...", "CORDIO"],
"features": ["target features ...", "BLE"] "features": ["target features ...", "BLE"]
} }
``` ```
## CordioHCIDriver implementation: ### Implement CordioHCIDriver:
A port shall include an HCI driver for the BLE module used by the target and Include an HCI driver for the BLE module used by the target, and a factory function that creates the BLE instance you use.
a factory function which create the BLE instance used by the user of BLE API.
### Create source folder #### Create source folder
The port shall live in the folder of BLE API which host targets port : 1. Navigate to the folder of the BLE API that hosts the target port `features/FEATURE_BLE/targets`.
`features/FEATURE_BLE/targets`.
To isolate the port code from other code a folder containing the port code has 1. Create a folder containing the port code to isolate it from other code.
to be created. The name of this folder shall start with `TARGET_` and end with * Begin this folder's name with `TARGET_` and the rest of the name in capital letters.
the target name in capital.
### Create the HCI driver #### Create the HCI driver
The HCI driver is split in two entities: one which handle HCI communication with The HCI driver is split into two entities. One handles HCI communication with the Bluetooth module. The other handles the initialization, reset sequence, and memory dedicated to the Bluetooth controller.
the Bluetooth module and the other handling the initialization, reset sequence
and memory dedicated for the Bluetooth controller.
More information about the architecture can be found in the More information about the architecture can be found in [HCI abstraction architecture](HCIAbstraction.md).
[HCI abstraction architecture](HCIAbstraction.md) document.
#### HCITransport #### HCITransport
> **Note:** If the Bluetooth controller uses an H4 communication interface and <span class="notes">**Note:** If the Bluetooth controller uses an H4 communication interface and the host exposes serial flow control in Mbed, you can skip this step. Use the class `ble::vendor::cordio::H4TransportDriver` as the transport driver.</span>
the host exposes serial flow control in mbed then this step can be skipped and
the class `ble::vendor::cordio::H4TransportDriver` can be used as the transport
driver.
An empty transport driver can be coded as: To code an empty transport driver:
```c++ ```
#include "CordioHCITransportDriver.h" #include "CordioHCITransportDriver.h"
namespace ble { namespace ble {
@ -97,30 +84,27 @@ private:
} // namespace ble } // namespace ble
``` ```
It shall inherits publicly from the base class `CordioHCITransportDriver`. It inherits publicly from the base class `CordioHCITransportDriver`.
* **Initialization/termination:** The functions `initialize` and `terminate` are ##### Functions
responsible for initializing and terminating the transport driver. It is not
necessary to initialize the transport in the constructor.
* **Sending data** The function `write` shall sends data in input to the * **Initialization/termination**: The functions `initialize` and `terminate` are responsible for initializing and terminating the transport driver. It is not necessary to initialize the transport in the constructor.
Bluetooth controller and return the number of bytes in the `data` buffer sent.
Depending on the type of transport being implemented, the packet `type` might
have to be sent to the controller before the packet data.
* **Receiving data**: HCI data from the Bluetooth controller shall be injected * **Sending data**: The function `write` sends data as input to the Bluetooth controller and returns the number of bytes in the `data` buffer sent. Depending on the type of transport you implement, you may need to send the packet `type` to the controller before the packet data.
in the system by invoking the function `on_data_received`. This function is
a static one and is provided by the base class. Its prototype is:
```c++ * **Receiving data**: Inject HCI data from the Bluetooth controller to the system by calling `on_data_received`. This is a static function provided by the base class.
**Example:**
```
void on_data_received(uint8_t* data_received, uint16_t length_of_data_received); void on_data_received(uint8_t* data_received, uint16_t length_of_data_received);
``` ```
#### HCIDriver #### HCIDriver
The skeleton of driver is: The HCIDriver template is:
```c++ ```
#include "CordioHCIDriver.h" #include "CordioHCIDriver.h"
namespace ble { namespace ble {
@ -156,19 +140,13 @@ private:
##### Initialization process ##### Initialization process
The initialization/termination process is achieved by the couple of functions The functions `do_initialize` and `do_terminate` handle initialization and termination processes. These functions manage the state of the Bluetooth controller.
`do_initialize` and `do_terminate`. Those function manages the state of the
bluetooth controller.
> **Important:** It is unnecessary to initialize or terminate the hci transport <span class="notes">**Note:** It is unnecessary to initialize or terminate the HCI transport in these functions, because that is handled by the base class. The HCI transport is initialized right before the call to `do_initialize` and is terminated right after the call to `do_terminate`.</span>
in those function because it is handled by the base class. The HCI transport is
initialized right before the call to `do_initialize` and is terminated right
after the call to `do_terminate`.
##### Memory pool ##### Memory pool
The implementation of the function `get_buffer_pool_description` in the base The function `get_buffer_pool_description` in the base class returns a buffer of 1040 bytes divided into different memory pools:
class will return a buffer of 1040 bytes divided in different memory pools:
| Chunk size (bytes) | Number of chunks | | Chunk size (bytes) | Number of chunks |
|--------------------|------------------| |--------------------|------------------|
@ -178,10 +156,11 @@ class will return a buffer of 1040 bytes divided in different memory pools:
| 128 | 2 | | 128 | 2 |
| 272 | 1 | | 272 | 1 |
A port shall override this function if the memory provided by the base class Porting overrides this function if the memory provided by the base class doesn't match what is required by the Bluetooth controller driver.
doesn't match what is required by the Bluetooth controller driven.
```c++ **Example:**
```
buf_pool_desc_t CordioHCIDriver::get_buffer_pool_description() { buf_pool_desc_t CordioHCIDriver::get_buffer_pool_description() {
static uint8_t buffer[/* buffer size */]; static uint8_t buffer[/* buffer size */];
static const wsfBufPoolDesc_t pool_desc[] = { static const wsfBufPoolDesc_t pool_desc[] = {
@ -200,62 +179,48 @@ buf_pool_desc_t CordioHCIDriver::get_buffer_pool_description() {
##### Reset sequence ##### Reset sequence
The reset sequence process is handled by three functions: Three functions handle the reset sequence process:
* `start_reset_sequence`: This function start the process. The basic * `start_reset_sequence`: This function starts the process. It sends an HCI reset command to the Bluetooth controller. You can override this function *if* the Bluetooth controller requires more than just sending the standard reset command.
implementation sends an HCI reset command to the Bluetooth controller. The
function shall be overridden in the driver if the bluetooth controller requires
more than just sending the standard reset command.
* `handle_reset_sequence`: Entry point to the state machine handling the reset * `handle_reset_sequence`: Entry point to the state machine handling the reset process. Every time an HCI packet is received during the reset sequence, this function is called with the HCI packet received. Its purpose is to prepare the Bluetooth controller and set the parameters the stack needs to operate properly. You can override this function if necessary.
process. Every time an HCI packet is received during the reset sequence, this
function is called with the HCI packet received. It's purpose is to prepare the
Bluetooth controller and set the parameters the stack needs to operate properly.
This function can be overridden if necessary.
* `signal_reset_sequence_done`: This function shall be called once the reset * `signal_reset_sequence_done`: Once the reset sequence is completed, you can call this function. You cannot override it.
sequence is achieved. It cannot be overridden.
###### Controller parameters to set ###### Controller parameters to set
This instruct the controller which events are relevant for the stack. This step tells the controller which events are relevant to the stack.
The following parameters should be set in the controller (if supported): You should set the following parameters in the controller (if supported):
* event mask: The reset sequence should issue the call
`HciSetEventMaskCmd((uint8_t *) hciEventMask)`
* LE event mask: The call `HciLeSetEventMaskCmd((uint8_t *) hciLeEventMask)`
should be issued.
* 2nd page of events mask: Can be achieved by invoking
`HciSetEventMaskPage2Cmd((uint8_t *) hciEventMaskPage2)`.
* **Event mask:** Call `HciSetEventMaskCmd((uint8_t *) hciEventMask)`.
* **LE event mask:** Call `HciLeSetEventMaskCmd((uint8_t *) hciLeEventMask)`.
* **2nd page of events mask:** Call `HciSetEventMaskPage2Cmd((uint8_t *) hciEventMaskPage2)`.
###### Stack runtime parameters ###### Stack runtime parameters
Some stack parameters shall be acquired at runtime from the controller: At runtime, you can get stack parameters from the controller:
* Bluetooth address: Can be queried with `HciReadBdAddrCmd`. Response shall be * **Bluetooth address:** Query this with `HciReadBdAddrCmd`. Copy the response into `hciCoreCb.bdAddr` with `BdaCpy`.
copied into `hciCoreCb.bdAddr` with `BdaCpy`.
* Buffer size of the controller: Can be obtained by `HciLeReadBufSizeCmd`. The
return parameter *HC_ACL_Data_Packet_Length* shall be copied into
`hciCoreCb.bufSize` and the response parameter
`HC_Synchronous_Data_Packet_Length`shall be copied into `hciCoreCb.numBufs`.
The value of `hciCoreCb.availBufs` shall be initialized with `hciCoreCb.numBufs`.
* Supported state: Queried with `HciLeReadSupStatesCmd`, the response shall go
into `hciCoreCb.leStates`.
* Whitelist size: Queried with `HciLeReadWhiteListSizeCmd` the response shall go
into `hciCoreCb.whiteListSize`.
* LE features supported: Obtained with `HciLeReadLocalSupFeatCmd`. The response
shall be stored into `hciCoreCb.leSupFeat`.
* Resolving list size: Obtained with `hciCoreReadResolvingListSize`. The response
shall go into `hciCoreCb.resListSize`.
* Max data length: Acquired with `hciCoreReadMaxDataLen`. The response parameter
`supportedMaxTxOctets` and `supportedMaxTxTime` shall be pass to the function
`HciLeWriteDefDataLen`.
* **Buffer size of the controller:** Query this with `HciLeReadBufSizeCmd`.
* The return parameter `HC_ACL_Data_Packet_Length` is copied to `hciCoreCb.bufSize`. Copy the response parameter `HC_Synchronous_Data_Packet_Length` into `hciCoreCb.numBufs`. `hciCoreCb.numBufs` initializes the value of `hciCoreCb.availBufs`.
The default implementation is: * **Supported state:** Query this with `HciLeReadSupStatesCmd`, and copy the response into `hciCoreCb.leStates`.
```c++ * **Whitelist size:** Query this with `HciLeReadWhiteListSizeCmd`, and copy the response into `hciCoreCb.whiteListSize`.
* **LE features supported:** Query this with `HciLeReadLocalSupFeatCmd`, and copy the response into `hciCoreCb.leSupFeat`.
* **Resolving list size:** Query this with `hciCoreReadResolvingListSize`, and copy the response into `hciCoreCb.resListSize`.
* **Max data length:** Query this with `hciCoreReadMaxDataLen`, and pass the response parameters `supportedMaxTxOctets` and `supportedMaxTxTime` to the function `HciLeWriteDefDataLen`.
**Example:**
```
void HCIDriver::handle_reset_sequence(uint8_t *pMsg) { void HCIDriver::handle_reset_sequence(uint8_t *pMsg) {
// only accept command complete event: // only accept command complete event:
if (*pMsg != HCI_CMD_CMPL_EVT) { if (*pMsg != HCI_CMD_CMPL_EVT) {
@ -277,90 +242,80 @@ void HCIDriver::handle_reset_sequence(uint8_t *pMsg) {
case HCI_OPCODE_RESET: case HCI_OPCODE_RESET:
/* initialize rand command count */ /* initialize rand command count */
randCnt = 0; randCnt = 0;
// set the event mask to control which events are generated by the // Set the event mask to control which events are generated by the controller for the host.
// controller for the host
HciSetEventMaskCmd((uint8_t *) hciEventMask); HciSetEventMaskCmd((uint8_t *) hciEventMask);
break; break;
case HCI_OPCODE_SET_EVENT_MASK: case HCI_OPCODE_SET_EVENT_MASK:
// set the event mask to control which LE events are generated by // Set the event mask to control which LE events are generated by the controller for the host.
// the controller for the host
HciLeSetEventMaskCmd((uint8_t *) hciLeEventMask); HciLeSetEventMaskCmd((uint8_t *) hciLeEventMask);
break; break;
case HCI_OPCODE_LE_SET_EVENT_MASK: case HCI_OPCODE_LE_SET_EVENT_MASK:
// set the event mask to control which events are generated by the // Set the event mask to control which events are generated by the controller for the host (2nd page of flags).
// controller for the host (2nd page of flags )
HciSetEventMaskPage2Cmd((uint8_t *) hciEventMaskPage2); HciSetEventMaskPage2Cmd((uint8_t *) hciEventMaskPage2);
break; break;
case HCI_OPCODE_SET_EVENT_MASK_PAGE2: case HCI_OPCODE_SET_EVENT_MASK_PAGE2:
// Ask the Bluetooth address of the controller // Ask the Bluetooth address of the controller.
HciReadBdAddrCmd(); HciReadBdAddrCmd();
break; break;
case HCI_OPCODE_READ_BD_ADDR: case HCI_OPCODE_READ_BD_ADDR:
// Store the Bluetooth address in the stack runtime parameter // Store the Bluetooth address in the stack runtime parameter.
BdaCpy(hciCoreCb.bdAddr, pMsg); BdaCpy(hciCoreCb.bdAddr, pMsg);
// Read the size of the buffer of the controller // Read the size of the buffer of the controller.
HciLeReadBufSizeCmd(); HciLeReadBufSizeCmd();
break; break;
case HCI_OPCODE_LE_READ_BUF_SIZE: case HCI_OPCODE_LE_READ_BUF_SIZE:
// Store the buffer parameters in the stack runtime parameters // Store the buffer parameters in the stack runtime parameters.
BSTREAM_TO_UINT16(hciCoreCb.bufSize, pMsg); BSTREAM_TO_UINT16(hciCoreCb.bufSize, pMsg);
BSTREAM_TO_UINT8(hciCoreCb.numBufs, pMsg); BSTREAM_TO_UINT8(hciCoreCb.numBufs, pMsg);
/* initialize ACL buffer accounting */ /* initialize ACL buffer accounting */
hciCoreCb.availBufs = hciCoreCb.numBufs; hciCoreCb.availBufs = hciCoreCb.numBufs;
// read the states and state combinations supported by the link // Read the states and state combinations supported by the link layer of the controller.
// layer of the controller
HciLeReadSupStatesCmd(); HciLeReadSupStatesCmd();
break; break;
case HCI_OPCODE_LE_READ_SUP_STATES: case HCI_OPCODE_LE_READ_SUP_STATES:
// store supported state and combination in the runtime parameters // Store supported state and combination in the runtime parameters of the stack.
// of the stack
memcpy(hciCoreCb.leStates, pMsg, HCI_LE_STATES_LEN); memcpy(hciCoreCb.leStates, pMsg, HCI_LE_STATES_LEN);
// read the total of whitelist entries that can be stored in the // Read the total of whitelist entries that can be stored in the controller.
// controller.
HciLeReadWhiteListSizeCmd(); HciLeReadWhiteListSizeCmd();
break; break;
case HCI_OPCODE_LE_READ_WHITE_LIST_SIZE: case HCI_OPCODE_LE_READ_WHITE_LIST_SIZE:
// store the number of whitelist entries in the stack runtime // Store the number of whitelist entries in the stack runtime parameters.
// parameters
BSTREAM_TO_UINT8(hciCoreCb.whiteListSize, pMsg); BSTREAM_TO_UINT8(hciCoreCb.whiteListSize, pMsg);
// Read the LE features supported by the controller // Read the LE features supported by the controller.
HciLeReadLocalSupFeatCmd(); HciLeReadLocalSupFeatCmd();
break; break;
case HCI_OPCODE_LE_READ_LOCAL_SUP_FEAT: case HCI_OPCODE_LE_READ_LOCAL_SUP_FEAT:
// Store the set of LE features supported by the controller // Store the set of LE features supported by the controller.
BSTREAM_TO_UINT16(hciCoreCb.leSupFeat, pMsg); BSTREAM_TO_UINT16(hciCoreCb.leSupFeat, pMsg);
// read the total number of address translation entries which can be // Read the total number of address translation entries which can be stored in the controller resolving list.
// stored in the controller resolving list.
hciCoreReadResolvingListSize(); hciCoreReadResolvingListSize();
break; break;
case HCI_OPCODE_LE_READ_RES_LIST_SIZE: case HCI_OPCODE_LE_READ_RES_LIST_SIZE:
// store the number of address translation entries in the stack // Store the number of address translation entries in the stack runtime parameter.
// runtime parameter
BSTREAM_TO_UINT8(hciCoreCb.resListSize, pMsg); BSTREAM_TO_UINT8(hciCoreCb.resListSize, pMsg);
// read the Controllers maximum supported payload octets and packet // Read the Controllers maximum supported payload octets and packet duration times for transmission and reception.
// duration times for transmission and reception
hciCoreReadMaxDataLen(); hciCoreReadMaxDataLen();
break; break;
case HCI_OPCODE_LE_READ_MAX_DATA_LEN: case HCI_OPCODE_LE_READ_MAX_DATA_LEN:
{ {
// store payload definition in the runtime stack parameters. // Store payload definition in the runtime stack parameters.
uint16_t maxTxOctets; uint16_t maxTxOctets;
uint16_t maxTxTime; uint16_t maxTxTime;
@ -458,24 +413,21 @@ static void hciCoreReadResolvingListSize(void)
} }
``` ```
### HCI accessor function ### HCI accessor function
The HCI driver is injected in the `CordioBLE` class at construction site. The HCI driver is injected to the `CordioBLE` class at manufacture.
Given that the CordioBLE class doesn't know what class shall be used to
construct the driver nor it knows how to construct it, the port shall provide a
function returning a reference to the HCI driver.
This function lives in the global namespace and its signature is: Given that the `CordioBLE` class doesn't know which class constructs the driver nor how to construct it, the port provides a function returning a reference to the HCI driver.
```c++ This function is in the global namespace, and its signature is:
```
ble::vendor::cordio::CordioHCIDriver& ble_cordio_get_hci_driver(); ble::vendor::cordio::CordioHCIDriver& ble_cordio_get_hci_driver();
``` ```
A common implementation might be: **Example:**
```c++ ```
ble::vendor::cordio::CordioHCIDriver& ble_cordio_get_hci_driver() { ble::vendor::cordio::CordioHCIDriver& ble_cordio_get_hci_driver() {
static ble::vendor::target_name::TransportDriver transport_driver( static ble::vendor::target_name::TransportDriver transport_driver(
/* transport parameters */ /* transport parameters */
@ -489,18 +441,22 @@ ble::vendor::cordio::CordioHCIDriver& ble_cordio_get_hci_driver() {
} }
``` ```
### Tests
We bundle Greentea tests with the Cordio port of the BLE API, so the transport driver and validation of Cordio stack initialization both work. You can run these tests with the following command:
```
mbed test -t <toolchain> -m <target> -n mbed-os-features-feature_ble-targets-target_cordio-tests-cordio_hci-driver,mbed-os-features-feature_ble-targets-target_cordio-tests-cordio_hci-transport
```
* `mbed-os-features-feature_ble-targets-target_cordio-tests-cordio_hci-transport`: Ensures that the transport is able to send an HCI reset command and receive the corresponding HCI status event.
* `mbed-os-features-feature_ble-targets-target_cordio-tests-cordio_hci-driver`: Runs the whole initialization process, then ensures the HCI driver has properly initialized the Cordio stack.
### Tools
You can use the application [mbed-os-cordio-hci-passthrough](https://github.com/ARMmbed/mbed-os-cordio-hci-passthrough) to proxify a Bluetooth controller connected to an Mbed board.
The host sent bytes over the board serial, which the `HCITransport Driver` forwards. Bytes sent by the controller go back to the host through the board serial.
You can use this application to validate the transport driver and debug the initialization process.