Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Documentation/ideas.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

## Higher Priority

- coreutils apps: Only emit colour escapes when stdout is the terminal (command_support.h color() function)
- NimBLE looses pairing key after reboot
- CrashDiagnostics shouldn't show a QR when there's no callstack
- Move USB host task stacks to SPIRAM when available: esp32_usbhost*.cpp
- Add bold fonts for e-ink readability improvement
Expand All @@ -18,7 +18,7 @@
- TCA9534 keyboards should use interrupts
- External app loading: Check the version of Tactility and check ESP target hardware to check for compatibility
Check during installation process, but also when starting (SD card might have old app install from before Tactility OS update)
- Support direct installation of an `.app` file with `tactility.py install helloworld.app <ip>`
- Support direct installation of an `.app` file with `tactility.py install --app helloworld.app`
- minitar/untarFile(): "entry->metadata.path" can escape its confined path (e.g. "../something")

## Medium Priority
Expand Down
4 changes: 4 additions & 0 deletions Drivers/lilygo-module/source/tdeck_keyboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ static error_t tdeck_keyboard_read_key(Device* device, KeyboardKeyData* data) {
data->key = internal->last_key;
data->pressed = false;
} else if (read_buffer != 0) {
// Return key is keycode 10, but keyboard passes 13
if (read_buffer == 13) {
read_buffer = 10;
}
data->key = read_buffer;
data->pressed = true;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ error_t api_load(AppLocation location, AppRuntime* out_runtime) {
// esp_elf_relocate() already frees elf->pdata/ptext itself on a relocation failure
free(runtime->file_data);
delete runtime;
LOG_E(TAG, "Failed to map elf");
LOG_E(TAG, "Failed to map elf. Possibly out of memory.");
return ERROR_RESOURCE;
}

Expand Down
192 changes: 192 additions & 0 deletions Modules/app-esp32-module/source/app_symbols.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: Apache-2.0

// Symbols resolved for ELF apps ahead of every other module's (see app_esp32_symbol_resolver()).
// Firmware code calling the same functions never gets here.
// Pending signals are delivered here, at the entry and again when a call was interrupted by one,
// since the app holds no lock of the system itself there.
// Allocations are counted per app instance (see app/memory.h).
#include <app/libc.h>
#include <app/memory.h>
#include <app/signal.h>

#include <esp_heap_caps.h>

#include <tactility/module.h>

#include <sys/poll.h>
#include <unistd.h>

#include <cerrno>
#include <csignal>
#include <cstdlib>
#include <cstring>
#include <new>

namespace {

ssize_t app_read(int fd, void* buffer, size_t size) {
app_signal_deliver_pending();
const ssize_t result = read(fd, buffer, size);
if (result < 0 && errno == EINTR) {
app_signal_deliver_pending();
}
return result;
}

ssize_t app_write(int fd, const void* buffer, size_t size) {
app_signal_deliver_pending();
const ssize_t result = write(fd, buffer, size);
if (result < 0 && errno == EINTR) {
app_signal_deliver_pending();
}
return result;
}

int app_poll(struct pollfd* fds, nfds_t nfds, int timeout) {
app_signal_deliver_pending();
const int result = poll(fds, nfds, timeout);
if (result < 0 && errno == EINTR) {
app_signal_deliver_pending();
}
return result;
}

int app_usleep(useconds_t usec) {
app_signal_deliver_pending();
int result;
if (!app_libc_try_usleep(usec, &result)) {
return usleep(usec);
}
app_signal_deliver_pending();
return result;
}

unsigned int app_sleep(unsigned int seconds) {
app_signal_deliver_pending();
unsigned int result;
if (!app_libc_try_sleep(seconds, &result)) {
return sleep(seconds);
}
app_signal_deliver_pending();
return result;
}

int app_kill(pid_t pid, int sig) {
const int result = kill(pid, sig);
// A signal sent to the app itself is delivered before kill() returns
app_signal_deliver_pending();
return result;
}

pid_t app_getpid() {
int result;
return app_libc_try_getpid(&result) ? result : 0;
}

pid_t app_getppid() {
int result;
return app_libc_try_getppid(&result) ? result : 0;
}

void* record_alloc(void* ptr) {
if (ptr != nullptr) {
app_memory_record_alloc(heap_caps_get_allocated_size(ptr));
}
return ptr;
}

void record_free(void* ptr) {
if (ptr != nullptr) {
app_memory_record_free(heap_caps_get_allocated_size(ptr));
}
}

void* app_malloc(size_t size) {
return record_alloc(malloc(size));
}

void* app_calloc(size_t count, size_t size) {
return record_alloc(calloc(count, size));
}

void* app_realloc(void* ptr, size_t size) {
const size_t old_size = (ptr != nullptr) ? heap_caps_get_allocated_size(ptr) : 0;
void* result = realloc(ptr, size);
// A failed realloc() leaves the old block allocated
if (result != nullptr || size == 0) {
if (ptr != nullptr) {
app_memory_record_free(old_size);
}
record_alloc(result);
}
return result;
}

void app_free(void* ptr) {
record_free(ptr);
free(ptr);
}

char* app_strdup(const char* s) {
return static_cast<char*>(record_alloc(strdup(s)));
}

char* app_strndup(const char* s, size_t n) {
return static_cast<char*>(record_alloc(strndup(s, n)));
}

void* app_operator_new(size_t size) {
return record_alloc(::operator new(size));
}

void* app_operator_new_array(size_t size) {
return record_alloc(::operator new[](size));
}

void app_operator_delete(void* ptr) {
record_free(ptr);
::operator delete(ptr);
}

void app_operator_delete_array(void* ptr) {
record_free(ptr);
::operator delete[](ptr);
}

void app_operator_delete_sized(void* ptr, size_t) {
app_operator_delete(ptr);
}

void app_operator_delete_array_sized(void* ptr, size_t) {
app_operator_delete_array(ptr);
}

} // namespace

extern "C" {

extern const ModuleSymbol app_esp32_symbols[] = {
{ "read", reinterpret_cast<void*>(app_read) },
{ "write", reinterpret_cast<void*>(app_write) },
{ "poll", reinterpret_cast<void*>(app_poll) },
{ "usleep", reinterpret_cast<void*>(app_usleep) },
{ "sleep", reinterpret_cast<void*>(app_sleep) },
{ "kill", reinterpret_cast<void*>(app_kill) },
{ "getpid", reinterpret_cast<void*>(app_getpid) },
{ "getppid", reinterpret_cast<void*>(app_getppid) },
{ "malloc", reinterpret_cast<void*>(app_malloc) },
{ "calloc", reinterpret_cast<void*>(app_calloc) },
{ "realloc", reinterpret_cast<void*>(app_realloc) },
{ "free", reinterpret_cast<void*>(app_free) },
{ "strdup", reinterpret_cast<void*>(app_strdup) },
{ "strndup", reinterpret_cast<void*>(app_strndup) },
{ "_Znwj", reinterpret_cast<void*>(app_operator_new) }, // operator new(unsigned int)
{ "_Znaj", reinterpret_cast<void*>(app_operator_new_array) }, // operator new[](unsigned int)
{ "_ZdlPv", reinterpret_cast<void*>(app_operator_delete) }, // operator delete(void*)
{ "_ZdaPv", reinterpret_cast<void*>(app_operator_delete_array) }, // operator delete[](void*)
{ "_ZdlPvj", reinterpret_cast<void*>(app_operator_delete_sized) }, // operator delete(void*, unsigned int)
{ "_ZdaPvj", reinterpret_cast<void*>(app_operator_delete_array_sized) }, // operator delete[](void*, unsigned int)
MODULE_SYMBOL_TERMINATOR
};

}
10 changes: 7 additions & 3 deletions Modules/app-esp32-module/source/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
extern "C" {

extern ServiceManifest loader_service_manifest;
extern const ModuleSymbol app_esp32_symbols[];
extern Module app_esp32_module;

// Overrides elf_loader's default KConfig-based symbol resolver with one that looks up symbols
// across every started kernel module's own symbol table instead.
// across every started kernel module's own symbol table instead. This module's own symbols come
// first, as they replace libc functions for apps (see app_symbols.cpp).
uintptr_t app_esp32_symbol_resolver(const char* symbolName) {
uintptr_t symbol_address;
if (module_resolve_symbol_global(symbolName, &symbol_address)) {
if (module_resolve_symbol(&app_esp32_module, symbolName, &symbol_address) ||
module_resolve_symbol_global(symbolName, &symbol_address)) {
return symbol_address;
}
return 0;
Expand All @@ -37,7 +41,7 @@ Module app_esp32_module = {
.start = start,
.stop = stop,
.drivers = nullptr,
.symbols = nullptr,
.symbols = app_esp32_symbols,
.internal = nullptr,
};

Expand Down
15 changes: 13 additions & 2 deletions Modules/app-module/include/app/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extern "C" {
enum AppEventType {
APP_EVENT_RESULT, // struct AppResultEventData
APP_EVENT_CLOSE, // no data - terminate now, permanently
APP_EVENT_SIGNAL, // struct AppSignalEventData, see app/signal.h
};

/** Data for APP_EVENT_RESULT. */
Expand All @@ -29,12 +30,22 @@ struct AppResultEventData {
int32_t result;
};

/** Data for APP_EVENT_SIGNAL. */
struct AppSignalEventData {
/** A POSIX signal number, e.g. SIGHUP */
int sig;
};

struct AppEvent {
enum AppEventType type;
/** Stamped by app_event_emit(); any value passed in by the caller is ignored. */
uint64_t timestamp;
/** Valid only when type == APP_EVENT_RESULT. */
struct AppResultEventData result;
union {
/** Valid only when type == APP_EVENT_RESULT. */
struct AppResultEventData result;
/** Valid only when type == APP_EVENT_SIGNAL. */
struct AppSignalEventData signal;
};
};

/**
Expand Down
28 changes: 24 additions & 4 deletions Modules/app-module/include/app/libc.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,40 @@ bool app_libc_try_tcsetattr(int fd, const struct termios* t, int* out_result);
typedef void (*AppLibcSignalHandler)(int sig);

/**
* Records @a handler for @a sig in the calling app instance. Signals aren't delivered to apps yet,
* so the handler is never called. SIGKILL, SIGSTOP and out-of-range signals fail with EINVAL.
* Records @a handler for @a sig in the calling app instance, called when a signal is delivered (see
* app/signal.h). SIGKILL, SIGSTOP and out-of-range signals fail with EINVAL.
* @param[out] out_previous the previously registered handler, or SIG_ERR on failure
*/
bool app_libc_try_signal(int sig, AppLibcSignalHandler handler, AppLibcSignalHandler* out_previous);

/** Apps can't send signals: kill() from an app fails with ENOSYS rather than reaching the process. */
/**
* Sends @a sig to the app instance @a pid via app_signal_send(), never to the process.
* A @a sig of 0 only checks that @a pid exists. SIGKILL, SIGSTOP and out-of-range signals fail
* with EINVAL. A @a pid that is not a running app instance fails with ESRCH, including pid <= 0,
* since there are no process groups.
*/
bool app_libc_try_kill(int pid, int sig, int* out_result);

/** The calling app instance's AppInstanceId. */
bool app_libc_try_getpid(int* out_result);

/** The AppInstanceId of the calling app instance's parent, or 0 for a top-level launch. */
bool app_libc_try_getppid(int* out_result);

/** Sleeps the calling app instance. Fails with EINTR when a signal is pending or arrives (see app/signal.h). */
bool app_libc_try_usleep(unsigned long usec, int* out_result);

/**
* Sleeps the calling app instance. Returns early when a signal is pending or arrives (see app/signal.h).
* @param[out] out_result the whole seconds not slept, 0 when not interrupted
*/
bool app_libc_try_sleep(unsigned int seconds, unsigned int* out_result);

typedef int (*AppLibcPollFunction)(struct pollfd* fds, nfds_t nfds, int timeout);

/**
* Handles poll() when @a fds contains an app fd. Other fds are checked with @a real_poll.
* Closed app fds report POLLNVAL.
* Closed app fds report POLLNVAL. Fails with EINTR when a signal is pending (see app/signal.h).
*/
bool app_libc_try_poll(struct pollfd* fds, nfds_t nfds, int timeout, AppLibcPollFunction real_poll, int* out_result);

Expand Down
25 changes: 25 additions & 0 deletions Modules/app-module/include/app/memory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
* Counts an allocation made by the calling app instance's own code. When the app ends with
* allocations left, a warning is logged. Does nothing outside an app's own task.
* @param[in] size the allocated block's size
*/
void app_memory_record_alloc(size_t size);

/**
* Counts a free by the calling app instance's own code. Does nothing outside an app's own task.
* @param[in] size the freed block's size
*/
void app_memory_record_free(size_t size);

#ifdef __cplusplus
}
#endif
Loading
Loading