1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/tests/sys/usbus/main.c

353 lines
11 KiB
C

/*
* Copyright (C) 2019 Koen Zandberg <koen@bergzand.net>
*
* 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 tests
* @{
*
* @file
* @brief Tests USB interaction with USBUS
*
* @author Koen Zandberg <koen@bergzand.net>
*
* @}
*/
#include <string.h>
#include <errno.h>
#include "mutex.h"
#include "periph/usbdev.h"
#include "embUnit.h"
#include "usbdev_mock.h"
#include "usb/descriptor.h"
#include "usb/usbus.h"
#define ENABLE_DEBUG 0
#include "debug.h"
/**
* @brief Test case states
*/
typedef enum {
TESTPHASE_START, /**< Initial start */
TESTPHASE_RESET, /**< Signal USB RESET */
TESTPHASE_REQ_DEV_INIT, /**< Request the first part of the device descriptor */
TESTPHASE_RESET2, /**< Second reset */
TESTPHASE_SET_ADDRESS, /**< Configure Address */
TESTPHASE_REQ_DEV_FULL, /**< Request the full device descriptor */
TESTPHASE_FINAL, /**< Testing done */
} testphase_t;
/**
* @brief Test control request phases
*/
typedef enum {
TEST_REQ_PHASE_IDLE,
TEST_REQ_PHASE_START_GET,
TEST_REQ_PHASE_START_SET,
TEST_REQ_PHASE_OUTDATA,
TEST_REQ_PHASE_INDATA,
TEST_REQ_PHASE_OUTACK,
TEST_REQ_PHASE_INACK,
} test_req_phase_t;
testphase_t phase = TESTPHASE_START;
test_req_phase_t req_phase = TEST_REQ_PHASE_IDLE;
static usbus_t usbus;
static char _stack[USBUS_STACKSIZE];
static mutex_t waiter = MUTEX_INIT;
static char *_state2str(test_req_phase_t state)
{
switch (state) {
case TEST_REQ_PHASE_IDLE:
return "idle";
case TEST_REQ_PHASE_START_GET:
return "start get";
case TEST_REQ_PHASE_START_SET:
return "start set";
case TEST_REQ_PHASE_OUTDATA:
return "OUT data";
case TEST_REQ_PHASE_INDATA:
return "IN data";
case TEST_REQ_PHASE_OUTACK:
return "OUT Ack";
case TEST_REQ_PHASE_INACK:
return "IN Ack";
}
return "unknown control request state";
}
static void _build_conf_req(uint8_t *buf, size_t reqlen)
{
usb_setup_t *setup = (usb_setup_t *)buf;
memset(setup, 0, sizeof(usb_setup_t));
setup->type = USB_SETUP_REQUEST_DEVICE2HOST;
setup->request = USB_SETUP_REQ_GET_DESCRIPTOR;
setup->value = USB_TYPE_DESCRIPTOR_DEVICE << 8;
setup->length = reqlen;
}
static void _build_set_addr(uint8_t *buf)
{
usb_setup_t *setup = (usb_setup_t *)buf;
memset(setup, 0, sizeof(usb_setup_t));
setup->request = USB_SETUP_REQ_SET_ADDRESS;
setup->value = 42;
}
static void _start_setup_get(usbdev_mock_t *dev)
{
req_phase = TEST_REQ_PHASE_START_GET;
dev->in[0].available = 0;
/* Trigger the OUT request at the USBUS stack */
dev->usbdev.epcb(&dev->out[0].ep, USBDEV_EVENT_ESR);
}
static void _start_setup_set(usbdev_mock_t *dev)
{
req_phase = TEST_REQ_PHASE_START_SET;
dev->in[0].available = 0;
dev->usbdev.epcb(&dev->out[0].ep, USBDEV_EVENT_ESR);
}
/* Validates the first 8 bytes of the device descriptor */
static void _validate_device_desc_init(usb_descriptor_device_t *desc)
{
TEST_ASSERT_EQUAL_INT(sizeof(usb_descriptor_device_t), desc->length);
TEST_ASSERT_EQUAL_INT(USB_TYPE_DESCRIPTOR_DEVICE, desc->type);
}
static void _test_sequence(usbdev_mock_t *dev)
{
testphase_t next_phase = TESTPHASE_FINAL;
/* Global sequence control */
/* Called as part of the ESR handling */
switch (phase) {
case TESTPHASE_START:
next_phase = TESTPHASE_RESET;
/* Signal reset condition */
DEBUG("[test]: Signalling USB reset condition\n");
dev->usbdev.cb(&dev->usbdev, USBDEV_EVENT_RESET);
dev->usbdev.cb(&dev->usbdev, USBDEV_EVENT_ESR);
break;
case TESTPHASE_RESET:
next_phase = TESTPHASE_REQ_DEV_INIT;
DEBUG("[test]: Requesting device descriptor\n");
_build_conf_req(dev->out[0].buf, 8); /* initial config request */
dev->req_len = 8;
dev->out[0].state = EP_STATE_DATA_AVAILABLE;
dev->out[0].available = 8;
_start_setup_get(dev);
break;
case TESTPHASE_REQ_DEV_INIT:
next_phase = TESTPHASE_RESET2;
DEBUG("[test]: validating device descriptor\n");
TEST_ASSERT_EQUAL_INT(dev->in[0].available, 8);
_validate_device_desc_init(
(usb_descriptor_device_t *)dev->in[0].buf);
/* Reset device */
DEBUG("[test]: Signalling second USB reset condition\n");
dev->usbdev.cb(&dev->usbdev, USBDEV_EVENT_RESET);
dev->usbdev.cb(&dev->usbdev, USBDEV_EVENT_ESR);
break;
case TESTPHASE_RESET2:
next_phase = TESTPHASE_SET_ADDRESS;
DEBUG("[test]: Set USB address\n");
_build_set_addr(dev->out[0].buf);
dev->req_len = 0;
dev->out[0].state = EP_STATE_DATA_AVAILABLE;
dev->out[0].available = 8;
_start_setup_set(dev);
break;
case TESTPHASE_SET_ADDRESS:
next_phase = TESTPHASE_REQ_DEV_FULL;
DEBUG("[test]: Requesting full device descriptor\n");
_build_conf_req(dev->out[0].buf,
sizeof(usb_descriptor_device_t));
dev->req_len = sizeof(usb_descriptor_device_t);
dev->out[0].state = EP_STATE_DATA_AVAILABLE;
dev->out[0].available = sizeof(usb_setup_t);
_start_setup_get(dev);
break;
case TESTPHASE_REQ_DEV_FULL:
TEST_ASSERT_EQUAL_INT(dev->in[0].available,
sizeof(usb_descriptor_device_t));
DEBUG("[test]: Validating full descriptor\n");
_validate_device_desc_init(
(usb_descriptor_device_t *)dev->in[0].buf);
next_phase = TESTPHASE_FINAL;
dev->usbdev.cb(&dev->usbdev, USBDEV_EVENT_ESR);
break;
case TESTPHASE_FINAL:
/* Testing done */
mutex_unlock(&waiter);
break;
default:
DEBUG("[test]: Unhandled situation in test script\n");
}
phase = next_phase;
}
/* Validates the ready call after submitting a setup request */
static void _validate_get_req_start(usbdev_mock_ep_t *ep)
{
DEBUG("[validation]: get request validation\n");
/* We should get an IN request with the data */
TEST_ASSERT_EQUAL_INT(ep->ep.dir, USB_EP_DIR_IN);
req_phase = TEST_REQ_PHASE_INDATA;
}
static void _validate_out_ack(usbdev_mock_t *dev, usbdev_mock_ep_t *ep)
{
DEBUG("[validation]: OUT ACK validation\n");
TEST_ASSERT_EQUAL_INT(ep->ep.dir, USB_EP_DIR_OUT);
dev->usbdev.epcb(&dev->out[0].ep, USBDEV_EVENT_ESR);
req_phase = TEST_REQ_PHASE_IDLE;
}
static void _validate_in_ack(usbdev_mock_t *dev, usbdev_mock_ep_t *ep,
size_t len)
{
DEBUG("[validation]: IN ACK validation\n");
TEST_ASSERT_EQUAL_INT(ep->ep.dir, USB_EP_DIR_IN);
TEST_ASSERT_EQUAL_INT(len, 0);
dev->usbdev.epcb(&dev->in[0].ep, USBDEV_EVENT_ESR);
req_phase = TEST_REQ_PHASE_IDLE;
}
static void _validate_set_req_start(usbdev_mock_t *dev, usbdev_mock_ep_t *ep,
size_t len)
{
DEBUG("[validation]: set request validation\n");
TEST_ASSERT_EQUAL_INT(dev->out[0].available, 8);
/* We should get an IN request with the data */
TEST_ASSERT_EQUAL_INT(ep->ep.dir, USB_EP_DIR_IN);
if (dev->req_len == 0) {
DEBUG("[validation]: No payload, performing IN ACK validation\n");
/* No data phase, this `ready` is the OUT ACK */
_validate_in_ack(dev, ep, len);
}
else {
req_phase = TEST_REQ_PHASE_OUTDATA;
}
}
static void _handle_data(usbdev_mock_t *dev, usbdev_mock_ep_t *ep, size_t len)
{
if (ep->ep.dir == USB_EP_DIR_IN) {
/* Zero length IN packet is not valid here */
DEBUG("[data]: Handling IN data from stack: %u\n", (unsigned)len);
TEST_ASSERT(len != 0);
dev->usbdev.epcb(&dev->in[0].ep, USBDEV_EVENT_ESR);
if (ep->available == dev->req_len) {
DEBUG("[data]: Full data received from stack\n");
req_phase = TEST_REQ_PHASE_OUTACK;
}
else {
DEBUG("[data]: Expecting more data from stack: %u/%u\n",
(unsigned)ep->available,
(unsigned)dev->req_len);
}
}
}
static void _ep_esr_validation(usbdev_mock_t *dev, usbdev_mock_ep_t *ep)
{
DEBUG("[ep esr]: Data available for stack: %u\n", (unsigned)ep->available);
if (req_phase == TEST_REQ_PHASE_IDLE) {
DEBUG("[ep esr]: Done with the request\n");
/* signal USBDEV_EVENT_ESR to call _test_sequence */
dev->usbdev.cb(&dev->usbdev, USBDEV_EVENT_ESR);
}
ep->ep.dev->epcb(&ep->ep, USBDEV_EVENT_TR_COMPLETE);
}
/* Handles Control endpoint validation from the usbdev side. *
* Called as part of the endpoint ready function. */
static void _ep0_validation(usbdev_mock_t *dev, usbdev_mock_ep_t *ep,
size_t len)
{
DEBUG("[validation]: req state: %s\n", _state2str(req_phase));
/* Validate direction and content for the current state */
switch (req_phase) {
case TEST_REQ_PHASE_START_GET:
_validate_get_req_start(ep);
_handle_data(dev, ep, len);
break;
case TEST_REQ_PHASE_START_SET:
_validate_set_req_start(dev, ep, len);
break;
case TEST_REQ_PHASE_OUTACK:
_validate_out_ack(dev, ep);
break;
case TEST_REQ_PHASE_INACK:
_validate_in_ack(dev, ep, len);
break;
default:
/* Nothing is validated at the moment for the cases of:
* - TEST_REQ_PHASE_IDLE
* - TEST_REQ_PHASE_INDATA
* - TEST_REQ_PHASE_OUTDATA
*/
break;
}
}
static void _setup(void)
{
mutex_lock(&waiter);
}
static void _test(void)
{
_setup();
/* Initialize basic usbus struct, don't start the thread yet */
usbdev_t *usbdev = usbdev_get_ctx(0);
usbus_init(&usbus, usbdev);
usbdev_mock_setup(&_test_sequence, &_ep_esr_validation, &_ep0_validation);
usbus_create(_stack, USBUS_STACKSIZE, USBUS_PRIO, USBUS_TNAME, &usbus);
/* Try to lock to block until the test thread has finished */
mutex_lock(&waiter);
}
static Test *tests_usbus(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(_test),
};
EMB_UNIT_TESTCALLER(tests, NULL, NULL, fixtures);
return (Test *)&tests;
}
int main(void)
{
TESTS_START();
TESTS_RUN(tests_usbus());
TESTS_END();
return 0;
}