Skip to content

Cordio: Add tests that validates a cordio port. #7221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>

#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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it actually acceptable for the initialisation process to take this long?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, at first I was thinking of setting it to 30 seconds; if the bitrate is low and the controller require a large service pack then initialization can takes a loooooot of time.


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);
}
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#include <algorithm>

#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);
}