diff --git a/features/FEATURE_BLE/targets/TARGET_CORDIO/TESTS/cordio_hci/driver/main.cpp b/features/FEATURE_BLE/targets/TARGET_CORDIO/TESTS/cordio_hci/driver/main.cpp new file mode 100644 index 0000000000..64f42bd0d8 --- /dev/null +++ b/features/FEATURE_BLE/targets/TARGET_CORDIO/TESTS/cordio_hci/driver/main.cpp @@ -0,0 +1,111 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "events/mbed_events.h" +#include "platform/Callback.h" + +#include "ble/BLE.h" + +#include "greentea-client/test_env.h" +#include "utest/utest.h" +#include "unity/unity.h" + +#include "hci_api.h" +#include "hci_cmd.h" +#include "hci_core.h" +#include "dm_api.h" +#include "bstream.h" + +using namespace utest::v1; +using mbed::callback; + +#define INITIALIZATION_TIMEOUT (10 * 1000) + +static EventQueue event_queue(/* event count */ 10 * EVENTS_EVENT_SIZE); + +enum initialization_state_t { + WAITING_FOR_INITIALIZATION, + INITIALIZATION_FAILURE, + INITIALIZATION_SUCCESS +}; + +static initialization_state_t initialization_state = WAITING_FOR_INITIALIZATION; + +static void process_ble_events(BLE::OnEventsToProcessCallbackContext* context) { + BLE &ble = BLE::Instance(); + event_queue.call(callback(&ble, &BLE::processEvents)); +} + +static void on_initialization_complete(BLE::InitializationCompleteCallbackContext *params) { + if (params->error == BLE_ERROR_NONE) { + initialization_state = INITIALIZATION_SUCCESS; + } else { + initialization_state = INITIALIZATION_FAILURE; + } + + event_queue.break_dispatch(); +} + +static void test_stack_initialization() { + BLE &ble = BLE::Instance(); + ble.onEventsToProcess(process_ble_events); + ble.init(on_initialization_complete); + event_queue.dispatch(INITIALIZATION_TIMEOUT); + + // At this point ble is suppose to be initialized; inspect the various state + // of the stack. + TEST_ASSERT_EQUAL(INITIALIZATION_SUCCESS, initialization_state); + + // ensure that the size of ACL buffer of the controller has been filled in + // during the initialisation + TEST_ASSERT_NOT_EQUAL(0, hciCoreCb.bufSize); + + // Ensure that the total number of buffer available in the controller has + // been filled in during the initialisation + TEST_ASSERT_NOT_EQUAL(0, hciCoreCb.numBufs); + + // Ensure that at least one HCI buffer is available + TEST_ASSERT_NOT_EQUAL(0, hciCoreCb.availBufs); + + // Ensure that allowed LE state has been filled in during initialisation + // Note: see BT command LE Read Supported States Command in BT specification + uint8_t invalid_le_states[HCI_LE_STATES_LEN] = { 0 }; + TEST_ASSERT_NOT_EQUAL(0, memcmp(invalid_le_states, hciCoreCb.leStates, HCI_LE_STATES_LEN)); + + // Ensure that the size of the whitelist of the controller has been filled + // in during the initialisation + TEST_ASSERT_NOT_EQUAL(0, hciCoreCb.whiteListSize); + + // Note: cannot test supported features are the list may be null + // Note: cannot test resolving list size as this may be null +} + +Case cases[] = { + Case("Test cordio stack reset sequence", test_stack_initialization), +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(15, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); + +int main() { + return !Harness::run(specification); +} diff --git a/features/FEATURE_BLE/targets/TARGET_CORDIO/TESTS/cordio_hci/transport/main.cpp b/features/FEATURE_BLE/targets/TARGET_CORDIO/TESTS/cordio_hci/transport/main.cpp new file mode 100644 index 0000000000..3aaadbb453 --- /dev/null +++ b/features/FEATURE_BLE/targets/TARGET_CORDIO/TESTS/cordio_hci/transport/main.cpp @@ -0,0 +1,268 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "driver/CordioHCITransportDriver.h" +#include "driver/CordioHCIDriver.h" +#include "hci_defs.h" +#include "rtos/EventFlags.h" + +#include "greentea-client/test_env.h" +#include "utest/utest.h" +#include "unity/unity.h" + +using namespace utest::v1; + +using ble::vendor::cordio::CordioHCIDriver; +using ble::vendor::cordio::CordioHCITransportDriver; + +extern ble::vendor::cordio::CordioHCIDriver& ble_cordio_get_hci_driver(); + +namespace ble { +namespace vendor { +namespace cordio { + +struct CordioHCIHook { + static CordioHCIDriver& get_driver() { + return ble_cordio_get_hci_driver(); + } + + static CordioHCITransportDriver& get_transport_driver() { + return get_driver()._transport_driver; + } + + static void set_data_received_handler(void (*handler)(uint8_t*, uint8_t)) { + get_transport_driver().set_data_received_handler(handler); + } +}; + +} // namespace cordio +} // namespace vendor +} // namespace ble + +using ble::vendor::cordio::CordioHCIHook; + +// +// Handle signal mechanism +// +#define RESET_COMMAND_TIMEOUT (10 * 1000) + +static const uint32_t RESET_RECEIVED_FLAG = 1 << 0; +static const uint32_t RECEPTION_ERROR_FLAG = 1 << 1; +static const uint32_t RESET_STATUS_ERROR_FLAG = 1 << 2; + +static const uint32_t WAITING_FLAGS = + RESET_RECEIVED_FLAG | RECEPTION_ERROR_FLAG | RESET_STATUS_ERROR_FLAG; + +static rtos::EventFlags event_channel; + +static void signal_flag(uint32_t flag) { + if (!(event_channel.get() & flag)) { + event_channel.set(flag); + } +} + +uint32_t wait_for_event() { + // clear reception flags + uint32_t flags = event_channel.get(); + event_channel.clear(flags & ~RESET_RECEIVED_FLAG); + + return event_channel.wait_any( + WAITING_FLAGS, + /* timeout */ RESET_COMMAND_TIMEOUT, + /* clear */ false + ); +} + +// +// Handle reset command reception +// + +#define RESET_PARAMETER_LENGTH 4 +#define RESET_EXPECTED_STATUS 0 +#define HCI_OPCODE_RESET_LSB (HCI_OPCODE_RESET & 0xFF) +#define HCI_OPCODE_RESET_MSB (HCI_OPCODE_RESET >> 8) +#define RESET_PACKET_LENGTH (1 + HCI_EVT_HDR_LEN + RESET_PARAMETER_LENGTH) +#define RESET_STATUS_INDEX 6 + +static bool is_reset_event(const uint8_t* data, uint16_t len) { + if (len != RESET_PACKET_LENGTH) { + return false; + } + + if (*data++ != HCI_EVT_TYPE) { + return false; + } + + if (*data++ != HCI_CMD_CMPL_EVT) { + return false; + } + + if (*data++ != RESET_PARAMETER_LENGTH) { + return false; + } + + // Note skip num of HCI packet as this is controller dependent + data++; + + if (*data++ != HCI_OPCODE_RESET_LSB) { + return false; + } + + if (*data++ != HCI_OPCODE_RESET_MSB) { + return false; + } + + return true; +} + +static void hci_driver_rx_reset_handler(uint8_t* data, uint8_t len) { + enum packet_state_t { + WAITING_FOR_PACKET_TYPE, + WAITING_FOR_HEADER_COMPLETE, + WAITING_FOR_DATA_COMPLETE, + SYNCHRONIZATION_ERROR, + STATUS_ERROR + }; + + static uint8_t packet[256] = { 0 }; + static uint16_t position = 0; + static uint16_t packet_length; + static packet_state_t reception_state = WAITING_FOR_PACKET_TYPE; + + while (len) { + switch (reception_state) { + case WAITING_FOR_PACKET_TYPE: + if (*data != HCI_EVT_TYPE) { + reception_state = SYNCHRONIZATION_ERROR; + signal_flag(RECEPTION_ERROR_FLAG); + return; + } + + packet[position++] = *data++; + --len; + packet_length = 1 + HCI_EVT_HDR_LEN; + reception_state = WAITING_FOR_HEADER_COMPLETE; + break; + + case WAITING_FOR_HEADER_COMPLETE: + case WAITING_FOR_DATA_COMPLETE: { + uint16_t step = std::min((uint16_t) len, (uint16_t) (packet_length - position)); + memcpy(packet + position, data, step); + position+= step; + data += step; + len -= step; + + if (reception_state == WAITING_FOR_HEADER_COMPLETE && + position == packet_length + ) { + reception_state = WAITING_FOR_DATA_COMPLETE; + packet_length += packet[HCI_EVT_HDR_LEN]; + } + } break; + + + // dead end; we never exit from the error state; just asignal it again. + case SYNCHRONIZATION_ERROR: + signal_flag(RECEPTION_ERROR_FLAG); + return; + + case STATUS_ERROR: + signal_flag(RESET_STATUS_ERROR_FLAG); + return; + } + + bool packet_complete = (reception_state == WAITING_FOR_DATA_COMPLETE) && + (position == packet_length); + + if (packet_complete) { + if (is_reset_event(packet, packet_length)) { + if (packet[RESET_STATUS_INDEX] != RESET_EXPECTED_STATUS) { + reception_state = STATUS_ERROR; + signal_flag(RESET_STATUS_ERROR_FLAG); + return; + } else { + signal_flag(RESET_RECEIVED_FLAG); + } + } + + reception_state = WAITING_FOR_PACKET_TYPE; + position = 0; + packet_length = 1; + } + } +} + +static uint8_t reset_cmd[] = { + HCI_OPCODE_RESET_LSB, HCI_OPCODE_RESET_MSB, // reset opcode + 0 // parameter length +}; + +void test_reset_command() { + CordioHCIDriver& driver = CordioHCIHook::get_driver(); + CordioHCITransportDriver& transport_driver = CordioHCIHook::get_transport_driver(); + + driver.initialize(); + + CordioHCIHook::set_data_received_handler(hci_driver_rx_reset_handler); + + transport_driver.write(HCI_CMD_TYPE, sizeof(reset_cmd), reset_cmd); + uint32_t events = wait_for_event(); + + TEST_ASSERT_EQUAL(RESET_RECEIVED_FLAG, events); + + driver.terminate(); +} + +#define EXPECTED_CONSECUTIVE_RESET 10 + +void test_multiple_reset_command() { + CordioHCIDriver& driver = CordioHCIHook::get_driver(); + CordioHCITransportDriver& transport_driver = CordioHCIHook::get_transport_driver(); + + driver.initialize(); + + CordioHCIHook::set_data_received_handler(hci_driver_rx_reset_handler); + + for (size_t i = 0; i < EXPECTED_CONSECUTIVE_RESET; ++i) { + transport_driver.write(HCI_CMD_TYPE, sizeof(reset_cmd), reset_cmd); + uint32_t events = wait_for_event(); + TEST_ASSERT_EQUAL(RESET_RECEIVED_FLAG, events); + if (events != RESET_RECEIVED_FLAG) { + break; + } + } + + driver.terminate(); +} + +Case cases[] = { + Case("Test reset command", test_reset_command), + Case("Test multiple reset commands", test_multiple_reset_command) +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(15, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); + +int main() { + return !Harness::run(specification); +}