Create hal-device module, fix GPIO, fix tests, fix TactilityC (#471)

* **New Features**
  * Added a HAL device module providing device enumeration, type queries and a HAL↔kernel bridge.

* **Improvements**
  * Integrated HAL module into startup; standardized module names, includes and lifecycle logging; safer file append behavior; broader build support.

* **API Changes**
  * Driver lifecycle now uses explicit add/remove semantics; C and HAL device type/lookup APIs clarified.

* **Chores**
  * Added module README and Apache‑2.0 license; updated build configuration to include the new module.

* **Fixes**
  * Updated tests and object file handling to behave correctly.
This commit is contained in:
Ken Van Hoeylandt 2026-02-01 01:05:16 +01:00 committed by GitHub
parent 5993ceb232
commit 3fe1dc0312
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
74 changed files with 836 additions and 211 deletions

View File

@ -32,6 +32,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
"Firmware"
"Devices/${TACTILITY_DEVICE_PROJECT}"
"Drivers"
"Modules"
"Platforms/PlatformEsp32"
"TactilityKernel"
"Tactility"
@ -84,6 +85,7 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION})
add_subdirectory(Libraries/QRCode)
add_subdirectory(Libraries/minitar)
add_subdirectory(Libraries/minmea)
add_subdirectory(Modules/hal-device)
# FreeRTOS
set(FREERTOS_CONFIG_FILE_DIRECTORY ${PROJECT_SOURCE_DIR}/Devices/simulator/Source CACHE STRING "")

View File

@ -36,7 +36,7 @@ static bool initBoot() {
using namespace tt::hal;
static std::vector<std::shared_ptr<Device>> createDevices() {
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createPower(),
ButtonControl::createOneButtonControl(0),

View File

@ -12,7 +12,7 @@ bool initBoot();
using namespace tt::hal;
static std::vector<std::shared_ptr<Device>> createDevices() {
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createPower(),
createDisplay(),

View File

@ -1,6 +1,6 @@
#pragma once
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <Tactility/TactilityCore.h>
class KeyboardBacklightDevice final : public tt::hal::Device {

View File

@ -5,7 +5,7 @@
#include <Tactility/settings/KeyboardSettings.h>
#include <Tactility/settings/DisplaySettings.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <Tactility/Logger.h>
#include <KeyboardBacklight/KeyboardBacklight.h>

View File

@ -1,6 +1,6 @@
#pragma once
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <lvgl.h>
class TrackballDevice : public tt::hal::Device {

View File

@ -9,7 +9,7 @@ bool initBoot();
using namespace tt::hal;
static std::vector<std::shared_ptr<Device>> createDevices() {
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createPower(),
createDisplay(),

View File

@ -11,7 +11,7 @@ static bool initBoot() {
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT);
}
static std::vector<std::shared_ptr<Device>> createDevices() {
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createDisplay(),
ButtonControl::createTwoButtonControl(35, 0)

View File

@ -10,7 +10,7 @@ bool initBoot();
using namespace tt::hal;
static std::vector<std::shared_ptr<Device>> createDevices() {
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createDisplay(),
createSdCard()

View File

@ -22,7 +22,7 @@ static DeviceVector createDevices() {
auto tca8418 = std::make_shared<Tca8418>(I2C_NUM_0);
auto keyboard = std::make_shared<TpagerKeyboard>(tca8418);
return std::vector<std::shared_ptr<Device>> {
return std::vector<std::shared_ptr<tt::hal::Device>> {
tca8418,
std::make_shared<Bq25896>(I2C_NUM_0),
bq27220,

View File

@ -9,14 +9,14 @@ extern Driver tlora_pager_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct(&tlora_pager_driver) == ERROR_NONE);
check(driver_construct_add(&tlora_pager_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_destruct(&tlora_pager_driver) == ERROR_NONE);
check(driver_remove_destruct(&tlora_pager_driver) == ERROR_NONE);
return ERROR_NONE;
}

View File

@ -28,7 +28,7 @@ static void deinitPower() {
#endif
}
static std::vector<std::shared_ptr<Device>> createDevices() {
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
std::make_shared<SdlDisplay>(),
std::make_shared<SdlKeyboard>(),

View File

@ -10,7 +10,7 @@ bool initBoot();
using namespace tt::hal;
static std::vector<std::shared_ptr<Device>> createDevices() {
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createDisplay(),
createSdCard()

View File

@ -2,7 +2,7 @@
#include "Xpt2046Touch.h"
#include <Tactility/Logger.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
static const auto LOGGER = tt::Logger("Xpt2046Power");

View File

@ -36,6 +36,7 @@ else ()
TactilityCore
TactilityFreeRtos
TactilityKernel
hal-device
Simulator
PlatformPosix
SDL2::SDL2-static SDL2-static

View File

@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.20)
if (DEFINED ENV{ESP_IDF_VERSION})
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
list(APPEND REQUIRES_LIST
TactilityKernel
TactilityCore
TactilityFreeRtos
)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Include/"
REQUIRES ${REQUIRES_LIST}
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${COMPONENT_LIB} PUBLIC -Wno-unused-variable)
endif ()
else ()
file(GLOB_RECURSE SOURCES "Source/*.c*")
add_library(hal-device OBJECT)
target_sources(hal-device PRIVATE ${SOURCES})
target_include_directories(hal-device
PRIVATE Private/
PUBLIC Include/
)
add_definitions(-D_Nullable=)
add_definitions(-D_Nonnull=)
target_link_libraries(hal-device PUBLIC
TactilityFreeRtos
TactilityCore
TactilityKernel
freertos_kernel
)
endif ()

View File

@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
enum class HalDeviceType {
HAL_DEVICE_TYPE_I2C,
HAL_DEVICE_TYPE_DISPLAY,
HAL_DEVICE_TYPE_TOUCH,
HAL_DEVICE_TYPE_SDCARD,
HAL_DEVICE_TYPE_KEYBOARD,
HAL_DEVICE_TYPE_ENCODER,
HAL_DEVICE_TYPE_POWER,
HAL_DEVICE_TYPE_GPS,
HAL_DEVICE_TYPE_OTHER
};
HalDeviceType hal_device_get_type(struct Device* device);
void hal_device_for_each_of_type(HalDeviceType type, void* context, bool(*onDevice)(struct Device* device, void* context));
extern const struct DeviceType HAL_DEVICE_TYPE;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "hal_device.h"
#include <memory>
#include <tactility/hal/Device.h>
namespace tt::hal {
/**
* @brief Get a tt::hal::Device object from a Kernel device.
* @warning The input device must be of type HAL_DEVICE_TYPE
* @param kernelDevice The kernel device
* @return std::shared_ptr<Device>
*/
std::shared_ptr<Device> hal_device_get_device(::Device* kernelDevice);
void hal_device_set_device(::Device* kernelDevice, std::shared_ptr<Device> halDevice);
}

View File

@ -1,3 +1,4 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <functional>
@ -7,6 +8,10 @@
#include <vector>
#include <cassert>
#include <tactility/device.h>
typedef ::Device KernelDevice;
namespace tt::hal {
/** Base class for HAL-related devices. */
class Device {
@ -27,9 +32,19 @@ public:
typedef uint32_t Id;
struct KernelDeviceHolder {
const std::string name;
std::shared_ptr<KernelDevice> device = std::make_shared<KernelDevice>();
explicit KernelDeviceHolder(std::string name) : name(name) {
device->name = this->name.c_str();
}
};
private:
Id id;
std::shared_ptr<KernelDeviceHolder> kernelDeviceHolder;
public:
@ -49,6 +64,10 @@ public:
* e.g. "USB charging controller with I2C interface."
*/
virtual std::string getDescription() const = 0;
void setKernelDeviceHolder(std::shared_ptr<KernelDeviceHolder> kernelDeviceHolder) { this->kernelDeviceHolder = kernelDeviceHolder; }
std::shared_ptr<KernelDeviceHolder> getKernelDeviceHolder() const { return kernelDeviceHolder; }
};
/**
@ -92,7 +111,7 @@ std::vector<std::shared_ptr<DeviceType>> findDevices(Device::Type type) {
assert(target_device != nullptr);
result.push_back(target_device);
}
return std::move(result);
return result;
}
}

View File

@ -0,0 +1,12 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module hal_device_module;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,8 @@
**WARNING: This module contains deprecated code**
This module is the basis for the old Tactility HAL.
This HAL existed before TactilityKernel.
The C++ `tt::hal::Device` class is replaced by `struct Device` from TactilityKernel.
License: Apache v2.0

View File

@ -0,0 +1,126 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/hal_device.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/log.h>
#include <tactility/hal/Device.h>
#include <memory>
#include <utility>
#define TAG LOG_TAG(HalDevice)
struct HalDevicePrivate {
std::shared_ptr<tt::hal::Device> halDevice;
};
#define GET_DATA(device) ((struct HalDevicePrivate*)device->internal.driver_data)
static enum HalDeviceType getHalDeviceType(tt::hal::Device::Type type) {
switch (type) {
case tt::hal::Device::Type::I2c:
return HalDeviceType::HAL_DEVICE_TYPE_I2C;
case tt::hal::Device::Type::Display:
return HalDeviceType::HAL_DEVICE_TYPE_DISPLAY;
case tt::hal::Device::Type::Touch:
return HalDeviceType::HAL_DEVICE_TYPE_TOUCH;
case tt::hal::Device::Type::SdCard:
return HalDeviceType::HAL_DEVICE_TYPE_SDCARD;
case tt::hal::Device::Type::Keyboard:
return HalDeviceType::HAL_DEVICE_TYPE_KEYBOARD;
case tt::hal::Device::Type::Encoder:
return HalDeviceType::HAL_DEVICE_TYPE_ENCODER;
case tt::hal::Device::Type::Power:
return HalDeviceType::HAL_DEVICE_TYPE_POWER;
case tt::hal::Device::Type::Gps:
return HalDeviceType::HAL_DEVICE_TYPE_GPS;
case tt::hal::Device::Type::Other:
return HalDeviceType::HAL_DEVICE_TYPE_OTHER;
default:
LOG_W(TAG, "Device type %d is not implemented", static_cast<int>(type));
return HalDeviceType::HAL_DEVICE_TYPE_OTHER;
}
}
extern "C" {
HalDeviceType hal_device_get_type(struct Device* device) {
auto type = GET_DATA(device)->halDevice->getType();
return getHalDeviceType(type);
}
void hal_device_for_each_of_type(HalDeviceType type, void* context, bool(*onDevice)(struct Device* device, void* context)) {
struct InternalContext {
HalDeviceType typeParam;
void* contextParam;
bool(*onDeviceParam)(struct Device* device, void* context);
};
InternalContext internal_context = {
.typeParam = type,
.contextParam = context,
.onDeviceParam = onDevice
};
for_each_device_of_type(&HAL_DEVICE_TYPE, &internal_context, [](Device* device, void* context){
auto* hal_device_private = GET_DATA(device);
auto* internal_context = static_cast<InternalContext*>(context);
auto hal_device_type = getHalDeviceType(hal_device_private->halDevice->getType());
if (hal_device_type == internal_context->typeParam) {
if (!internal_context->onDeviceParam(device, internal_context->contextParam)) {
return false;
}
}
return true;
});
}
}
namespace tt::hal {
std::shared_ptr<Device> hal_device_get_device(::Device* device) {
auto* hal_device_private = GET_DATA(device);
return hal_device_private->halDevice;
}
void hal_device_set_device(::Device* kernelDevice, std::shared_ptr<Device> halDevice) {
GET_DATA(kernelDevice)->halDevice = std::move(halDevice);
}
}
#pragma region Lifecycle
static error_t start(Device* device) {
LOG_I(TAG, "start %s", device->name);
device->internal.driver_data = new HalDevicePrivate();
return ERROR_NONE;
}
static error_t stop(Device* device) {
LOG_I(TAG, "stop %s", device->name);
delete GET_DATA(device);
return ERROR_NONE;
}
#pragma endregion
extern "C" {
const struct DeviceType HAL_DEVICE_TYPE {0};
extern struct Module hal_device_module;
Driver hal_device_driver = {
.name = "hal-device",
.compatible = (const char*[]) {"hal-device", nullptr},
.startDevice = start,
.stopDevice = stop,
.api = nullptr,
.deviceType = &HAL_DEVICE_TYPE,
.owner = &hal_device_module,
.driver_private = nullptr
};
}

View File

@ -0,0 +1,143 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/hal/Device.h>
#include <tactility/driver.h>
#include <tactility/drivers/hal_device.hpp>
#include <Tactility/Logger.h>
#include <Tactility/RecursiveMutex.h>
#include <algorithm>
namespace tt::hal {
RecursiveMutex mutex;
static Device::Id nextId = 0;
static const auto LOGGER = Logger("Devices");
Device::Device() : id(nextId++) {}
static std::shared_ptr<Device::KernelDeviceHolder> createKernelDeviceHolder(const std::shared_ptr<Device>& device) {
auto kernel_device_name = std::format("hal-device-{}", device->getId());
LOGGER.info("Registering {} with id {} as kernel device {}", device->getName(), device->getId(), kernel_device_name);
auto kernel_device_holder = std::make_shared<Device::KernelDeviceHolder>(kernel_device_name);
auto* kernel_device = kernel_device_holder->device.get();
check(device_construct(kernel_device) == ERROR_NONE);
check(device_add(kernel_device) == ERROR_NONE);
auto* driver = driver_find_compatible("hal-device");
check(driver);
device_set_driver(kernel_device, driver);
check(device_start(kernel_device) == ERROR_NONE);
hal_device_set_device(kernel_device, device);
return kernel_device_holder;
}
static void destroyKernelDeviceHolder(std::shared_ptr<Device::KernelDeviceHolder>& holder) {
auto kernel_device = holder->device.get();
hal_device_set_device(kernel_device, nullptr);
check(device_stop(kernel_device) == ERROR_NONE);
check(device_remove(kernel_device) == ERROR_NONE);
check(device_destruct(kernel_device) == ERROR_NONE);
holder->device = nullptr;
}
void registerDevice(const std::shared_ptr<Device>& device) {
auto scoped_mutex = mutex.asScopedLock();
scoped_mutex.lock();
if (device->getKernelDeviceHolder() == nullptr) {
// Kernel device
auto kernel_device_holder = createKernelDeviceHolder(device);
device->setKernelDeviceHolder(kernel_device_holder);
} else {
LOGGER.warn("Device {} with id {} was already registered", device->getName(), device->getId());
}
}
void deregisterDevice(const std::shared_ptr<Device>& device) {
auto scoped_mutex = mutex.asScopedLock();
scoped_mutex.lock();
// Kernel device
auto kernel_device_holder = device->getKernelDeviceHolder();
if (kernel_device_holder) {
destroyKernelDeviceHolder(kernel_device_holder);
device->setKernelDeviceHolder(nullptr);
} else {
LOGGER.warn("Device {} with id {} was not registered", device->getName(), device->getId());
}
}
template<typename R>
auto toVector(R&& range) {
using T = std::ranges::range_value_t<R>;
std::vector<T> result;
if constexpr (std::ranges::common_range<R>) {
result.reserve(std::ranges::distance(range));
}
std::ranges::copy(range, std::back_inserter(result));
return result;
}
std::vector<std::shared_ptr<Device>> findDevices(const std::function<bool(const std::shared_ptr<Device>&)>& filterFunction) {
auto scoped_mutex = mutex.asScopedLock();
scoped_mutex.lock();
auto devices_view = getDevices() | std::views::filter([&filterFunction](auto& device) {
return filterFunction(device);
});
return toVector(devices_view);
}
std::shared_ptr<Device> _Nullable findDevice(const std::function<bool(const std::shared_ptr<Device>&)>& filterFunction) {
auto scoped_mutex = mutex.asScopedLock();
scoped_mutex.lock();
auto result_set = getDevices() | std::views::filter([&filterFunction](auto& device) {
return filterFunction(device);
});
if (!result_set.empty()) {
return result_set.front();
} else {
return nullptr;
}
}
std::shared_ptr<Device> _Nullable findDevice(std::string name) {
return findDevice([&name](auto& device){
return device->getName() == name;
});
}
std::shared_ptr<Device> _Nullable findDevice(Device::Id id) {
return findDevice([id](auto& device){
return device->getId() == id;
});
}
std::vector<std::shared_ptr<Device>> findDevices(Device::Type type) {
return findDevices([type](auto& device) {
return device->getType() == type;
});
}
std::vector<std::shared_ptr<Device>> getDevices() {
std::vector<std::shared_ptr<Device>> devices;
for_each_device_of_type(&HAL_DEVICE_TYPE, &devices ,[](auto* kernelDevice, auto* context) {
auto devices_ptr = static_cast<std::vector<std::shared_ptr<Device>>*>(context);
auto hal_device = hal_device_get_device(kernelDevice);
(*devices_ptr).push_back(hal_device);
return true;
});
return devices;
}
bool hasDevice(Device::Type type) {
auto scoped_mutex = mutex.asScopedLock();
scoped_mutex.lock();
auto result_set = getDevices() | std::views::filter([&type](auto& device) {
return device->getType() == type;
});
return !result_set.empty();
}
}

View File

@ -0,0 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver hal_device_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&hal_device_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove(&hal_device_driver) == ERROR_NONE);
check(driver_destruct(&hal_device_driver) == ERROR_NONE);
return ERROR_NONE;
}
struct Module hal_device_module = {
.name = "hal-device",
.start = start,
.stop = stop
};
}

View File

@ -11,7 +11,7 @@
#define TAG LOG_TAG(esp32_gpio)
#define GET_CONFIG(device) ((struct Esp32GpioConfig*)device->internal.driver_data)
#define GET_CONFIG(device) ((struct Esp32GpioConfig*)device->config)
extern "C" {

View File

@ -10,22 +10,22 @@ extern Driver esp32_i2c_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct(&esp32_gpio_driver) == ERROR_NONE);
check(driver_construct(&esp32_i2c_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_gpio_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_destruct(&esp32_gpio_driver) == ERROR_NONE);
check(driver_destruct(&esp32_i2c_driver) == ERROR_NONE);
check(driver_remove_destruct(&esp32_gpio_driver) == ERROR_NONE);
check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE);
return ERROR_NONE;
}
// The name must be exactly "platform_module"
struct Module platform_module = {
.name = "ESP32 Platform",
.name = "platform-esp32",
.start = start,
.stop = stop
};

View File

@ -14,7 +14,7 @@ static error_t stop() {
// The name must be exactly "platform_module"
struct Module platform_module = {
.name = "POSIX Platform",
.name = "platform-posix",
.start = start,
.stop = stop
};

View File

@ -8,6 +8,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
PlatformEsp32
TactilityCore
TactilityFreeRtos
hal-device
lvgl
driver
elf_loader
@ -71,6 +72,7 @@ else()
TactilityCore
TactilityKernel
PlatformPosix
hal-device
freertos_kernel
lvgl
lv_screenshot

View File

@ -1,6 +1,6 @@
#pragma once
#include "../Device.h"
#include <tactility/hal/Device.h>
#include <lvgl.h>

View File

@ -1,6 +1,6 @@
#pragma once
#include "../Device.h"
#include <tactility/hal/Device.h>
#include <lvgl.h>

View File

@ -1,6 +1,6 @@
#pragma once
#include "../Device.h"
#include <tactility/hal/Device.h>
#include "GpsConfiguration.h"
#include "Satellites.h"
@ -110,7 +110,7 @@ public:
return lastRmcSubscriptionId;
}
void unsubscribeRmc(GgaSubscriptionId subscriptionId) {
void unsubscribeRmc(RmcSubscriptionId subscriptionId) {
auto lock = mutex.asScopedLock();
lock.lock();
std::erase_if(rmcSubscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; });

View File

@ -1,6 +1,6 @@
#pragma once
#include "../Device.h"
#include <tactility/hal/Device.h>
#include "I2c.h"
namespace tt::hal::i2c {

View File

@ -1,6 +1,6 @@
#pragma once
#include "../Device.h"
#include <tactility/hal/Device.h>
#include <lvgl.h>

View File

@ -1,6 +1,6 @@
#pragma once
#include "../Device.h"
#include <tactility/hal/Device.h>
#include <cstdint>
namespace tt::hal::power {
@ -44,4 +44,4 @@ public:
virtual void powerOff() { /* NO-OP*/ }
};
} // namespace tt
}

View File

@ -1,6 +1,6 @@
#pragma once
#include "../Device.h"
#include <tactility/hal/Device.h>
#include <Tactility/TactilityCore.h>
#include <Tactility/Lock.h>

View File

@ -5,7 +5,7 @@
#include "SdCardDevice.h"
#include <Tactility/RecursiveMutex.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <Tactility/hal/spi/Spi.h>
#include <sd_protocol_types.h>
#include <soc/gpio_num.h>

View File

@ -1,6 +1,6 @@
#pragma once
#include "../Device.h"
#include <tactility/hal/Device.h>
#include "TouchDriver.h"
#include <lvgl.h>

View File

@ -1,7 +1,7 @@
#include "Tactility/MountPoints.h"
#include "Tactility/TactilityConfig.h"
#include "Tactility/hal/Device.h"
#include <tactility/hal/Device.h>
#include "Tactility/hal/sdcard/SdCardDevice.h"
#include <Tactility/file/File.h>

View File

@ -23,6 +23,7 @@
#include <Tactility/settings/TimePrivate.h>
#include <tactility/kernel_init.h>
#include <tactility/hal_device_module.h>
#include <map>
#include <format>
@ -38,6 +39,11 @@ static auto LOGGER = Logger("Tactility");
static const Configuration* config_instance = nullptr;
static Dispatcher mainDispatcher;
struct ModuleParent tactility_module_parent {
"tactility",
nullptr
};
// region Default services
namespace service {
// Primary
@ -330,6 +336,11 @@ void run(const Configuration& config, Module* platformModule, Module* deviceModu
return;
}
// HAL compatibility module: it creates kernel driver wrappers for tt::hal::Device
check(module_parent_construct(&tactility_module_parent) == ERROR_NONE);
check(module_set_parent(&hal_device_module, &tactility_module_parent) == ERROR_NONE);
check(module_start(&hal_device_module) == ERROR_NONE);
const hal::Configuration& hardware = *config.hardware;
// Assign early so starting services can use it

View File

@ -5,7 +5,7 @@
#include <Tactility/file/File.h>
#include <Tactility/file/FileLock.h>
#include <Tactility/file/PropertiesFile.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <Tactility/Logger.h>
#include <Tactility/Paths.h>

View File

@ -3,7 +3,7 @@
#include <Tactility/app/crashdiagnostics/QrHelpers.h>
#include <Tactility/app/crashdiagnostics/QrUrl.h>
#include <Tactility/app/launcher/Launcher.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <Tactility/Logger.h>
#include <Tactility/lvgl/Statusbar.h>
#include <Tactility/service/loader/Loader.h>

View File

@ -7,7 +7,7 @@
#include <Tactility/hal/power/PowerDevice.h>
#include <Tactility/Timer.h>
#include <Tactility/Assets.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <lvgl.h>

View File

@ -3,7 +3,7 @@
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/Assets.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <Tactility/Tactility.h>
#include <Tactility/Timer.h>

View File

@ -16,7 +16,7 @@ bool ObjectFileWriter::open() {
}
// Edit existing or create a new file
auto opening_file = std::unique_ptr<FILE, FileCloser>(std::fopen(filePath.c_str(), "wb"));
auto opening_file = std::unique_ptr<FILE, FileCloser>(std::fopen(filePath.c_str(), edit_existing ? "rb+" : "wb"));
if (opening_file == nullptr) {
LOGGER.error("Failed to open file {}", filePath);
return false;

View File

@ -1,106 +0,0 @@
#include <Tactility/hal/Device.h>
#include <Tactility/Logger.h>
#include <Tactility/RecursiveMutex.h>
#include <algorithm>
namespace tt::hal {
std::vector<std::shared_ptr<Device>> devices;
RecursiveMutex mutex;
static Device::Id nextId = 0;
static const auto LOGGER = Logger("Devices");
Device::Device() : id(nextId++) {}
template <std::ranges::range RangeType>
auto toVector(RangeType&& range) {
auto view = range | std::views::common;
return std::vector(view.begin(), view.end());
}
void registerDevice(const std::shared_ptr<Device>& device) {
auto scoped_mutex = mutex.asScopedLock();
scoped_mutex.lock();
if (findDevice(device->getId()) == nullptr) {
devices.push_back(device);
LOGGER.info("Registered {} with id {}", device->getName(), device->getId());
} else {
LOGGER.warn("Device {} with id {} was already registered", device->getName(), device->getId());
}
}
void deregisterDevice(const std::shared_ptr<Device>& device) {
auto scoped_mutex = mutex.asScopedLock();
scoped_mutex.lock();
auto id_to_remove = device->getId();
auto remove_iterator = std::remove_if(devices.begin(), devices.end(), [id_to_remove](const auto& device) {
return device->getId() == id_to_remove;
});
if (remove_iterator != devices.end()) {
LOGGER.info("Deregistering {} with id {}", device->getName(), device->getId());
devices.erase(remove_iterator);
} else {
LOGGER.warn("Deregistering {} with id {} failed: not found", device->getName(), device->getId());
}
}
std::vector<std::shared_ptr<Device>> findDevices(const std::function<bool(const std::shared_ptr<Device>&)>& filterFunction) {
auto scoped_mutex = mutex.asScopedLock();
scoped_mutex.lock();
auto devices_view = devices | std::views::filter([&filterFunction](auto& device) {
return filterFunction(device);
});
return toVector(devices_view);
}
std::shared_ptr<Device> _Nullable findDevice(const std::function<bool(const std::shared_ptr<Device>&)>& filterFunction) {
auto scoped_mutex = mutex.asScopedLock();
scoped_mutex.lock();
auto result_set = devices | std::views::filter([&filterFunction](auto& device) {
return filterFunction(device);
});
if (!result_set.empty()) {
return result_set.front();
} else {
return nullptr;
}
}
std::shared_ptr<Device> _Nullable findDevice(std::string name) {
return findDevice([&name](auto& device){
return device->getName() == name;
});
}
std::shared_ptr<Device> _Nullable findDevice(Device::Id id) {
return findDevice([id](auto& device){
return device->getId() == id;
});
}
std::vector<std::shared_ptr<Device>> findDevices(Device::Type type) {
return findDevices([type](auto& device) {
return device->getType() == type;
});
}
std::vector<std::shared_ptr<Device>> getDevices() {
return devices;
}
bool hasDevice(Device::Type type) {
auto scoped_mutex = mutex.asScopedLock();
scoped_mutex.lock();
auto result_set = devices | std::views::filter([&type](auto& device) {
return device->getType() == type;
});
return !result_set.empty();
}
}

View File

@ -2,7 +2,7 @@
#include <Tactility/Tactility.h>
#include <tactility/check.h>
#include <Tactility/hal/Configuration.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <Tactility/hal/power/PowerDevice.h>
#include <Tactility/hal/spi/SpiInit.h>
#include <Tactility/hal/uart/UartInit.h>

View File

@ -1,4 +1,4 @@
#include "Tactility/hal/Device.h"
#include <tactility/hal/Device.h>
#include "Tactility/hal/sdcard/SdCardDevice.h"
namespace tt::hal::sdcard {

View File

@ -14,7 +14,7 @@
#include <Tactility/Tactility.h>
#include <Tactility/TactilityConfig.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <Tactility/app/AppRegistration.h>
#include <Tactility/app/AppManifest.h>
#include <Tactility/app/App.h>

View File

@ -1,7 +1,7 @@
#include <Tactility/settings/DisplaySettings.h>
#include <Tactility/file/PropertiesFile.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <map>

View File

@ -15,7 +15,7 @@ typedef enum {
DEVICE_TYPE_KEYBOARD,
DEVICE_TYPE_POWER,
DEVICE_TYPE_GPS
} DeviceType;
} TtDeviceType;
typedef uint32_t DeviceId;
@ -27,7 +27,7 @@ typedef uint32_t DeviceId;
* @param[in] maxCount the maximum number of items that the "deviceIds" output can contain (minimum value is 1)
* @return true if one or more devices were found
*/
bool tt_hal_device_find(DeviceType type, DeviceId* deviceIds, uint16_t* count, uint16_t maxCount);
bool tt_hal_device_find(TtDeviceType type, DeviceId* deviceIds, uint16_t* count, uint16_t maxCount);
#ifdef __cplusplus
}

View File

@ -2,9 +2,9 @@
#include <tactility/check.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
static tt::hal::Device::Type toTactilityDeviceType(DeviceType type) {
static tt::hal::Device::Type toTactilityDeviceType(TtDeviceType type) {
switch (type) {
case DEVICE_TYPE_I2C:
return tt::hal::Device::Type::I2c;
@ -27,7 +27,7 @@ static tt::hal::Device::Type toTactilityDeviceType(DeviceType type) {
extern "C" {
bool tt_hal_device_find(DeviceType type, DeviceId* deviceIds, uint16_t* count, uint16_t maxCount) {
bool tt_hal_device_find(TtDeviceType type, DeviceId* deviceIds, uint16_t* count, uint16_t maxCount) {
assert(maxCount > 0);
int16_t currentIndex = -1;

View File

@ -2,7 +2,7 @@
#include <tactility/check.h>
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/display/DisplayDriver.h>

View File

@ -24,7 +24,7 @@ bool tt_hal_gpio_get_level(GpioPin pin) {
Device* device_result = find_first_gpio_controller();
if (device_result == nullptr) return false;
bool pin_state = false;
if (!gpio_controller_get_level(device_result, pin, &pin_state)) return false;
if (gpio_controller_get_level(device_result, pin, &pin_state) != ERROR_NONE) return false;
return pin_state;
}
@ -32,7 +32,7 @@ int tt_hal_gpio_get_pin_count() {
Device* device_result = find_first_gpio_controller();
if (device_result == nullptr) return 0;
uint32_t pin_count = 0;
if (!gpio_controller_get_pin_count(device_result, &pin_count)) return 0;
if (gpio_controller_get_pin_count(device_result, &pin_count) != ERROR_NONE) return 0;
return (int)pin_count;
}

View File

@ -1,6 +1,6 @@
#include "tt_hal_touch.h"
#include "Tactility/hal/Device.h"
#include <tactility/hal/Device.h>
#include "Tactility/hal/touch/TouchDevice.h"
#include "Tactility/hal/touch/TouchDriver.h"

View File

@ -4,5 +4,10 @@
#include "RTOS.h"
#ifndef ESP_PLATFORM
#define xPortInIsrContext(x) (false)
// TactilityKernel co-existence check
#ifndef xPortInIsrContext
#define xPortInIsrContext() (false)
#endif
#endif

View File

@ -37,6 +37,14 @@ error_t driver_construct(struct Driver* driver);
error_t driver_destruct(struct Driver* driver);
error_t driver_add(struct Driver* driver);
error_t driver_remove(struct Driver* driver);
error_t driver_construct_add(struct Driver* driver);
error_t driver_remove_destruct(struct Driver* driver);
error_t driver_bind(struct Driver* driver, struct Device* device);
error_t driver_unbind(struct Driver* driver, struct Device* device);

View File

@ -5,6 +5,15 @@
#include "freertos.h"
#ifndef ESP_PLATFORM
// TactilityFreeRTOS co-existence check
#ifndef xPortInIsrContext
#define xPortInIsrContext() (pdFALSE)
#endif
// TactilityFreeRTOS co-existence check
#ifndef vPortAssertIfInISR
#define vPortAssertIfInISR()
#endif
#endif

View File

@ -11,8 +11,8 @@ extern "C" {
/**
* Initialize the kernel with platform and device modules, and a device tree.
* @param platform_module The platform module to start. This module should not be constructed yet.
* @param device_module The device module to start. This module should not be constructed yet.
* @param devicetree_devices The list of generated devices from the devicetree. The array must be terminated by an entry { NULL, NULL }
* @param device_module The device module to start. This module should not be constructed yet. This parameter can be NULL.
* @param devicetree_devices The list of generated devices from the devicetree. The array must be terminated by an entry { NULL, NULL }. This parameter can be NULL.
* @return ERROR_NONE on success, otherwise an error code
*/
error_t kernel_init(struct Module* platform_module, struct Module* device_module, struct CompatibleDevice devicetree_devices[]);

View File

@ -17,6 +17,8 @@ struct Module {
/**
* The name of the module, for logging/debugging purposes
* Should never be NULL.
* Characters allowed: a-z A-Z 0-9 - _ .
* Desirable format "platform-esp32", "lilygo-tdeck", etc.
*/
const char* name;

View File

@ -1,3 +1,4 @@
#include <cstdlib>
#include <tactility/freertos/task.h>
#include <tactility/log.h>
@ -31,7 +32,7 @@ __attribute__((noreturn)) void __crash(void) {
#ifdef ESP_PLATFORM
esp_system_abort("System halted. Connect debugger for more info.");
#else
while (true) { /* Indefinite lock-up */ }
exit(1);
#endif
__builtin_unreachable();
}

View File

@ -49,7 +49,7 @@ error_t device_construct(Device* device) {
if (device->internal.device_private == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
LOG_I(TAG, "construct %s", device->name);
LOG_D(TAG, "construct %s", device->name);
mutex_construct(&device->internal.mutex);
return ERROR_NONE;
}
@ -61,7 +61,7 @@ error_t device_destruct(Device* device) {
if (!get_device_private(device)->children.empty()) {
return ERROR_INVALID_STATE;
}
LOG_I(TAG, "destruct %s", device->name);
LOG_D(TAG, "destruct %s", device->name);
mutex_destruct(&device->internal.mutex);
delete get_device_private(device);
device->internal.device_private = nullptr;
@ -88,7 +88,7 @@ static void device_remove_child(struct Device* device, struct Device* child) {
}
error_t device_add(Device* device) {
LOG_I(TAG, "add %s", device->name);
LOG_D(TAG, "add %s", device->name);
// Already added
if (device->internal.state.started || device->internal.state.added) {
@ -111,7 +111,7 @@ error_t device_add(Device* device) {
}
error_t device_remove(Device* device) {
LOG_I(TAG, "remove %s", device->name);
LOG_D(TAG, "remove %s", device->name);
if (device->internal.state.started || !device->internal.state.added) {
return ERROR_INVALID_STATE;

View File

@ -58,28 +58,6 @@ static DriverLedger& get_ledger() {
#define driver_lock(driver) mutex_lock(&get_driver_private(driver)->mutex);
#define driver_unlock(driver) mutex_unlock(&get_driver_private(driver)->mutex);
static void driver_add(Driver* driver) {
LOG_I(TAG, "add %s", driver->name);
ledger.lock();
ledger.drivers.push_back(driver);
ledger.unlock();
}
static error_t driver_remove(Driver* driver) {
LOG_I(TAG, "remove %s", driver->name);
ledger.lock();
const auto iterator = std::ranges::find(ledger.drivers, driver);
if (iterator == ledger.drivers.end()) {
ledger.unlock();
return ERROR_NOT_FOUND;
}
ledger.drivers.erase(iterator);
ledger.unlock();
return ERROR_NONE;
}
extern "C" {
error_t driver_construct(Driver* driver) {
@ -87,7 +65,6 @@ error_t driver_construct(Driver* driver) {
if (driver->driver_private == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
driver_add(driver);
return ERROR_NONE;
}
@ -104,10 +81,6 @@ error_t driver_destruct(Driver* driver) {
}
get_driver_private(driver)->destroying = true;
if (driver_remove(driver) != ERROR_NONE) {
LOG_W(TAG, "Failed to remove driver from ledger: %s", driver->name);
}
driver_unlock(driver);
delete get_driver_private(driver);
driver->driver_private = nullptr;
@ -115,6 +88,41 @@ error_t driver_destruct(Driver* driver) {
return ERROR_NONE;
}
error_t driver_add(Driver* driver) {
LOG_I(TAG, "add %s", driver->name);
ledger.lock();
ledger.drivers.push_back(driver);
ledger.unlock();
return ERROR_NONE;
}
error_t driver_remove(Driver* driver) {
LOG_I(TAG, "remove %s", driver->name);
ledger.lock();
const auto iterator = std::ranges::find(ledger.drivers, driver);
if (iterator == ledger.drivers.end()) {
ledger.unlock();
return ERROR_NOT_FOUND;
}
ledger.drivers.erase(iterator);
ledger.unlock();
return ERROR_NONE;
}
error_t driver_construct_add(struct Driver* driver) {
if (driver_construct(driver) != ERROR_NONE) return ERROR_RESOURCE;
if (driver_add(driver) != ERROR_NONE) return ERROR_RESOURCE;
return ERROR_NONE;
}
error_t driver_remove_destruct(struct Driver* driver) {
if (driver_remove(driver) != ERROR_NONE) return ERROR_RESOURCE;
if (driver_destruct(driver) != ERROR_NONE) return ERROR_RESOURCE;
return ERROR_NONE;
}
bool driver_is_compatible(Driver* driver, const char* compatible) {
if (compatible == nullptr || driver->compatible == nullptr) {
return false;

View File

@ -14,7 +14,7 @@ struct ModuleParent kernel_module_parent = {
static error_t init_kernel_drivers() {
extern Driver root_driver;
if (driver_construct(&root_driver) != ERROR_NONE) return ERROR_RESOURCE;
if (driver_construct_add(&root_driver) != ERROR_NONE) return ERROR_RESOURCE;
return ERROR_NONE;
}
@ -37,19 +37,23 @@ error_t kernel_init(struct Module* platform_module, struct Module* device_module
return ERROR_RESOURCE;
}
module_set_parent(device_module, &kernel_module_parent);
if (module_start(device_module) != ERROR_NONE) {
LOG_E(TAG, "init failed to start device module");
return ERROR_RESOURCE;
}
CompatibleDevice* compatible_device = devicetree_devices;
while (compatible_device->device != nullptr) {
if (device_construct_add_start(compatible_device->device, compatible_device->compatible) != ERROR_NONE) {
LOG_E(TAG, "kernel_init failed to construct device: %s (%s)", compatible_device->device->name, compatible_device->compatible);
if (device_module != nullptr) {
module_set_parent(device_module, &kernel_module_parent);
if (module_start(device_module) != ERROR_NONE) {
LOG_E(TAG, "init failed to start device module");
return ERROR_RESOURCE;
}
compatible_device++;
}
if (devicetree_devices) {
CompatibleDevice* compatible_device = devicetree_devices;
while (compatible_device->device != nullptr) {
if (device_construct_add_start(compatible_device->device, compatible_device->compatible) != ERROR_NONE) {
LOG_E(TAG, "kernel_init failed to construct device: %s (%s)", compatible_device->device->name, compatible_device->compatible);
return ERROR_RESOURCE;
}
compatible_device++;
}
}
LOG_I(TAG, "init done");

View File

@ -3,6 +3,8 @@
#include <tactility/concurrent/mutex.h>
#include <tactility/module.h>
#define TAG LOG_TAG(module)
struct ModuleParentPrivate {
std::vector<struct Module*> modules;
struct Mutex mutex = { 0 };
@ -72,6 +74,8 @@ error_t module_set_parent(struct Module* module, struct ModuleParent* parent) {
}
error_t module_start(struct Module* module) {
LOG_I(TAG, "start %s", module->name);
if (module->internal.started) return ERROR_NONE;
if (!module->internal.parent) return ERROR_INVALID_STATE;
@ -85,6 +89,8 @@ bool module_is_started(struct Module* module) {
}
error_t module_stop(struct Module* module) {
LOG_I(TAG, "stop %s", module->name);
if (!module->internal.started) return ERROR_NONE;
error_t error = module->stop();

View File

@ -22,6 +22,8 @@ target_link_libraries(TactilityTests PRIVATE
Tactility
TactilityCore
TactilityKernel
PlatformPosix
hal-device
Simulator
SDL2::SDL2-static SDL2-static
)

View File

@ -1,5 +1,5 @@
#include "doctest.h"
#include <Tactility/hal/Device.h>
#include <tactility/hal/Device.h>
#include <utility>

View File

@ -5,12 +5,27 @@
#include "FreeRTOS.h"
#include "task.h"
#include <tactility/kernel_init.h>
#include <tactility/hal_device_module.h>
typedef struct {
int argc;
char** argv;
int result;
} TestTaskData;
extern "C" {
// From the relevant platform
extern struct Module platform_module;
// From the relevant device
extern struct Module device_module;
}
struct ModuleParent tactility_tests_module_parent {
"tactility-tests",
nullptr
};
void test_task(void* parameter) {
auto* data = (TestTaskData*)parameter;
@ -21,6 +36,12 @@ void test_task(void* parameter) {
// overrides
context.setOption("no-breaks", true); // don't break in the debugger when assertions fail
check(kernel_init(&platform_module, &device_module, nullptr) == ERROR_NONE);
// HAL compatibility module: it creates kernel driver wrappers for tt::hal::Device
check(module_parent_construct(&tactility_tests_module_parent) == ERROR_NONE);
check(module_set_parent(&hal_device_module, &tactility_tests_module_parent) == ERROR_NONE);
check(module_start(&hal_device_module) == ERROR_NONE);
data->result = context.run();
vTaskEndScheduler();
@ -46,4 +67,6 @@ int main(int argc, char** argv) {
assert(task_result == pdPASS);
vTaskStartScheduler();
return data.result;
}

View File

@ -56,8 +56,10 @@ TEST_CASE("Appending records to a file") {
CHECK_EQ(reader.open(), true);
CHECK_EQ(reader.hasNext(), true);
CHECK_EQ(reader.readNext(&record_in), true);
CHECK_EQ(record_in.value, 0xAAAAAAAA);
CHECK_EQ(reader.hasNext(), true);
CHECK_EQ(reader.readNext(&record_in), true);
CHECK_EQ(record_in.value, 0xBBBBBBBB);
CHECK_EQ(reader.hasNext(), false);
reader.close();

View File

@ -11,4 +11,8 @@ target_include_directories(TactilityKernelTests PRIVATE ${DOCTESTINC})
add_test(NAME TactilityKernelTests COMMAND TactilityKernelTests)
target_link_libraries(TactilityKernelTests PUBLIC TactilityKernel)
target_link_libraries(TactilityKernelTests PUBLIC
TactilityKernel
PlatformPosix
hal-device
)

View File

@ -177,7 +177,7 @@ TEST_CASE("device_is_ready should return true only when it is started") {
Device device = { 0 };
CHECK_EQ(driver_construct(&driver), ERROR_NONE);
CHECK_EQ(driver_construct_add(&driver), ERROR_NONE);
CHECK_EQ(device_construct(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.started, false);
@ -192,6 +192,6 @@ TEST_CASE("device_is_ready should return true only when it is started") {
CHECK_EQ(device_remove(&device), ERROR_NONE);
CHECK_EQ(device.internal.state.started, false);
CHECK_EQ(driver_destruct(&driver), ERROR_NONE);
CHECK_EQ(device_destruct(&device), ERROR_NONE);
CHECK_EQ(driver_remove_destruct(&driver), ERROR_NONE);
}

View File

@ -55,7 +55,7 @@ TEST_CASE("driver with with start success and stop success should start and stop
.parent = nullptr,
};
CHECK_EQ(driver_construct(&integration_driver), ERROR_NONE);
CHECK_EQ(driver_construct_add(&integration_driver), ERROR_NONE);
CHECK_EQ(device_construct(&integration_device), ERROR_NONE);
device_add(&integration_device);
@ -68,5 +68,5 @@ TEST_CASE("driver with with start success and stop success should start and stop
CHECK_EQ(device_remove(&integration_device), ERROR_NONE);
CHECK_EQ(device_destruct(&integration_device), ERROR_NONE);
CHECK_EQ(driver_destruct(&integration_driver), ERROR_NONE);
CHECK_EQ(driver_remove_destruct(&integration_driver), ERROR_NONE);
}

View File

@ -14,7 +14,9 @@ TEST_CASE("driver_construct and driver_destruct should set and unset the correct
driver.owner = &module;
CHECK_EQ(driver_construct(&driver), ERROR_NONE);
CHECK_EQ(driver_add(&driver), ERROR_NONE);
CHECK_NE(driver.driver_private, nullptr);
CHECK_EQ(driver_remove(&driver), ERROR_NONE);
CHECK_EQ(driver_destruct(&driver), ERROR_NONE);
CHECK_EQ(driver.driver_private, nullptr);
}
@ -62,10 +64,12 @@ TEST_CASE("driver_find should only find a compatible driver when the driver was
CHECK_EQ(found_driver, nullptr);
CHECK_EQ(driver_construct(&driver), ERROR_NONE);
CHECK_EQ(driver_add(&driver), ERROR_NONE);
found_driver = driver_find_compatible("test_compatible");
CHECK_EQ(found_driver, &driver);
CHECK_EQ(driver_remove(&driver), ERROR_NONE);
CHECK_EQ(driver_destruct(&driver), ERROR_NONE);
found_driver = driver_find_compatible("test_compatible");

View File

@ -3,6 +3,7 @@
#include <cassert>
#include <tactility/freertos/task.h>
#include <tactility/kernel_init.h>
typedef struct {
int argc;
@ -10,6 +11,11 @@ typedef struct {
int result;
} TestTaskData;
extern "C" {
// From the relevant platform
extern struct Module platform_module;
}
void test_task(void* parameter) {
auto* data = (TestTaskData*)parameter;
@ -20,6 +26,8 @@ void test_task(void* parameter) {
// overrides
context.setOption("no-breaks", true); // don't break in the debugger when assertions fail
check(kernel_init(&platform_module, nullptr, nullptr) == ERROR_NONE);
data->result = context.run();
vTaskEndScheduler();