Compare commits

...

3 Commits

Author SHA1 Message Date
Ken Van Hoeylandt
99bbe105eb Fixes 2026-01-21 00:15:26 +01:00
Ken Van Hoeylandt
f50fbfe331 WIP 2026-01-21 00:04:16 +01:00
Ken Van Hoeylandt
cd7ffd3cb1 WIP 2026-01-20 23:33:41 +01:00
58 changed files with 1365 additions and 619 deletions

View File

@ -88,7 +88,9 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding]) ->
def write_config(file, device: Device, bindings: list[Binding], type_name: str):
device_identifier = get_device_identifier_safe(device)
file.write(f"const static struct {type_name}_config {device_identifier}_config_instance" " = {\n")
config_type = f"{type_name}_config_dt"
config_variable_name = f"{device_identifier}_config"
file.write(f"static const {config_type} {config_variable_name}" " = {\n")
config_params = resolve_parameters_from_bindings(device, bindings)
# Indent all params
for index, config_param in enumerate(config_params):
@ -99,49 +101,48 @@ def write_config(file, device: Device, bindings: list[Binding], type_name: str):
file.write(f"{config_params_joined}\n")
file.write("};\n\n")
def write_device(file, device: Device, bindings: list[Binding], verbose: bool):
def write_device_structs(file, device: Device, bindings: list[Binding], verbose: bool):
if verbose:
print(f"Processing device '{device.identifier}'")
print(f"Writing device struct for '{device.identifier}'")
# Assemble some pre-requisites
type_name = get_device_type_name(device, bindings)
compatible_property = find_binding_property(device, "compatible")
if compatible_property is None:
raise Exception(f"Cannot find 'compatible' property for {device.identifier}")
identifier = get_device_identifier_safe(device)
device_binding = find_binding(compatible_property.value, bindings)
config_variable_name = f"{identifier}_config"
# Write config struct
write_config(file, device, bindings, type_name)
# Type & instance names
api_type_name = f"{type_name}_api"
api_instance_name = f"{type_name}_api_instance"
init_function_name = f"{type_name}_init"
deinit_function_name = f"{type_name}_deinit"
config_instance_name = f"{identifier}_config_instance"
# Write device struct
file.write(f"extern const struct {api_type_name} {api_instance_name};\n\n")
file.write("static struct device " f"{identifier}" " = {\n")
file.write("\t.name = \"" f"{identifier}" "\",\n")
file.write(f"\t.config = &{config_instance_name},\n")
file.write(f"\t.api = &{api_instance_name},\n")
file.write("\t.state = { .init_result = 0, .initialized = false },\n")
file.write("\t.data = NULL,\n")
file.write("\t.operations = { ")
file.write(f".init = {init_function_name}, ")
file.write(f".deinit = {deinit_function_name}")
file.write("},\n")
file.write("\t.metadata = {\n")
node_label_count = len(device_binding.includes) + 1
file.write(f"\t\t.compatible_count = {node_label_count},\n")
file.write("\t\t.compatible = (const char*[]) {\n")
file.write(f"\t\t\t\"{device_binding.compatible}\",\n")
for include in device_binding.includes:
include_compatible = include.removesuffix(".yaml")
file.write(f"\t\t\t\"{include_compatible}\",\n")
file.write("\t\t}\n")
file.write("\t}\n")
file.write(f"static struct Device {identifier}" " = {\n")
file.write(f"\t.name = \"{device.identifier}\",\n") # Use original name
file.write(f"\t.config = (void*)&{config_variable_name},\n")
file.write("};\n\n")
# Child devices
for child_device in device.devices:
write_device(file, child_device, bindings, verbose)
write_device_structs(file, child_device, bindings, verbose)
def write_device_init(file, device: Device, parent_device: Device, bindings: list[Binding], verbose: bool):
if verbose:
print(f"Processing device init code for '{device.identifier}'")
# Assemble some pre-requisites
type_name = get_device_type_name(device, bindings)
compatible_property = find_binding_property(device, "compatible")
if compatible_property is None:
raise Exception(f"Cannot find 'compatible' property for {device.identifier}")
# Type & instance names
identifier = get_device_identifier_safe(device)
device_variable = identifier
if parent_device is not None:
parent_identifier = get_device_identifier_safe(parent_device)
parent_value = f"&{parent_identifier}"
else:
parent_value = "NULL"
# Write device struct
file.write(f"\tif (init_builtin_device(&{device_variable}, \"{compatible_property.value}\", {parent_value}) != 0) return -1;\n")
# Write children
for child_device in device.devices:
write_device_init(file, child_device, device, bindings, verbose)
def write_device_list_entry(file, device: Device):
compatible_property = find_binding_property(device, "compatible")
@ -162,38 +163,67 @@ def write_device_list(file, devices: list[Device]):
def generate_devicetree_c(filename: str, items: list[object], bindings: list[Binding], verbose: bool):
with open(filename, "w") as file:
file.write(dedent('''\
// Generated includes
#include <Tactility/Device.h>
#include <Tactility/Driver.h>
#include <Tactility/Log.h>
// DTS includes
'''))
# Write all headers first
for item in items:
if type(item) is IncludeC:
write_include(file, item, verbose)
file.write("\n")
file.write(dedent('''\
#define TAG LOG_TAG(devicetree)
static int init_builtin_device(struct Device* device, const char* compatible, struct Device* parent_device) {
struct Driver* driver = driver_find(compatible);
if (driver == NULL) {
LOG_E(TAG, "Can't find driver: %s", compatible);
return -1;
}
device_construct(device);
device_set_driver(device, driver);
device_set_parent(device, parent_device);
device_add(device);
const int err = device_start(device);
if (err != 0) {
LOG_E(TAG, "Failed to start device %s with driver %s: error code %d", device->name, compatible, err);
return -1;
}
return 0;
}
'''))
# Then write all devices
devices = []
for item in items:
if type(item) is Device:
devices.append(item)
write_device(file, item, bindings, verbose)
write_device_list(file, devices)
file.write(dedent('''\
struct device** devices_builtin_get() {
return devices_builtin;
}
'''))
write_device_structs(file, item, bindings, verbose)
# Init function body start
file.write("int devices_builtin_init() {\n")
# Init function body logic
for item in items:
if type(item) is Device:
write_device_init(file, item, None, bindings, verbose)
file.write("\treturn 0;\n")
# Init function body end
file.write("}\n")
def generate_devicetree_h(filename: str):
with open(filename, "w") as file:
file.write(dedent('''\
#pragma once
#include <tactility/device.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @return an array of device* where the last item in the array is NULL
*/
struct device** devices_builtin_get();
extern int devices_builtin_init();
#ifdef __cplusplus
}

View File

@ -0,0 +1,15 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <Tactility/bindings/bindings.h>
#include <Tactility/drivers/Root.h>
#include <drivers/TloraPager.h>
DEFINE_DEVICETREE(tlora_pager, struct RootConfig)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,26 @@
#include "TloraPager.h"
#include <Tactility/Driver.h>
#include <esp_log.h>
extern "C" {
static int start(Device* device) {
return 0;
}
static int stop(Device* device) {
return 0;
}
Driver tlora_pager_driver = {
.name = "T-Lora Pager",
.compatible = (const char*[]) { "lilygo,tlora-pager", nullptr },
.start_device = start,
.stop_device = stop,
.api = nullptr,
.internal = { 0 }
};
}

View File

@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <Tactility/drivers/Root.h>
#ifdef __cplusplus
}
#endif

View File

@ -1,18 +0,0 @@
#include "tlora_pager.h"
#include <esp_log.h>
#include <stdio.h>
#define TAG "tlora_pager"
const struct tlora_pager_api tlora_pager_api_instance = {
};
int tlora_pager_init(const struct device* device) {
ESP_LOGI(TAG, "init %s", device->name);
return 0;
}
int tlora_pager_deinit(const struct device* device) {
ESP_LOGI(TAG, "deinit %s", device->name);
return 0;
}

View File

@ -1,22 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <tactility/device.h>
#include <tactility/drivers/root.h>
// Inherit base config
#define tlora_pager_config root_config
// Inherit base API
#define tlora_pager_api root_api
int tlora_pager_init(const struct device* device);
int tlora_pager_deinit(const struct device* device);
#ifdef __cplusplus
}
#endif

View File

@ -1,8 +1,8 @@
/dts-v1/;
#include <drivers/tlora_pager.h>
#include <tactility/drivers/esp32_gpio.h>
#include <tactility/drivers/esp32_i2c.h>
#include <bindings/tlora_pager.h>
#include <Tactility/bindings/esp32_gpio.h>
#include <Tactility/bindings/esp32_i2c.h>
/ {
compatible = "lilygo,tlora-pager";

View File

@ -1,7 +1,6 @@
/dts-v1/;
#include <tactility/device.h>
#include <tactility/drivers/root.h>
#include <Tactility/bindings/root.h>
/ {
model = "Simulator";

View File

@ -0,0 +1,14 @@
#pragma once
#include <Tactility/bindings/bindings.h>
#include <Tactility/drivers/Esp32Gpio.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_gpio, struct Esp32GpioConfig)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,14 @@
#pragma once
#include <Tactility/bindings/bindings.h>
#include <Tactility/drivers/Esp32I2c.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_i2c, struct Esp32I2cConfig)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,15 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
struct Esp32GpioConfig {
uint8_t gpio_count;
};
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,19 @@
#pragma once
#include <Tactility/drivers/Gpio.h>
#include <hal/i2c_types.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Esp32I2cConfig {
uint32_t clock_frequency;
struct GpioPinConfig pin_sda;
struct GpioPinConfig pin_scl;
const i2c_port_t port;
};
#ifdef __cplusplus
}
#endif

View File

@ -1,22 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <tactility/device.h>
#include <tactility/drivers/gpio_controller.h>
struct esp32_gpio_config {
uint8_t gpio_count;
};
#define esp32_gpio_api gpio_controller_api
int esp32_gpio_init(const struct device* device);
int esp32_gpio_deinit(const struct device* device);
#ifdef __cplusplus
}
#endif

View File

@ -1,28 +0,0 @@
#pragma once
#include <hal/i2c_types.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <tactility/device.h>
#include <tactility/drivers/i2c_controller.h>
struct esp32_i2c_config {
uint32_t clock_frequency;
struct gpio_pin_config pin_sda;
struct gpio_pin_config pin_scl;
const i2c_port_t port;
};
// Inherit base API
#define esp32_i2c_api i2c_controller_api
int esp32_i2c_init(const struct device* device);
int esp32_i2c_deinit(const struct device* device);
#ifdef __cplusplus
}
#endif

View File

@ -1,24 +1,28 @@
#include <tactility/drivers/esp32_gpio.h>
#include <tactility/drivers/gpio_controller.h>
#include <driver/gpio.h>
#include <esp_log.h>
#define TAG "esp32_gpio"
#include <Tactility/Driver.h>
#include <Tactility/drivers/Esp32Gpio.h>
#include <Tactility/drivers/GpioController.h>
#include <Tactility/drivers/Gpio.h>
#include <Tactility/Log.h>
#define GET_CONFIG(dev) ((struct esp32_gpio_config*)dev->config)
#define TAG LOG_TAG(esp32_gpio)
static bool set_level(const struct device* dev, gpio_pin_t pin, bool high) {
return gpio_set_level(pin, high) == ESP_OK;
#define GET_CONFIG(device) ((struct Esp32GpioConfig*)device->internal.driver_data)
extern "C" {
static bool set_level(Device* device, gpio_pin_t pin, bool high) {
return gpio_set_level(static_cast<gpio_num_t>(pin), high) == ESP_OK;
}
static bool get_level(const struct device* dev, gpio_pin_t pin, bool* high) {
*high = gpio_get_level(pin) != 0;
static bool get_level(Device* device, gpio_pin_t pin, bool* high) {
*high = gpio_get_level(static_cast<gpio_num_t>(pin)) != 0;
return true;
}
static bool set_options(const struct device* dev, gpio_pin_t pin, gpio_flags_t options) {
const struct esp32_gpio_config* config = GET_CONFIG(dev);
static bool set_options(Device* device, gpio_pin_t pin, gpio_flags_t options) {
const Esp32GpioConfig* config = GET_CONFIG(device);
if (pin >= config->gpio_count) {
return false;
@ -49,7 +53,7 @@ static bool set_options(const struct device* dev, gpio_pin_t pin, gpio_flags_t o
return gpio_config(&esp_config) == ESP_OK;
}
static bool get_options(const struct device* dev, gpio_pin_t pin, gpio_flags_t* options) {
static bool get_options(Device* device, gpio_pin_t pin, gpio_flags_t* options) {
gpio_io_config_t esp_config;
if (gpio_get_io_config((gpio_num_t)pin, &esp_config) != ESP_OK) {
return false;
@ -85,19 +89,30 @@ static bool get_options(const struct device* dev, gpio_pin_t pin, gpio_flags_t*
return true;
}
const struct esp32_gpio_api esp32_gpio_api_instance = {
static int start(Device* device) {
ESP_LOGI(TAG, "start %s", device->name);
return 0;
}
static int stop(Device* device) {
ESP_LOGI(TAG, "stop %s", device->name);
return 0;
}
const static GpioControllerApi esp32_gpio_api = {
.set_level = set_level,
.get_level = get_level,
.set_options = set_options,
.get_options = get_options
};
int esp32_gpio_init(const struct device* device) {
ESP_LOGI(TAG, "init %s", device->name);
return 0;
}
Driver esp32_gpio_driver = {
.name = "esp32_gpio",
.compatible = (const char*[]) { "espressif,esp32-gpio", nullptr },
.start_device = start,
.stop_device = stop,
.api = (void*)&esp32_gpio_api,
.internal = { 0 }
};
int esp32_gpio_deinit(const struct device* device) {
ESP_LOGI(TAG, "deinit %s", device->name);
return 0;
}
} // extern "C"

View File

@ -0,0 +1,90 @@
#include <driver/i2c.h>
#include <Tactility/Driver.h>
#include <Tactility/drivers/Esp32I2c.h>
#include <Tactility/drivers/I2cController.h>
#include <Tactility/Log.h>
#define TAG LOG_TAG(esp32_i2c)
struct InternalData {
Mutex mutex {};
InternalData() {
mutex_construct(&mutex);
}
~InternalData() {
mutex_destruct(&mutex);
}
};
#define GET_CONFIG(device) ((Esp32I2cConfig*)device->internal.driver_data)
#define GET_DATA(device) ((InternalData*)device->internal.driver_data)
#define lock(data) mutex_lock(&data->mutex);
#define unlock(data) mutex_unlock(&data->mutex);
extern "C" {
static bool read(Device* device, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout) {
vPortAssertIfInISR();
auto* driver_data = GET_DATA(device);
lock(driver_data);
const esp_err_t result = i2c_master_read_from_device(GET_CONFIG(device)->port, address, data, dataSize, timeout);
unlock(driver_data);
ESP_ERROR_CHECK_WITHOUT_ABORT(result);
return result == ESP_OK;
}
static bool write(Device* device, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
vPortAssertIfInISR();
auto* driver_data = GET_DATA(device);
lock(driver_data);
const esp_err_t result = i2c_master_write_to_device(GET_CONFIG(device)->port, address, data, dataSize, timeout);
unlock(driver_data);
ESP_ERROR_CHECK_WITHOUT_ABORT(result);
return result == ESP_OK;
}
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) {
vPortAssertIfInISR();
auto* driver_data = GET_DATA(device);
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);
unlock(driver_data)
ESP_ERROR_CHECK_WITHOUT_ABORT(result);
return result == ESP_OK;
}
static int start(Device* device) {
ESP_LOGI(TAG, "start %s", device->name);
auto* data = new InternalData();
device_set_driver_data(device, data);
return 0;
}
static int stop(Device* device) {
ESP_LOGI(TAG, "stop %s", device->name);
auto* driver_data = static_cast<InternalData*>(device_get_driver_data(device));
device_set_driver_data(device, nullptr);
delete driver_data;
return 0;
}
const I2cControllerApi esp32_i2c_api = {
.read = read,
.write = write,
.write_read = write_read
};
Driver esp32_i2c_driver = {
.name = "esp32_i2c",
.compatible = (const char*[]) { "espressif,esp32-i2c", nullptr },
.start_device = start,
.stop_device = stop,
.api = (void*)&esp32_i2c_api,
.internal = { 0 }
};
} // extern "C"

View File

@ -1,47 +0,0 @@
#include "tactility/drivers/esp32_i2c.h"
#include "driver/i2c.h"
#include <esp_log.h>
#include <tactility/drivers/i2c_controller.h>
#define TAG "esp32_i2c"
#define GET_CONFIG(dev) ((struct esp32_i2c_config*)dev->config)
static bool read(struct device* dev, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout) {
// TODO: mutex
esp_err_t result = i2c_master_read_from_device(GET_CONFIG(dev)->port, address, data, dataSize, timeout);
ESP_ERROR_CHECK_WITHOUT_ABORT(result);
return result == ESP_OK;
}
static bool write(struct device* dev, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
// TODO: mutex
esp_err_t result = i2c_master_write_to_device(GET_CONFIG(dev)->port, address, data, dataSize, timeout);
ESP_ERROR_CHECK_WITHOUT_ABORT(result);
return result == ESP_OK;
}
static bool write_read(struct device* dev, 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) {
// TODO: mutex
esp_err_t result = i2c_master_write_read_device(GET_CONFIG(dev)->port, address, write_data, write_data_size, read_data, read_data_size, timeout);
ESP_ERROR_CHECK_WITHOUT_ABORT(result);
return result == ESP_OK;
}
const struct esp32_i2c_api esp32_i2c_api_instance = {
.read = read,
.write = write,
.write_read = write_read
};
int esp32_i2c_init(const struct device* device) {
ESP_LOGI(TAG, "init %s", device->name);
return 0;
}
int esp32_i2c_deinit(const struct device* device) {
ESP_LOGI(TAG, "deinit %s", device->name);
return 0;
}

View File

@ -1,4 +1,6 @@
#include <Tactility/Tactility.h>
#include <Tactility/Driver.h>
#include <devicetree.h>
#ifdef ESP_PLATFORM
@ -25,8 +27,17 @@ void app_main() {
tt_init_tactility_c(); // ELF bindings for side-loading on ESP32
#endif
auto devices = devices_builtin_get();
tt::run(config, devices);
extern Driver root_driver;
extern Driver tlora_pager_driver;
extern Driver esp32_gpio_driver;
extern Driver esp32_i2c_driver;
driver_construct(&root_driver);
driver_construct(&tlora_pager_driver);
driver_construct(&esp32_gpio_driver);
driver_construct(&esp32_i2c_driver);
devices_builtin_init();
tt::run(config);
}
} // extern

View File

@ -5,8 +5,6 @@
#include <Tactility/hal/Configuration.h>
#include <Tactility/service/ServiceManifest.h>
#include <tactility/device.h>
namespace tt {
/** @brief The configuration for the operating system
@ -21,7 +19,7 @@ struct Configuration {
* Attempts to initialize Tactility and all configured hardware.
* @param[in] config
*/
void run(const Configuration& config, device** devices);
void run(const Configuration& config);
/**
* While technically nullable, this instance is always set if tt_init() succeeds.

View File

@ -306,7 +306,7 @@ void registerApps() {
registerInstalledAppsFromSdCards();
}
void run(const Configuration& config, device** devices) {
void run(const Configuration& config) {
LOGGER.info("Tactility v{} on {} ({})", TT_VERSION, CONFIG_TT_DEVICE_NAME, CONFIG_TT_DEVICE_ID);
assert(config.hardware);
@ -315,11 +315,6 @@ void run(const Configuration& config, device** devices) {
// Assign early so starting services can use it
config_instance = &config;
if (devices != nullptr) {
device_add_all(devices);
device_init_all(devices);
}
#ifdef ESP_PLATFORM
initEsp();
#endif

View File

@ -0,0 +1,49 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
struct Device;
/**
* A bus contains a group of devices owned by a parent device.
* It is used to make a group of devices discoverable.
*
* Example usage:
* The "i2c0" bus is created by the i2c_controller driver when a device is started by a driver.
* The postfix "0" means that it was the first device instance (at index 0).
*/
struct Bus {
/** The name of the bus (e.g. "i2c0" or "spi1"). Valid characters: a-z a-Z 0-9 - _ . */
const char* name;
/** Internal data */
struct {
/** The device this bus belongs to */
struct Device* parent_device;
/** The bus' private data */
void* data;
} internal;
};
// region Bus management
int bus_construct(struct Bus* bus);
int bus_destruct(struct Bus* bus);
struct Bus* bus_find(const char* name);
// endregion
// region Bus device management
int bus_add_device(struct Bus*, struct Device* dev);
void bus_remove_device(struct Bus*, struct Device* dev);
// endregion
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,163 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <Tactility/Bus.h>
#include <Tactility/concurrent/Mutex.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct Driver;
/** Represents a piece of hardware */
struct Device {
/** The name of the device. Valid characters: a-z a-Z 0-9 - _ . */
const char* name;
/** The configuration data for the device's driver */
void* config;
/** Internal data */
struct {
/** The parent device that this device belongs to. Can be NULL, but only the root device should have a NULL parent. */
struct Device* parent;
/** Address of the API exposed by the device instance. */
struct Driver* driver;
/** The driver data for this device (e.g. a mutex) */
void* driver_data;
/** The bus that is owned by this device. This bus can contain child devices to make them discoverable. Can be NULL. */
struct Bus* bus;
/** The mutex for device operations */
struct Mutex mutex;
/** The device state */
struct {
uint8_t start_result;
bool started : 1;
bool added : 1;
} state;
/** Private data */
void* data;
} internal;
};
/**
* Initialize the properties of a device.
*
* @param[in] dev a device with all non-internal properties set
*/
void device_construct(struct Device* device);
/**
* Deinitialize the properties of a device.
* This fails when a device is busy or has children.
*
* @param[in] dev
* @return the result code (0 for success)
*/
int device_destruct(struct Device* device);
/**
* Indicates whether the device is in a state where its API is available
*
* @param[in] dev non-null device pointer
* @return true if the device is ready for use
*/
static inline bool device_is_ready(const struct Device* device) {
return device->internal.state.started;
}
/**
* Register a device to all relevant systems:
* - the global ledger
* - its parent (if any)
* - a bus (if any)
*
* @param[in] device non-null device pointer
*/
void device_add(struct Device* device);
/**
* Deregister a device. Remove it from all relevant systems:
* - the global ledger
* - its parent (if any)
* - a bus (if any)
*
* @param[in] device non-null device pointer
* @return true when the device was found and deregistered
*/
bool device_remove(struct Device* device);
/**
* Attach the driver.
*
* @warning must call device_construct() and device_add() first
* @param device
* @return ERROR_INVALID_STATE or otherwise the value of the driver binding result (0 on success)
*/
int device_start(struct Device* device);
/**
* Detach the driver.
*
* @param device
* @return ERROR_INVALID_STATE or otherwise the value of the driver unbinding result (0 on success)
*/
int device_stop(struct Device* device);
/**
* Set or unset a parent.
* @warning must call before device_add()
* @param device non-NULL device
* @param parent nullable parent device
*/
void device_set_parent(struct Device* device, struct Device* parent);
static inline void device_set_driver(struct Device* device, struct Driver* driver) {
device->internal.driver = driver;
}
static inline struct Driver* device_get_driver(struct Device* device) {
return device->internal.driver;
}
static inline void device_set_driver_data(struct Device* device, void* driver_data) {
device->internal.driver_data = driver_data;
}
static inline void* device_get_driver_data(struct Device* device) {
return device->internal.driver_data;
}
static inline bool device_is_added(const struct Device* device) {
return device->internal.state.added;
}
static inline struct Bus* device_get_bus(const struct Device* device) {
return device->internal.bus;
}
static inline void device_set_bus(struct Device* device, struct Bus* bus) {
device->internal.bus = bus;
}
static inline const char* device_get_bus_name(const struct Device* device) {
return device->internal.bus ? device->internal.bus->name : "";
}
static inline void device_lock(struct Device* device) {
mutex_lock(&device->internal.mutex);
}
static inline int device_try_lock(struct Device* device) {
return mutex_try_lock(&device->internal.mutex);
}
static inline void device_unlock(struct Device* device) {
mutex_unlock(&device->internal.mutex);
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,40 @@
#pragma once
#include <Tactility/Bus.h>
#include <Tactility/Device.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Driver {
/** The driver name */
const char* name;
/** Array of const char*, terminated by NULL */
const char** compatible;
/** Function to initialize the driver for a device */
int (*start_device)(struct Device* dev);
/** Function to deinitialize the driver for a device */
int (*stop_device)(struct Device* dev);
/** Contains the driver's functions */
const void* api;
/** Internal data */
struct {
/** Contains private data */
void* data;
} internal;
};
int driver_construct(struct Driver* drv);
int driver_destruct(struct Driver* drv);
struct Driver* driver_find(const char* compatible);
int driver_bind(struct Driver* drv, struct Device* dev);
int driver_unbind(struct Driver* drv, struct Device* dev);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,10 @@
#pragma once
#include <sys/errno.h>
#define CUSTOM_ERROR_CODE(x) (-__ELASTERROR - x)
#define ERROR_UNDEFINED CUSTOM_ERROR_CODE(1)
#define ERROR_INVALID_STATE CUSTOM_ERROR_CODE(2)
#define ERROR_INVALID_PARAMETER CUSTOM_ERROR_CODE(3)
#define ERROR_MISSING_PARAMETER CUSTOM_ERROR_CODE(4)

View File

@ -1,6 +1,6 @@
#pragma once
#include "freertos.h"
#include "FreeRTOS.h"
#ifndef ESP_PLATFORM
#define xPortInIsrContext(x) (false)

View File

@ -0,0 +1,9 @@
#pragma once
#ifdef ESP_PLATFORM
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#else
#include <FreeRTOS.h>
#include <semphr.h>
#endif

View File

@ -0,0 +1,35 @@
#pragma once
#ifdef ESP_PLATFORM
#include <esp_log.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define LOG_TAG(x) "\033[37m"#x"\033[0m"
#ifndef ESP_PLATFORM
void log_generic(const char* tag, const char* format, ...);
#define LOG_E(x, ...) log_generic(x, ##__VA_ARGS__)
#define LOG_W(x, ...) log_generic(x, ##__VA_ARGS__)
#define LOG_I(x, ...) log_generic(x, ##__VA_ARGS__)
#define LOG_D(x, ...) log_generic(x, ##__VA_ARGS__)
#define LOG_V(x, ...) log_generic(x, ##__VA_ARGS__)
#else
#define LOG_E(x, ...) ESP_LOGE(x, ##__VA_ARGS__)
#define LOG_W(x, ...) ESP_LOGW(x, ##__VA_ARGS__)
#define LOG_I(x, ...) ESP_LOGI(x, ##__VA_ARGS__)
#define LOG_D(x, ...) ESP_LOGD(x, ##__VA_ARGS__)
#define LOG_V(x, ...) ESP_LOGV(x, ##__VA_ARGS__)
#endif
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,8 @@
#pragma once
/**
* Creates required aliases for the devicetree generation.
* @param compatible_name the "compatible" value for the related driver
* @param config_type the internal configuration type for a device
*/
#define DEFINE_DEVICETREE(compatible_name, config_type) typedef config_type compatible_name##_config_dt;

View File

@ -0,0 +1,3 @@
#pragma once
#include <Tactility/drivers/Gpio.h>

View File

@ -0,0 +1,7 @@
#pragma once
#include <Tactility/bindings/bindings.h>
#include <Tactility/drivers/Root.h>
DEFINE_DEVICETREE(root, struct RootConfig)

View File

@ -0,0 +1,48 @@
#pragma once
#include <Tactility/FreeRTOS/semphr.h>
#include <assert.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Mutex {
QueueHandle_t handle;
};
inline static void mutex_construct(struct Mutex* mutex) {
mutex->handle = xSemaphoreCreateMutex();
}
inline static void mutex_destruct(struct Mutex* mutex) {
assert(mutex != NULL);
assert(mutex->handle != NULL);
vPortAssertIfInISR();
vSemaphoreDelete(mutex->handle);
mutex->handle = NULL;
}
inline static void mutex_lock(struct Mutex* mutex) {
assert(mutex->handle != NULL);
xSemaphoreTake(mutex->handle, portMAX_DELAY);
}
inline static bool mutex_try_lock(struct Mutex* mutex) {
assert(mutex->handle != NULL);
return xSemaphoreTake(mutex->handle, 0) == pdTRUE;
}
inline static bool mutex_is_locked(struct Mutex* mutex) {
assert(mutex->handle != NULL);
return xSemaphoreGetMutexHolder(mutex->handle) != NULL;
}
inline static void mutex_unlock(struct Mutex* mutex) {
assert(mutex->handle != NULL);
xSemaphoreGive(mutex->handle);
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,46 @@
#pragma once
#include <Tactility/FreeRTOS/semphr.h>
#ifdef __cplusplus
extern "C" {
#endif
struct RecursiveMutex {
QueueHandle_t handle;
};
inline static void recursive_mutex_construct(struct RecursiveMutex* mutex) {
mutex->handle = xSemaphoreCreateRecursiveMutex();
}
inline static void recursive_mutex_destruct(struct RecursiveMutex* mutex) {
assert(mutex != NULL);
assert(mutex->handle != NULL);
vSemaphoreDelete(mutex->handle);
mutex->handle = NULL;
}
inline static void recursive_mutex_lock(struct RecursiveMutex* mutex) {
assert(mutex->handle != NULL);
xSemaphoreTake(mutex->handle, portMAX_DELAY);
}
inline static bool recursive_mutex_is_locked(struct RecursiveMutex* mutex) {
assert(mutex->handle != NULL);
return xSemaphoreGetMutexHolder(mutex->handle) != NULL;
}
inline static bool recursive_mutex_try_lock(struct RecursiveMutex* mutex) {
assert(mutex->handle != NULL);
return xSemaphoreTake(mutex->handle, 0) == pdTRUE;
}
inline static void recursive_mutex_unlock(struct RecursiveMutex* mutex) {
assert(mutex->handle != NULL);
xSemaphoreGive(mutex->handle);
}
#ifdef __cplusplus
}
#endif

View File

@ -4,7 +4,7 @@
extern "C" {
#endif
#include <tactility/device.h>
#include <Tactility/Device.h>
#define GPIO_OPTIONS_MASK 0x1f
@ -19,7 +19,7 @@ extern "C" {
#define GPIO_PULL_DOWN (1 << 4)
#define GPIO_INTERRUPT_BITMASK (0b111 << 5) // 3 bits to hold the values [0, 5]
#define GPIO_INTERRUPT_FROM_OPTIONS(options) (gpio_interrupt_type_t)((options & GPIO_INTERRUPT_BITMASK) >> 5)
#define GPIO_INTERRUPT_FROM_OPTIONS(options) (gpio_int_type_t)((options & GPIO_INTERRUPT_BITMASK) >> 5)
#define GPIO_INTERRUPT_TO_OPTIONS(options, interrupt) (options | (interrupt << 5))
typedef enum {
@ -30,7 +30,7 @@ typedef enum {
GPIO_INTERRUPT_LOW_LEVEL = 4,
GPIO_INTERRUPT_HIGH_LEVEL = 5,
GPIO__MAX,
} gpio_interrupt_type_t;
} GpioInterruptType;
/**
* @brief Provides a type to hold a GPIO pin index.
@ -67,25 +67,21 @@ typedef uint16_t gpio_flags_t;
* controlled by that device, and the subset of pin configuration
* flags which may be given in devicetree.
*/
struct gpio_pin_config {
struct GpioPinConfig {
/** GPIO device controlling the pin */
const struct device* port;
const struct Device* port;
/** The pin's number on the device */
gpio_pin_t pin;
/** The pin's configuration flags as specified in devicetree */
gpio_flags_t dt_flags;
};
/**
* @brief Validate that GPIO port is ready.
*
* @param spec GPIO specification from devicetree
*
* @retval true if the GPIO spec is ready for use.
* @retval false if the GPIO spec is not ready for use.
* Check if the pin is ready to be used.
* @param pin_config the specifications of the pin
* @return true if the pin is ready to be used
*/
inline bool gpio_is_ready(const struct gpio_pin_config* pin_config) {
static inline bool gpio_is_ready(const struct GpioPinConfig* pin_config) {
return device_is_ready(pin_config->port);
}

View File

@ -0,0 +1,28 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "Gpio.h"
#include <stdbool.h>
struct GpioControllerApi {
bool (*set_level)(struct Device* device, gpio_pin_t pin, bool high);
bool (*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);
bool (*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);
bool 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);
bool 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) {
return gpio_controller_set_options(device, config->pin, config->dt_flags);
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,26 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "Gpio.h"
#include <Tactility/FreeRTOS/FreeRTOS.h>
#include <stdbool.h>
#include <stdint.h>
struct I2cControllerApi {
bool (*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);
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);
};
bool 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);
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);
#ifdef __cplusplus
}
#endif

View File

@ -4,16 +4,10 @@
extern "C" {
#endif
struct root_config {
struct RootConfig {
const char* model;
};
struct root_api {
};
#define root_init nullptr
#define root_deinit nullptr
#ifdef __cplusplus
}
#endif

View File

@ -1,111 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
struct device;
struct device_operations {
/** Initialization function */
int (*init)(const struct device* device);
/** De-initialization function */
int (*deinit)(const struct device* device);
};
struct device_state {
uint8_t init_result;
bool initialized : 1;
};
struct device_metadata {
/** @brief number of elements in the compatible array */
size_t compatible_count;
/** @brief array of strings containing the compatible device names */
const char** compatible;
};
struct device {
/** Name of the device as defined in the dts file */
const char* name;
/** Address of device instance configuration. This relates to the parameters that are specified in the dts file*/
const void* config;
/** Address of the API exposed by the device instance */
const void* api;
/** The device state */
struct device_state state;
/** Address of the device's private data */
void* data;
/** Device operations: used for initializing and deinitializing */
struct device_operations operations;
/** Device metadata */
struct device_metadata metadata;
};
/**
* Initialize a device.
* @param[in] dev
* @return the return code of the device's init function
*/
uint8_t device_init(struct device* dev);
/**
* Initialize an array of devices.
* @param[in] device_array a null-terminated array of devices
* @retval true if all devices initialized successfully
* @retval false if any device failed to initialize
*/
bool device_init_all(struct device** device_array);
/**
* Deinitialize a device.
* @param[in] dev
* @return the return code of the device's deinit function
*/
uint8_t device_deinit(struct device* dev);
/**
* Indicated whether the device is in a state where its API is available
*
* @param[in] dev non-null device pointer
* @return true if the device is ready for use
*/
bool device_is_ready(const struct device* dev);
/**
* Register a single device.
*
* @param[in] dev non-null device pointer
*/
void device_add(const struct device* dev);
/**
* Register all devices in the specified array.
* The array must be null-terminated.
*
* @param[in] device_array non-null array of devices
*/
void device_add_all(struct device** device_array);
/**
* Deregister a device
* @param[in] dev non-null device pointer
* @return true when the device was found and deregistered
*/
bool device_remove(const struct device* dev);
/**
* Iterate the devicetree. Find the next with the specified label.
* @param[in] identifier the identifier of the device, such as "root" or "i2c-controller"
* @param[inout] dev a pointer to a device pointer in the tree, or nullptr to start searching for the first device
* @return true if a device was found
*/
bool device_find_next_by_compatible(const char* identifier, const struct device** dev);
#ifdef __cplusplus
}
#endif

View File

@ -1,28 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include "gpio.h"
struct gpio_controller_api {
bool (*set_level)(const struct device*, gpio_pin_t pin, bool high);
bool (*get_level)(const struct device*, gpio_pin_t pin, bool* high);
bool (*set_options)(const struct device*, gpio_pin_t pin, gpio_flags_t options);
bool (*get_options)(const struct device*, gpio_pin_t pin, gpio_flags_t* options);
};
bool gpio_controller_set_level(const struct device* dev, gpio_pin_t pin, bool high);
bool gpio_controller_get_level(const struct device* dev, gpio_pin_t pin, bool* high);
bool gpio_controller_set_options(const struct device* dev, gpio_pin_t pin, gpio_flags_t options);
bool gpio_controller_get_options(const struct device* dev, gpio_pin_t pin, gpio_flags_t* options);
inline bool gpio_set_options_config(const struct gpio_pin_config* config) {
return gpio_controller_set_options(config->port, config->pin, config->dt_flags);
}
#ifdef __cplusplus
}
#endif

View File

@ -1,26 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <tactility/freertos/freertos.h>
#include "gpio.h"
struct i2c_controller_api {
bool (*read)(struct device* dev, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout);
bool (*write)(struct device* dev, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout);
bool (*write_read)(struct device* dev, 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);
};
bool i2c_controller_read(struct device* dev, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout);
bool i2c_controller_write(struct device* dev, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout);
bool i2c_controller_write_read(struct device* dev, 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);
#ifdef __cplusplus
}
#endif

View File

@ -1,19 +0,0 @@
#pragma once
#ifdef ESP_PLATFORM
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#else
#include <FreeRTOS.h>
#include <semphr.h>
#endif
#include <assert.h>
struct SemaphoreHandleDeleter {
static void operator()(QueueHandle_t handleToDelete) {
assert(xPortInIsrContext() == pdFALSE);
vSemaphoreDelete(handleToDelete);
}
};

View File

@ -1,25 +0,0 @@
#pragma once
#ifdef ESP_PLATFORM
#include <esp_log.h>
#endif
#ifndef ESP_PLATFORM
void log_generic(const char* tag, const char* format, ...);
#define LOG_E(x, ...) log(x, ##__VA_ARGS__)
#define LOG_W(x, ...) log(x, ##__VA_ARGS__)
#define LOG_I(x, ...) log(x, ##__VA_ARGS__)
#define LOG_D(x, ...) log(x, ##__VA_ARGS__)
#define LOG_V(x, ...) log(x, ##__VA_ARGS__)
#else
#define LOG_E(x, ...) ESP_LOGD(x, ##__VA_ARGS__)
#define LOG_W(x, ...) ESP_LOGW(x, ##__VA_ARGS__)
#define LOG_I(x, ...) ESP_LOGI(x, ##__VA_ARGS__)
#define LOG_D(x, ...) ESP_LOGD(x, ##__VA_ARGS__)
#define LOG_V(x, ...) ESP_LOGV(x, ##__VA_ARGS__)
#endif

115
core/source/Bus.cpp Normal file
View File

@ -0,0 +1,115 @@
#include <algorithm>
#include <cstring>
#include <vector>
#include <Tactility/concurrent/Mutex.h>
#include <Tactility/Bus.h>
#include <Tactility/Device.h>
#include <Tactility/Driver.h>
/** Keeps track of all existing buses */
struct BusLedger {
std::vector<Bus*> buses = {};
Mutex mutex {};
BusLedger() {
mutex_construct(&mutex);
}
~BusLedger() {
mutex_destruct(&mutex);
}
};
/* Internal data for a Bus */
struct BusData {
std::vector<Device*> devices = {};
std::vector<Driver*> drivers = {};
Mutex mutex {};
BusData() {
mutex_construct(&mutex);
}
~BusData() {
mutex_destruct(&mutex);
}
};
static BusLedger ledger;
#define ledger_lock() mutex_lock(&ledger.mutex);
#define ledger_unlock() mutex_unlock(&ledger.mutex);
#define bus_lock(bus_data) mutex_lock(&bus_data->mutex);
#define bus_unlock(bus_data) mutex_unlock(&bus_data->mutex);
#define BUS_INSTANCE_DATA(bus) ((struct BusData*)bus->internal.data)
extern "C" {
// region Bus management
static int bus_add(Bus* bus) {
ledger_lock();
ledger.buses.push_back(bus);
ledger_unlock();
return 0;
}
static void bus_remove(Bus* bus) {
ledger_lock();
const auto iterator = std::ranges::find(ledger.buses, bus);
if (iterator != ledger.buses.end()) {
ledger.buses.erase(iterator);
}
ledger_unlock();
}
int bus_construct(Bus* bus) {
bus->internal.data = new BusData();
bus_add(bus);
return 0;
}
int bus_destruct(Bus* bus) {
delete BUS_INSTANCE_DATA(bus);
bus_remove(bus);
return 0;
}
// endregion
// region Bus device management
Bus* bus_find(const char* name) {
ledger_lock();
const auto it = std::ranges::find_if(ledger.buses, [name](Bus* bus) {
return strcmp(name, bus->name) == 0;
});
Bus* result = (it != ledger.buses.end()) ? *it : nullptr;
ledger_unlock();
return result;
}
int bus_add_device(Bus* bus, Device* dev) {
auto* bus_data = BUS_INSTANCE_DATA(bus);
bus_lock(bus_data);
bus_data->devices.push_back(dev);
bus_unlock(bus_data);
return 0;
}
void bus_remove_device(Bus* bus, Device* dev) {
auto* bus_data = BUS_INSTANCE_DATA(bus);
bus_lock(bus_data);
const auto iterator = std::ranges::find(bus_data->devices, dev);
if (iterator != bus_data->devices.end()) {
bus_data->devices.erase(iterator);
}
bus_unlock(bus_data);
}
// endregion
} // extern "C"

202
core/source/Device.cpp Normal file
View File

@ -0,0 +1,202 @@
#include <Tactility/Device.h>
#include <Tactility/Error.h>
#include <Tactility/Driver.h>
#include <Tactility/Log.h>
#include <algorithm>
#include <cassert>
#include <cstring>
#include <sys/errno.h>
#include <vector>
#define TAG LOG_TAG(device)
struct DeviceData {
std::vector<Device*> children;
};
struct DeviceLedger {
std::vector<Device*> devices;
Mutex mutex {};
DeviceLedger() {
mutex_construct(&mutex);
}
~DeviceLedger() {
mutex_destruct(&mutex);
}
};
static DeviceLedger ledger;
extern "C" {
#define ledger_lock() mutex_lock(&ledger.mutex);
#define ledger_unlock() mutex_unlock(&ledger.mutex);
#define get_device_data(device) static_cast<DeviceData*>(device->internal.data)
void device_construct(Device* device) {
LOG_I(TAG, "construct %s", device->name);
device->internal.data = new DeviceData();
mutex_construct(&device->internal.mutex);
}
int device_destruct(Device* device) {
LOG_I(TAG, "destruct %s", device->name);
mutex_destruct(&device->internal.mutex);
delete get_device_data(device);
device->internal.data = nullptr;
return 0;
}
/** Add a child to the list of children */
static void device_add_child(struct Device* device, struct Device* child) {
device_lock(device);
get_device_data(device)->children.push_back(device);
device_unlock(device);
}
/** Remove a child from the list of children */
static void device_remove_child(struct Device* device, struct Device* child) {
device_lock(device);
auto* parent_data = get_device_data(device);
const auto iterator = std::ranges::find(parent_data->children, device);
if (iterator != parent_data->children.end()) {
parent_data->children.erase(iterator);
}
device_unlock(device);
}
void device_add(Device* device) {
LOG_I(TAG, "add %s", device->name);
assert(!device->internal.state.started);
// Already added
if (device->internal.state.added) {
return;
}
// Add to ledger
ledger_lock();
ledger.devices.push_back(device);
ledger_unlock();
// Add self to parent's children list
auto* parent = device->internal.parent;
if (parent != nullptr) {
device_add_child(parent, device);
}
auto* bus = device->internal.bus;
if (bus != nullptr) {
bus_add_device(bus, device);
}
device->internal.state.added = true;
}
bool device_remove(Device* device) {
LOG_I(TAG, "remove %s", device->name);
assert(!device->internal.state.started);
// Already removed
if (!device->internal.state.added) {
return true;
}
auto* bus = device->internal.bus;
if (bus != nullptr) {
bus_remove_device(bus, device);
}
// Remove self from parent's children list
auto* parent = device->internal.parent;
if (parent != nullptr) {
device_remove_child(parent, device);
}
ledger_lock();
const auto iterator = std::ranges::find(ledger.devices, device);
if (iterator == ledger.devices.end()) {
ledger_unlock();
goto failed_ledger_lookup;
}
ledger.devices.erase(iterator);
ledger_unlock();
device->internal.state.added = false;
return true;
failed_ledger_lookup:
// Re-add to parent
if (parent != nullptr) {
device_add_child(parent, device);
}
// Re-add to bus
if (bus != nullptr) {
bus_add_device(bus, device);
}
return false;
}
int device_start(Device* device) {
if (!device->internal.state.added) {
return ERROR_INVALID_STATE;
}
// Already started
if (device->internal.state.started) {
return 0;
}
if (device->internal.driver == nullptr) {
LOG_E(TAG, "start error: no driver for %s", device->name);
return ERROR_INVALID_STATE;
}
int result = driver_bind(device->internal.driver, device);
if (result != 0) {
device->internal.state.started = true;
device->internal.state.start_result = result;
}
return 0;
}
int device_stop(struct Device* device) {
if (!device->internal.state.added) {
return ERROR_INVALID_STATE;
}
// Already stopped
if (!device->internal.state.started) {
return 0;
}
int result = driver_unbind(device->internal.driver, device);
if (result != 0) {
// Re-add to bus
if (device->internal.bus != nullptr) {
bus_add_device(device->internal.bus, device);
}
return result;
}
device->internal.state.started = false;
device->internal.state.start_result = 0;
return 0;
}
void device_set_parent(Device* device, Device* parent) {
assert(!device->internal.state.started);
device->internal.parent = parent;
}
} // extern "C"

161
core/source/Driver.cpp Normal file
View File

@ -0,0 +1,161 @@
#include <sys/errno.h>
#include <vector>
#include <Tactility/concurrent/Mutex.h>
#include <Tactility/Driver.h>
#include <Tactility/Error.h>
#include <Tactility/Log.h>
#define TAG LOG_TAG(driver)
struct DriverInternalData {
Mutex mutex {};
int use_count = 0;
DriverInternalData() {
mutex_construct(&mutex);
}
~DriverInternalData() {
mutex_destruct(&mutex);
}
};
struct DriverLedger {
std::vector<Driver*> drivers = {};
Mutex mutex {};
DriverLedger() {
mutex_construct(&mutex);
}
~DriverLedger() {
mutex_destruct(&mutex);
}
};
static DriverLedger ledger;
#define ledger_lock() mutex_lock(&ledger.mutex);
#define ledger_unlock() mutex_unlock(&ledger.mutex);
#define driver_internal_data(driver) static_cast<DriverInternalData*>(driver->internal.data)
#define driver_lock(driver) mutex_lock(&driver_internal_data(driver)->mutex);
#define driver_unlock(driver) mutex_unlock(&driver_internal_data(driver)->mutex);
static void driver_add(Driver* dev) {
LOG_I(TAG, "add %s", dev->name);
ledger_lock();
ledger.drivers.push_back(dev);
ledger_unlock();
}
static bool driver_remove(Driver* dev) {
LOG_I(TAG, "remove %s", dev->name);
ledger_lock();
const auto iterator = std::ranges::find(ledger.drivers, dev);
// check that there actually is a 3 in our vector
if (iterator == ledger.drivers.end()) {
return false;
}
ledger.drivers.erase(iterator);
ledger_unlock();
return true;
}
extern "C" {
int driver_construct(Driver* driver) {
driver->internal.data = new DriverInternalData();
driver_add(driver);
return 0;
}
int driver_destruct(Driver* driver) {
// Check if in use
if (driver_internal_data(driver)->use_count == 0) {
return ERROR_INVALID_STATE;
}
driver_remove(driver);
delete driver_internal_data(driver);
return 0;
}
Driver* driver_find(const char* compatible) {
ledger_lock();
const auto it = std::ranges::find_if(ledger.drivers, [compatible](Driver* driver) {
const char** current_compatible = driver->compatible;
assert(current_compatible != nullptr);
while (*current_compatible != nullptr) {
if (strcmp(compatible, *current_compatible) == 0) {
return true;
}
current_compatible++;
}
return false;
});
auto* driver = (it != ledger.drivers.end()) ? *it : nullptr;
ledger_unlock();
return driver;
}
int driver_bind(Driver* driver, Device* device) {
driver_lock(driver);
int err = 0;
if (!device_is_added(device)) {
err = -ENODEV;
goto error;
}
device_set_driver(device, driver);
if (driver->start_device != nullptr) {
err = driver->start_device(device);
if (err != 0) {
goto error;
}
}
driver_internal_data(driver)->use_count++;
driver_unlock(driver);
LOG_I(TAG, "bound %s to %s", driver->name, device->name);
return 0;
error:
driver_unlock(driver);
return err;
}
int driver_unbind(Driver* driver, Device* device) {
driver_lock(driver);
if (driver->stop_device == nullptr) {
return 0;
}
int err = driver->stop_device(device);
if (err != 0) {
goto error;
}
device_set_driver(device, nullptr);
driver_internal_data(driver)->use_count--;
driver_unlock(driver);
LOG_I(TAG, "unbound %s to %s", driver->name, device->name);
return 0;
error:
driver_unlock(driver);
return err;
}
} // extern "C"

View File

@ -1,7 +1,7 @@
#include <tactility/log.h>
#ifndef ESP_PLATFORM
#include <tactility/log.h>
#include <stdio.h>
#include <stdarg.h>

View File

@ -1,129 +0,0 @@
#include <tactility/device.h>
#include <tactility/log.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define CONFIG_DEVICE_INDEX_SIZE 64
#define TAG "device"
// TODO: Automatically increase allocated size
static const struct device* device_index[CONFIG_DEVICE_INDEX_SIZE ] = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
uint8_t device_init(struct device* const dev) {
assert(dev != NULL);
LOG_I(TAG, "init %s", dev->name);
if (!dev->state.initialized) {
if (dev->operations.init == NULL) {
dev->state.initialized = true;
dev->state.init_result = 0U;
} else {
dev->state.init_result = dev->operations.init(dev);
dev->state.initialized = dev->state.init_result == 0U;
}
}
return dev->state.init_result;
}
bool device_init_all(struct device** const device_array) {
assert(device_array != NULL);
struct device** current_device = device_array;
bool all_succeeded = true;
while (*current_device != NULL) {
struct device* device = *current_device;
if (device_init(device) != 0U) {
all_succeeded = false;
}
current_device++;
}
return all_succeeded;
}
uint8_t device_deinit(struct device* const dev) {
assert(dev != NULL);
LOG_I(TAG, "deinit %s", dev->name);
if (dev->state.initialized) {
if (dev->operations.deinit != NULL) {
dev->state.init_result = dev->operations.deinit(dev);
if (dev->state.init_result == 0U) {
dev->state.initialized = false;
}
} else {
dev->state.initialized = false;
}
}
return !dev->state.init_result;
}
bool device_is_ready(const struct device* const dev) {
assert(dev != NULL);
return dev->state.initialized && (dev->state.init_result == 0U);
}
void device_add(const struct device* dev) {
assert(dev != NULL);
LOG_I(TAG, "add %s", dev->name);
for (int i = 0; i < CONFIG_DEVICE_INDEX_SIZE; i++) {
if (device_index[i] == NULL) {
device_index[i] = dev;
return;
}
}
assert(false); // out of space
}
void device_add_all(struct device** const device_array) {
assert(device_array != NULL);
struct device** current_device = device_array;
while (*current_device != NULL) {
struct device* device = *current_device;
device_add(device);
current_device++;
}
}
bool device_remove(const struct device* dev) {
assert(dev != NULL);
LOG_I(TAG, "remove %s", dev->name);
for (int i = 0; i < CONFIG_DEVICE_INDEX_SIZE; i++) {
if (device_index[i] == dev) {
device_index[i] = NULL;
return true;
}
}
return false;
}
bool device_find_next_by_compatible(const char* identifier, const struct device** dev) {
bool found_first = (*dev == NULL);
for (int device_idx = 0; device_idx < CONFIG_DEVICE_INDEX_SIZE; device_idx++) {
const struct device* indexed_device = device_index[device_idx];
if (indexed_device != NULL) {
if (!found_first) {
if (indexed_device == *dev) {
found_first = true;
}
} else {
for (int label_idx = 0; label_idx< indexed_device->metadata.compatible_count; ++label_idx) {
const char* indexed_device_label = indexed_device->metadata.compatible[label_idx];
if (strcmp(indexed_device_label, identifier) == 0) {
*dev = indexed_device;
return true;
}
}
}
}
}
return false;
}

View File

@ -0,0 +1,28 @@
#include <Tactility/drivers/GpioController.h>
#include <Tactility/Driver.h>
#define GPIO_DRIVER_API(driver) ((struct GpioControllerApi*)driver->api)
extern "C" {
bool gpio_controller_set_level(Device* device, gpio_pin_t pin, bool high) {
const auto* driver = device_get_driver(device);
return GPIO_DRIVER_API(driver)->set_level(device, pin, high);
}
bool gpio_controller_get_level(Device* device, gpio_pin_t pin, bool* high) {
const auto* driver = device_get_driver(device);
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) {
const auto* driver = device_get_driver(device);
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) {
const auto* driver = device_get_driver(device);
return GPIO_DRIVER_API(driver)->get_options(device, pin, options);
}
}

View File

@ -0,0 +1,23 @@
#include <Tactility/drivers/I2cController.h>
#include <Tactility/Driver.h>
#define I2C_DRIVER_API(driver) ((struct I2cControllerApi*)driver->api)
extern "C" {
bool i2c_controller_read(Device* device, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout) {
const auto* driver = device_get_driver(device);
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) {
const auto* driver = device_get_driver(device);
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) {
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);
}
}

View File

@ -0,0 +1,15 @@
#include <Tactility/drivers/Root.h>
#include <Tactility/Driver.h>
extern "C" {
Driver root_driver = {
.name = "root",
.compatible = (const char*[]) { "root", nullptr },
.start_device = nullptr,
.stop_device = nullptr,
.api = nullptr,
.internal = { 0 }
};
}

View File

@ -1,19 +0,0 @@
#include <tactility/drivers/gpio_controller.h>
#define GPIO_API(dev) ((struct gpio_controller_api*)dev->api)
bool gpio_controller_set_level(const struct device* dev, gpio_pin_t pin, bool high) {
return GPIO_API(dev)->set_level(dev, pin, high);
}
bool gpio_controller_get_level(const struct device* dev, gpio_pin_t pin, bool* high) {
return GPIO_API(dev)->get_level(dev, pin, high);
}
bool gpio_controller_set_options(const struct device* dev, gpio_pin_t pin, gpio_flags_t options) {
return GPIO_API(dev)->set_options(dev, pin, options);
}
bool gpio_controller_get_options(const struct device* dev, gpio_pin_t pin, gpio_flags_t* options) {
return GPIO_API(dev)->get_options(dev, pin, options);
}

View File

@ -1,15 +0,0 @@
#include <tactility/drivers/i2c_controller.h>
#define GPIO_API(dev) ((struct i2c_controller_api*)dev->api)
bool i2c_controller_read(struct device* dev, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout) {
return GPIO_API(dev)->read(dev, address, data, dataSize, timeout);
}
bool i2c_controller_write(struct device* dev, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
return GPIO_API(dev)->write(dev, address, data, dataSize, timeout);
}
bool i2c_controller_write_read(struct device* dev, 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) {
return GPIO_API(dev)->write_read(dev, address, write_data, write_data_size, read_data, read_data_size, timeout);
}

View File

@ -1,3 +0,0 @@
#include "tactility/drivers/root.h"
const struct root_api root_api_instance = {};