TactilityKernel improvements and more (#459)

This commit is contained in:
Ken Van Hoeylandt 2026-01-25 23:54:33 +01:00 committed by GitHub
parent 96eccbdc8d
commit dfe2c865d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 759 additions and 331 deletions

View File

@ -3,6 +3,7 @@
#include "devices/TrackballDevice.h" #include "devices/TrackballDevice.h"
#include <Tactility/hal/gps/GpsConfiguration.h> #include <Tactility/hal/gps/GpsConfiguration.h>
#include <Tactility/kernel/Kernel.h>
#include <Tactility/kernel/SystemEvents.h> #include <Tactility/kernel/SystemEvents.h>
#include <Tactility/Logger.h> #include <Tactility/Logger.h>
#include <Tactility/LogMessages.h> #include <Tactility/LogMessages.h>
@ -30,6 +31,9 @@ static bool powerOn() {
return false; return false;
} }
// Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw.
tt::kernel::delayMillis(100);
return true; return true;
} }

View File

@ -17,10 +17,10 @@ static int stop(Device* device) {
Driver tlora_pager_driver = { Driver tlora_pager_driver = {
.name = "T-Lora Pager", .name = "T-Lora Pager",
.compatible = (const char*[]) { "lilygo,tlora-pager", nullptr }, .compatible = (const char*[]) { "lilygo,tlora-pager", nullptr },
.start_device = start, .startDevice = start,
.stop_device = stop, .stopDevice = stop,
.api = nullptr, .api = nullptr,
.device_type = nullptr, .deviceType = nullptr,
.internal = { 0 } .internal = { 0 }
}; };

View File

@ -0,0 +1,7 @@
#pragma once
#include <esp_err.h>
#include <Tactility/Error.h>
error_t esp_err_to_error(esp_err_t error);

View File

@ -8,7 +8,7 @@ extern "C" {
#include <stdint.h> #include <stdint.h>
struct Esp32GpioConfig { struct Esp32GpioConfig {
uint8_t gpio_count; uint8_t gpioCount;
}; };
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -9,9 +9,9 @@ extern "C" {
#endif #endif
struct Esp32I2cConfig { struct Esp32I2cConfig {
uint32_t clock_frequency; uint32_t clockFrequency;
struct GpioPinConfig pin_sda; struct GpioPinConfig pinSda;
struct GpioPinConfig pin_scl; struct GpioPinConfig pinScl;
const i2c_port_t port; const i2c_port_t port;
}; };

View File

@ -0,0 +1,16 @@
#include <Tactility/ErrorEsp32.h>
error_t esp_err_to_error(esp_err_t error) {
switch (error) {
case ESP_OK:
return ERROR_NONE;
case ESP_ERR_INVALID_ARG:
return ERROR_INVALID_ARGUMENT;
case ESP_ERR_INVALID_STATE:
return ERROR_INVALID_STATE;
case ESP_ERR_TIMEOUT:
return ERROR_TIMEOUT;
default:
return ERROR_UNDEFINED;
}
}

View File

@ -3,9 +3,11 @@
#include <Tactility/Driver.h> #include <Tactility/Driver.h>
#include <Tactility/drivers/Esp32Gpio.h> #include <Tactility/drivers/Esp32Gpio.h>
#include <Tactility/drivers/GpioController.h>
#include <Tactility/drivers/Gpio.h> #include <Tactility/ErrorEsp32.h>
#include <Tactility/Log.h> #include <Tactility/Log.h>
#include <Tactility/drivers/Gpio.h>
#include <Tactility/drivers/GpioController.h>
#define TAG LOG_TAG(esp32_gpio) #define TAG LOG_TAG(esp32_gpio)
@ -13,20 +15,21 @@
extern "C" { extern "C" {
static bool set_level(Device* device, gpio_pin_t pin, bool high) { static error_t set_level(Device* device, gpio_pin_t pin, bool high) {
return gpio_set_level(static_cast<gpio_num_t>(pin), high) == ESP_OK; auto esp_error = gpio_set_level(static_cast<gpio_num_t>(pin), high);
return esp_err_to_error(esp_error);
} }
static bool get_level(Device* device, gpio_pin_t pin, bool* high) { static error_t get_level(Device* device, gpio_pin_t pin, bool* high) {
*high = gpio_get_level(static_cast<gpio_num_t>(pin)) != 0; *high = gpio_get_level(static_cast<gpio_num_t>(pin)) != 0;
return true; return ERROR_NONE;
} }
static bool set_options(Device* device, gpio_pin_t pin, gpio_flags_t options) { static error_t set_options(Device* device, gpio_pin_t pin, gpio_flags_t options) {
const Esp32GpioConfig* config = GET_CONFIG(device); const Esp32GpioConfig* config = GET_CONFIG(device);
if (pin >= config->gpio_count) { if (pin >= config->gpioCount) {
return false; return ERROR_INVALID_ARGUMENT;
} }
gpio_mode_t mode; gpio_mode_t mode;
@ -37,8 +40,7 @@ static bool set_options(Device* device, gpio_pin_t pin, gpio_flags_t options) {
} else if (options & GPIO_DIRECTION_OUTPUT) { } else if (options & GPIO_DIRECTION_OUTPUT) {
mode = GPIO_MODE_OUTPUT; mode = GPIO_MODE_OUTPUT;
} else { } else {
ESP_LOGE(TAG, "set_options: no direction flag specified for pin %d", pin); return ERROR_INVALID_ARGUMENT;
return false;
} }
const gpio_config_t esp_config = { const gpio_config_t esp_config = {
@ -52,13 +54,14 @@ static bool set_options(Device* device, gpio_pin_t pin, gpio_flags_t options) {
#endif #endif
}; };
return gpio_config(&esp_config) == ESP_OK; auto esp_error = gpio_config(&esp_config);
return esp_err_to_error(esp_error);
} }
static bool get_options(Device* device, gpio_pin_t pin, gpio_flags_t* options) { static int get_options(Device* device, gpio_pin_t pin, gpio_flags_t* options) {
gpio_io_config_t esp_config; gpio_io_config_t esp_config;
if (gpio_get_io_config((gpio_num_t)pin, &esp_config) != ESP_OK) { if (gpio_get_io_config(static_cast<gpio_num_t>(pin), &esp_config) != ESP_OK) {
return false; return ERROR_RESOURCE;
} }
gpio_flags_t output = 0; gpio_flags_t output = 0;
@ -84,17 +87,17 @@ static bool get_options(Device* device, gpio_pin_t pin, gpio_flags_t* options) {
} }
*options = output; *options = output;
return true; return ERROR_NONE;
} }
static int start(Device* device) { static error_t start(Device* device) {
ESP_LOGI(TAG, "start %s", device->name); ESP_LOGI(TAG, "start %s", device->name);
return 0; return ERROR_NONE;
} }
static int stop(Device* device) { static error_t stop(Device* device) {
ESP_LOGI(TAG, "stop %s", device->name); ESP_LOGI(TAG, "stop %s", device->name);
return 0; return ERROR_NONE;
} }
const static GpioControllerApi esp32_gpio_api = { const static GpioControllerApi esp32_gpio_api = {
@ -107,10 +110,10 @@ const static GpioControllerApi esp32_gpio_api = {
Driver esp32_gpio_driver = { Driver esp32_gpio_driver = {
.name = "esp32_gpio", .name = "esp32_gpio",
.compatible = (const char*[]) { "espressif,esp32-gpio", nullptr }, .compatible = (const char*[]) { "espressif,esp32-gpio", nullptr },
.start_device = start, .startDevice = start,
.stop_device = stop, .stopDevice = stop,
.api = (void*)&esp32_gpio_api, .api = (void*)&esp32_gpio_api,
.device_type = nullptr, .deviceType = nullptr,
.internal = { 0 } .internal = { 0 }
}; };

View File

@ -3,8 +3,11 @@
#include <Tactility/Driver.h> #include <Tactility/Driver.h>
#include <Tactility/drivers/Esp32I2c.h> #include <Tactility/drivers/Esp32I2c.h>
#include <Tactility/drivers/I2cController.h>
#include "Tactility/ErrorEsp32.h"
#include <Tactility/Log.h> #include <Tactility/Log.h>
#include <Tactility/drivers/I2cController.h>
#define TAG LOG_TAG(esp32_i2c) #define TAG LOG_TAG(esp32_i2c)
@ -28,41 +31,41 @@ struct InternalData {
extern "C" { extern "C" {
static bool read(Device* device, uint8_t address, uint8_t* data, size_t data_size, TickType_t timeout) { static int read(Device* device, uint8_t address, uint8_t* data, size_t data_size, TickType_t timeout) {
vPortAssertIfInISR(); vPortAssertIfInISR();
auto* driver_data = GET_DATA(device); auto* driver_data = GET_DATA(device);
lock(driver_data); lock(driver_data);
const esp_err_t result = i2c_master_read_from_device(GET_CONFIG(device)->port, address, data, data_size, timeout); const esp_err_t esp_error = i2c_master_read_from_device(GET_CONFIG(device)->port, address, data, data_size, timeout);
unlock(driver_data); unlock(driver_data);
ESP_ERROR_CHECK_WITHOUT_ABORT(result); ESP_ERROR_CHECK_WITHOUT_ABORT(esp_error);
return result == ESP_OK; return esp_err_to_error(esp_error);
} }
static bool write(Device* device, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) { static int write(Device* device, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
vPortAssertIfInISR(); vPortAssertIfInISR();
auto* driver_data = GET_DATA(device); auto* driver_data = GET_DATA(device);
lock(driver_data); lock(driver_data);
const esp_err_t result = i2c_master_write_to_device(GET_CONFIG(device)->port, address, data, dataSize, timeout); const esp_err_t esp_error = i2c_master_write_to_device(GET_CONFIG(device)->port, address, data, dataSize, timeout);
unlock(driver_data); unlock(driver_data);
ESP_ERROR_CHECK_WITHOUT_ABORT(result); ESP_ERROR_CHECK_WITHOUT_ABORT(esp_error);
return result == ESP_OK; return esp_err_to_error(esp_error);
} }
static bool write_read(Device* device, uint8_t address, const uint8_t* write_data, size_t write_data_size, uint8_t* read_data, size_t read_data_size, TickType_t timeout) { static int write_read(Device* device, uint8_t address, const uint8_t* write_data, size_t write_data_size, uint8_t* read_data, size_t read_data_size, TickType_t timeout) {
vPortAssertIfInISR(); vPortAssertIfInISR();
auto* driver_data = GET_DATA(device); auto* driver_data = GET_DATA(device);
lock(driver_data); lock(driver_data);
const esp_err_t result = i2c_master_write_read_device(GET_CONFIG(device)->port, address, write_data, write_data_size, read_data, read_data_size, timeout); const esp_err_t esp_error = i2c_master_write_read_device(GET_CONFIG(device)->port, address, write_data, write_data_size, read_data, read_data_size, timeout);
unlock(driver_data); unlock(driver_data);
ESP_ERROR_CHECK_WITHOUT_ABORT(result); ESP_ERROR_CHECK_WITHOUT_ABORT(esp_error);
return result == ESP_OK; return esp_err_to_error(esp_error);
} }
static int start(Device* device) { static int start(Device* device) {
ESP_LOGI(TAG, "start %s", device->name); ESP_LOGI(TAG, "start %s", device->name);
auto* data = new InternalData(); auto* data = new InternalData();
device_set_driver_data(device, data); device_set_driver_data(device, data);
return 0; return ERROR_NONE;
} }
static int stop(Device* device) { static int stop(Device* device) {
@ -70,10 +73,10 @@ static int stop(Device* device) {
auto* driver_data = static_cast<InternalData*>(device_get_driver_data(device)); auto* driver_data = static_cast<InternalData*>(device_get_driver_data(device));
device_set_driver_data(device, nullptr); device_set_driver_data(device, nullptr);
delete driver_data; delete driver_data;
return 0; return ERROR_NONE;
} }
const I2cControllerApi esp32_i2c_api = { const static I2cControllerApi esp32_i2c_api = {
.read = read, .read = read,
.write = write, .write = write,
.write_read = write_read .write_read = write_read
@ -82,10 +85,10 @@ const I2cControllerApi esp32_i2c_api = {
Driver esp32_i2c_driver = { Driver esp32_i2c_driver = {
.name = "esp32_i2c", .name = "esp32_i2c",
.compatible = (const char*[]) { "espressif,esp32-i2c", nullptr }, .compatible = (const char*[]) { "espressif,esp32-i2c", nullptr },
.start_device = start, .startDevice = start,
.stop_device = stop, .stopDevice = stop,
.api = (void*)&esp32_i2c_api, .api = (void*)&esp32_i2c_api,
.device_type = &I2C_CONTROLLER_TYPE, .deviceType = &I2C_CONTROLLER_TYPE,
.internal = { 0 } .internal = { 0 }
}; };

View File

@ -31,7 +31,7 @@ int32_t GuiService::guiMain() {
while (true) { while (true) {
uint32_t flags = 0; uint32_t flags = 0;
if (service->threadFlags.wait(GUI_THREAD_FLAG_ALL, false, true, portMAX_DELAY, &flags)) { if (service->threadFlags.wait(GUI_THREAD_FLAG_ALL, false, true, &flags, portMAX_DELAY)) {
// When service not started or starting -> exit // When service not started or starting -> exit
State service_state = getState(manifest.id); State service_state = getState(manifest.id);
if (service_state != State::Started && service_state != State::Starting) { if (service_state != State::Started && service_state != State::Starting) {

View File

@ -799,11 +799,11 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) /* Waiting until either the connection is established (WIFI_CONNECTED_BIT)
* or connection failed for the maximum number of re-tries (WIFI_FAIL_BIT). * or connection failed for the maximum number of re-tries (WIFI_FAIL_BIT).
* The bits are set by wifi_event_handler() */ * The bits are set by wifi_event_handler() */
uint32_t bits; uint32_t flags;
if (wifi_singleton->connection_wait_flags.wait(WIFI_FAIL_BIT | WIFI_CONNECTED_BIT, false, true, kernel::MAX_TICKS, &bits)) { if (wifi_singleton->connection_wait_flags.wait(WIFI_FAIL_BIT | WIFI_CONNECTED_BIT, false, true, &flags, kernel::MAX_TICKS)) {
LOGGER.info("Waiting for EventGroup by event_handler()"); LOGGER.info("Waiting for EventGroup by event_handler()");
if (bits & WIFI_CONNECTED_BIT) { if (flags & WIFI_CONNECTED_BIT) {
wifi->setSecureConnection(config.sta.password[0] != 0x00U); wifi->setSecureConnection(config.sta.password[0] != 0x00U);
wifi->setRadioState(RadioState::ConnectionActive); wifi->setRadioState(RadioState::ConnectionActive);
publish_event(wifi, WifiEvent::ConnectionSuccess); publish_event(wifi, WifiEvent::ConnectionSuccess);
@ -815,7 +815,7 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
LOGGER.info("Stored credentials"); LOGGER.info("Stored credentials");
} }
} }
} else if (bits & WIFI_FAIL_BIT) { } else if (flags & WIFI_FAIL_BIT) {
wifi->setRadioState(RadioState::On); wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::ConnectionFailed); publish_event(wifi, WifiEvent::ConnectionFailed);
LOGGER.info("Failed to connect to {}", wifi->connection_target.ssid.c_str()); LOGGER.info("Failed to connect to {}", wifi->connection_target.ssid.c_str());

View File

@ -67,6 +67,7 @@ public:
} }
if (shutdown) { if (shutdown) {
mutex.unlock();
return false; return false;
} }
@ -86,14 +87,14 @@ public:
} }
/** /**
* Consume 1 or more dispatched function (if any) until the queue is empty. * Consume 1 or more dispatched functions (if any) until the queue is empty.
* @warning The timeout is only the wait time before consuming the message! It is not a limit to the total execution time when calling this method. * @warning The timeout is only the wait time before consuming the message! It is not a limit to the total execution time when calling this method.
* @param[in] timeout the ticks to wait for a message * @param[in] timeout the ticks to wait for a message
* @return the amount of messages that were consumed * @return the amount of messages that were consumed
*/ */
uint32_t consume(TickType_t timeout = kernel::MAX_TICKS) { uint32_t consume(TickType_t timeout = kernel::MAX_TICKS) {
// Wait for signal // Wait for signal
if (!eventFlag.wait(WAIT_FLAG, false, true, timeout)) { if (!eventFlag.wait(WAIT_FLAG, false, true, nullptr, timeout)) {
return 0; return 0;
} }

View File

@ -33,42 +33,27 @@ public:
} }
enum class Error { enum class Error {
Unknown,
Timeout, Timeout,
Resource, Resource
Parameter,
IsrStatus
}; };
/** /**
* Set the flags. * Set the flags.
* @param[in] flags the flags to set * @param[in] flags the flags to set
* @param[out] outFlags optional resulting flags: this is set when the return value is true
* @param[out] outError optional error output: this is set when the return value is false
* @return true on success * @return true on success
*/ */
bool set(uint32_t flags, uint32_t* outFlags = nullptr, Error* outError = nullptr) const { bool set(uint32_t flags) const {
assert(handle); assert(handle);
if (xPortInIsrContext() == pdTRUE) { if (xPortInIsrContext() == pdTRUE) {
uint32_t result;
BaseType_t yield = pdFALSE; BaseType_t yield = pdFALSE;
if (xEventGroupSetBitsFromISR(handle.get(), flags, &yield) == pdFAIL) { if (xEventGroupSetBitsFromISR(handle.get(), flags, &yield) == pdFAIL) {
if (outError != nullptr) {
*outError = Error::Resource;
}
return false; return false;
} else { } else {
if (outFlags != nullptr) {
*outFlags = flags;
}
portYIELD_FROM_ISR(yield); portYIELD_FROM_ISR(yield);
return true; return true;
} }
} else { } else {
auto result = xEventGroupSetBits(handle.get(), flags); xEventGroupSetBits(handle.get(), flags);
if (outFlags != nullptr) {
*outFlags = result;
}
return true; return true;
} }
} }
@ -76,32 +61,20 @@ public:
/** /**
* Clear flags * Clear flags
* @param[in] flags the flags to clear * @param[in] flags the flags to clear
* @param[out] outFlags optional resulting flags: this is set when the return value is true
* @param[out] outError optional error output: this is set when the return value is false
* @return true on success * @return true on success
*/ */
bool clear(uint32_t flags, uint32_t* outFlags = nullptr, Error* outError = nullptr) const { bool clear(uint32_t flags) const {
if (xPortInIsrContext() == pdTRUE) { if (xPortInIsrContext() == pdTRUE) {
uint32_t result = xEventGroupGetBitsFromISR(handle.get()); uint32_t result = xEventGroupGetBitsFromISR(handle.get());
if (xEventGroupClearBitsFromISR(handle.get(), flags) == pdFAIL) { if (xEventGroupClearBitsFromISR(handle.get(), flags) == pdFAIL) {
if (outError != nullptr) {
*outError = Error::Resource;
}
return false; return false;
} }
if (outFlags != nullptr) {
*outFlags = result;
}
portYIELD_FROM_ISR(pdTRUE); portYIELD_FROM_ISR(pdTRUE);
return true;
} else { } else {
auto result = xEventGroupClearBits(handle.get(), flags); xEventGroupClearBits(handle.get(), flags);
if (outFlags != nullptr) {
*outFlags = result;
} }
return true; return true;
} }
}
/** /**
* @return the current flags * @return the current flags
@ -120,16 +93,14 @@ public:
* @param[in] awaitAll If true, await for all bits to be set. Otherwise, await for any. * @param[in] awaitAll If true, await for all bits to be set. Otherwise, await for any.
* @param[in] clearOnExit If true, clears all the bits on exit, otherwise don't clear. * @param[in] clearOnExit If true, clears all the bits on exit, otherwise don't clear.
* @param[in] timeout the maximum amount of ticks to wait for flags to be set * @param[in] timeout the maximum amount of ticks to wait for flags to be set
* @param[out] outFlags optional resulting flags: this is set when the return value is true * @param[out] outFlags optional resulting flags: this is set when the return value is true. Only set when return value is true.
* @param[out] outError optional error output: this is set when the return value is false
*/ */
bool wait( bool wait(
uint32_t flags, uint32_t flags,
bool awaitAll = false, bool awaitAll = false,
bool clearOnExit = true, bool clearOnExit = true,
TickType_t timeout = kernel::MAX_TICKS,
uint32_t* outFlags = nullptr, uint32_t* outFlags = nullptr,
Error* outError = nullptr TickType_t timeout = kernel::MAX_TICKS
) const { ) const {
assert(xPortInIsrContext() == pdFALSE); assert(xPortInIsrContext() == pdFALSE);
@ -144,14 +115,8 @@ public:
auto invalid_flags = awaitAll auto invalid_flags = awaitAll
? ((flags & result_flags) != flags) // await all ? ((flags & result_flags) != flags) // await all
: ((flags & result_flags) == 0U); // await any : ((flags & result_flags) == 0U); // await any
if (invalid_flags) { if (invalid_flags) {
if (outError != nullptr) {
if (timeout > 0U) { // assume time-out
*outError = Error::Timeout;
} else {
*outError = Error::Resource;
}
}
return false; return false;
} }

View File

@ -8,12 +8,13 @@
extern "C" { extern "C" {
#endif #endif
#include <Tactility/concurrent/Mutex.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "Error.h"
#include <Tactility/concurrent/Mutex.h>
struct Driver; struct Driver;
/** Enables discovering devices of the same type */ /** Enables discovering devices of the same type */
@ -52,24 +53,26 @@ struct Device {
/** /**
* Initialize the properties of a device. * Initialize the properties of a device.
* *
* @param[in] dev a device with all non-internal properties set * @param[in] device a device with all non-internal properties set
* @return the result code (0 for success) * @retval ERROR_OUT_OF_MEMORY
* @retval ERROR_NONE
*/ */
int device_construct(struct Device* device); error_t device_construct(struct Device* device);
/** /**
* Deinitialize the properties of a device. * Deinitialize the properties of a device.
* This fails when a device is busy or has children. * This fails when a device is busy or has children.
* *
* @param[in] dev * @param[in] device
* @return the result code (0 for success) * @retval ERROR_INVALID_STATE
* @retval ERROR_NONE
*/ */
int device_destruct(struct Device* device); error_t device_destruct(struct Device* device);
/** /**
* Indicates whether the device is in a state where its API is available * Indicates whether the device is in a state where its API is available
* *
* @param[in] dev non-null device pointer * @param[in] device non-null device pointer
* @return true if the device is ready for use * @return true if the device is ready for use
*/ */
static inline bool device_is_ready(const struct Device* device) { static inline bool device_is_ready(const struct Device* device) {
@ -83,9 +86,10 @@ static inline bool device_is_ready(const struct Device* device) {
* - a bus (if any) * - a bus (if any)
* *
* @param[in] device non-null device pointer * @param[in] device non-null device pointer
* @return 0 on success * @retval ERROR_INVALID_STATE
* @retval ERROR_NONE
*/ */
int device_add(struct Device* device); error_t device_add(struct Device* device);
/** /**
* Deregister a device. Remove it from all relevant systems: * Deregister a device. Remove it from all relevant systems:
@ -94,26 +98,32 @@ int device_add(struct Device* device);
* - a bus (if any) * - a bus (if any)
* *
* @param[in] device non-null device pointer * @param[in] device non-null device pointer
* @return 0 on success * @retval ERROR_INVALID_STATE
* @retval ERROR_NOT_FOUND
* @retval ERROR_NONE
*/ */
int device_remove(struct Device* device); error_t device_remove(struct Device* device);
/** /**
* Attach the driver. * Attach the driver.
* *
* @warning must call device_construct() and device_add() first * @warning must call device_construct() and device_add() first
* @param device * @param device
* @return ERROR_INVALID_STATE or otherwise the value of the driver binding result (0 on success) * @retval ERROR_INVALID_STATE
* @retval ERROR_RESOURCE when driver binding fails
* @retval ERROR_NONE
*/ */
int device_start(struct Device* device); error_t device_start(struct Device* device);
/** /**
* Detach the driver. * Detach the driver.
* *
* @param device * @param device
* @return ERROR_INVALID_STATE or otherwise the value of the driver unbinding result (0 on success) * @retval ERROR_INVALID_STATE
* @retval ERROR_RESOURCE when driver unbinding fails
* @retval ERROR_NONE
*/ */
int device_stop(struct Device* device); error_t device_stop(struct Device* device);
/** /**
* Set or unset a parent. * Set or unset a parent.
@ -147,7 +157,7 @@ static inline void device_lock(struct Device* device) {
mutex_lock(&device->internal.mutex); mutex_lock(&device->internal.mutex);
} }
static inline int device_try_lock(struct Device* device) { static inline bool device_try_lock(struct Device* device) {
return mutex_try_lock(&device->internal.mutex); return mutex_try_lock(&device->internal.mutex);
} }
@ -156,7 +166,7 @@ static inline void device_unlock(struct Device* device) {
} }
static inline const struct DeviceType* device_get_type(struct Device* device) { static inline const struct DeviceType* device_get_type(struct Device* device) {
return device->internal.driver ? device->internal.driver->device_type : NULL; return device->internal.driver ? device->internal.driver->deviceType : NULL;
} }
/** /**
* Iterate through all the known devices * Iterate through all the known devices
@ -167,18 +177,18 @@ void for_each_device(void* callback_context, bool(*on_device)(struct Device* dev
/** /**
* Iterate through all the child devices of the specified device * Iterate through all the child devices of the specified device
* @param callback_context the parameter to pass to the callback. NULL is valid. * @param callbackContext the parameter to pass to the callback. NULL is valid.
* @param on_device the function to call for each filtered device. return true to continue iterating or false to stop. * @param on_device the function to call for each filtered device. return true to continue iterating or false to stop.
*/ */
void for_each_device_child(struct Device* device, void* callback_context, bool(*on_device)(struct Device* device, void* context)); void for_each_device_child(struct Device* device, void* callbackContext, bool(*on_device)(struct Device* device, void* context));
/** /**
* Iterate through all the known devices of a specific type * Iterate through all the known devices of a specific type
* @param type the type to filter * @param type the type to filter
* @param callback_context the parameter to pass to the callback. NULL is valid. * @param callbackContext the parameter to pass to the callback. NULL is valid.
* @param on_device the function to call for each filtered device. return true to continue iterating or false to stop. * @param on_device the function to call for each filtered device. return true to continue iterating or false to stop.
*/ */
void for_each_device_of_type(const struct DeviceType* type, void* callback_context, bool(*on_device)(struct Device* device, void* context)); void for_each_device_of_type(const struct DeviceType* type, void* callbackContext, bool(*on_device)(struct Device* device, void* context));
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -7,6 +7,7 @@ extern "C" {
#endif #endif
#include <stdbool.h> #include <stdbool.h>
#include "Error.h"
struct Device; struct Device;
struct DeviceType; struct DeviceType;
@ -17,13 +18,13 @@ struct Driver {
/** Array of const char*, terminated by NULL */ /** Array of const char*, terminated by NULL */
const char**compatible; const char**compatible;
/** Function to initialize the driver for a device */ /** Function to initialize the driver for a device */
int (*start_device)(struct Device* dev); error_t (*startDevice)(struct Device* dev);
/** Function to deinitialize the driver for a device */ /** Function to deinitialize the driver for a device */
int (*stop_device)(struct Device* dev); error_t (*stopDevice)(struct Device* dev);
/** Contains the driver's functions */ /** Contains the driver's functions */
const void* api; const void* api;
/** Which type of devices this driver creates (can be NULL) */ /** Which type of devices this driver creates (can be NULL) */
const struct DeviceType* device_type; const struct DeviceType* deviceType;
/** Internal data */ /** Internal data */
struct { struct {
/** Contains private data */ /** Contains private data */
@ -31,20 +32,20 @@ struct Driver {
} internal; } internal;
}; };
int driver_construct(struct Driver* driver); error_t driver_construct(struct Driver* driver);
int driver_destruct(struct Driver* driver); error_t driver_destruct(struct Driver* driver);
int driver_bind(struct Driver* driver, struct Device* device); error_t driver_bind(struct Driver* driver, struct Device* device);
int driver_unbind(struct Driver* driver, struct Device* device); error_t driver_unbind(struct Driver* driver, struct Device* device);
bool driver_is_compatible(struct Driver* driver, const char* compatible); bool driver_is_compatible(struct Driver* driver, const char* compatible);
struct Driver* driver_find_compatible(const char* compatible); struct Driver* driver_find_compatible(const char* compatible);
static inline const struct DeviceType* driver_get_device_type(struct Driver* driver) { static inline const struct DeviceType* driver_get_device_type(struct Driver* driver) {
return driver->device_type; return driver->deviceType;
} }
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -2,9 +2,18 @@
#pragma once #pragma once
// Avoid potential clash with bits/types/error_t.h
#ifndef __error_t_defined
typedef int error_t;
#endif
#define ERROR_NONE 0
#define ERROR_UNDEFINED 1 #define ERROR_UNDEFINED 1
#define ERROR_INVALID_STATE 2 #define ERROR_INVALID_STATE 2
#define ERROR_INVALID_ARGUMENT 3 #define ERROR_INVALID_ARGUMENT 3
#define ERROR_MISSING_PARAMETER 4 #define ERROR_MISSING_PARAMETER 4
#define ERROR_NOT_FOUND 5 #define ERROR_NOT_FOUND 5
#define ERROR_ISR_STATUS 6
#define ERROR_RESOURCE 7 // A problem with a resource/dependency
#define ERROR_TIMEOUT 8
#define ERROR_OUT_OF_MEMORY 9

View File

@ -0,0 +1,78 @@
// SPDX-License-Identifier: Apache-2.0
/**
* Dispatcher is a thread-safe code execution queue.
*/
#pragma once
#include <Tactility/Error.h>
#include <Tactility/FreeRTOS/FreeRTOS.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*DispatcherCallback)(void* context);
typedef void* DispatcherHandle_t;
DispatcherHandle_t dispatcher_alloc(void);
void dispatcher_free(DispatcherHandle_t dispatcher);
/**
* Queue a function to be consumed elsewhere.
*
* @param[in] callbackContext the data to pass to the function upon execution
* @param[in] callback the function to execute elsewhere
* @param[in] timeout lock acquisition timeout
* @retval ERROR_TIMEOUT
* @retval ERROR_RESOURCE when failing to set event
* @retval ERROR_INVALID_STATE when the dispatcher is in the process of shutting down
* @retval ERROR_NONE
*/
error_t dispatcher_dispatch_timed(DispatcherHandle_t dispatcher, void* callbackContext, DispatcherCallback callback, TickType_t timeout);
/**
* Queue a function to be consumed elsewhere.
*
* @param[in] callbackContext the data to pass to the function upon execution
* @param[in] callback the function to execute elsewhere
* @retval ERROR_RESOURCE when failing to set event
* @retval ERROR_TIMEOUT unlikely to occur unless there's an issue with the internal mutex
* @retval ERROR_INVALID_STATE when the dispatcher is in the process of shutting down
* @retval ERROR_NONE
*/
static inline error_t dispatcher_dispatch(DispatcherHandle_t dispatcher, void* callbackContext, DispatcherCallback callback) {
return dispatcher_dispatch_timed(dispatcher, callbackContext, callback, portMAX_DELAY);
}
/**
* Consume 1 or more dispatched functions (if any) until the queue is empty.
*
* @warning The timeout is only the wait time before consuming the message! It is not a limit to the total execution time when calling this method.
*
* @param[in] timeout the ticks to wait for a message
* @retval ERROR_TIMEOUT
* @retval ERROR_RESOURCE failed to wait for event
* @retval ERROR_INVALID_STATE when the dispatcher is in the process of shutting down
* @retval ERROR_NONE
*/
error_t dispatcher_consume_timed(DispatcherHandle_t dispatcher, TickType_t timeout);
/**
* Consume 1 or more dispatched functions (if any) until the queue is empty.
*
* @warning The timeout is only the wait time before consuming the message! It is not a limit to the total execution time when calling this method.
*
* @retval ERROR_TIMEOUT unlikely to occur unless there's an issue with the internal mutex
* @retval ERROR_RESOURCE failed to wait for event
* @retval ERROR_INVALID_STATE when the dispatcher is in the process of shutting down
* @retval ERROR_NONE
*/
static inline error_t dispatcher_consume(DispatcherHandle_t dispatcher) {
return dispatcher_consume_timed(dispatcher, portMAX_DELAY);
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,78 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <Tactility/Error.h>
#include <Tactility/FreeRTOS/event_groups.h>
static inline void event_group_construct(EventGroupHandle_t* eventGroup) {
assert(xPortInIsrContext() == pdFALSE);
*eventGroup = xEventGroupCreate();
}
static inline void event_group_destruct(EventGroupHandle_t* eventGroup) {
assert(xPortInIsrContext() == pdFALSE);
assert(*eventGroup != NULL);
vEventGroupDelete(*eventGroup);
*eventGroup = NULL;
}
/**
* Set the flags.
*
* @param[in] eventGroup the event group
* @param[in] flags the flags to set
* @retval ERROR_RESOURCE when setting failed
* @retval ERROR_NONE
*/
error_t event_group_set(EventGroupHandle_t eventGroup, uint32_t flags);
/**
* Clear flags
*
* @param[in] eventGroup the event group
* @param[in] flags the flags to clear
* @retval ERROR_RESOURCE when clearing failed
* @retval ERROR_NONE
*/
error_t event_group_clear(EventGroupHandle_t eventGroup, uint32_t flags);
/**
* @param[in] eventGroup the event group
* @return the bitset (always succeeds)
*/
uint32_t event_group_get(EventGroupHandle_t eventGroup);
/**
* Wait for flags to be set
*
* @param[in] eventGroup the event group
* @param[in] inFlags the flags to await
* @param[in] awaitAll If true, await for all bits to be set. Otherwise, await for any.
* @param[in] clearOnExit If true, clears all the bits on exit, otherwise don't clear.
* @param[out] outFlags If set to non-NULL value, this will hold the resulting flags. Only set when return value is ERROR_NONE
* @param[in] timeout the maximum amount of ticks to wait for flags to be set
* @retval ERROR_ISR_STATUS when the function was called from an ISR context
* @retval ERROR_TIMEOUT
* @retval ERROR_RESOURCE when flags were triggered, but not in a way that was expected (e.g. waiting for all flags, but was only partially set)
* @retval ERROR_NONE
*/
error_t event_group_wait(
EventGroupHandle_t eventGroup,
uint32_t inFlags,
bool awaitAll,
bool clearOnExit,
uint32_t* outFlags,
TickType_t timeout
);
#ifdef __cplusplus
}
#endif

View File

@ -12,6 +12,7 @@ extern "C" {
struct Mutex { struct Mutex {
QueueHandle_t handle; QueueHandle_t handle;
// TODO: Debugging functionality
}; };
inline static void mutex_construct(struct Mutex* mutex) { inline static void mutex_construct(struct Mutex* mutex) {
@ -27,18 +28,30 @@ inline static void mutex_destruct(struct Mutex* mutex) {
} }
inline static void mutex_lock(struct Mutex* mutex) { inline static void mutex_lock(struct Mutex* mutex) {
assert(xPortInIsrContext() != pdTRUE);
xSemaphoreTake(mutex->handle, portMAX_DELAY); xSemaphoreTake(mutex->handle, portMAX_DELAY);
} }
inline static bool mutex_try_lock(struct Mutex* mutex) { inline static bool mutex_try_lock(struct Mutex* mutex) {
assert(xPortInIsrContext() != pdTRUE);
return xSemaphoreTake(mutex->handle, 0) == pdTRUE; return xSemaphoreTake(mutex->handle, 0) == pdTRUE;
} }
inline static bool mutex_try_lock_timed(struct Mutex* mutex, TickType_t timeout) {
assert(xPortInIsrContext() != pdTRUE);
return xSemaphoreTake(mutex->handle, timeout) == pdTRUE;
}
inline static bool mutex_is_locked(struct Mutex* mutex) { inline static bool mutex_is_locked(struct Mutex* mutex) {
if (xPortInIsrContext() == pdTRUE) {
return xSemaphoreGetMutexHolderFromISR(mutex->handle) != NULL;
} else {
return xSemaphoreGetMutexHolder(mutex->handle) != NULL; return xSemaphoreGetMutexHolder(mutex->handle) != NULL;
} }
}
inline static void mutex_unlock(struct Mutex* mutex) { inline static void mutex_unlock(struct Mutex* mutex) {
assert(xPortInIsrContext() != pdTRUE);
xSemaphoreGive(mutex->handle); xSemaphoreGive(mutex->handle);
} }

View File

@ -26,18 +26,30 @@ inline static void recursive_mutex_destruct(struct RecursiveMutex* mutex) {
} }
inline static void recursive_mutex_lock(struct RecursiveMutex* mutex) { inline static void recursive_mutex_lock(struct RecursiveMutex* mutex) {
assert(xPortInIsrContext() != pdTRUE);
xSemaphoreTakeRecursive(mutex->handle, portMAX_DELAY); xSemaphoreTakeRecursive(mutex->handle, portMAX_DELAY);
} }
inline static bool recursive_mutex_is_locked(struct RecursiveMutex* mutex) { inline static bool recursive_mutex_is_locked(struct RecursiveMutex* mutex) {
if (xPortInIsrContext() == pdTRUE) {
return xSemaphoreGetMutexHolderFromISR(mutex->handle) != NULL;
} else {
return xSemaphoreGetMutexHolder(mutex->handle) != NULL; return xSemaphoreGetMutexHolder(mutex->handle) != NULL;
} }
}
inline static bool recursive_mutex_try_lock(struct RecursiveMutex* mutex) { inline static bool recursive_mutex_try_lock(struct RecursiveMutex* mutex) {
assert(xPortInIsrContext() != pdTRUE);
return xSemaphoreTakeRecursive(mutex->handle, 0) == pdTRUE; return xSemaphoreTakeRecursive(mutex->handle, 0) == pdTRUE;
} }
inline static bool recursive_mutex_try_lock_timed(struct RecursiveMutex* mutex, TickType_t timeout) {
assert(xPortInIsrContext() != pdTRUE);
return xSemaphoreTakeRecursive(mutex->handle, timeout) == pdTRUE;
}
inline static void recursive_mutex_unlock(struct RecursiveMutex* mutex) { inline static void recursive_mutex_unlock(struct RecursiveMutex* mutex) {
assert(xPortInIsrContext() != pdTRUE);
xSemaphoreGiveRecursive(mutex->handle); xSemaphoreGiveRecursive(mutex->handle);
} }

View File

@ -6,6 +6,8 @@
extern "C" { extern "C" {
#endif #endif
#include <stdbool.h>
#include <stdint.h>
#include <Tactility/Device.h> #include <Tactility/Device.h>
#define GPIO_OPTIONS_MASK 0x1f #define GPIO_OPTIONS_MASK 0x1f
@ -34,57 +36,30 @@ typedef enum {
GPIO__MAX, GPIO__MAX,
} GpioInterruptType; } GpioInterruptType;
/** /** The index of a GPIO pin on a GPIO Controller */
* @brief Provides a type to hold a GPIO pin index.
*
* This reduced-size type is sufficient to record a pin number,
* e.g. from a devicetree GPIOS property.
*/
typedef uint8_t gpio_pin_t; typedef uint8_t gpio_pin_t;
/** /** Specifies the configuration flags for a GPIO pin (or set of pins) */
* @brief Identifies a set of pins associated with a port.
*
* The pin with index n is present in the set if and only if the bit
* identified by (1U << n) is set.
*/
typedef uint32_t gpio_pinset_t;
/**
* @brief Provides a type to hold GPIO devicetree flags.
*
* All GPIO flags that can be expressed in devicetree fit in the low 16
* bits of the full flags field, so use a reduced-size type to record
* that part of a GPIOS property.
*
* The lower 8 bits are used for standard flags. The upper 8 bits are reserved
* for SoC specific flags.
*/
typedef uint16_t gpio_flags_t; typedef uint16_t gpio_flags_t;
/** /** A configuration for a single GPIO pin */
* @brief Container for GPIO pin information specified in dts files
*
* This type contains a pointer to a GPIO device, pin identifier for a pin
* controlled by that device, and the subset of pin configuration
* flags which may be given in devicetree.
*/
struct GpioPinConfig { struct GpioPinConfig {
/** GPIO device controlling the pin */ /** GPIO device controlling the pin */
const struct Device* port; const struct Device* port;
/** The pin's number on the device */ /** The pin's number on the device */
gpio_pin_t pin; gpio_pin_t pin;
/** The pin's configuration flags as specified in devicetree */ /** The pin's configuration flags as specified in devicetree */
gpio_flags_t dt_flags; gpio_flags_t flags;
}; };
/** /**
* Check if the pin is ready to be used. * Check if the pin is ready to be used.
* @param pin_config the specifications of the pin *
* @param pinConfig the specifications of the pin
* @return true if the pin is ready to be used * @return true if the pin is ready to be used
*/ */
static inline bool gpio_is_ready(const struct GpioPinConfig* pin_config) { static inline bool gpio_is_ready(const struct GpioPinConfig* pinConfig) {
return device_is_ready(pin_config->port); return device_is_ready(pinConfig->port);
} }
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -7,22 +7,22 @@ extern "C" {
#endif #endif
#include "Gpio.h" #include "Gpio.h"
#include <stdbool.h> #include <Tactility/Error.h>
struct GpioControllerApi { struct GpioControllerApi {
bool (*set_level)(struct Device* device, gpio_pin_t pin, bool high); error_t (*set_level)(struct Device* device, gpio_pin_t pin, bool high);
bool (*get_level)(struct Device* device, gpio_pin_t pin, bool* high); error_t (*get_level)(struct Device* device, gpio_pin_t pin, bool* high);
bool (*set_options)(struct Device* device, gpio_pin_t pin, gpio_flags_t options); error_t (*set_options)(struct Device* device, gpio_pin_t pin, gpio_flags_t options);
bool (*get_options)(struct Device* device, gpio_pin_t pin, gpio_flags_t* options); error_t (*get_options)(struct Device* device, gpio_pin_t pin, gpio_flags_t* options);
}; };
bool gpio_controller_set_level(struct Device* device, gpio_pin_t pin, bool high); error_t gpio_controller_set_level(struct Device* device, gpio_pin_t pin, bool high);
bool gpio_controller_get_level(struct Device* device, gpio_pin_t pin, bool* high); error_t gpio_controller_get_level(struct Device* device, gpio_pin_t pin, bool* high);
bool gpio_controller_set_options(struct Device* device, gpio_pin_t pin, gpio_flags_t options); error_t gpio_controller_set_options(struct Device* device, gpio_pin_t pin, gpio_flags_t options);
bool gpio_controller_get_options(struct Device* device, gpio_pin_t pin, gpio_flags_t* options); error_t gpio_controller_get_options(struct Device* device, gpio_pin_t pin, gpio_flags_t* options);
inline bool gpio_set_options_config(struct Device* device, struct GpioPinConfig* config) { static inline error_t gpio_set_options_config(struct Device* device, const struct GpioPinConfig* config) {
return gpio_controller_set_options(device, config->pin, config->dt_flags); return gpio_controller_set_options(device, config->pin, config->flags);
} }
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -6,22 +6,24 @@
extern "C" { extern "C" {
#endif #endif
#include "Gpio.h"
#include <Tactility/FreeRTOS/FreeRTOS.h>
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "Gpio.h"
#include <Tactility/FreeRTOS/FreeRTOS.h>
#include <Tactility/Error.h>
struct I2cControllerApi { struct I2cControllerApi {
bool (*read)(struct Device* device, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout); error_t (*read)(struct Device* device, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout);
bool (*write)(struct Device* device, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout); error_t (*write)(struct Device* device, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout);
bool (*write_read)(struct Device* device, uint8_t address, const uint8_t* write_data, size_t write_data_size, uint8_t* read_data, size_t read_data_size, TickType_t timeout); error_t (*write_read)(struct Device* device, uint8_t address, const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout);
}; };
bool i2c_controller_read(struct Device* device, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout); error_t i2c_controller_read(struct Device* device, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout);
bool i2c_controller_write(struct Device* device, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout); error_t i2c_controller_write(struct Device* device, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout);
bool i2c_controller_write_read(struct Device* device, uint8_t address, const uint8_t* write_data, size_t write_data_size, uint8_t* read_data, size_t read_data_size, TickType_t timeout); error_t i2c_controller_write_read(struct Device* device, uint8_t address, const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout);
extern const struct DeviceType I2C_CONTROLLER_TYPE; extern const struct DeviceType I2C_CONTROLLER_TYPE;

View File

@ -44,17 +44,17 @@ extern "C" {
#define get_device_data(device) static_cast<DeviceData*>(device->internal.data) #define get_device_data(device) static_cast<DeviceData*>(device->internal.data)
int device_construct(Device* device) { error_t device_construct(Device* device) {
device->internal.data = new(std::nothrow) DeviceData; device->internal.data = new(std::nothrow) DeviceData;
if (device->internal.data == nullptr) { if (device->internal.data == nullptr) {
return ENOMEM; return ERROR_OUT_OF_MEMORY;
} }
LOG_I(TAG, "construct %s", device->name); LOG_I(TAG, "construct %s", device->name);
mutex_construct(&device->internal.mutex); mutex_construct(&device->internal.mutex);
return 0; return ERROR_NONE;
} }
int device_destruct(Device* device) { error_t device_destruct(Device* device) {
if (device->internal.state.started || device->internal.state.added) { if (device->internal.state.started || device->internal.state.added) {
return ERROR_INVALID_STATE; return ERROR_INVALID_STATE;
} }
@ -65,7 +65,7 @@ int device_destruct(Device* device) {
mutex_destruct(&device->internal.mutex); mutex_destruct(&device->internal.mutex);
delete get_device_data(device); delete get_device_data(device);
device->internal.data = nullptr; device->internal.data = nullptr;
return 0; return ERROR_NONE;
} }
/** Add a child to the list of children */ /** Add a child to the list of children */
@ -87,7 +87,7 @@ static void device_remove_child(struct Device* device, struct Device* child) {
device_unlock(device); device_unlock(device);
} }
int device_add(Device* device) { error_t device_add(Device* device) {
LOG_I(TAG, "add %s", device->name); LOG_I(TAG, "add %s", device->name);
// Already added // Already added
@ -107,10 +107,10 @@ int device_add(Device* device) {
} }
device->internal.state.added = true; device->internal.state.added = true;
return 0; return ERROR_NONE;
} }
int device_remove(Device* device) { error_t device_remove(Device* device) {
LOG_I(TAG, "remove %s", device->name); LOG_I(TAG, "remove %s", device->name);
if (device->internal.state.started || !device->internal.state.added) { if (device->internal.state.started || !device->internal.state.added) {
@ -133,7 +133,7 @@ int device_remove(Device* device) {
ledger_unlock(); ledger_unlock();
device->internal.state.added = false; device->internal.state.added = false;
return 0; return ERROR_NONE;
failed_ledger_lookup: failed_ledger_lookup:
@ -145,7 +145,7 @@ failed_ledger_lookup:
return ERROR_NOT_FOUND; return ERROR_NOT_FOUND;
} }
int device_start(Device* device) { error_t device_start(Device* device) {
if (!device->internal.state.added) { if (!device->internal.state.added) {
return ERROR_INVALID_STATE; return ERROR_INVALID_STATE;
} }
@ -156,33 +156,31 @@ int device_start(Device* device) {
// Already started // Already started
if (device->internal.state.started) { if (device->internal.state.started) {
return 0; return ERROR_NONE;
} }
int result = driver_bind(device->internal.driver, device); error_t bind_error = driver_bind(device->internal.driver, device);
device->internal.state.started = (result == 0); device->internal.state.started = (bind_error == ERROR_NONE);
device->internal.state.start_result = result; device->internal.state.start_result = bind_error;
return result; return bind_error == ERROR_NONE ? ERROR_NONE : ERROR_RESOURCE;
} }
int device_stop(struct Device* device) { error_t device_stop(struct Device* device) {
if (!device->internal.state.added) { if (!device->internal.state.added) {
return ERROR_INVALID_STATE; return ERROR_INVALID_STATE;
} }
// Not started
if (!device->internal.state.started) { if (!device->internal.state.started) {
return 0; return ERROR_NONE;
} }
int result = driver_unbind(device->internal.driver, device); if (driver_unbind(device->internal.driver, device) != ERROR_NONE) {
if (result != 0) { return ERROR_RESOURCE;
return result;
} }
device->internal.state.started = false; device->internal.state.started = false;
device->internal.state.start_result = 0; device->internal.state.start_result = 0;
return 0; return ERROR_NONE;
} }
void device_set_parent(Device* device, Device* parent) { void device_set_parent(Device* device, Device* parent) {
@ -200,22 +198,22 @@ void for_each_device(void* callback_context, bool(*on_device)(Device* device, vo
ledger_unlock(); ledger_unlock();
} }
void for_each_device_child(Device* device, void* callback_context, bool(*on_device)(struct Device* device, void* context)) { void for_each_device_child(Device* device, void* callbackContext, bool(*on_device)(struct Device* device, void* context)) {
auto* data = get_device_data(device); auto* data = get_device_data(device);
for (auto* child_device : data->children) { for (auto* child_device : data->children) {
if (!on_device(child_device, callback_context)) { if (!on_device(child_device, callbackContext)) {
break; break;
} }
} }
} }
void for_each_device_of_type(const DeviceType* type, void* callback_context, bool(*on_device)(Device* device, void* context)) { void for_each_device_of_type(const DeviceType* type, void* callbackContext, bool(*on_device)(Device* device, void* context)) {
ledger_lock(); ledger_lock();
for (auto* device : ledger.devices) { for (auto* device : ledger.devices) {
auto* driver = device->internal.driver; auto* driver = device->internal.driver;
if (driver != nullptr) { if (driver != nullptr) {
if (driver->device_type == type) { if (driver->deviceType == type) {
if (!on_device(device, callback_context)) { if (!on_device(device, callbackContext)) {
break; break;
} }
} }

View File

@ -15,6 +15,7 @@
struct DriverInternalData { struct DriverInternalData {
Mutex mutex { 0 }; Mutex mutex { 0 };
int use_count = 0; int use_count = 0;
bool destroying = false;
DriverInternalData() { DriverInternalData() {
mutex_construct(&mutex); mutex_construct(&mutex);
@ -64,7 +65,7 @@ static void driver_add(Driver* driver) {
ledger.unlock(); ledger.unlock();
} }
static bool driver_remove(Driver* driver) { static error_t driver_remove(Driver* driver) {
LOG_I(TAG, "remove %s", driver->name); LOG_I(TAG, "remove %s", driver->name);
ledger.lock(); ledger.lock();
@ -72,35 +73,41 @@ static bool driver_remove(Driver* driver) {
// check that there actually is a 3 in our vector // check that there actually is a 3 in our vector
if (iterator == ledger.drivers.end()) { if (iterator == ledger.drivers.end()) {
ledger.unlock(); ledger.unlock();
return false; return ERROR_NOT_FOUND;
} }
ledger.drivers.erase(iterator); ledger.drivers.erase(iterator);
ledger.unlock(); ledger.unlock();
return true; return ERROR_NONE;
} }
extern "C" { extern "C" {
int driver_construct(Driver* driver) { error_t driver_construct(Driver* driver) {
driver->internal.data = new(std::nothrow) DriverInternalData; driver->internal.data = new(std::nothrow) DriverInternalData;
if (driver->internal.data == nullptr) { if (driver->internal.data == nullptr) {
return ENOMEM; return ERROR_OUT_OF_MEMORY;
} }
driver_add(driver); driver_add(driver);
return 0; return ERROR_NONE;
} }
int driver_destruct(Driver* driver) { error_t driver_destruct(Driver* driver) {
// Check if in use driver_lock(driver);
if (driver_internal_data(driver)->use_count != 0) { if (driver_internal_data(driver)->use_count != 0 || driver_internal_data(driver)->destroying) {
driver_unlock(driver);
return ERROR_INVALID_STATE; return ERROR_INVALID_STATE;
} }
driver_internal_data(driver)->destroying = true;
driver_unlock(driver);
if (driver_remove(driver) != ERROR_NONE) {
LOG_W(TAG, "Failed to remove driver from ledger: %s", driver->name);
}
driver_remove(driver);
delete driver_internal_data(driver); delete driver_internal_data(driver);
driver->internal.data = nullptr; driver->internal.data = nullptr;
return 0; return ERROR_NONE;
} }
bool driver_is_compatible(Driver* driver, const char* compatible) { bool driver_is_compatible(Driver* driver, const char* compatible) {
@ -130,18 +137,18 @@ Driver* driver_find_compatible(const char* compatible) {
return result; return result;
} }
int driver_bind(Driver* driver, Device* device) { error_t driver_bind(Driver* driver, Device* device) {
driver_lock(driver); driver_lock(driver);
int err = 0; error_t error = ERROR_NONE;
if (!device_is_added(device)) { if (driver_internal_data(driver)->destroying || !device_is_added(device)) {
err = ERROR_INVALID_STATE; error = ERROR_INVALID_STATE;
goto error; goto error;
} }
if (driver->start_device != nullptr) { if (driver->startDevice != nullptr) {
err = driver->start_device(device); error = driver->startDevice(device);
if (err != 0) { if (error != ERROR_NONE) {
goto error; goto error;
} }
} }
@ -150,26 +157,26 @@ int driver_bind(Driver* driver, Device* device) {
driver_unlock(driver); driver_unlock(driver);
LOG_I(TAG, "bound %s to %s", driver->name, device->name); LOG_I(TAG, "bound %s to %s", driver->name, device->name);
return 0; return ERROR_NONE;
error: error:
driver_unlock(driver); driver_unlock(driver);
return err; return error;
} }
int driver_unbind(Driver* driver, Device* device) { error_t driver_unbind(Driver* driver, Device* device) {
driver_lock(driver); driver_lock(driver);
int err = 0; error_t error = ERROR_NONE;
if (!device_is_added(device)) { if (driver_internal_data(driver)->destroying || !device_is_added(device)) {
err = ERROR_INVALID_STATE; error = ERROR_INVALID_STATE;
goto error; goto error;
} }
if (driver->stop_device != nullptr) { if (driver->stopDevice != nullptr) {
err = driver->stop_device(device); error = driver->stopDevice(device);
if (err != 0) { if (error != ERROR_NONE) {
goto error; goto error;
} }
} }
@ -179,12 +186,12 @@ int driver_unbind(Driver* driver, Device* device) {
LOG_I(TAG, "unbound %s to %s", driver->name, device->name); LOG_I(TAG, "unbound %s to %s", driver->name, device->name);
return 0; return ERROR_NONE;
error: error:
driver_unlock(driver); driver_unlock(driver);
return err; return error;
} }
} // extern "C" } // extern "C"

View File

@ -0,0 +1,142 @@
// SPDX-License-Identifier: Apache-2.0
#include <queue>
#include <Tactility/concurrent/Dispatcher.h>
#include "Tactility/Error.h"
#include <Tactility/Log.h>
#include <Tactility/concurrent/EventGroup.h>
#include <Tactility/concurrent/Mutex.h>
#include <atomic>
#define TAG LOG_TAG("Dispatcher")
static constexpr EventBits_t BACKPRESSURE_WARNING_COUNT = 100U;
static constexpr EventBits_t WAIT_FLAG = 1U;
struct QueuedItem {
DispatcherCallback callback;
void* context;
};
struct DispatcherData {
Mutex mutex = { 0 };
std::queue<QueuedItem> queue = {};
EventGroupHandle_t eventGroup = nullptr;
std::atomic<bool> shutdown{false}; // TODO: Use EventGroup
DispatcherData() {
event_group_construct(&eventGroup);
mutex_construct(&mutex);
}
~DispatcherData() {
event_group_destruct(&eventGroup);
mutex_destruct(&mutex);
}
};
#define dispatcher_data(handle) static_cast<DispatcherData*>(handle)
extern "C" {
DispatcherHandle_t dispatcher_alloc(void) {
return new DispatcherData();
}
void dispatcher_free(DispatcherHandle_t dispatcher) {
auto* data = dispatcher_data(dispatcher);
data->shutdown.store(true, std::memory_order_release);
mutex_lock(&data->mutex);
mutex_unlock(&data->mutex);
delete data;
}
error_t dispatcher_dispatch_timed(DispatcherHandle_t dispatcher, void* callbackContext, DispatcherCallback callback, TickType_t timeout) {
auto* data = dispatcher_data(dispatcher);
// Mutate
if (!mutex_try_lock_timed(&data->mutex, timeout)) {
#ifdef ESP_PLATFORM
LOG_E(TAG, "Mutex acquisition timeout");
#endif
return ERROR_TIMEOUT;
}
if (data->shutdown.load(std::memory_order_acquire)) {
mutex_unlock(&data->mutex);
return ERROR_INVALID_STATE;
}
data->queue.push({
.callback = callback,
.context = callbackContext
});
if (data->queue.size() == BACKPRESSURE_WARNING_COUNT) {
#ifdef ESP_PLATFORM
LOG_W(TAG, "Backpressure: You're not consuming fast enough (100 queued)");
#endif
}
mutex_unlock(&data->mutex);
if (event_group_set(data->eventGroup, WAIT_FLAG) != ERROR_NONE) {
#ifdef ESP_PLATFORM
LOG_E(TAG, "Failed to set flag");
#endif
return ERROR_RESOURCE;
}
return ERROR_NONE;
}
error_t dispatcher_consume_timed(DispatcherHandle_t dispatcher, TickType_t timeout) {
auto* data = dispatcher_data(dispatcher);
// TODO: keep track of time and consider the timeout input as total timeout
// Wait for signal
error_t error = event_group_wait(data->eventGroup, WAIT_FLAG, false, true, nullptr, timeout);
if (error != ERROR_NONE) {
if (error == ERROR_TIMEOUT) {
return ERROR_TIMEOUT;
} else {
return ERROR_RESOURCE;
}
}
if (data->shutdown.load(std::memory_order_acquire)) {
return ERROR_INVALID_STATE;
}
// Mutate
bool processing = true;
do {
if (mutex_try_lock_timed(&data->mutex, 10)) {
if (!data->queue.empty()) {
// Make a copy, so it's thread-safe when we unlock
auto entry = data->queue.front();
data->queue.pop();
processing = !data->queue.empty();
// Don't keep lock as callback might be slow and we want to allow dispatch in the meanwhile
mutex_unlock(&data->mutex);
entry.callback(entry.context);
} else {
processing = false;
mutex_unlock(&data->mutex);
}
} else {
#ifdef ESP_PLATFORM
LOG_W(TAG, "Mutex acquisition timeout");
#endif
}
} while (processing && !data->shutdown.load(std::memory_order_acquire));
return ERROR_NONE;
}
}

View File

@ -0,0 +1,84 @@
// SPDX-License-Identifier: Apache-2.0
#include <Tactility/concurrent/EventGroup.h>
#include <Tactility/Error.h>
#ifdef __cplusplus
extern "C" {
#endif
error_t event_group_set(EventGroupHandle_t eventGroup, uint32_t inFlags) {
if (xPortInIsrContext() == pdTRUE) {
BaseType_t yield = pdFALSE;
if (xEventGroupSetBitsFromISR(eventGroup, inFlags, &yield) == pdFAIL) {
return ERROR_RESOURCE;
}
portYIELD_FROM_ISR(yield);
} else {
xEventGroupSetBits(eventGroup, inFlags);
}
return ERROR_NONE;
}
error_t event_group_clear(EventGroupHandle_t eventGroup, uint32_t flags) {
if (xPortInIsrContext() == pdTRUE) {
if (xEventGroupClearBitsFromISR(eventGroup, flags) == pdFAIL) {
return ERROR_RESOURCE;
}
portYIELD_FROM_ISR(pdTRUE);
} else {
xEventGroupClearBits(eventGroup, flags);
}
return ERROR_NONE;
}
uint32_t event_group_get(EventGroupHandle_t eventGroup) {
if (xPortInIsrContext() == pdTRUE) {
return xEventGroupGetBitsFromISR(eventGroup);
} else {
return xEventGroupGetBits(eventGroup);
}
}
error_t event_group_wait(
EventGroupHandle_t eventGroup,
uint32_t inFlags,
bool awaitAll,
bool clearOnExit,
uint32_t* outFlags,
TickType_t timeout
) {
if (xPortInIsrContext()) {
return ERROR_ISR_STATUS;
}
uint32_t result_flags = xEventGroupWaitBits(
eventGroup,
inFlags,
clearOnExit ? pdTRUE : pdFALSE,
awaitAll ? pdTRUE : pdFALSE,
timeout
);
auto invalid_flags = awaitAll
? ((inFlags & result_flags) != inFlags) // await all
: ((inFlags & result_flags) == 0U); // await any
if (invalid_flags) {
const uint32_t matched = inFlags & result_flags;
if (matched == 0U) {
return ERROR_TIMEOUT;
}
return ERROR_RESOURCE;
}
if (outFlags != nullptr) {
*outFlags = result_flags;
}
return ERROR_NONE;
}
#ifdef __cplusplus
}
#endif

View File

@ -7,22 +7,22 @@
extern "C" { extern "C" {
bool gpio_controller_set_level(Device* device, gpio_pin_t pin, bool high) { error_t gpio_controller_set_level(Device* device, gpio_pin_t pin, bool high) {
const auto* driver = device_get_driver(device); const auto* driver = device_get_driver(device);
return GPIO_DRIVER_API(driver)->set_level(device, pin, high); return GPIO_DRIVER_API(driver)->set_level(device, pin, high);
} }
bool gpio_controller_get_level(Device* device, gpio_pin_t pin, bool* high) { error_t gpio_controller_get_level(Device* device, gpio_pin_t pin, bool* high) {
const auto* driver = device_get_driver(device); const auto* driver = device_get_driver(device);
return GPIO_DRIVER_API(driver)->get_level(device, pin, high); return GPIO_DRIVER_API(driver)->get_level(device, pin, high);
} }
bool gpio_controller_set_options(Device* device, gpio_pin_t pin, gpio_flags_t options) { error_t gpio_controller_set_options(Device* device, gpio_pin_t pin, gpio_flags_t options) {
const auto* driver = device_get_driver(device); const auto* driver = device_get_driver(device);
return GPIO_DRIVER_API(driver)->set_options(device, pin, options); return GPIO_DRIVER_API(driver)->set_options(device, pin, options);
} }
bool gpio_controller_get_options(Device* device, gpio_pin_t pin, gpio_flags_t* options) { error_t gpio_controller_get_options(Device* device, gpio_pin_t pin, gpio_flags_t* options) {
const auto* driver = device_get_driver(device); const auto* driver = device_get_driver(device);
return GPIO_DRIVER_API(driver)->get_options(device, pin, options); return GPIO_DRIVER_API(driver)->get_options(device, pin, options);
} }

View File

@ -2,24 +2,25 @@
#include <Tactility/drivers/I2cController.h> #include <Tactility/drivers/I2cController.h>
#include <Tactility/Driver.h> #include <Tactility/Driver.h>
#include <Tactility/Error.h>
#define I2C_DRIVER_API(driver) ((struct I2cControllerApi*)driver->api) #define I2C_DRIVER_API(driver) ((struct I2cControllerApi*)driver->api)
extern "C" { extern "C" {
bool i2c_controller_read(Device* device, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout) { error_t i2c_controller_read(Device* device, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout) {
const auto* driver = device_get_driver(device); const auto* driver = device_get_driver(device);
return I2C_DRIVER_API(driver)->read(device, address, data, dataSize, timeout); return I2C_DRIVER_API(driver)->read(device, address, data, dataSize, timeout);
} }
bool i2c_controller_write(Device* device, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) { error_t i2c_controller_write(Device* device, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
const auto* driver = device_get_driver(device); const auto* driver = device_get_driver(device);
return I2C_DRIVER_API(driver)->write(device, address, data, dataSize, timeout); return I2C_DRIVER_API(driver)->write(device, address, data, dataSize, timeout);
} }
bool i2c_controller_write_read(Device* device, uint8_t address, const uint8_t* write_data, size_t write_data_size, uint8_t* read_data, size_t read_data_size, TickType_t timeout) { error_t i2c_controller_write_read(Device* device, uint8_t address, const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout) {
const auto* driver = device_get_driver(device); const auto* driver = device_get_driver(device);
return I2C_DRIVER_API(driver)->write_read(device, address, write_data, write_data_size, read_data, read_data_size, timeout); return I2C_DRIVER_API(driver)->write_read(device, address, writeData, writeDataSize, readData, readDataSize, timeout);
} }
const struct DeviceType I2C_CONTROLLER_TYPE { 0 }; const struct DeviceType I2C_CONTROLLER_TYPE { 0 };

View File

@ -8,10 +8,10 @@ extern "C" {
Driver root_driver = { Driver root_driver = {
.name = "root", .name = "root",
.compatible = (const char*[]) { "root", nullptr }, .compatible = (const char*[]) { "root", nullptr },
.start_device = nullptr, .startDevice = nullptr,
.stop_device = nullptr, .stopDevice = nullptr,
.api = nullptr, .api = nullptr,
.device_type = nullptr, .deviceType = nullptr,
.internal = { 0 } .internal = { 0 }
}; };

View File

@ -8,14 +8,13 @@
TEST_CASE("device_construct and device_destruct should set and unset the correct fields") { TEST_CASE("device_construct and device_destruct should set and unset the correct fields") {
Device device = { 0 }; Device device = { 0 };
int error = device_construct(&device); error_t error = device_construct(&device);
CHECK_EQ(error, 0); CHECK_EQ(error, ERROR_NONE);
CHECK_NE(device.internal.data, nullptr); CHECK_NE(device.internal.data, nullptr);
CHECK_NE(device.internal.mutex.handle, nullptr); CHECK_NE(device.internal.mutex.handle, nullptr);
error = device_destruct(&device); CHECK_EQ(device_destruct(&device), ERROR_NONE);
CHECK_EQ(error, 0);
CHECK_EQ(device.internal.data, nullptr); CHECK_EQ(device.internal.data, nullptr);
CHECK_EQ(device.internal.mutex.handle, nullptr); CHECK_EQ(device.internal.mutex.handle, nullptr);
@ -30,13 +29,13 @@ TEST_CASE("device_construct and device_destruct should set and unset the correct
TEST_CASE("device_add should add the device to the list of all devices") { TEST_CASE("device_add should add the device to the list of all devices") {
Device device = { 0 }; Device device = { 0 };
CHECK_EQ(device_construct(&device), 0); CHECK_EQ(device_construct(&device), ERROR_NONE);
CHECK_EQ(device_add(&device), 0); CHECK_EQ(device_add(&device), ERROR_NONE);
// Gather all devices // Gather all devices
std::vector<Device*> devices; std::vector<Device*> devices;
for_each_device(&devices, [](auto* device, auto* context) { for_each_device(&devices, [](auto* device, auto* context) {
auto* devices_ptr = (std::vector<Device*>*)context; auto* devices_ptr = static_cast<std::vector<Device*>*>(context);
devices_ptr->push_back(device); devices_ptr->push_back(device);
return true; return true;
}); });
@ -44,8 +43,8 @@ TEST_CASE("device_add should add the device to the list of all devices") {
CHECK_EQ(devices.size(), 1); CHECK_EQ(devices.size(), 1);
CHECK_EQ(devices[0], &device); CHECK_EQ(devices[0], &device);
CHECK_EQ(device_remove(&device), 0); CHECK_EQ(device_remove(&device), ERROR_NONE);
CHECK_EQ(device_destruct(&device), 0); CHECK_EQ(device_destruct(&device), ERROR_NONE);
} }
TEST_CASE("device_add should add the device to its parent") { TEST_CASE("device_add should add the device to its parent") {
@ -57,11 +56,11 @@ TEST_CASE("device_add should add the device to its parent") {
.parent = &parent .parent = &parent
}; };
CHECK_EQ(device_construct(&parent), 0); CHECK_EQ(device_construct(&parent), ERROR_NONE);
CHECK_EQ(device_add(&parent), 0); CHECK_EQ(device_add(&parent), ERROR_NONE);
CHECK_EQ(device_construct(&child), 0); CHECK_EQ(device_construct(&child), ERROR_NONE);
CHECK_EQ(device_add(&child), 0); CHECK_EQ(device_add(&child), ERROR_NONE);
// Gather all child devices // Gather all child devices
std::vector<Device*> children; std::vector<Device*> children;
@ -74,30 +73,30 @@ TEST_CASE("device_add should add the device to its parent") {
CHECK_EQ(children.size(), 1); CHECK_EQ(children.size(), 1);
CHECK_EQ(children[0], &child); CHECK_EQ(children[0], &child);
CHECK_EQ(device_remove(&child), 0); CHECK_EQ(device_remove(&child), ERROR_NONE);
CHECK_EQ(device_destruct(&child), 0); CHECK_EQ(device_destruct(&child), ERROR_NONE);
CHECK_EQ(device_remove(&parent), 0); CHECK_EQ(device_remove(&parent), ERROR_NONE);
CHECK_EQ(device_destruct(&parent), 0); CHECK_EQ(device_destruct(&parent), ERROR_NONE);
} }
TEST_CASE("device_add should set the state to 'added'") { TEST_CASE("device_add should set the state to 'added'") {
Device device = { 0 }; Device device = { 0 };
CHECK_EQ(device_construct(&device), 0); CHECK_EQ(device_construct(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.added, false); CHECK_EQ(device.internal.state.added, false);
CHECK_EQ(device_add(&device), 0); CHECK_EQ(device_add(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.added, true); CHECK_EQ(device.internal.state.added, true);
CHECK_EQ(device_remove(&device), 0); CHECK_EQ(device_remove(&device), ERROR_NONE);
CHECK_EQ(device_destruct(&device), 0); CHECK_EQ(device_destruct(&device), ERROR_NONE);
} }
TEST_CASE("device_remove should remove it from the list of all devices") { TEST_CASE("device_remove should remove it from the list of all devices") {
Device device = { 0 }; Device device = { 0 };
CHECK_EQ(device_construct(&device), 0); CHECK_EQ(device_construct(&device), ERROR_NONE);
CHECK_EQ(device_add(&device), 0); CHECK_EQ(device_add(&device), ERROR_NONE);
CHECK_EQ(device_remove(&device), 0); CHECK_EQ(device_remove(&device), ERROR_NONE);
// Gather all devices // Gather all devices
std::vector<Device*> devices; std::vector<Device*> devices;
@ -109,7 +108,7 @@ TEST_CASE("device_remove should remove it from the list of all devices") {
CHECK_EQ(devices.size(), 0); CHECK_EQ(devices.size(), 0);
CHECK_EQ(device_destruct(&device), 0); CHECK_EQ(device_destruct(&device), ERROR_NONE);
} }
TEST_CASE("device_remove should remove the device from its parent") { TEST_CASE("device_remove should remove the device from its parent") {
@ -121,12 +120,12 @@ TEST_CASE("device_remove should remove the device from its parent") {
.parent = &parent .parent = &parent
}; };
CHECK_EQ(device_construct(&parent), 0); CHECK_EQ(device_construct(&parent), ERROR_NONE);
CHECK_EQ(device_add(&parent), 0); CHECK_EQ(device_add(&parent), ERROR_NONE);
CHECK_EQ(device_construct(&child), 0); CHECK_EQ(device_construct(&child), ERROR_NONE);
CHECK_EQ(device_add(&child), 0); CHECK_EQ(device_add(&child), ERROR_NONE);
CHECK_EQ(device_remove(&child), 0); CHECK_EQ(device_remove(&child), ERROR_NONE);
// Gather all child devices // Gather all child devices
std::vector<Device*> children; std::vector<Device*> children;
@ -138,22 +137,22 @@ TEST_CASE("device_remove should remove the device from its parent") {
CHECK_EQ(children.size(), 0); CHECK_EQ(children.size(), 0);
CHECK_EQ(device_destruct(&child), 0); CHECK_EQ(device_destruct(&child), ERROR_NONE);
CHECK_EQ(device_remove(&parent), 0); CHECK_EQ(device_remove(&parent), ERROR_NONE);
CHECK_EQ(device_destruct(&parent), 0); CHECK_EQ(device_destruct(&parent), ERROR_NONE);
} }
TEST_CASE("device_remove should clear the state 'added'") { TEST_CASE("device_remove should clear the state 'added'") {
Device device = { 0 }; Device device = { 0 };
CHECK_EQ(device_construct(&device), 0); CHECK_EQ(device_construct(&device), ERROR_NONE);
CHECK_EQ(device_add(&device), 0); CHECK_EQ(device_add(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.added, true); CHECK_EQ(device.internal.state.added, true);
CHECK_EQ(device_remove(&device), 0); CHECK_EQ(device_remove(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.added, false); CHECK_EQ(device.internal.state.added, false);
CHECK_EQ(device_destruct(&device), 0); CHECK_EQ(device_destruct(&device), ERROR_NONE);
} }
TEST_CASE("device_is_ready should return true only when it is started") { TEST_CASE("device_is_ready should return true only when it is started") {
@ -161,30 +160,30 @@ TEST_CASE("device_is_ready should return true only when it is started") {
Driver driver = { Driver driver = {
.name = "test_driver", .name = "test_driver",
.compatible = compatible, .compatible = compatible,
.start_device = nullptr, .startDevice = nullptr,
.stop_device = nullptr, .stopDevice = nullptr,
.api = nullptr, .api = nullptr,
.device_type = nullptr, .deviceType = nullptr,
.internal = { 0 } .internal = { 0 }
}; };
Device device = { 0 }; Device device = { 0 };
CHECK_EQ(driver_construct(&driver), 0); CHECK_EQ(driver_construct(&driver), ERROR_NONE);
CHECK_EQ(device_construct(&device), 0); CHECK_EQ(device_construct(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.started, false); CHECK_EQ(device.internal.state.started, false);
device_set_driver(&device, &driver); device_set_driver(&device, &driver);
CHECK_EQ(device.internal.state.started, false); CHECK_EQ(device.internal.state.started, false);
CHECK_EQ(device_add(&device), 0); CHECK_EQ(device_add(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.started, false); CHECK_EQ(device.internal.state.started, false);
CHECK_EQ(device_start(&device), 0); CHECK_EQ(device_start(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.started, true); CHECK_EQ(device.internal.state.started, true);
CHECK_EQ(device_stop(&device), 0); CHECK_EQ(device_stop(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.started, false); CHECK_EQ(device.internal.state.started, false);
CHECK_EQ(device_remove(&device), 0); CHECK_EQ(device_remove(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.started, false); CHECK_EQ(device.internal.state.started, false);
CHECK_EQ(driver_destruct(&driver), 0); CHECK_EQ(driver_destruct(&driver), ERROR_NONE);
CHECK_EQ(device_destruct(&device), 0); CHECK_EQ(device_destruct(&device), ERROR_NONE);
} }

View File

@ -0,0 +1,25 @@
#include "doctest.h"
#include <Tactility/FreeRTOS/task.h>
#include <Tactility/concurrent/Dispatcher.h>
TEST_CASE("dispatcher test") {
DispatcherHandle_t dispatcher = dispatcher_alloc();
CHECK_NE(dispatcher, nullptr);
int count = 0;
auto error = dispatcher_dispatch(dispatcher, &count, [](void* context) {
int* count_ptr = static_cast<int*>(context);
(*count_ptr)++;
});
CHECK_EQ(error, ERROR_NONE);
vTaskDelay(1);
CHECK_EQ(count, 0);
CHECK_EQ(dispatcher_consume(dispatcher), ERROR_NONE);
CHECK_EQ(count, 1);
dispatcher_free(dispatcher);
}

View File

@ -26,10 +26,10 @@ static int stop(Device* device) {
static Driver integration_driver = { static Driver integration_driver = {
.name = "integration_test_driver", .name = "integration_test_driver",
.compatible = (const char*[]) { "integration", nullptr }, .compatible = (const char*[]) { "integration", nullptr },
.start_device = start, .startDevice = start,
.stop_device = stop, .stopDevice = stop,
.api = nullptr, .api = nullptr,
.device_type = nullptr, .deviceType = nullptr,
.internal = { 0 } .internal = { 0 }
}; };
@ -47,18 +47,18 @@ TEST_CASE("driver with with start success and stop success should start and stop
.parent = nullptr, .parent = nullptr,
}; };
CHECK_EQ(driver_construct(&integration_driver), 0); CHECK_EQ(driver_construct(&integration_driver), ERROR_NONE);
CHECK_EQ(device_construct(&integration_device), 0); CHECK_EQ(device_construct(&integration_device), ERROR_NONE);
device_add(&integration_device); device_add(&integration_device);
CHECK_EQ(startCalled, 0); CHECK_EQ(startCalled, 0);
CHECK_EQ(driver_bind(&integration_driver, &integration_device), 0); CHECK_EQ(driver_bind(&integration_driver, &integration_device), ERROR_NONE);
CHECK_EQ(startCalled, 1); CHECK_EQ(startCalled, 1);
CHECK_EQ(stopCalled, 0); CHECK_EQ(stopCalled, 0);
CHECK_EQ(driver_unbind(&integration_driver, &integration_device), 0); CHECK_EQ(driver_unbind(&integration_driver, &integration_device), ERROR_NONE);
CHECK_EQ(stopCalled, 1); CHECK_EQ(stopCalled, 1);
CHECK_EQ(device_remove(&integration_device), 0); CHECK_EQ(device_remove(&integration_device), ERROR_NONE);
CHECK_EQ(device_destruct(&integration_device), 0); CHECK_EQ(device_destruct(&integration_device), ERROR_NONE);
CHECK_EQ(driver_destruct(&integration_driver), 0); CHECK_EQ(driver_destruct(&integration_driver), ERROR_NONE);
} }

View File

@ -4,12 +4,9 @@
TEST_CASE("driver_construct and driver_destruct should set and unset the correct fields") { TEST_CASE("driver_construct and driver_destruct should set and unset the correct fields") {
Driver driver = { 0 }; Driver driver = { 0 };
int error = driver_construct(&driver); CHECK_EQ(driver_construct(&driver), ERROR_NONE);
CHECK_EQ(error, 0);
CHECK_NE(driver.internal.data, nullptr); CHECK_NE(driver.internal.data, nullptr);
CHECK_EQ(driver_destruct(&driver), ERROR_NONE);
error = driver_destruct(&driver);
CHECK_EQ(error, 0);
CHECK_EQ(driver.internal.data, nullptr); CHECK_EQ(driver.internal.data, nullptr);
} }
@ -18,10 +15,10 @@ TEST_CASE("driver_is_compatible should return true if a compatible value is foun
Driver driver = { Driver driver = {
.name = "test_driver", .name = "test_driver",
.compatible = compatible, .compatible = compatible,
.start_device = nullptr, .startDevice = nullptr,
.stop_device = nullptr, .stopDevice = nullptr,
.api = nullptr, .api = nullptr,
.device_type = nullptr, .deviceType = nullptr,
.internal = { 0 } .internal = { 0 }
}; };
CHECK_EQ(driver_is_compatible(&driver, "test_compatible"), true); CHECK_EQ(driver_is_compatible(&driver, "test_compatible"), true);
@ -34,24 +31,22 @@ TEST_CASE("driver_find should only find a compatible driver when the driver was
Driver driver = { Driver driver = {
.name = "test_driver", .name = "test_driver",
.compatible = compatible, .compatible = compatible,
.start_device = nullptr, .startDevice = nullptr,
.stop_device = nullptr, .stopDevice = nullptr,
.api = nullptr, .api = nullptr,
.device_type = nullptr, .deviceType = nullptr,
.internal = { 0 } .internal = { 0 }
}; };
Driver* found_driver = driver_find_compatible("test_compatible"); Driver* found_driver = driver_find_compatible("test_compatible");
CHECK_EQ(found_driver, nullptr); CHECK_EQ(found_driver, nullptr);
int error = driver_construct(&driver); CHECK_EQ(driver_construct(&driver), ERROR_NONE);
CHECK_EQ(error, 0);
found_driver = driver_find_compatible("test_compatible"); found_driver = driver_find_compatible("test_compatible");
CHECK_EQ(found_driver, &driver); CHECK_EQ(found_driver, &driver);
error = driver_destruct(&driver); CHECK_EQ(driver_destruct(&driver), ERROR_NONE);
CHECK_EQ(error, 0);
found_driver = driver_find_compatible("test_compatible"); found_driver = driver_find_compatible("test_compatible");
CHECK_EQ(found_driver, nullptr); CHECK_EQ(found_driver, nullptr);

View File

@ -60,7 +60,7 @@ TEST_CASE("mutex_lock in another task should block when a lock is active") {
CHECK_EQ(task_lock_counter, 0); CHECK_EQ(task_lock_counter, 0);
mutex_unlock(&mutex); mutex_unlock(&mutex);
vTaskDelay(1); vTaskDelay(2); // 1 is sufficient most of the time, but not always
CHECK_EQ(task_lock_counter, 1); CHECK_EQ(task_lock_counter, 1);
mutex_destruct(&mutex); mutex_destruct(&mutex);
} }

View File

@ -80,7 +80,7 @@ TEST_CASE("recursive_mutex_lock in another task should block when a lock is acti
CHECK_EQ(task_lock_counter, 0); CHECK_EQ(task_lock_counter, 0);
recursive_mutex_unlock(&mutex); recursive_mutex_unlock(&mutex);
vTaskDelay(1); vTaskDelay(2); // 1 is sufficient most of the time, but not always
CHECK_EQ(task_lock_counter, 1); CHECK_EQ(task_lock_counter, 1);
recursive_mutex_destruct(&mutex); recursive_mutex_destruct(&mutex);
} }