Compare commits

...

4 Commits

Author SHA1 Message Date
Ken Van Hoeylandt
b4966c36f6 Remove Bus and add DeviceType 2026-01-21 22:25:02 +01:00
Ken Van Hoeylandt
c85c4da300 Move device parent 2026-01-21 20:44:07 +01:00
Ken Van Hoeylandt
95f010d3f7 Read "compatible" from binding files 2026-01-21 20:37:48 +01:00
Ken Van Hoeylandt
cc5050b7c6 Rename core to TactilityKernel 2026-01-21 19:59:05 +01:00
49 changed files with 99 additions and 244 deletions

View File

@ -34,6 +34,7 @@ def parse_binding(file_path: str, binding_dirs: list[str]) -> Binding:
includes.append(include)
# Parse local properties
compatible = data.get('compatible', None)
properties_raw = data.get('properties', {})
for name, details in properties_raw.items():
prop = BindingProperty(
@ -43,9 +44,7 @@ def parse_binding(file_path: str, binding_dirs: list[str]) -> Binding:
description=details.get('description', '').strip(),
)
properties_dict[name] = prop
filename = os.path.basename(file_path)
compatible = filename.removesuffix(".yaml").removesuffix(".yml")
return Binding(
filename=filename,
compatible=compatible,

View File

@ -18,8 +18,10 @@ def get_device_identifier_safe(device: Device):
def get_device_type_name(device: Device, bindings: list[Binding]):
device_binding = find_device_binding(device, bindings)
if device_binding == None:
if device_binding is None:
raise Exception(f"Binding not found for {device.identifier}")
if device_binding.compatible is None:
raise Exception(f"Couldn't find compatible binding for {device.identifier}")
compatible_safe = device_binding.compatible.split(",")[-1]
return compatible_safe.replace("-", "_")
@ -101,7 +103,7 @@ 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_structs(file, device: Device, bindings: list[Binding], verbose: bool):
def write_device_structs(file, device: Device, parent_device: Device, bindings: list[Binding], verbose: bool):
if verbose:
print(f"Writing device struct for '{device.identifier}'")
# Assemble some pre-requisites
@ -111,38 +113,38 @@ def write_device_structs(file, device: Device, bindings: list[Binding], verbose:
raise Exception(f"Cannot find 'compatible' property for {device.identifier}")
identifier = get_device_identifier_safe(device)
config_variable_name = f"{identifier}_config"
if parent_device is not None:
parent_identifier = get_device_identifier_safe(parent_device)
parent_value = f"&{parent_identifier}"
else:
parent_value = "NULL"
# Write config struct
write_config(file, device, bindings, type_name)
# Write device struct
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(f"\t.parent = {parent_value},\n")
file.write("};\n\n")
# Child devices
for child_device in device.devices:
write_device_structs(file, child_device, bindings, verbose)
write_device_structs(file, child_device, device, bindings, verbose)
def write_device_init(file, device: Device, parent_device: Device, bindings: list[Binding], verbose: bool):
def write_device_init(file, 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")
file.write(f"\tif (init_builtin_device(&{device_variable}, \"{compatible_property.value}\") != 0) return -1;\n")
# Write children
for child_device in device.devices:
write_device_init(file, child_device, device, bindings, verbose)
write_device_init(file, child_device, bindings, verbose)
def write_device_list_entry(file, device: Device):
compatible_property = find_binding_property(device, "compatible")
@ -164,11 +166,11 @@ 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
// Default headers
#include <Tactility/Device.h>
#include <Tactility/Driver.h>
#include <Tactility/Log.h>
// DTS includes
// DTS headers
'''))
# Write all headers first
@ -180,7 +182,7 @@ def generate_devicetree_c(filename: str, items: list[object], bindings: list[Bin
file.write(dedent('''\
#define TAG LOG_TAG(devicetree)
static int init_builtin_device(struct Device* device, const char* compatible, struct Device* parent_device) {
static int init_builtin_device(struct Device* device, const char* compatible) {
struct Driver* driver = driver_find(compatible);
if (driver == NULL) {
LOG_E(TAG, "Can't find driver: %s", compatible);
@ -188,7 +190,6 @@ def generate_devicetree_c(filename: str, items: list[object], bindings: list[Bin
}
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) {
@ -203,13 +204,13 @@ def generate_devicetree_c(filename: str, items: list[object], bindings: list[Bin
# Then write all devices
for item in items:
if type(item) is Device:
write_device_structs(file, item, bindings, verbose)
write_device_structs(file, item, None, 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)
write_device_init(file, item, bindings, verbose)
file.write("\treturn 0;\n")
# Init function body end
file.write("}\n")

View File

@ -35,7 +35,7 @@ class BindingProperty:
@dataclass
class Binding:
filename: str
compatible: str
compatible: list[str]
description: str
properties: list[BindingProperty]
includes: list[str]

View File

@ -32,7 +32,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
"Firmware"
"Devices/${TACTILITY_DEVICE_PROJECT}"
"Drivers"
"core"
"TactilityKernel"
"Tactility"
"TactilityC"
"TactilityCore"
@ -75,13 +75,13 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION})
add_subdirectory(Tactility)
add_subdirectory(TactilityCore)
add_subdirectory(TactilityFreeRtos)
add_subdirectory(TactilityKernel)
add_subdirectory(Devices/simulator)
add_subdirectory(Libraries/cJSON)
add_subdirectory(Libraries/lv_screenshot)
add_subdirectory(Libraries/QRCode)
add_subdirectory(Libraries/minitar)
add_subdirectory(Libraries/minmea)
add_subdirectory(core)
# FreeRTOS
set(FREERTOS_CONFIG_FILE_DIRECTORY ${PROJECT_SOURCE_DIR}/Devices/simulator/Source CACHE STRING "")

View File

@ -20,6 +20,7 @@ Driver tlora_pager_driver = {
.start_device = start,
.stop_device = stop,
.api = nullptr,
.device_type = nullptr,
.internal = { 0 }
};

View File

@ -1,3 +1,5 @@
description: LilyGO T-Lora Pager
include: ["root.yaml"]
compatible: "lilygo,tlora-pager"

View File

@ -7,7 +7,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
idf_component_register(
SRCS ${SOURCES}
INCLUDE_DIRS "include/"
REQUIRES core driver
REQUIRES TactilityKernel driver
)
else ()

View File

@ -1,3 +1,5 @@
description: ESP32 GPIO Controller
compatible: "espressif,esp32-gpio"
include: ["gpio-controller.yaml"]

View File

@ -2,6 +2,8 @@ description: ESP32 GPIO Controller
include: ["i2c-controller.yaml"]
compatible: "espressif,esp32-i2c"
properties:
port:
type: int

View File

@ -1,3 +1,3 @@
dependencies:
- core
- TactilityKernel
bindings: bindings

View File

@ -112,6 +112,7 @@ Driver esp32_gpio_driver = {
.start_device = start,
.stop_device = stop,
.api = (void*)&esp32_gpio_api,
.device_type = nullptr,
.internal = { 0 }
};

View File

@ -84,6 +84,7 @@ Driver esp32_i2c_driver = {
.start_device = start,
.stop_device = stop,
.api = (void*)&esp32_i2c_api,
.device_type = &I2C_CONTROLLER_TYPE,
.internal = { 0 }
};

View File

@ -22,7 +22,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
idf_component_register(
SRCS ${SOURCE_FILES}
REQUIRES ${DEVICE_COMPONENTS}
REQUIRES Tactility TactilityC core drivers-esp ${TACTILITY_DEVICE_PROJECT}
REQUIRES Tactility TactilityC TactilityKernel drivers-esp ${TACTILITY_DEVICE_PROJECT}
)
else ()
@ -32,9 +32,9 @@ else ()
PRIVATE Tactility
PRIVATE TactilityCore
PRIVATE TactilityFreeRtos
PRIVATE TactilityKernel
PRIVATE Simulator
PRIVATE SDL2::SDL2-static SDL2-static
PRIVATE core
)
target_include_directories(FirmwareSim PRIVATE "${CMAKE_SOURCE_DIR}/Firmware/Generated")

View File

@ -4,7 +4,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
list(APPEND REQUIRES_LIST
core
TactilityKernel
TactilityCore
TactilityFreeRtos
lvgl
@ -72,12 +72,12 @@ else()
PUBLIC cJSON
PUBLIC TactilityFreeRtos
PUBLIC TactilityCore
PUBLIC TactilityKernel
PUBLIC freertos_kernel
PUBLIC lvgl
PUBLIC lv_screenshot
PUBLIC minmea
PUBLIC minitar
PUBLIC core
)
endif()

View File

@ -1,3 +1,5 @@
compatible: "root"
properties:
model:
required: true

View File

@ -1,10 +1,11 @@
#pragma once
#include "Driver.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <Tactility/Bus.h>
#include <Tactility/concurrent/Mutex.h>
#include <stdbool.h>
@ -13,22 +14,26 @@ extern "C" {
struct Driver;
/** Enables discovering devices of the same type */
struct DeviceType {
/* Placeholder because empty structs have a different size with C vs C++ compilers */
uint8_t _;
};
/** 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;
/** The parent device that this device belongs to. Can be NULL, but only the root device should have a NULL parent. */
struct Device* parent;
/** 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 */
@ -134,18 +139,6 @@ 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);
}
@ -158,6 +151,18 @@ static inline void device_unlock(struct Device* device) {
mutex_unlock(&device->internal.mutex);
}
static inline const struct DeviceType* device_get_type(struct Device* device) {
return device->internal.driver->device_type;
}
/**
* Iterate through all the known devices of a specific type
* @param type the type to filter
* @param callback_context 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.
*/
void for_each_device_of_type(const struct DeviceType* type, void* callback_context, bool(*on_device)(struct Device* device, void* context));
#ifdef __cplusplus
}
#endif

View File

@ -1,12 +1,12 @@
#pragma once
#include <Tactility/Bus.h>
#include <Tactility/Device.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Device;
struct DeviceType;
struct Driver {
/** The driver name */
const char* name;
@ -18,6 +18,8 @@ struct Driver {
int (*stop_device)(struct Device* dev);
/** Contains the driver's functions */
const void* api;
/** Which type of devices this driver creates (can be NULL) */
const struct DeviceType* device_type;
/** Internal data */
struct {
/** Contains private data */
@ -25,15 +27,19 @@ struct Driver {
} internal;
};
int driver_construct(struct Driver* drv);
int driver_construct(struct Driver* driver);
int driver_destruct(struct Driver* drv);
int driver_destruct(struct Driver* driver);
struct Driver* driver_find(const char* compatible);
int driver_bind(struct Driver* drv, struct Device* dev);
int driver_bind(struct Driver* driver, struct Device* device);
int driver_unbind(struct Driver* drv, struct Device* dev);
int driver_unbind(struct Driver* driver, struct Device* device);
static inline const struct DeviceType* driver_get_device_type(struct Driver* driver) {
return driver->device_type;
}
#ifdef __cplusplus
}

View File

@ -21,6 +21,8 @@ bool i2c_controller_write(struct Device* device, uint8_t address, const uint8_t*
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);
extern const struct DeviceType I2C_CONTROLLER_TYPE;
#ifdef __cplusplus
}
#endif

View File

@ -85,16 +85,11 @@ void device_add(Device* device) {
ledger_unlock();
// Add self to parent's children list
auto* parent = device->internal.parent;
auto* parent = device->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;
}
@ -108,13 +103,8 @@ bool device_remove(Device* device) {
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;
auto* parent = device->parent;
if (parent != nullptr) {
device_remove_child(parent, device);
}
@ -138,11 +128,6 @@ failed_ledger_lookup:
device_add_child(parent, device);
}
// Re-add to bus
if (bus != nullptr) {
bus_add_device(bus, device);
}
return false;
}
@ -180,15 +165,6 @@ int device_stop(struct Device* device) {
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;
@ -196,7 +172,22 @@ int device_stop(struct Device* device) {
void device_set_parent(Device* device, Device* parent) {
assert(!device->internal.state.started);
device->internal.parent = parent;
device->parent = parent;
}
void for_each_device_of_type(const DeviceType* type, void* callback_context, bool(*on_device)(Device* device, void* context)) {
ledger_lock();
for (auto* device : ledger.devices) {
auto* driver = device->internal.driver;
if (driver != nullptr) {
if (driver->device_type == type) {
if (!on_device(device, callback_context)) {
break;
}
}
}
}
ledger_unlock()
}
} // extern "C"

View File

@ -2,6 +2,7 @@
#include <vector>
#include <Tactility/concurrent/Mutex.h>
#include <Tactility/Device.h>
#include <Tactility/Driver.h>
#include <Tactility/Error.h>
#include <Tactility/Log.h>

View File

@ -20,4 +20,6 @@ bool i2c_controller_write_read(Device* device, uint8_t address, const uint8_t* w
return I2C_DRIVER_API(driver)->write_read(device, address, write_data, write_data_size, read_data, read_data_size, timeout);
}
const struct DeviceType I2C_CONTROLLER_TYPE { 0 };
}

View File

@ -9,6 +9,7 @@ Driver root_driver = {
.start_device = nullptr,
.stop_device = nullptr,
.api = nullptr,
.device_type = nullptr,
.internal = { 0 }
};

View File

@ -1,49 +0,0 @@
#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

@ -1,115 +0,0 @@
#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"