File locking refactored (#631)

Remove FileMutex and wire locking into driver subsystems.
This commit is contained in:
Ken Van Hoeylandt
2026-08-28 01:06:53 +02:00
committed by GitHub
parent 92ca046681
commit 020aa471e2
44 changed files with 766 additions and 993 deletions
@@ -10,6 +10,13 @@ A driver generally consists of:
Drivers are part of a kernel module. Drivers are part of a kernel module.
A driver whose device sits on a shared SPI controller (parent device type `SPI_CONTROLLER_TYPE`)
must bracket its own bus access with `spi_controller_lock()`/`spi_controller_unlock()`
(`spi_controller_lock_bus_of()`/`spi_controller_unlock_bus_of()` for the common case of a direct
child), at the logical-operation level rather than per primitive. This is not done for you by the
kernel's generic device-type wrappers (`display.cpp`, `pointer.cpp`, etc.) - only the driver knows
for certain which of its calls touch the bus.
Modules with drivers can be stored in: Modules with drivers can be stored in:
- TactilityKernel - TactilityKernel
- A subproject in `Platforms` folder - A subproject in `Platforms` folder
-2
View File
@@ -12,7 +12,6 @@
## Higher Priority ## Higher Priority
- CrashDiagnostics shouldn't show a QR when there's no callstack - CrashDiagnostics shouldn't show a QR when there's no callstack
- lvgl file lock won't work with display vs sdcard when lvgl is stopped (external app bug risk)
- Apps should be able to specify stack size in their manifest, per architecture. - Apps should be able to specify stack size in their manifest, per architecture.
Use thread_get_stack_space() to find out the unused bytes Use thread_get_stack_space() to find out the unused bytes
- Apps currently have a `Context` object with an `appInstanceId` in it, purely for being able to close the app. - Apps currently have a `Context` object with an `appInstanceId` in it, purely for being able to close the app.
@@ -24,7 +23,6 @@
- Get rid of WiFi service (Wifi.cpp/h) in Tactility.cpp - Get rid of WiFi service (Wifi.cpp/h) in Tactility.cpp
- Make it more clear to end-users that an SD card is required to run Tactility - Make it more clear to end-users that an SD card is required to run Tactility
- Make it possible to override stack size for an app via config file (loaded at boot), and make it possible to set preferred memory location (e.g. internal/external) - Make it possible to override stack size for an app via config file (loaded at boot), and make it possible to set preferred memory location (e.g. internal/external)
- Wrap file operations like fopen/fclose with file_mutex
- Add bold fonts for e-ink readability improvement - Add bold fonts for e-ink readability improvement
- Httpd.cpp: warn if running on same CPU core (or task) as UI/LVGL/window manager. - Httpd.cpp: warn if running on same CPU core (or task) as UI/LVGL/window manager.
- Improve Setup: Show "Step done" screen - Improve Setup: Show "Step done" screen
@@ -27,6 +27,7 @@ constexpr auto* TAG = "esp_epaper";
constexpr uint16_t MAX_PANEL_DIMENSION = 2048; constexpr uint16_t MAX_PANEL_DIMENSION = 2048;
struct EspEpaperInternal { struct EspEpaperInternal {
Device* spi_controller;
/** Opaque esp_epaper device, owns the panel's pins and SPI device. */ /** Opaque esp_epaper device, owns the panel's pins and SPI device. */
epd_handle_t epd; epd_handle_t epd;
epd_panel_info_t info; epd_panel_info_t info;
@@ -77,7 +78,9 @@ static error_t esp_epaper_reset(Device* device) {
auto* internal = static_cast<EspEpaperInternal*>(device_get_driver_data(device)); auto* internal = static_cast<EspEpaperInternal*>(device_get_driver_data(device));
xSemaphoreTake(internal->panel_mutex, portMAX_DELAY); xSemaphoreTake(internal->panel_mutex, portMAX_DELAY);
// epd_wake() toggles the reset pin and re-runs the full init sequence. // epd_wake() toggles the reset pin and re-runs the full init sequence.
spi_controller_lock(internal->spi_controller);
const esp_err_t ret = epd_wake(internal->epd); const esp_err_t ret = epd_wake(internal->epd);
spi_controller_unlock(internal->spi_controller);
// epd_wake() re-inits the panel, so it is awake (and drawable) again. // epd_wake() re-inits the panel, so it is awake (and drawable) again.
if (ret == ESP_OK) { if (ret == ESP_OK) {
internal->display_on = true; internal->display_on = true;
@@ -89,7 +92,9 @@ static error_t esp_epaper_reset(Device* device) {
static error_t esp_epaper_init(Device* device) { static error_t esp_epaper_init(Device* device) {
auto* internal = static_cast<EspEpaperInternal*>(device_get_driver_data(device)); auto* internal = static_cast<EspEpaperInternal*>(device_get_driver_data(device));
xSemaphoreTake(internal->panel_mutex, portMAX_DELAY); xSemaphoreTake(internal->panel_mutex, portMAX_DELAY);
spi_controller_lock(internal->spi_controller);
const esp_err_t ret = epd_wake(internal->epd); const esp_err_t ret = epd_wake(internal->epd);
spi_controller_unlock(internal->spi_controller);
if (ret == ESP_OK) { if (ret == ESP_OK) {
internal->display_on = true; internal->display_on = true;
} }
@@ -133,7 +138,9 @@ static error_t esp_epaper_draw_bitmap(Device* device, int32_t x_start, int32_t y
source = internal->rotate_buffer; source = internal->rotate_buffer;
} }
spi_controller_lock(internal->spi_controller);
const esp_err_t ret = epd_update(internal->epd, source, config->update_mode); const esp_err_t ret = epd_update(internal->epd, source, config->update_mode);
spi_controller_unlock(internal->spi_controller);
xSemaphoreGive(internal->panel_mutex); xSemaphoreGive(internal->panel_mutex);
if (ret != ESP_OK) { if (ret != ESP_OK) {
LOG_E(TAG, "epd_update failed: %s", esp_err_to_name(ret)); LOG_E(TAG, "epd_update failed: %s", esp_err_to_name(ret));
@@ -152,6 +159,7 @@ static error_t esp_epaper_disp_on_off(Device* device, bool on_off) {
} }
bool ok = true; bool ok = true;
spi_controller_lock(internal->spi_controller);
if (on_off) { if (on_off) {
if (epd_wake(internal->epd) != ESP_OK) { if (epd_wake(internal->epd) != ESP_OK) {
LOG_E(TAG, "epd_wake failed"); LOG_E(TAG, "epd_wake failed");
@@ -163,6 +171,7 @@ static error_t esp_epaper_disp_on_off(Device* device, bool on_off) {
ok = false; ok = false;
} }
} }
spi_controller_unlock(internal->spi_controller);
if (ok) { if (ok) {
internal->display_on = on_off; internal->display_on = on_off;
@@ -220,7 +229,9 @@ static const DisplayApi esp_epaper_display_api = {
static void free_internal(EspEpaperInternal* internal) { static void free_internal(EspEpaperInternal* internal) {
if (internal->epd != nullptr) { if (internal->epd != nullptr) {
spi_controller_lock(internal->spi_controller);
epd_deinit(internal->epd); epd_deinit(internal->epd);
spi_controller_unlock(internal->spi_controller);
} }
if (internal->rotate_buffer != nullptr) { if (internal->rotate_buffer != nullptr) {
free(internal->rotate_buffer); free(internal->rotate_buffer);
@@ -284,6 +295,7 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
internal->epd = epd; internal->epd = epd;
internal->panel_mutex = xSemaphoreCreateMutex(); internal->panel_mutex = xSemaphoreCreateMutex();
if (internal->panel_mutex == nullptr) { if (internal->panel_mutex == nullptr) {
@@ -331,7 +343,9 @@ static error_t stop(Device* device) {
xSemaphoreTake(internal->panel_mutex, portMAX_DELAY); xSemaphoreTake(internal->panel_mutex, portMAX_DELAY);
if (internal->display_on) { if (internal->display_on) {
// Leave the panel in deep sleep. // Leave the panel in deep sleep.
spi_controller_lock(internal->spi_controller);
epd_sleep(internal->epd); epd_sleep(internal->epd);
spi_controller_unlock(internal->spi_controller);
internal->display_on = false; internal->display_on = false;
} }
xSemaphoreGive(internal->panel_mutex); xSemaphoreGive(internal->panel_mutex);
+48 -9
View File
@@ -25,6 +25,7 @@ constexpr auto* TAG = "GC9A01";
#define GET_CONFIG(device) (static_cast<const Gc9a01Config*>((device)->config)) #define GET_CONFIG(device) (static_cast<const Gc9a01Config*>((device)->config))
struct Gc9a01Internal { struct Gc9a01Internal {
Device* spi_controller;
esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_io_handle_t io_handle;
esp_lcd_panel_handle_t panel_handle; esp_lcd_panel_handle_t panel_handle;
// See st7796-module's identical field for why this exists: draw_bitmap() must block until // See st7796-module's identical field for why this exists: draw_bitmap() must block until
@@ -63,6 +64,7 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
internal->draw_done_semaphore = xSemaphoreCreateBinary(); internal->draw_done_semaphore = xSemaphoreCreateBinary();
if (internal->draw_done_semaphore == nullptr) { if (internal->draw_done_semaphore == nullptr) {
free(internal); free(internal);
@@ -123,6 +125,7 @@ static error_t start(Device* device) {
// Bring-up sequence, order matches the deprecated HAL's Gc9a01Display (proven correct on real // Bring-up sequence, order matches the deprecated HAL's Gc9a01Display (proven correct on real
// Waveshare S3 Touch LCD 1.28 hardware). Every failure path below must clean up fully: unlike // Waveshare S3 Touch LCD 1.28 hardware). Every failure path below must clean up fully: unlike
// stop_device, this is never retried by the kernel if start_device fails. // stop_device, this is never retried by the kernel if start_device fails.
spi_controller_lock(internal->spi_controller);
bool ok = bool ok =
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK && esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK; esp_lcd_panel_init(internal->panel_handle) == ESP_OK;
@@ -134,6 +137,7 @@ static error_t start(Device* device) {
ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK); ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK);
ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
spi_controller_unlock(internal->spi_controller);
if (!ok) { if (!ok) {
LOG_E(TAG, "Failed to bring up panel"); LOG_E(TAG, "Failed to bring up panel");
@@ -151,9 +155,11 @@ static error_t start(Device* device) {
static error_t stop(Device* device) { static error_t stop(Device* device) {
auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
if (internal->panel_handle != nullptr) { if (internal->panel_handle != nullptr) {
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) { if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel"); LOG_E(TAG, "Failed to delete panel");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->panel_handle = nullptr; internal->panel_handle = nullptr;
@@ -162,10 +168,12 @@ static error_t stop(Device* device) {
if (internal->io_handle != nullptr) { if (internal->io_handle != nullptr) {
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO"); LOG_E(TAG, "Failed to delete panel IO");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->io_handle = nullptr; internal->io_handle = nullptr;
} }
spi_controller_unlock(internal->spi_controller);
vSemaphoreDelete(internal->draw_done_semaphore); vSemaphoreDelete(internal->draw_done_semaphore);
free(internal); free(internal);
@@ -179,12 +187,18 @@ static error_t stop(Device* device) {
static error_t gc9a01_reset(Device* device) { static error_t gc9a01_reset(Device* device) {
auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t gc9a01_init(Device* device) { static error_t gc9a01_init(Device* device) {
auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t gc9a01_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { static error_t gc9a01_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
@@ -192,22 +206,35 @@ static error_t gc9a01_draw_bitmap(Device* device, int32_t x_start, int32_t y_sta
xSemaphoreTake(internal->draw_done_semaphore, 0); xSemaphoreTake(internal->draw_done_semaphore, 0);
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { spi_controller_lock(internal->spi_controller);
esp_err_t ret = esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data);
if (ret != ESP_OK) {
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
// Hold the bus lock across the wait too, not just the queueing call: the command/data phases
// aren't wrapped in their own acquire_bus by esp_lcd_panel_io_spi, so another bus user could
// otherwise interleave with this transfer while it's still in flight.
xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t gc9a01_mirror(Device* device, bool x_axis, bool y_axis) { static error_t gc9a01_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t gc9a01_swap_xy(Device* device, bool swap_axes) { static error_t gc9a01_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static bool gc9a01_get_swap_xy(Device* device) { static bool gc9a01_get_swap_xy(Device* device) {
@@ -224,7 +251,10 @@ static bool gc9a01_get_mirror_y(Device* device) {
static error_t gc9a01_set_gap(Device* device, int32_t x_gap, int32_t y_gap) { static error_t gc9a01_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device));
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static int32_t gc9a01_get_gap_x(Device* device) { static int32_t gc9a01_get_gap_x(Device* device) {
@@ -237,17 +267,26 @@ static int32_t gc9a01_get_gap_y(Device* device) {
static error_t gc9a01_invert_color(Device* device, bool invert_color_data) { static error_t gc9a01_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device));
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t gc9a01_disp_on_off(Device* device, bool on_off) { static error_t gc9a01_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t gc9a01_disp_sleep(Device* device, bool sleep) { static error_t gc9a01_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Gc9a01Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// The deprecated HAL's Gc9a01Display always set swap_bytes=true on its lvgl_port config // The deprecated HAL's Gc9a01Display always set swap_bytes=true on its lvgl_port config
@@ -51,6 +51,7 @@ static constexpr uint8_t DEEP_SLEEP_CHECK_CODE = 0xA5;
extern "C" { extern "C" {
struct Gdeq031t10Internal { struct Gdeq031t10Internal {
Device* spi_controller;
spi_device_handle_t spi_device; spi_device_handle_t spi_device;
struct GpioDescriptor* dc; struct GpioDescriptor* dc;
struct GpioDescriptor* reset; // optional struct GpioDescriptor* reset; // optional
@@ -123,12 +124,14 @@ static bool wait_while_busy(Gdeq031t10Internal* internal) {
} }
static void hardware_reset(Gdeq031t10Internal* internal) { static void hardware_reset(Gdeq031t10Internal* internal) {
spi_controller_lock(internal->spi_controller);
if (internal->reset != nullptr) { if (internal->reset != nullptr) {
gpio_descriptor_set_level(internal->reset, false); gpio_descriptor_set_level(internal->reset, false);
delay_millis(10); delay_millis(10);
gpio_descriptor_set_level(internal->reset, true); gpio_descriptor_set_level(internal->reset, true);
delay_millis(10); delay_millis(10);
} }
spi_controller_unlock(internal->spi_controller);
} }
static bool init_full(Gdeq031t10Internal* internal, bool mirror_180) { static bool init_full(Gdeq031t10Internal* internal, bool mirror_180) {
@@ -137,10 +140,12 @@ static bool init_full(Gdeq031t10Internal* internal, bool mirror_180) {
// mode change needs partial mode's lingering VCOM/data-interval setting cleared, and // mode change needs partial mode's lingering VCOM/data-interval setting cleared, and
// waking from deep sleep is only possible by toggling RST. // waking from deep sleep is only possible by toggling RST.
hardware_reset(internal); hardware_reset(internal);
spi_controller_lock(internal->spi_controller);
bool ok = write_command(internal, CMD_PANEL_SETTING); bool ok = write_command(internal, CMD_PANEL_SETTING);
ok = ok && write_data_byte(internal, mirror_180 ? 0x13 : 0x1F); ok = ok && write_data_byte(internal, mirror_180 ? 0x13 : 0x1F);
ok = ok && write_command(internal, CMD_POWER_ON); ok = ok && write_command(internal, CMD_POWER_ON);
ok = ok && wait_while_busy(internal); ok = ok && wait_while_busy(internal);
spi_controller_unlock(internal->spi_controller);
if (!ok) { if (!ok) {
LOG_E(TAG, "Full init failed"); LOG_E(TAG, "Full init failed");
return false; return false;
@@ -164,6 +169,7 @@ static bool init_with_fast_lut(Gdeq031t10Internal* internal, bool mirror_180, en
case GDEQ031T10_REFRESH_PARTIAL: timing = 0x79; break; case GDEQ031T10_REFRESH_PARTIAL: timing = 0x79; break;
default: return true; // GDEQ031T10_REFRESH_FULL: init_full() already did everything default: return true; // GDEQ031T10_REFRESH_FULL: init_full() already did everything
} }
spi_controller_lock(internal->spi_controller);
bool ok = write_command(internal, CMD_FAST_MODE_ENABLE); bool ok = write_command(internal, CMD_FAST_MODE_ENABLE);
ok = ok && write_data_byte(internal, 0x02); ok = ok && write_data_byte(internal, 0x02);
ok = ok && write_command(internal, CMD_FAST_MODE_TIMING); ok = ok && write_command(internal, CMD_FAST_MODE_TIMING);
@@ -172,6 +178,7 @@ static bool init_with_fast_lut(Gdeq031t10Internal* internal, bool mirror_180, en
ok = write_command(internal, CMD_VCOM_DATA_INTERVAL); ok = write_command(internal, CMD_VCOM_DATA_INTERVAL);
ok = ok && write_data_byte(internal, 0xD7); ok = ok && write_data_byte(internal, 0xD7);
} }
spi_controller_unlock(internal->spi_controller);
if (!ok) { if (!ok) {
LOG_E(TAG, "Mode init failed"); LOG_E(TAG, "Mode init failed");
return false; return false;
@@ -185,7 +192,10 @@ static bool ensure_panel_ready(Gdeq031t10Internal* internal, bool mirror_180, en
return init_with_fast_lut(internal, mirror_180, mode); return init_with_fast_lut(internal, mirror_180, mode);
} else if (!internal->panel_power_on) { } else if (!internal->panel_power_on) {
// Registers still hold the mode; only the charge pump was idled. // Registers still hold the mode; only the charge pump was idled.
if (!write_command(internal, CMD_POWER_ON) || !wait_while_busy(internal)) { spi_controller_lock(internal->spi_controller);
bool ok = write_command(internal, CMD_POWER_ON) && wait_while_busy(internal);
spi_controller_unlock(internal->spi_controller);
if (!ok) {
LOG_E(TAG, "Panel did not become ready after power-on"); LOG_E(TAG, "Panel did not become ready after power-on");
return false; return false;
} }
@@ -198,11 +208,13 @@ static bool panel_power_off(Gdeq031t10Internal* internal) {
// Command the panel off regardless of whether BUSY confirms it: retrying // Command the panel off regardless of whether BUSY confirms it: retrying
// forever here would just as likely hang, and a stuck-BUSY panel is // forever here would just as likely hang, and a stuck-BUSY panel is
// already unusable either way. // already unusable either way.
spi_controller_lock(internal->spi_controller);
bool ok = write_command(internal, CMD_POWER_ON_OFF); // 0x02 standalone = power off bool ok = write_command(internal, CMD_POWER_ON_OFF); // 0x02 standalone = power off
if (!ok || !wait_while_busy(internal)) { if (!ok || !wait_while_busy(internal)) {
LOG_E(TAG, "Panel did not confirm power-off"); LOG_E(TAG, "Panel did not confirm power-off");
ok = false; ok = false;
} }
spi_controller_unlock(internal->spi_controller);
internal->panel_power_on = false; internal->panel_power_on = false;
return ok; return ok;
} }
@@ -222,12 +234,18 @@ static void refresh_full(Gdeq031t10Internal* internal, bool mirror_180, enum Gde
return; return;
} }
// Single logical bus operation spanning old/new frame data and the refresh trigger: an SD
// transaction slipping in between any of these writes would corrupt the sequence the panel
// is mid-way through parsing.
spi_controller_lock(internal->spi_controller);
// shadow_framebuffer holds the panel's actual current content (matches the vendor's tracked // shadow_framebuffer holds the panel's actual current content (matches the vendor's tracked
// oldData[] buffer) - send it as "old" data so the controller computes correct per-pixel // oldData[] buffer) - send it as "old" data so the controller computes correct per-pixel
// transitions. // transitions.
LOG_I(TAG, "write old data from shadow_buffer"); LOG_I(TAG, "write old data from shadow_buffer");
if (!write_command(internal, CMD_DATA_START_OLD) || !write_data(internal, internal->shadow_framebuffer, FRAMEBUFFER_SIZE)) { if (!write_command(internal, CMD_DATA_START_OLD) || !write_data(internal, internal->shadow_framebuffer, FRAMEBUFFER_SIZE)) {
LOG_E(TAG, "Failed to send old frame data"); LOG_E(TAG, "Failed to send old frame data");
spi_controller_unlock(internal->spi_controller);
return; return;
} }
@@ -235,11 +253,13 @@ static void refresh_full(Gdeq031t10Internal* internal, bool mirror_180, enum Gde
// is showing right now until this refresh is confirmed below. // is showing right now until this refresh is confirmed below.
if (!write_command(internal, CMD_DATA_START_NEW) || !write_data(internal, render_bitmap, FRAMEBUFFER_SIZE)) { if (!write_command(internal, CMD_DATA_START_NEW) || !write_data(internal, render_bitmap, FRAMEBUFFER_SIZE)) {
LOG_E(TAG, "Failed to send new frame data"); LOG_E(TAG, "Failed to send new frame data");
spi_controller_unlock(internal->spi_controller);
return; return;
} }
if (!write_command(internal, CMD_DISPLAY_REFRESH)) { if (!write_command(internal, CMD_DISPLAY_REFRESH)) {
LOG_E(TAG, "Failed to trigger display refresh"); LOG_E(TAG, "Failed to trigger display refresh");
spi_controller_unlock(internal->spi_controller);
return; return;
} }
delay_millis(1); // datasheet requires >=200us settle before polling BUSY delay_millis(1); // datasheet requires >=200us settle before polling BUSY
@@ -252,6 +272,7 @@ static void refresh_full(Gdeq031t10Internal* internal, bool mirror_180, enum Gde
// content the panel never displayed, and hide the difference from the change scan. // content the panel never displayed, and hide the difference from the change scan.
LOG_E(TAG, "Full refresh did not complete"); LOG_E(TAG, "Full refresh did not complete");
} }
spi_controller_unlock(internal->spi_controller);
// EPD_DeepSleep(): power off, then actually deep-sleep rather than just idling the charge // EPD_DeepSleep(): power off, then actually deep-sleep rather than just idling the charge
// pump. panel_mode_valid=false forces the next refresh_full() call back through a fresh // pump. panel_mode_valid=false forces the next refresh_full() call back through a fresh
@@ -277,6 +298,11 @@ static void refresh_window(Gdeq031t10Internal* internal, bool mirror_180, const
const uint16_t y = static_cast<uint16_t>(first_row); const uint16_t y = static_cast<uint16_t>(first_row);
const uint16_t ye = static_cast<uint16_t>(last_row); const uint16_t ye = static_cast<uint16_t>(last_row);
// Single logical bus operation spanning the window setup, old/new region data and the
// refresh trigger: an SD transaction slipping in between any of these writes would corrupt
// the sequence the panel is mid-way through parsing.
spi_controller_lock(internal->spi_controller);
// Set the partial RAM window (GxEPD2 GDEQ031T10 sequence). // Set the partial RAM window (GxEPD2 GDEQ031T10 sequence).
bool ok = write_command(internal, CMD_PARTIAL_IN); bool ok = write_command(internal, CMD_PARTIAL_IN);
ok = ok && write_command(internal, CMD_PARTIAL_WINDOW); ok = ok && write_command(internal, CMD_PARTIAL_WINDOW);
@@ -290,6 +316,7 @@ static void refresh_window(Gdeq031t10Internal* internal, bool mirror_180, const
if (!ok) { if (!ok) {
LOG_E(TAG, "Failed to set partial refresh window"); LOG_E(TAG, "Failed to set partial refresh window");
write_command(internal, CMD_PARTIAL_OUT); // best-effort: leave partial-window mode write_command(internal, CMD_PARTIAL_OUT); // best-effort: leave partial-window mode
spi_controller_unlock(internal->spi_controller);
return; return;
} }
@@ -303,6 +330,7 @@ static void refresh_window(Gdeq031t10Internal* internal, bool mirror_180, const
if (!write_command(internal, CMD_DATA_START_OLD) || !write_data(internal, internal->region_buffer, n)) { if (!write_command(internal, CMD_DATA_START_OLD) || !write_data(internal, internal->region_buffer, n)) {
LOG_E(TAG, "Failed to send old window data"); LOG_E(TAG, "Failed to send old window data");
write_command(internal, CMD_PARTIAL_OUT); write_command(internal, CMD_PARTIAL_OUT);
spi_controller_unlock(internal->spi_controller);
return; return;
} }
@@ -317,6 +345,7 @@ static void refresh_window(Gdeq031t10Internal* internal, bool mirror_180, const
if (!write_command(internal, CMD_DATA_START_NEW) || !write_data(internal, internal->region_buffer, n)) { if (!write_command(internal, CMD_DATA_START_NEW) || !write_data(internal, internal->region_buffer, n)) {
LOG_E(TAG, "Failed to send new window data"); LOG_E(TAG, "Failed to send new window data");
write_command(internal, CMD_PARTIAL_OUT); write_command(internal, CMD_PARTIAL_OUT);
spi_controller_unlock(internal->spi_controller);
return; return;
} }
@@ -340,6 +369,7 @@ static void refresh_window(Gdeq031t10Internal* internal, bool mirror_180, const
} }
} }
write_command(internal, CMD_PARTIAL_OUT); write_command(internal, CMD_PARTIAL_OUT);
spi_controller_unlock(internal->spi_controller);
} }
// endregion // endregion
@@ -467,8 +497,10 @@ static error_t gdeq031t10_disp_on_off(Device* device, bool on_off) {
panel_power_off(internal); panel_power_off(internal);
} }
delay_millis(100); delay_millis(100);
spi_controller_lock(internal->spi_controller);
write_command(internal, CMD_DEEP_SLEEP); write_command(internal, CMD_DEEP_SLEEP);
write_data_byte(internal, DEEP_SLEEP_CHECK_CODE); write_data_byte(internal, DEEP_SLEEP_CHECK_CODE);
spi_controller_unlock(internal->spi_controller);
// Deep sleep needs a reset to wake, which restores register defaults. // Deep sleep needs a reset to wake, which restores register defaults.
internal->panel_mode_valid = false; internal->panel_mode_valid = false;
} }
@@ -566,6 +598,7 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
internal->dc = gpio_descriptor_acquire( internal->dc = gpio_descriptor_acquire(
config->pin_dc.gpio_controller, config->pin_dc.gpio_controller,
config->pin_dc.pin, config->pin_dc.pin,
@@ -651,8 +684,10 @@ static error_t stop(Device* device) {
panel_power_off(internal); panel_power_off(internal);
} }
delay_millis(100); delay_millis(100);
spi_controller_lock(internal->spi_controller);
write_command(internal, CMD_DEEP_SLEEP); write_command(internal, CMD_DEEP_SLEEP);
write_data_byte(internal, DEEP_SLEEP_CHECK_CODE); write_data_byte(internal, DEEP_SLEEP_CHECK_CODE);
spi_controller_unlock(internal->spi_controller);
internal->display_on = false; internal->display_on = false;
} }
xSemaphoreGive(internal->panel_mutex); xSemaphoreGive(internal->panel_mutex);
+16
View File
@@ -111,6 +111,7 @@ constexpr uint8_t INIT_CMDS[] = {
} // namespace } // namespace
struct Hx8357Internal { struct Hx8357Internal {
Device* spi_controller;
spi_device_handle_t spi_handle; spi_device_handle_t spi_handle;
gpio_num_t dc_pin; gpio_num_t dc_pin;
size_t max_transfer_size; size_t max_transfer_size;
@@ -210,6 +211,7 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
internal->dc_pin = static_cast<gpio_num_t>(pin_or_unused(config->pin_dc)); internal->dc_pin = static_cast<gpio_num_t>(pin_or_unused(config->pin_dc));
// Clamped below the bus's configured max_transfer_size: that value only bounds the DMA // Clamped below the bus's configured max_transfer_size: that value only bounds the DMA
// buffer/descriptor allocation, not the SPI peripheral's own per-transaction bit-length // buffer/descriptor allocation, not the SPI peripheral's own per-transaction bit-length
@@ -275,9 +277,11 @@ static error_t start(Device* device) {
internal->mirror_x = config->mirror_x; internal->mirror_x = config->mirror_x;
internal->mirror_y = config->mirror_y; internal->mirror_y = config->mirror_y;
spi_controller_lock(internal->spi_controller);
run_init_cmds(internal); run_init_cmds(internal);
send_madctl(internal); send_madctl(internal);
send_cmd(internal, config->invert_color ? HX8357_INVON : HX8357_INVOFF); send_cmd(internal, config->invert_color ? HX8357_INVON : HX8357_INVOFF);
spi_controller_unlock(internal->spi_controller);
device_set_driver_data(device, internal); device_set_driver_data(device, internal);
return ERROR_NONE; return ERROR_NONE;
@@ -329,6 +333,7 @@ static error_t hx8357_draw_bitmap(Device* device, int32_t x_start, int32_t y_sta
static_cast<uint8_t>((y2 >> 8) & 0xFF), static_cast<uint8_t>(y2 & 0xFF), static_cast<uint8_t>((y2 >> 8) & 0xFF), static_cast<uint8_t>(y2 & 0xFF),
}; };
spi_controller_lock(internal->spi_controller);
send_cmd(internal, HX8357_CASET); send_cmd(internal, HX8357_CASET);
send_data(internal, xb, 4); send_data(internal, xb, 4);
send_cmd(internal, HX8357_PASET); send_cmd(internal, HX8357_PASET);
@@ -337,6 +342,7 @@ static error_t hx8357_draw_bitmap(Device* device, int32_t x_start, int32_t y_sta
const size_t pixel_count = static_cast<size_t>(x_end - x_start) * static_cast<size_t>(y_end - y_start); const size_t pixel_count = static_cast<size_t>(x_end - x_start) * static_cast<size_t>(y_end - y_start);
send_data(internal, static_cast<const uint8_t*>(color_data), pixel_count * 3); // RGB888 = 3 bytes/pixel send_data(internal, static_cast<const uint8_t*>(color_data), pixel_count * 3); // RGB888 = 3 bytes/pixel
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
@@ -345,14 +351,18 @@ static error_t hx8357_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<Hx8357Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Hx8357Internal*>(device_get_driver_data(device));
internal->mirror_x = x_axis; internal->mirror_x = x_axis;
internal->mirror_y = y_axis; internal->mirror_y = y_axis;
spi_controller_lock(internal->spi_controller);
send_madctl(internal); send_madctl(internal);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t hx8357_swap_xy(Device* device, bool swap_axes) { static error_t hx8357_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<Hx8357Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Hx8357Internal*>(device_get_driver_data(device));
internal->swap_xy = swap_axes; internal->swap_xy = swap_axes;
spi_controller_lock(internal->spi_controller);
send_madctl(internal); send_madctl(internal);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
@@ -373,19 +383,25 @@ static bool hx8357_get_mirror_y(Device* device) {
static error_t hx8357_invert_color(Device* device, bool invert_color_data) { static error_t hx8357_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<Hx8357Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Hx8357Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
send_cmd(internal, invert_color_data ? HX8357_INVON : HX8357_INVOFF); send_cmd(internal, invert_color_data ? HX8357_INVON : HX8357_INVOFF);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t hx8357_disp_on_off(Device* device, bool on_off) { static error_t hx8357_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<Hx8357Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Hx8357Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
send_cmd(internal, on_off ? HX8357_DISPON : 0x28 /* HX8357_DISPOFF */); send_cmd(internal, on_off ? HX8357_DISPON : 0x28 /* HX8357_DISPOFF */);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t hx8357_disp_sleep(Device* device, bool sleep) { static error_t hx8357_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<Hx8357Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Hx8357Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
send_cmd(internal, sleep ? 0x10 /* HX8357_SLPIN */ : HX8357_SLPOUT); send_cmd(internal, sleep ? 0x10 /* HX8357_SLPIN */ : HX8357_SLPOUT);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
+53 -12
View File
@@ -34,6 +34,7 @@
static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 }; static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 };
struct Ili9341Internal { struct Ili9341Internal {
Device* spi_controller;
esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_io_handle_t io_handle;
esp_lcd_panel_handle_t panel_handle; esp_lcd_panel_handle_t panel_handle;
// Given from ISR context by on_color_trans_done() once a queued SPI transfer physically // Given from ISR context by on_color_trans_done() once a queued SPI transfer physically
@@ -108,6 +109,7 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
internal->draw_done_semaphore = xSemaphoreCreateBinary(); internal->draw_done_semaphore = xSemaphoreCreateBinary();
if (internal->draw_done_semaphore == nullptr) { if (internal->draw_done_semaphore == nullptr) {
free(internal); free(internal);
@@ -187,8 +189,10 @@ static error_t start(Device* device) {
// Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ILI9341 panels). // Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ILI9341 panels).
// Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel // Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel
// if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak. // if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak.
bool ok = bool ok = pulse_reset(internal->reset_descriptor) == ERROR_NONE;
pulse_reset(internal->reset_descriptor) == ERROR_NONE &&
spi_controller_lock(internal->spi_controller);
ok = ok &&
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK && esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK && esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
(!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
@@ -203,6 +207,7 @@ static error_t start(Device* device) {
ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK); ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK);
ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
spi_controller_unlock(internal->spi_controller);
if (!ok) { if (!ok) {
LOG_E(TAG, "Failed to bring up panel"); LOG_E(TAG, "Failed to bring up panel");
@@ -223,9 +228,11 @@ static error_t start(Device* device) {
static error_t stop(Device* device) { static error_t stop(Device* device) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
if (internal->panel_handle != nullptr) { if (internal->panel_handle != nullptr) {
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) { if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel"); LOG_E(TAG, "Failed to delete panel");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->panel_handle = nullptr; internal->panel_handle = nullptr;
@@ -234,10 +241,12 @@ static error_t stop(Device* device) {
if (internal->io_handle != nullptr) { if (internal->io_handle != nullptr) {
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO"); LOG_E(TAG, "Failed to delete panel IO");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->io_handle = nullptr; internal->io_handle = nullptr;
} }
spi_controller_unlock(internal->spi_controller);
if (internal->reset_descriptor != nullptr) { if (internal->reset_descriptor != nullptr) {
gpio_descriptor_release(internal->reset_descriptor); gpio_descriptor_release(internal->reset_descriptor);
@@ -256,16 +265,23 @@ static error_t stop(Device* device) {
static error_t ili9341_reset(Device* device) { static error_t ili9341_reset(Device* device) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
error_t error = pulse_reset(internal->reset_descriptor); error_t error = pulse_reset(internal->reset_descriptor);
if (error != ERROR_NONE) { if (error != ERROR_NONE) {
spi_controller_unlock(internal->spi_controller);
return error; return error;
} }
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; error_t result = esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t ili9341_init(Device* device) { static error_t ili9341_init(Device* device) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t ili9341_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { static error_t ili9341_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
@@ -276,26 +292,39 @@ static error_t ili9341_draw_bitmap(Device* device, int32_t x_start, int32_t y_st
// satisfied by this draw's own transfer completing. // satisfied by this draw's own transfer completing.
xSemaphoreTake(internal->draw_done_semaphore, 0); xSemaphoreTake(internal->draw_done_semaphore, 0);
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { spi_controller_lock(internal->spi_controller);
esp_err_t ret = esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data);
if (ret != ESP_OK) {
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
// Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous // Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous
// contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite // contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite
// color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the // color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the
// transfer and returns once it's handed to the SPI peripheral, not once it's finished. // transfer and returns once it's handed to the SPI peripheral, not once it's finished. Hold the
// bus lock across the wait too, not just the queueing call: the command/data phases aren't
// wrapped in their own acquire_bus by esp_lcd_panel_io_spi, so another bus user could otherwise
// interleave with this transfer while it's still in flight.
xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t ili9341_mirror(Device* device, bool x_axis, bool y_axis) { static error_t ili9341_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t ili9341_swap_xy(Device* device, bool swap_axes) { static error_t ili9341_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after // Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after
@@ -314,7 +343,10 @@ static bool ili9341_get_mirror_y(Device* device) {
static error_t ili9341_set_gap(Device* device, int32_t x_gap, int32_t y_gap) { static error_t ili9341_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x(). // Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
@@ -334,17 +366,26 @@ static int32_t ili9341_get_gap_y(Device* device) {
static error_t ili9341_invert_color(Device* device, bool invert_color_data) { static error_t ili9341_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t ili9341_disp_on_off(Device* device, bool on_off) { static error_t ili9341_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t ili9341_disp_sleep(Device* device, bool sleep) { static error_t ili9341_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// bgr_order only selects the panel controller's rgb_ele_order (applied in start(), below) so the // bgr_order only selects the panel controller's rgb_ele_order (applied in start(), below) so the
+49 -10
View File
@@ -26,6 +26,7 @@
#define GET_CONFIG(device) (static_cast<const Ili9488Config*>((device)->config)) #define GET_CONFIG(device) (static_cast<const Ili9488Config*>((device)->config))
struct Ili9488Internal { struct Ili9488Internal {
Device* spi_controller;
esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_io_handle_t io_handle;
esp_lcd_panel_handle_t panel_handle; esp_lcd_panel_handle_t panel_handle;
// Given from ISR context by on_color_trans_done() once a queued SPI transfer physically // Given from ISR context by on_color_trans_done() once a queued SPI transfer physically
@@ -68,6 +69,7 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
internal->draw_done_semaphore = xSemaphoreCreateBinary(); internal->draw_done_semaphore = xSemaphoreCreateBinary();
if (internal->draw_done_semaphore == nullptr) { if (internal->draw_done_semaphore == nullptr) {
free(internal); free(internal);
@@ -131,6 +133,7 @@ static error_t start(Device* device) {
// Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ILI9488 panels). // Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ILI9488 panels).
// Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel // Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel
// if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak. // if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak.
spi_controller_lock(internal->spi_controller);
bool ok = bool ok =
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK && esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK && esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
@@ -145,6 +148,7 @@ static error_t start(Device* device) {
ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK); ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK);
ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
spi_controller_unlock(internal->spi_controller);
if (!ok) { if (!ok) {
LOG_E(TAG, "Failed to bring up panel"); LOG_E(TAG, "Failed to bring up panel");
@@ -162,9 +166,11 @@ static error_t start(Device* device) {
static error_t stop(Device* device) { static error_t stop(Device* device) {
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
if (internal->panel_handle != nullptr) { if (internal->panel_handle != nullptr) {
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) { if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel"); LOG_E(TAG, "Failed to delete panel");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->panel_handle = nullptr; internal->panel_handle = nullptr;
@@ -173,10 +179,12 @@ static error_t stop(Device* device) {
if (internal->io_handle != nullptr) { if (internal->io_handle != nullptr) {
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO"); LOG_E(TAG, "Failed to delete panel IO");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->io_handle = nullptr; internal->io_handle = nullptr;
} }
spi_controller_unlock(internal->spi_controller);
vSemaphoreDelete(internal->draw_done_semaphore); vSemaphoreDelete(internal->draw_done_semaphore);
free(internal); free(internal);
@@ -190,12 +198,18 @@ static error_t stop(Device* device) {
static error_t ili9488_reset(Device* device) { static error_t ili9488_reset(Device* device) {
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t ili9488_init(Device* device) { static error_t ili9488_init(Device* device) {
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t ili9488_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { static error_t ili9488_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
@@ -206,26 +220,39 @@ static error_t ili9488_draw_bitmap(Device* device, int32_t x_start, int32_t y_st
// satisfied by this draw's own transfer completing. // satisfied by this draw's own transfer completing.
xSemaphoreTake(internal->draw_done_semaphore, 0); xSemaphoreTake(internal->draw_done_semaphore, 0);
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { spi_controller_lock(internal->spi_controller);
esp_err_t ret = esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data);
if (ret != ESP_OK) {
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
// Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous // Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous
// contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite // contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite
// color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the // color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the
// transfer and returns once it's handed to the SPI peripheral, not once it's finished. // transfer and returns once it's handed to the SPI peripheral, not once it's finished. Hold the
// bus lock across the wait too, not just the queueing call: the command/data phases aren't
// wrapped in their own acquire_bus by esp_lcd_panel_io_spi, so another bus user could otherwise
// interleave with this transfer while it's still in flight.
xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t ili9488_mirror(Device* device, bool x_axis, bool y_axis) { static error_t ili9488_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t ili9488_swap_xy(Device* device, bool swap_axes) { static error_t ili9488_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after // Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after
@@ -244,7 +271,10 @@ static bool ili9488_get_mirror_y(Device* device) {
static error_t ili9488_set_gap(Device* device, int32_t x_gap, int32_t y_gap) { static error_t ili9488_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x(). // Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
@@ -264,17 +294,26 @@ static int32_t ili9488_get_gap_y(Device* device) {
static error_t ili9488_invert_color(Device* device, bool invert_color_data) { static error_t ili9488_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t ili9488_disp_on_off(Device* device, bool on_off) { static error_t ili9488_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t ili9488_disp_sleep(Device* device, bool sleep) { static error_t ili9488_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// bgr_order only selects the panel controller's rgb_ele_order (applied in start(), above) so the // bgr_order only selects the panel controller's rgb_ele_order (applied in start(), above) so the
+48 -9
View File
@@ -31,6 +31,7 @@ constexpr auto* TAG = "JD9853";
static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 }; static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 };
struct Jd9853Internal { struct Jd9853Internal {
Device* spi_controller;
esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_io_handle_t io_handle;
esp_lcd_panel_handle_t panel_handle; esp_lcd_panel_handle_t panel_handle;
// See st7796-module's identical field for why this exists: draw_bitmap() must block until // See st7796-module's identical field for why this exists: draw_bitmap() must block until
@@ -69,6 +70,7 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
internal->draw_done_semaphore = xSemaphoreCreateBinary(); internal->draw_done_semaphore = xSemaphoreCreateBinary();
if (internal->draw_done_semaphore == nullptr) { if (internal->draw_done_semaphore == nullptr) {
free(internal); free(internal);
@@ -134,6 +136,7 @@ static error_t start(Device* device) {
// Bring-up sequence, order matches the deprecated HAL's Jd9853Display (proven correct on real // Bring-up sequence, order matches the deprecated HAL's Jd9853Display (proven correct on real
// Waveshare S3 Touch LCD 1.47 hardware). Every failure path below must clean up fully: unlike // Waveshare S3 Touch LCD 1.47 hardware). Every failure path below must clean up fully: unlike
// stop_device, this is never retried by the kernel if start_device fails. // stop_device, this is never retried by the kernel if start_device fails.
spi_controller_lock(internal->spi_controller);
bool ok = bool ok =
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK && esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK && esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
@@ -147,6 +150,7 @@ static error_t start(Device* device) {
ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK); ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK);
ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
spi_controller_unlock(internal->spi_controller);
if (!ok) { if (!ok) {
LOG_E(TAG, "Failed to bring up panel"); LOG_E(TAG, "Failed to bring up panel");
@@ -164,9 +168,11 @@ static error_t start(Device* device) {
static error_t stop(Device* device) { static error_t stop(Device* device) {
auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
if (internal->panel_handle != nullptr) { if (internal->panel_handle != nullptr) {
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) { if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel"); LOG_E(TAG, "Failed to delete panel");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->panel_handle = nullptr; internal->panel_handle = nullptr;
@@ -175,10 +181,12 @@ static error_t stop(Device* device) {
if (internal->io_handle != nullptr) { if (internal->io_handle != nullptr) {
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO"); LOG_E(TAG, "Failed to delete panel IO");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->io_handle = nullptr; internal->io_handle = nullptr;
} }
spi_controller_unlock(internal->spi_controller);
vSemaphoreDelete(internal->draw_done_semaphore); vSemaphoreDelete(internal->draw_done_semaphore);
free(internal); free(internal);
@@ -192,12 +200,18 @@ static error_t stop(Device* device) {
static error_t jd9853_reset(Device* device) { static error_t jd9853_reset(Device* device) {
auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t jd9853_init(Device* device) { static error_t jd9853_init(Device* device) {
auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t jd9853_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { static error_t jd9853_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
@@ -205,22 +219,35 @@ static error_t jd9853_draw_bitmap(Device* device, int32_t x_start, int32_t y_sta
xSemaphoreTake(internal->draw_done_semaphore, 0); xSemaphoreTake(internal->draw_done_semaphore, 0);
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { spi_controller_lock(internal->spi_controller);
esp_err_t ret = esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data);
if (ret != ESP_OK) {
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
// Hold the bus lock across the wait too, not just the queueing call: the command/data phases
// aren't wrapped in their own acquire_bus by esp_lcd_panel_io_spi, so another bus user could
// otherwise interleave with this transfer while it's still in flight.
xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t jd9853_mirror(Device* device, bool x_axis, bool y_axis) { static error_t jd9853_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t jd9853_swap_xy(Device* device, bool swap_axes) { static error_t jd9853_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static bool jd9853_get_swap_xy(Device* device) { static bool jd9853_get_swap_xy(Device* device) {
@@ -237,7 +264,10 @@ static bool jd9853_get_mirror_y(Device* device) {
static error_t jd9853_set_gap(Device* device, int32_t x_gap, int32_t y_gap) { static error_t jd9853_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device));
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static int32_t jd9853_get_gap_x(Device* device) { static int32_t jd9853_get_gap_x(Device* device) {
@@ -250,17 +280,26 @@ static int32_t jd9853_get_gap_y(Device* device) {
static error_t jd9853_invert_color(Device* device, bool invert_color_data) { static error_t jd9853_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device));
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t jd9853_disp_on_off(Device* device, bool on_off) { static error_t jd9853_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t jd9853_disp_sleep(Device* device, bool sleep) { static error_t jd9853_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Jd9853Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// The deprecated HAL's Jd9853Display always set swap_bytes=true on its lvgl_port config // The deprecated HAL's Jd9853Display always set swap_bytes=true on its lvgl_port config
+49 -10
View File
@@ -32,6 +32,7 @@
static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 }; static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 };
struct St7735Internal { struct St7735Internal {
Device* spi_controller;
esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_io_handle_t io_handle;
esp_lcd_panel_handle_t panel_handle; esp_lcd_panel_handle_t panel_handle;
// Given from ISR context by on_color_trans_done() once a queued SPI transfer physically // Given from ISR context by on_color_trans_done() once a queued SPI transfer physically
@@ -74,6 +75,7 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
internal->draw_done_semaphore = xSemaphoreCreateBinary(); internal->draw_done_semaphore = xSemaphoreCreateBinary();
if (internal->draw_done_semaphore == nullptr) { if (internal->draw_done_semaphore == nullptr) {
free(internal); free(internal);
@@ -134,6 +136,7 @@ static error_t start(Device* device) {
// Bring-up sequence, order matches st7789-module (proven correct on real panels). // Bring-up sequence, order matches st7789-module (proven correct on real panels).
// Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel // Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel
// if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak. // if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak.
spi_controller_lock(internal->spi_controller);
bool ok = bool ok =
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK && esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK && esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
@@ -152,6 +155,7 @@ static error_t start(Device* device) {
ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK); ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK);
ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
spi_controller_unlock(internal->spi_controller);
if (!ok) { if (!ok) {
LOG_E(TAG, "Failed to bring up panel"); LOG_E(TAG, "Failed to bring up panel");
@@ -169,9 +173,11 @@ static error_t start(Device* device) {
static error_t stop(Device* device) { static error_t stop(Device* device) {
auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
if (internal->panel_handle != nullptr) { if (internal->panel_handle != nullptr) {
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) { if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel"); LOG_E(TAG, "Failed to delete panel");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->panel_handle = nullptr; internal->panel_handle = nullptr;
@@ -180,10 +186,12 @@ static error_t stop(Device* device) {
if (internal->io_handle != nullptr) { if (internal->io_handle != nullptr) {
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO"); LOG_E(TAG, "Failed to delete panel IO");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->io_handle = nullptr; internal->io_handle = nullptr;
} }
spi_controller_unlock(internal->spi_controller);
vSemaphoreDelete(internal->draw_done_semaphore); vSemaphoreDelete(internal->draw_done_semaphore);
free(internal); free(internal);
@@ -197,12 +205,18 @@ static error_t stop(Device* device) {
static error_t st7735_reset(Device* device) { static error_t st7735_reset(Device* device) {
auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7735_init(Device* device) { static error_t st7735_init(Device* device) {
auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7735_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { static error_t st7735_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
@@ -213,26 +227,39 @@ static error_t st7735_draw_bitmap(Device* device, int32_t x_start, int32_t y_sta
// satisfied by this draw's own transfer completing. // satisfied by this draw's own transfer completing.
xSemaphoreTake(internal->draw_done_semaphore, 0); xSemaphoreTake(internal->draw_done_semaphore, 0);
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { spi_controller_lock(internal->spi_controller);
esp_err_t ret = esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data);
if (ret != ESP_OK) {
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
// Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous // Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous
// contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite // contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite
// color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the // color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the
// transfer and returns once it's handed to the SPI peripheral, not once it's finished. // transfer and returns once it's handed to the SPI peripheral, not once it's finished. Hold the
// bus lock across the wait too, not just the queueing call: the command/data phases aren't
// wrapped in their own acquire_bus by esp_lcd_panel_io_spi, so another bus user could otherwise
// interleave with this transfer while it's still in flight.
xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t st7735_mirror(Device* device, bool x_axis, bool y_axis) { static error_t st7735_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7735_swap_xy(Device* device, bool swap_axes) { static error_t st7735_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after // Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after
@@ -251,7 +278,10 @@ static bool st7735_get_mirror_y(Device* device) {
static error_t st7735_set_gap(Device* device, int32_t x_gap, int32_t y_gap) { static error_t st7735_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device));
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x(). // Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
@@ -271,17 +301,26 @@ static int32_t st7735_get_gap_y(Device* device) {
static error_t st7735_invert_color(Device* device, bool invert_color_data) { static error_t st7735_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device));
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7735_disp_on_off(Device* device, bool on_off) { static error_t st7735_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7735_disp_sleep(Device* device, bool sleep) { static error_t st7735_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7735Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static enum DisplayColorFormat st7735_get_color_format(Device* device) { static enum DisplayColorFormat st7735_get_color_format(Device* device) {
+49 -10
View File
@@ -32,6 +32,7 @@
static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 }; static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 };
struct St7789Internal { struct St7789Internal {
Device* spi_controller;
esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_io_handle_t io_handle;
esp_lcd_panel_handle_t panel_handle; esp_lcd_panel_handle_t panel_handle;
// Given from ISR context by on_color_trans_done() once a queued SPI transfer physically // Given from ISR context by on_color_trans_done() once a queued SPI transfer physically
@@ -74,6 +75,7 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
internal->draw_done_semaphore = xSemaphoreCreateBinary(); internal->draw_done_semaphore = xSemaphoreCreateBinary();
if (internal->draw_done_semaphore == nullptr) { if (internal->draw_done_semaphore == nullptr) {
free(internal); free(internal);
@@ -134,6 +136,7 @@ static error_t start(Device* device) {
// Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ST7789 panels). // Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ST7789 panels).
// Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel // Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel
// if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak. // if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak.
spi_controller_lock(internal->spi_controller);
bool ok = bool ok =
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK && esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK && esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
@@ -152,6 +155,7 @@ static error_t start(Device* device) {
ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK); ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK);
ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
spi_controller_unlock(internal->spi_controller);
if (!ok) { if (!ok) {
LOG_E(TAG, "Failed to bring up panel"); LOG_E(TAG, "Failed to bring up panel");
@@ -169,9 +173,11 @@ static error_t start(Device* device) {
static error_t stop(Device* device) { static error_t stop(Device* device) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
if (internal->panel_handle != nullptr) { if (internal->panel_handle != nullptr) {
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) { if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel"); LOG_E(TAG, "Failed to delete panel");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->panel_handle = nullptr; internal->panel_handle = nullptr;
@@ -180,10 +186,12 @@ static error_t stop(Device* device) {
if (internal->io_handle != nullptr) { if (internal->io_handle != nullptr) {
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO"); LOG_E(TAG, "Failed to delete panel IO");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->io_handle = nullptr; internal->io_handle = nullptr;
} }
spi_controller_unlock(internal->spi_controller);
vSemaphoreDelete(internal->draw_done_semaphore); vSemaphoreDelete(internal->draw_done_semaphore);
free(internal); free(internal);
@@ -197,12 +205,18 @@ static error_t stop(Device* device) {
static error_t st7789_reset(Device* device) { static error_t st7789_reset(Device* device) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7789_init(Device* device) { static error_t st7789_init(Device* device) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7789_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { static error_t st7789_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
@@ -213,26 +227,39 @@ static error_t st7789_draw_bitmap(Device* device, int32_t x_start, int32_t y_sta
// satisfied by this draw's own transfer completing. // satisfied by this draw's own transfer completing.
xSemaphoreTake(internal->draw_done_semaphore, 0); xSemaphoreTake(internal->draw_done_semaphore, 0);
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { spi_controller_lock(internal->spi_controller);
esp_err_t ret = esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data);
if (ret != ESP_OK) {
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
// Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous // Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous
// contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite // contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite
// color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the // color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the
// transfer and returns once it's handed to the SPI peripheral, not once it's finished. // transfer and returns once it's handed to the SPI peripheral, not once it's finished. Hold the
// bus lock across the wait too, not just the queueing call: the command/data phases aren't
// wrapped in their own acquire_bus by esp_lcd_panel_io_spi, so another bus user could otherwise
// interleave with this transfer while it's still in flight.
xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t st7789_mirror(Device* device, bool x_axis, bool y_axis) { static error_t st7789_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7789_swap_xy(Device* device, bool swap_axes) { static error_t st7789_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after // Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after
@@ -251,7 +278,10 @@ static bool st7789_get_mirror_y(Device* device) {
static error_t st7789_set_gap(Device* device, int32_t x_gap, int32_t y_gap) { static error_t st7789_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x(). // Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
@@ -271,17 +301,26 @@ static int32_t st7789_get_gap_y(Device* device) {
static error_t st7789_invert_color(Device* device, bool invert_color_data) { static error_t st7789_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7789_disp_on_off(Device* device, bool on_off) { static error_t st7789_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7789_disp_sleep(Device* device, bool sleep) { static error_t st7789_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7789Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static enum DisplayColorFormat st7789_get_color_format(Device* device) { static enum DisplayColorFormat st7789_get_color_format(Device* device) {
+49 -10
View File
@@ -32,6 +32,7 @@
static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 }; static const uint8_t GAMMA_CURVE_VALUES[4] = { 0x01, 0x04, 0x02, 0x08 };
struct St7796Internal { struct St7796Internal {
Device* spi_controller;
esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_io_handle_t io_handle;
esp_lcd_panel_handle_t panel_handle; esp_lcd_panel_handle_t panel_handle;
// Given from ISR context by on_color_trans_done() once a queued SPI transfer physically // Given from ISR context by on_color_trans_done() once a queued SPI transfer physically
@@ -74,6 +75,7 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
internal->draw_done_semaphore = xSemaphoreCreateBinary(); internal->draw_done_semaphore = xSemaphoreCreateBinary();
if (internal->draw_done_semaphore == nullptr) { if (internal->draw_done_semaphore == nullptr) {
free(internal); free(internal);
@@ -134,6 +136,7 @@ static error_t start(Device* device) {
// Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ST7796 panels). // Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ST7796 panels).
// Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel // Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel
// if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak. // if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak.
spi_controller_lock(internal->spi_controller);
bool ok = bool ok =
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK && esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK && esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
@@ -151,6 +154,7 @@ static error_t start(Device* device) {
ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK); ok = ok && (config->gamma_curve >= 4 || esp_lcd_panel_io_tx_param(internal->io_handle, LCD_CMD_GAMSET, &GAMMA_CURVE_VALUES[config->gamma_curve], 1) == ESP_OK);
ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
spi_controller_unlock(internal->spi_controller);
if (!ok) { if (!ok) {
LOG_E(TAG, "Failed to bring up panel"); LOG_E(TAG, "Failed to bring up panel");
@@ -168,9 +172,11 @@ static error_t start(Device* device) {
static error_t stop(Device* device) { static error_t stop(Device* device) {
auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device));
spi_controller_lock(internal->spi_controller);
if (internal->panel_handle != nullptr) { if (internal->panel_handle != nullptr) {
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) { if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel"); LOG_E(TAG, "Failed to delete panel");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->panel_handle = nullptr; internal->panel_handle = nullptr;
@@ -179,10 +185,12 @@ static error_t stop(Device* device) {
if (internal->io_handle != nullptr) { if (internal->io_handle != nullptr) {
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO"); LOG_E(TAG, "Failed to delete panel IO");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->io_handle = nullptr; internal->io_handle = nullptr;
} }
spi_controller_unlock(internal->spi_controller);
vSemaphoreDelete(internal->draw_done_semaphore); vSemaphoreDelete(internal->draw_done_semaphore);
free(internal); free(internal);
@@ -196,12 +204,18 @@ static error_t stop(Device* device) {
static error_t st7796_reset(Device* device) { static error_t st7796_reset(Device* device) {
auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7796_init(Device* device) { static error_t st7796_init(Device* device) {
auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7796_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { static error_t st7796_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
@@ -212,26 +226,39 @@ static error_t st7796_draw_bitmap(Device* device, int32_t x_start, int32_t y_sta
// satisfied by this draw's own transfer completing. // satisfied by this draw's own transfer completing.
xSemaphoreTake(internal->draw_done_semaphore, 0); xSemaphoreTake(internal->draw_done_semaphore, 0);
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) { spi_controller_lock(internal->spi_controller);
esp_err_t ret = esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data);
if (ret != ESP_OK) {
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
// Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous // Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous
// contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite // contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite
// color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the // color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the
// transfer and returns once it's handed to the SPI peripheral, not once it's finished. // transfer and returns once it's handed to the SPI peripheral, not once it's finished. Hold the
// bus lock across the wait too, not just the queueing call: the command/data phases aren't
// wrapped in their own acquire_bus by esp_lcd_panel_io_spi, so another bus user could otherwise
// interleave with this transfer while it's still in flight.
xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY); xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY);
spi_controller_unlock(internal->spi_controller);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t st7796_mirror(Device* device, bool x_axis, bool y_axis) { static error_t st7796_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7796_swap_xy(Device* device, bool swap_axes) { static error_t st7796_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after // Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after
@@ -250,7 +277,10 @@ static bool st7796_get_mirror_y(Device* device) {
static error_t st7796_set_gap(Device* device, int32_t x_gap, int32_t y_gap) { static error_t st7796_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device));
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x(). // Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
@@ -264,17 +294,26 @@ static int32_t st7796_get_gap_y(Device* device) {
static error_t st7796_invert_color(Device* device, bool invert_color_data) { static error_t st7796_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device));
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7796_disp_on_off(Device* device, bool on_off) { static error_t st7796_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t st7796_disp_sleep(Device* device, bool sleep) { static error_t st7796_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device)); auto* internal = static_cast<St7796Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
// bgr_order only selects the panel controller's rgb_ele_order (applied in start(), above) so the // bgr_order only selects the panel controller's rgb_ele_order (applied in start(), above) so the
+29 -3
View File
@@ -28,6 +28,7 @@
#define POWER_SUPPLY_MIN_MV 3200 #define POWER_SUPPLY_MIN_MV 3200
struct Xpt2046Internal { struct Xpt2046Internal {
Device* spi_controller;
esp_lcd_panel_io_handle_t io_handle; esp_lcd_panel_io_handle_t io_handle;
esp_lcd_touch_handle_t touch_handle; esp_lcd_touch_handle_t touch_handle;
Device* power_supply_device; Device* power_supply_device;
@@ -74,7 +75,9 @@ static error_t ps_get_property(Device* device, PowerSupplyProperty property, Pow
auto* parent_internal = static_cast<Xpt2046Internal*>(device_get_driver_data(parent)); auto* parent_internal = static_cast<Xpt2046Internal*>(device_get_driver_data(parent));
int battery_mv; int battery_mv;
spi_controller_lock(parent_internal->spi_controller);
error_t error = read_battery_mv(parent_internal->touch_handle, &battery_mv); error_t error = read_battery_mv(parent_internal->touch_handle, &battery_mv);
spi_controller_unlock(parent_internal->spi_controller);
if (error != ERROR_NONE) { if (error != ERROR_NONE) {
return error; return error;
} }
@@ -181,8 +184,12 @@ static error_t start(Device* device) {
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
internal->spi_controller = parent;
const esp_lcd_panel_io_spi_config_t io_config = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(cs_pin.pin); const esp_lcd_panel_io_spi_config_t io_config = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(cs_pin.pin);
spi_controller_lock(internal->spi_controller);
esp_err_t ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)spi_config->host, &io_config, &internal->io_handle); esp_err_t ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)spi_config->host, &io_config, &internal->io_handle);
spi_controller_unlock(internal->spi_controller);
if (ret != ESP_OK) { if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create panel IO: %s", esp_err_to_name(ret)); LOG_E(TAG, "Failed to create panel IO: %s", esp_err_to_name(ret));
free(internal); free(internal);
@@ -209,10 +216,14 @@ static error_t start(Device* device) {
.driver_data = nullptr, .driver_data = nullptr,
}; };
spi_controller_lock(internal->spi_controller);
ret = esp_lcd_touch_new_spi_xpt2046(internal->io_handle, &touch_config, &internal->touch_handle); ret = esp_lcd_touch_new_spi_xpt2046(internal->io_handle, &touch_config, &internal->touch_handle);
spi_controller_unlock(internal->spi_controller);
if (ret != ESP_OK) { if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create touch handle: %s", esp_err_to_name(ret)); LOG_E(TAG, "Failed to create touch handle: %s", esp_err_to_name(ret));
spi_controller_lock(internal->spi_controller);
esp_lcd_panel_io_del(internal->io_handle); esp_lcd_panel_io_del(internal->io_handle);
spi_controller_unlock(internal->spi_controller);
free(internal); free(internal);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
@@ -224,8 +235,10 @@ static error_t start(Device* device) {
error_t error = create_power_supply_child(device, internal->power_supply_device); error_t error = create_power_supply_child(device, internal->power_supply_device);
if (error != ERROR_NONE) { if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to create power-supply device"); LOG_E(TAG, "Failed to create power-supply device");
spi_controller_lock(internal->spi_controller);
esp_lcd_touch_del(internal->touch_handle); esp_lcd_touch_del(internal->touch_handle);
esp_lcd_panel_io_del(internal->io_handle); esp_lcd_panel_io_del(internal->io_handle);
spi_controller_unlock(internal->spi_controller);
free(internal); free(internal);
return error; return error;
} }
@@ -244,9 +257,11 @@ static error_t stop(Device* device) {
// esp_lcd_touch_del() only releases the touch-side resources; the panel IO handle is owned // esp_lcd_touch_del() only releases the touch-side resources; the panel IO handle is owned
// separately and needs its own deletion. // separately and needs its own deletion.
spi_controller_lock(internal->spi_controller);
if (internal->touch_handle != nullptr) { if (internal->touch_handle != nullptr) {
if (esp_lcd_touch_del(internal->touch_handle) != ESP_OK) { if (esp_lcd_touch_del(internal->touch_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete touch handle"); LOG_E(TAG, "Failed to delete touch handle");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->touch_handle = nullptr; internal->touch_handle = nullptr;
@@ -255,10 +270,12 @@ static error_t stop(Device* device) {
if (internal->io_handle != nullptr) { if (internal->io_handle != nullptr) {
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO handle"); LOG_E(TAG, "Failed to delete panel IO handle");
spi_controller_unlock(internal->spi_controller);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
internal->io_handle = nullptr; internal->io_handle = nullptr;
} }
spi_controller_unlock(internal->spi_controller);
free(internal); free(internal);
device_set_driver_data(device, nullptr); device_set_driver_data(device, nullptr);
@@ -271,18 +288,27 @@ static error_t stop(Device* device) {
static error_t xpt2046_enter_sleep(Device* device) { static error_t xpt2046_enter_sleep(Device* device) {
auto* internal = static_cast<Xpt2046Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Xpt2046Internal*>(device_get_driver_data(device));
return esp_lcd_touch_enter_sleep(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_touch_enter_sleep(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t xpt2046_exit_sleep(Device* device) { static error_t xpt2046_exit_sleep(Device* device) {
auto* internal = static_cast<Xpt2046Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Xpt2046Internal*>(device_get_driver_data(device));
return esp_lcd_touch_exit_sleep(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_touch_exit_sleep(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static error_t xpt2046_read_data(Device* device, TickType_t timeout) { static error_t xpt2046_read_data(Device* device, TickType_t timeout) {
(void)timeout; // esp_lcd_touch_read_data() has no timeout parameter (void)timeout; // esp_lcd_touch_read_data() has no timeout parameter
auto* internal = static_cast<Xpt2046Internal*>(device_get_driver_data(device)); auto* internal = static_cast<Xpt2046Internal*>(device_get_driver_data(device));
return esp_lcd_touch_read_data(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; spi_controller_lock(internal->spi_controller);
error_t result = esp_lcd_touch_read_data(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
spi_controller_unlock(internal->spi_controller);
return result;
} }
static bool xpt2046_get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) { static bool xpt2046_get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) {
@@ -8,7 +8,6 @@
#include <tactility/error.h> #include <tactility/error.h>
#include <tactility/check.h> #include <tactility/check.h>
#include <tactility/filesystem/file_mutex.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <service/manager.h> #include <service/manager.h>
@@ -31,14 +30,9 @@ struct Esp32AppRuntime {
}; };
error_t read_file(const char* path, uint8_t** out_data, size_t* out_size) { error_t read_file(const char* path, uint8_t** out_data, size_t* out_size) {
FileMutex mutex;
file_mutex_get(&mutex, path);
file_mutex_lock(&mutex);
FILE* file = fopen(path, "rb"); FILE* file = fopen(path, "rb");
if (file == nullptr) { if (file == nullptr) {
LOG_E(TAG, "Failed to open %s", path); LOG_E(TAG, "Failed to open %s", path);
file_mutex_unlock(&mutex);
return ERROR_NOT_FOUND; return ERROR_NOT_FOUND;
} }
@@ -47,20 +41,17 @@ error_t read_file(const char* path, uint8_t** out_data, size_t* out_size) {
fseek(file, 0, SEEK_SET); fseek(file, 0, SEEK_SET);
if (size <= 0) { if (size <= 0) {
fclose(file); fclose(file);
file_mutex_unlock(&mutex);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
auto* data = static_cast<uint8_t*>(malloc(static_cast<size_t>(size))); auto* data = static_cast<uint8_t*>(malloc(static_cast<size_t>(size)));
if (data == nullptr) { if (data == nullptr) {
fclose(file); fclose(file);
file_mutex_unlock(&mutex);
return ERROR_OUT_OF_MEMORY; return ERROR_OUT_OF_MEMORY;
} }
size_t read = fread(data, 1, static_cast<size_t>(size), file); size_t read = fread(data, 1, static_cast<size_t>(size), file);
fclose(file); fclose(file);
file_mutex_unlock(&mutex);
if (read != static_cast<size_t>(size)) { if (read != static_cast<size_t>(size)) {
free(data); free(data);
+6 -51
View File
@@ -1,12 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#pragma once #pragma once
// Minimal filesystem helpers shared by app-module internals that need to look at on-disk app // Minimal filesystem helpers shared by app-module internals.
// directories (app_install.cpp, manager.cpp's install-path scan) - app-module may not depend
// upward on Tactility::file, so this is a small local re-implementation (see
// app_metadata_parsing.cpp for the same constraint applied to properties-file loading).
#include <tactility/filesystem/file_mutex.h>
#include <cstring> #include <cstring>
#include <dirent.h> #include <dirent.h>
@@ -21,22 +16,12 @@
inline bool app_fs_is_directory(const std::string& path) { inline bool app_fs_is_directory(const std::string& path) {
struct stat result {}; struct stat result {};
FileMutex file_mutex; return stat(path.c_str(), &result) == 0 && S_ISDIR(result.st_mode);
file_mutex_get(&file_mutex, path.c_str());
file_mutex_lock(&file_mutex);
auto is_dir = stat(path.c_str(), &result) == 0 && S_ISDIR(result.st_mode);
file_mutex_unlock(&file_mutex);
return is_dir;
} }
inline bool app_fs_is_file(const std::string& path) { inline bool app_fs_is_file(const std::string& path) {
FileMutex file_mutex;
file_mutex_get(&file_mutex, path.c_str());
file_mutex_lock(&file_mutex);
struct stat result {}; struct stat result {};
auto retval = stat(path.c_str(), &result) == 0 && S_ISREG(result.st_mode); return stat(path.c_str(), &result) == 0 && S_ISREG(result.st_mode);
file_mutex_unlock(&file_mutex);
return retval;
} }
// Appends the full path of every direct subdirectory of @a path to @a out. // Appends the full path of every direct subdirectory of @a path to @a out.
@@ -53,15 +38,11 @@ inline bool app_fs_delete_recursively(const std::string& path) {
// ESP-IDF newlib has no lstat(); ESP32 filesystems (FAT/SPIFFS) don't // ESP-IDF newlib has no lstat(); ESP32 filesystems (FAT/SPIFFS) don't
// support symlinks, so stat() is equivalent there. // support symlinks, so stat() is equivalent there.
struct stat st {}; struct stat st {};
FileMutex file_mutex;
file_mutex_get(&file_mutex, path.c_str());
file_mutex_lock(&file_mutex);
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
int rc = stat(path.c_str(), &st); int rc = stat(path.c_str(), &st);
#else #else
int rc = lstat(path.c_str(), &st); int rc = lstat(path.c_str(), &st);
#endif #endif
file_mutex_unlock(&file_mutex);
if (rc != 0) { if (rc != 0) {
return false; return false;
@@ -70,24 +51,15 @@ inline bool app_fs_delete_recursively(const std::string& path) {
#ifndef ESP_PLATFORM #ifndef ESP_PLATFORM
if (S_ISLNK(st.st_mode)) { if (S_ISLNK(st.st_mode)) {
// Symlink — remove as a leaf regardless of its target. // Symlink — remove as a leaf regardless of its target.
file_mutex_lock(&file_mutex); return unlink(path.c_str()) == 0;
bool result = unlink(path.c_str()) == 0;
file_mutex_unlock(&file_mutex);
return result;
} }
#endif #endif
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
// Collect child names while locked, then release before recursing —
// child paths can resolve to the same mount mutex (see
// app_fs_list_direct_subdirectories comment), so holding the parent
// lock across the recursive call would self-deadlock.
std::vector<std::string> children; std::vector<std::string> children;
file_mutex_lock(&file_mutex);
DIR* dir = opendir(path.c_str()); DIR* dir = opendir(path.c_str());
if (dir == nullptr) { if (dir == nullptr) {
file_mutex_unlock(&file_mutex);
return false; return false;
} }
@@ -99,7 +71,6 @@ inline bool app_fs_delete_recursively(const std::string& path) {
children.push_back(path + "/" + entry->d_name); children.push_back(path + "/" + entry->d_name);
} }
closedir(dir); closedir(dir);
file_mutex_unlock(&file_mutex);
bool success = true; bool success = true;
for (const auto& child : children) { for (const auto& child : children) {
@@ -109,33 +80,18 @@ inline bool app_fs_delete_recursively(const std::string& path) {
} }
} }
file_mutex_lock(&file_mutex); return rmdir(path.c_str()) == 0;
bool result = rmdir(path.c_str()) == 0;
file_mutex_unlock(&file_mutex);
return result;
} }
// Regular file or other — unlink. // Regular file or other — unlink.
file_mutex_lock(&file_mutex); return unlink(path.c_str()) == 0;
bool result = unlink(path.c_str()) == 0;
file_mutex_unlock(&file_mutex);
return result;
} }
inline void app_fs_list_direct_subdirectories(const std::string& path, std::vector<std::string>& out) { inline void app_fs_list_direct_subdirectories(const std::string& path, std::vector<std::string>& out) {
// Collect child names while the directory lock is held, then release it before classifying
// each one with app_fs_is_directory() - that function looks up and locks a FileMutex too,
// and file_mutex_get() resolves a child path to the same registered mutex as its parent
// mount. Calling it while still holding the directory's own lock would be a nested
// acquisition of that same (possibly non-recursive) mutex, and could self-deadlock.
std::vector<std::string> children; std::vector<std::string> children;
FileMutex file_mutex;
file_mutex_get(&file_mutex, path.c_str());
file_mutex_lock(&file_mutex);
DIR* dir = opendir(path.c_str()); DIR* dir = opendir(path.c_str());
if (dir == nullptr) { if (dir == nullptr) {
file_mutex_unlock(&file_mutex);
return; return;
} }
@@ -148,7 +104,6 @@ inline void app_fs_list_direct_subdirectories(const std::string& path, std::vect
} }
closedir(dir); closedir(dir);
file_mutex_unlock(&file_mutex);
for (const auto& child_path : children) { for (const auto& child_path : children) {
if (app_fs_is_directory(child_path)) { if (app_fs_is_directory(child_path)) {
+70 -22
View File
@@ -8,7 +8,6 @@
#include <app/private/ledger.h> #include <app/private/ledger.h>
#include <tactility/concurrent/mutex.h> #include <tactility/concurrent/mutex.h>
#include <tactility/filesystem/file_mutex.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <tactility/paths.h> #include <tactility/paths.h>
@@ -44,11 +43,7 @@ bool ensure_directory(const std::string& path) {
return true; return true;
} }
FileMutex mutex {};
file_mutex_get(&mutex, path.c_str());
file_mutex_lock(&mutex);
bool created = mkdir(path.c_str(), 0777) == 0 || errno == EEXIST; bool created = mkdir(path.c_str(), 0777) == 0 || errno == EEXIST;
file_mutex_unlock(&mutex);
if (!created) { if (!created) {
return false; return false;
} }
@@ -140,6 +135,66 @@ bool untar(const std::string& tar_path, const std::string& destination_path) {
// endregion // endregion
// region Staging-path lock: at most one caller may clean up/populate a given staging_path at a
// time, keyed by source basename. The HTTP server and app tasks can call app_install()
// concurrently, e.g. two uploads sharing a source basename - without this, one call's cleanup
// can delete or overwrite the staging directory another call is still extracting into.
struct StagingLock {
Mutex mutex {};
int refcount = 0;
};
struct StagingLockTable {
std::unordered_map<std::string, std::unique_ptr<StagingLock>> locks;
Mutex table_mutex {};
StagingLockTable() { mutex_construct(&table_mutex); }
};
StagingLockTable& staging_lock_table() {
static StagingLockTable table;
return table;
}
// Blocks until any other caller staging @a path has released it, then locks it for this caller.
// Must be paired with exactly one release_staging_lock(path) call.
void acquire_staging_lock(const std::string& path) {
auto& table = staging_lock_table();
mutex_lock(&table.table_mutex);
auto iterator = table.locks.find(path);
if (iterator == table.locks.end()) {
auto lock = std::make_unique<StagingLock>();
mutex_construct(&lock->mutex);
iterator = table.locks.emplace(path, std::move(lock)).first;
}
StagingLock* lock = iterator->second.get();
lock->refcount++;
mutex_unlock(&table.table_mutex);
mutex_lock(&lock->mutex);
}
// Erases the table entry once nothing references it anymore, so the table doesn't grow forever
// across installs with distinct basenames (e.g. unique upload temp names).
void release_staging_lock(const std::string& path) {
auto& table = staging_lock_table();
mutex_lock(&table.table_mutex);
auto iterator = table.locks.find(path);
if (iterator == table.locks.end()) {
mutex_unlock(&table.table_mutex);
return;
}
StagingLock* lock = iterator->second.get();
mutex_unlock(&lock->mutex);
if (--lock->refcount == 0) {
table.locks.erase(iterator);
}
mutex_unlock(&table.table_mutex);
}
// endregion
// region Installed-app registry: owns the AppManifest (and its id/name/path strings) that // region Installed-app registry: owns the AppManifest (and its id/name/path strings) that
// app_manager's ledger only keeps a non-owning pointer to (see app_manager_add()'s contract). // app_manager's ledger only keeps a non-owning pointer to (see app_manager_add()'s contract).
@@ -282,22 +337,14 @@ error_t app_install(const char* source_path) {
} }
auto staging_path = app_parent_path + "/" + last_path_segment(source_path); auto staging_path = app_parent_path + "/" + last_path_segment(source_path);
acquire_staging_lock(staging_path);
delete_recursively(staging_path); delete_recursively(staging_path);
FileMutex target_mutex {}; if (!untar(source_path, staging_path)) {
file_mutex_get(&target_mutex, app_parent_path.c_str());
FileMutex source_mutex {};
file_mutex_get(&source_mutex, source_path);
file_mutex_lock(&target_mutex);
file_mutex_lock(&source_mutex);
bool untar_success = untar(source_path, staging_path);
file_mutex_unlock(&source_mutex);
file_mutex_unlock(&target_mutex);
if (!untar_success) {
LOG_E(TAG, "Failed to extract %s", source_path); LOG_E(TAG, "Failed to extract %s", source_path);
delete_recursively(staging_path); delete_recursively(staging_path);
release_staging_lock(staging_path);
return ERROR_NOT_FOUND; return ERROR_NOT_FOUND;
} }
@@ -305,6 +352,7 @@ error_t app_install(const char* source_path) {
if (!app_fs_is_file(manifest_path)) { if (!app_fs_is_file(manifest_path)) {
LOG_E(TAG, "Manifest not found at %s", manifest_path.c_str()); LOG_E(TAG, "Manifest not found at %s", manifest_path.c_str());
delete_recursively(staging_path); delete_recursively(staging_path);
release_staging_lock(staging_path);
return ERROR_INVALID_ARGUMENT; return ERROR_INVALID_ARGUMENT;
} }
@@ -312,6 +360,7 @@ error_t app_install(const char* source_path) {
if (app_metadata_parse(manifest_path.c_str(), &metadata) != ERROR_NONE) { if (app_metadata_parse(manifest_path.c_str(), &metadata) != ERROR_NONE) {
LOG_E(TAG, "Install failed: invalid manifest"); LOG_E(TAG, "Install failed: invalid manifest");
delete_recursively(staging_path); delete_recursively(staging_path);
release_staging_lock(staging_path);
return ERROR_INVALID_ARGUMENT; return ERROR_INVALID_ARGUMENT;
} }
@@ -331,22 +380,21 @@ error_t app_install(const char* source_path) {
LOG_E(TAG, "Install failed: failed to remove existing installation"); LOG_E(TAG, "Install failed: failed to remove existing installation");
mutex_unlock(&registry.mutex); mutex_unlock(&registry.mutex);
delete_recursively(staging_path); delete_recursively(staging_path);
release_staging_lock(staging_path);
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
auto final_path = app_parent_path + "/" + metadata.app_id; auto final_path = app_parent_path + "/" + metadata.app_id;
delete_recursively(final_path); delete_recursively(final_path);
file_mutex_lock(&target_mutex); if (rename(staging_path.c_str(), final_path.c_str()) != 0) {
bool rename_success = rename(staging_path.c_str(), final_path.c_str()) == 0;
file_mutex_unlock(&target_mutex);
if (!rename_success) {
LOG_E(TAG, "Failed to rename \"%s\" to \"%s\"", staging_path.c_str(), final_path.c_str()); LOG_E(TAG, "Failed to rename \"%s\" to \"%s\"", staging_path.c_str(), final_path.c_str());
delete_recursively(staging_path); delete_recursively(staging_path);
release_staging_lock(staging_path);
mutex_unlock(&registry.mutex); mutex_unlock(&registry.mutex);
return ERROR_NOT_FOUND; return ERROR_NOT_FOUND;
} }
release_staging_lock(staging_path);
// Only remaining failure mode is a duplicate id - can't happen, uninstall_locked() above // Only remaining failure mode is a duplicate id - can't happen, uninstall_locked() above
// already removed any previous registration for this exact id. // already removed any previous registration for this exact id.
@@ -1,6 +1,4 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/filesystem/file_mutex.h>
#include <app/metadata.h> #include <app/metadata.h>
#include <app/private/metadata_parsing_internal.h> #include <app/private/metadata_parsing_internal.h>
@@ -62,13 +60,8 @@ bool validate_csv_list(const std::string& value, bool (*is_valid_item)(const std
* minimal re-implementation rather than depending on Tactility's file::loadPropertiesFile() - * minimal re-implementation rather than depending on Tactility's file::loadPropertiesFile() -
* app-module (like every other kernel module) may not depend upward on the Tactility layer. */ * app-module (like every other kernel module) may not depend upward on the Tactility layer. */
bool load_properties(const std::string& path, std::map<std::string, std::string>& out_properties, std::string& out_first_line) { bool load_properties(const std::string& path, std::map<std::string, std::string>& out_properties, std::string& out_first_line) {
FileMutex mutex;
file_mutex_get(&mutex, path.c_str());
file_mutex_lock(&mutex);
std::ifstream file(path); std::ifstream file(path);
if (!file.is_open()) { if (!file.is_open()) {
file_mutex_unlock(&mutex);
return false; return false;
} }
@@ -103,7 +96,6 @@ bool load_properties(const std::string& path, std::map<std::string, std::string>
out_properties[key] = value; out_properties[key] = value;
} }
file_mutex_unlock(&mutex);
return true; return true;
} }
@@ -4,7 +4,6 @@
#include <service/paths.h> #include <service/paths.h>
#include <tactility/filesystem/file_mutex.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -42,37 +41,12 @@ static bool get_configuration_path(char* out_path, size_t out_path_size) {
return service_paths_get_user_data_path(GPS_SETTINGS_STORAGE_ID, "config.bin", out_path, out_path_size) == ERROR_NONE; return service_paths_get_user_data_path(GPS_SETTINGS_STORAGE_ID, "config.bin", out_path, out_path_size) == ERROR_NONE;
} }
// Holds the lock (if any) that `path` needs for the lifetime of the guard - see file_find_lock().
class FileLockGuard {
FileMutex mutex;
bool locked;
public:
explicit FileLockGuard(const char* path) {
file_mutex_get(&mutex, path);
file_mutex_lock(&mutex);
locked = true;
}
~FileLockGuard() {
unlock();
}
void unlock() {
if (locked) {
file_mutex_unlock(&mutex);
locked = false;
}
}
};
void gps_settings_for_each_configuration(void* context, void (*on_configuration)(const GpsConfiguration* configuration, size_t index, void* context)) { void gps_settings_for_each_configuration(void* context, void (*on_configuration)(const GpsConfiguration* configuration, size_t index, void* context)) {
char path[224]; char path[224];
if (!get_configuration_path(path, sizeof(path))) { if (!get_configuration_path(path, sizeof(path))) {
return; return;
} }
FileLockGuard lock(path);
FILE* file = fopen(path, "rb"); FILE* file = fopen(path, "rb");
if (file == nullptr) { if (file == nullptr) {
return; // No configurations saved yet return; // No configurations saved yet
@@ -107,8 +81,6 @@ static error_t write_configurations(const std::vector<GpsConfiguration>& configu
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
FileLockGuard lock(path);
ensure_directory_exists(directory); ensure_directory_exists(directory);
FILE* file = fopen(path, "wb"); FILE* file = fopen(path, "wb");
@@ -130,7 +102,6 @@ static error_t write_configurations(const std::vector<GpsConfiguration>& configu
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
lock.unlock();
gps_ledger_sync(); gps_ledger_sync();
return ERROR_NONE; return ERROR_NONE;
@@ -2,6 +2,7 @@
#pragma once #pragma once
#include <sd_protocol_types.h> #include <sd_protocol_types.h>
#include <tactility/error.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -16,6 +17,27 @@ struct Device;
*/ */
sdmmc_card_t* esp32_sdcard_get_card(struct Device* device); sdmmc_card_t* esp32_sdcard_get_card(struct Device* device);
/**
* @brief Wraps the card's do_transaction function pointer so every SD command takes the parent
* SPI controller's bus lock.
* @param[in] device the SD card device
* @param[in] card the card returned by the mount call, passed directly rather than re-resolved
* via esp32_sdcard_get_card(): this is called from within the driver's own start_device
* callback, before device_is_ready() is true, so the readiness-gated getters can't be used
* here.
* @retval ERROR_NONE on success, or when the device's parent is not a SPI controller (no-op,
* e.g. SDMMC cards, which have no shared bus to arbitrate)
*/
error_t esp32_sdcard_install_bus_lock(struct Device* device, sdmmc_card_t* card);
/**
* @brief Removes the bus lock installed by esp32_sdcard_install_bus_lock(), restoring the card's
* original do_transaction. No-op if no lock was installed for this device. Must be called before
* the card handle is freed.
* @param[in] device the SD card device
*/
void esp32_sdcard_remove_bus_lock(struct Device* device);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
@@ -3,6 +3,9 @@
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/drivers/esp32_sdcard.h> #include <tactility/drivers/esp32_sdcard.h>
#include <tactility/drivers/esp32_sdspi.h> #include <tactility/drivers/esp32_sdspi.h>
#include <tactility/drivers/spi_controller.h>
#include <sdmmc_cmd.h>
#include <soc/soc_caps.h> #include <soc/soc_caps.h>
#if SOC_SDMMC_HOST_SUPPORTED #if SOC_SDMMC_HOST_SUPPORTED
@@ -24,4 +27,78 @@ sdmmc_card_t* esp32_sdcard_get_card(Device* device) {
return nullptr; return nullptr;
} }
// No board in the tree has more than one SD-over-SPI card.
static constexpr size_t MAX_BUS_LOCKS = 2;
struct SdcardBusLock {
Device* sdcard = nullptr; // owner, for removal
Device* controller = nullptr; // parent SPI controller
int slot = 0; // sdspi_dev_handle_t, read back from card->host.slot
esp_err_t (*inner)(int, sdmmc_command_t*) = nullptr;
};
static SdcardBusLock bus_locks[MAX_BUS_LOCKS];
static SdcardBusLock* find_by_slot(int slot) {
for (auto& entry : bus_locks) {
if (entry.sdcard != nullptr && entry.slot == slot) {
return &entry;
}
}
return nullptr;
}
static esp_err_t locked_do_transaction(int slot, sdmmc_command_t* cmd) {
const SdcardBusLock* entry = find_by_slot(slot);
if (entry == nullptr) {
return ESP_ERR_INVALID_STATE;
}
spi_controller_lock(entry->controller);
esp_err_t result = entry->inner(slot, cmd);
spi_controller_unlock(entry->controller);
return result;
}
error_t esp32_sdcard_install_bus_lock(Device* device, sdmmc_card_t* card) {
auto* parent = device_get_parent(device);
if (parent == nullptr || device_get_type(parent) != &SPI_CONTROLLER_TYPE) {
return ERROR_NONE;
}
if (card == nullptr) {
return ERROR_INVALID_STATE;
}
SdcardBusLock* slot_entry = nullptr;
for (auto& entry : bus_locks) {
if (entry.sdcard == nullptr) {
slot_entry = &entry;
break;
}
}
if (slot_entry == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
slot_entry->sdcard = device;
slot_entry->controller = parent;
slot_entry->slot = card->host.slot;
slot_entry->inner = card->host.do_transaction;
card->host.do_transaction = locked_do_transaction;
return ERROR_NONE;
}
void esp32_sdcard_remove_bus_lock(Device* device) {
for (auto& entry : bus_locks) {
if (entry.sdcard == device) {
auto* card = esp32_sdcard_get_card(device);
if (card != nullptr) {
card->host.do_transaction = entry.inner;
}
entry = SdcardBusLock{};
return;
}
}
}
} }
@@ -5,6 +5,7 @@
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/drivers/esp32_gpio_helpers.h> #include <tactility/drivers/esp32_gpio_helpers.h>
#include <tactility/drivers/esp32_sdcard.h>
#include <tactility/drivers/esp32_sdspi.h> #include <tactility/drivers/esp32_sdspi.h>
#include <tactility/drivers/esp32_sdspi_fs.h> #include <tactility/drivers/esp32_sdspi_fs.h>
#include <tactility/drivers/esp32_spi.h> #include <tactility/drivers/esp32_spi.h>
@@ -92,6 +93,12 @@ static error_t start(Device* device) {
auto* spi_config = static_cast<const Esp32SpiConfig*>(parent->config); auto* spi_config = static_cast<const Esp32SpiConfig*>(parent->config);
// Card init below runs through the SD host driver's own do_transaction, which is not yet
// patched to take the bus lock (that only happens once esp32_sdcard_install_bus_lock() runs,
// after a successful mount) - so this whole window, from CS bit-banging through mount, needs
// the coarse controller lock instead.
spi_controller_lock(parent);
// Lower all CS pins // Lower all CS pins
esp32_spi_deselect_all_cs(parent); esp32_spi_deselect_all_cs(parent);
// Manually set the CS pin fo // Manually set the CS pin fo
@@ -100,6 +107,7 @@ static error_t start(Device* device) {
data->fs_handle = esp32_sdspi_fs_alloc(config, spi_config->host, cs_pin_spec.pin, "/sdcard"); data->fs_handle = esp32_sdspi_fs_alloc(config, spi_config->host, cs_pin_spec.pin, "/sdcard");
if (!data->fs_handle) { if (!data->fs_handle) {
spi_controller_unlock(parent);
data->cleanup_pins(); data->cleanup_pins();
device_set_driver_data(device, nullptr); device_set_driver_data(device, nullptr);
data->unlock(); data->unlock();
@@ -111,8 +119,18 @@ static error_t start(Device* device) {
file_system_set_owner(data->file_system, device); file_system_set_owner(data->file_system, device);
if (file_system_mount(data->file_system) != ERROR_NONE) { if (file_system_mount(data->file_system) != ERROR_NONE) {
LOG_E(TAG, "Failed to mount SD card filesystem"); LOG_E(TAG, "Failed to mount SD card filesystem");
} else {
// Pass the card straight from the just-succeeded mount, not via esp32_sdcard_get_card():
// that getter gates on device_is_ready(), which is still false here -- device_start()
// only flips it after this start_device callback returns. Going through the gated getter
// silently no-ops the lock install here every time, leaving the card's do_transaction
// unwrapped and racing the display for the device's entire lifetime.
if (esp32_sdcard_install_bus_lock(device, esp32_sdspi_fs_get_card(data->fs_handle)) != ERROR_NONE) {
LOG_E(TAG, "Failed to install SD card bus lock");
}
} }
spi_controller_unlock(parent);
data->unlock(); data->unlock();
return ERROR_NONE; return ERROR_NONE;
} }
@@ -130,6 +148,7 @@ static error_t stop(Device* device) {
data->unlock(); data->unlock();
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
esp32_sdcard_remove_bus_lock(device);
} }
file_system_remove(data->file_system); file_system_remove(data->file_system);
-31
View File
@@ -1,13 +1,7 @@
/**
* All functions in this file can be safely called without manually applying file locks.
* For calls to C stdlib APIs such as fopen(), always lock with file::FileMutexGuard(path) first!
*/
#pragma once #pragma once
#include <Tactility/TactilityCore.h> #include <Tactility/TactilityCore.h>
#include <tactility/filesystem/file_mutex.h>
#include <cstdio> #include <cstdio>
#include <dirent.h> #include <dirent.h>
#include <functional> #include <functional>
@@ -16,10 +10,6 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <vector> #include <vector>
/**
* @warning SD card access requires a locking mechanism:
* @warning When using this in the Tactility main project, use `file::FileMutexGuard`
*/
namespace tt::file { namespace tt::file {
/** File types for `dirent`'s `d_type`. */ /** File types for `dirent`'s `d_type`. */
@@ -49,27 +39,6 @@ struct FileCloser {
} }
}; };
/**
* RAII lock over TactilityKernel's file_mutex.h for the file system mount that owns `path`.
* Locks in the constructor, unlocks in the destructor - no heap allocation, no virtual dispatch.
*/
class FileMutexGuard final {
FileMutex mutex {};
public:
explicit FileMutexGuard(const std::string& path) {
file_mutex_get(&mutex, path.c_str());
file_mutex_lock(&mutex);
}
~FileMutexGuard() {
file_mutex_unlock(&mutex);
}
FileMutexGuard(const FileMutexGuard&) = delete;
FileMutexGuard& operator=(const FileMutexGuard&) = delete;
};
long getSize(FILE* file); long getSize(FILE* file);
/** Read a file and return its data. /** Read a file and return its data.
@@ -2,13 +2,8 @@
#include <Tactility/file/File.h> #include <Tactility/file/File.h>
#include <tactility/filesystem/file_mutex.h>
#include <string> #include <string>
/**
* @warning The functionality below does NOT safely acquire file locks. Use file::FileMutexGuard when using the functionality below.
*/
namespace tt::file { namespace tt::file {
class ObjectFileReader { class ObjectFileReader {
@@ -45,7 +40,6 @@ class ObjectFileWriter {
const uint32_t recordSize; const uint32_t recordSize;
const uint32_t recordVersion; const uint32_t recordVersion;
const bool append; const bool append;
FileMutex mutex {};
std::unique_ptr<FILE, FileCloser> file; std::unique_ptr<FILE, FileCloser> file;
uint32_t recordsWritten = 0; uint32_t recordsWritten = 0;
@@ -57,9 +51,7 @@ public:
recordSize(recordSize), recordSize(recordSize),
recordVersion(recordVersion), recordVersion(recordVersion),
append(append) append(append)
{ {}
file_mutex_get(&mutex, this->filePath.c_str());
}
~ObjectFileWriter() { ~ObjectFileWriter() {
+11 -26
View File
@@ -61,7 +61,6 @@
#include <tactility/drivers/rtc.h> #include <tactility/drivers/rtc.h>
#include <tactility/drivers/trackball.h> #include <tactility/drivers/trackball.h>
#include <tactility/drivers/uart_controller.h> #include <tactility/drivers/uart_controller.h>
#include <tactility/filesystem/file_mutex.h>
#include <tactility/filesystem/file_system.h> #include <tactility/filesystem/file_system.h>
#include <tactility/kernel_init.h> #include <tactility/kernel_init.h>
#include <tactility/log.h> #include <tactility/log.h>
@@ -73,9 +72,6 @@ constexpr auto* TAG = "Tactility";
static DispatcherHandle_t mainDispatcherHandle = dispatcher_alloc(); static DispatcherHandle_t mainDispatcherHandle = dispatcher_alloc();
void initFileMutexForLvgl();
void deinitFileMutexForLvgl();
namespace { namespace {
void mainDispatcherTrampoline(void* context) { void mainDispatcherTrampoline(void* context) {
@@ -321,19 +317,12 @@ void createTempDirectory() {
auto data_path = getDataPath(); auto data_path = getDataPath();
auto temp_path = std::format("{}/tmp", data_path); auto temp_path = std::format("{}/tmp", data_path);
if (!file::isDirectory(temp_path)) { if (!file::isDirectory(temp_path)) {
FileMutex mutex; if (!file::findOrCreateParentDirectory(temp_path, 0777)) {
file_mutex_get(&mutex, data_path.c_str()); LOG_E(TAG, "Failed to create %s", data_path.c_str());
if (file_mutex_try_lock(&mutex, 1000 / portTICK_PERIOD_MS)) { } else if (mkdir(temp_path.c_str(), 0777) == 0) {
if (!file::findOrCreateParentDirectory(temp_path, 0777)) { LOG_I(TAG, "Created %s", temp_path.c_str());
LOG_E(TAG, "Failed to create %s", data_path.c_str());
} else if (mkdir(temp_path.c_str(), 0777) == 0) {
LOG_I(TAG, "Created %s", temp_path.c_str());
} else {
LOG_E(TAG, "Failed to create %s", temp_path.c_str());
}
file_mutex_unlock(&mutex);
} else { } else {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, data_path.c_str()); LOG_E(TAG, "Failed to create %s", temp_path.c_str());
} }
} else { } else {
LOG_I(TAG, "Found existing %s", temp_path.c_str()); LOG_I(TAG, "Found existing %s", temp_path.c_str());
@@ -419,8 +408,6 @@ static void applySavedTouchCalibration() {
#endif // CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED #endif // CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED
static void onLvglStarted() { static void onLvglStarted() {
initFileMutexForLvgl();
window_manager_configure(windowManagerScreenInit); window_manager_configure(windowManagerScreenInit);
check(module_ensure_started(&lvgl_window_manager_module) == ERROR_NONE); check(module_ensure_started(&lvgl_window_manager_module) == ERROR_NONE);
@@ -451,14 +438,6 @@ static void onLvglStarted() {
} }
static void onLvglStopped() { static void onLvglStopped() {
deinitFileMutexForLvgl();
if (softwareKeyboard.object != nullptr) {
lvgl_software_keyboard_destruct(&softwareKeyboard);
}
module_stop(&lvgl_window_manager_module);
lvgl::stopKeyboardDeviceListener(); lvgl::stopKeyboardDeviceListener();
lvgl::stopUsbHidInput(); lvgl::stopUsbHidInput();
@@ -474,6 +453,12 @@ static void onLvglStopped() {
check(service::removeService(service::memorychecker::manifest.id)); check(service::removeService(service::memorychecker::manifest.id));
check(service::removeService(service::statusbar::manifest.id)); check(service::removeService(service::statusbar::manifest.id));
if (softwareKeyboard.object != nullptr) {
lvgl_software_keyboard_destruct(&softwareKeyboard);
}
module_stop(&lvgl_window_manager_module);
memory_print_stats(); memory_print_stats();
} }
@@ -21,8 +21,6 @@ static bool parseEntry(const cJSON* object, AppHubEntry& entry) {
} }
bool parseJson(const std::string& filePath, AppHubEntryList& entries) { bool parseJson(const std::string& filePath, AppHubEntryList& entries) {
file::FileMutexGuard guard(filePath);
auto data = file::readString(filePath); auto data = file::readString(filePath);
if (data == nullptr) { if (data == nullptr) {
LOG_E(TAG, "Failed to read %s", filePath.c_str()); LOG_E(TAG, "Failed to read %s", filePath.c_str());
@@ -110,7 +110,6 @@ void writeCrashLogFile(const CrashData& crashData) {
} }
std::string path = std::string(root) + "/crash.txt"; std::string path = std::string(root) + "/crash.txt";
file::FileMutexGuard guard(path);
if (!file::writeString(path, formatCrashData(crashData))) { if (!file::writeString(path, formatCrashData(crashData))) {
LOG_E(TAG, "Failed to write %s", path.c_str()); LOG_E(TAG, "Failed to write %s", path.c_str());
} }
+40 -95
View File
@@ -13,17 +13,17 @@
#include <Tactility/file/File.h> #include <Tactility/file/File.h>
#include <Tactility/Platform.h> #include <Tactility/Platform.h>
#include <Tactility/StringUtils.h> #include <Tactility/StringUtils.h>
#include <Tactility/Tactility.h>
#include <tactility/check.h> #include <tactility/check.h>
#include <tactility/device.h> #include <tactility/device.h>
#include <tactility/drivers/usb_host_msc.h> #include <tactility/drivers/usb_host_msc.h>
#include <tactility/filesystem/file_mutex.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <cctype> #include <cctype>
#include <cerrno>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <fcntl.h>
#include <unistd.h> #include <unistd.h>
namespace tt::app::files { namespace tt::app::files {
@@ -106,31 +106,13 @@ static void onPastePressedCallback(lv_event_t* event) {
// region File helpers // region File helpers
static bool copyFileContents(const std::string& src, const std::string& dst) { static bool copyFileContents(const std::string& src, const std::string& dst) {
FileMutex src_mutex;
file_mutex_get(&src_mutex, src.c_str());
FileMutex dst_mutex;
file_mutex_get(&dst_mutex, dst.c_str());
const bool same_lock = (src_mutex.lock == dst_mutex.lock &&
src_mutex.try_lock == dst_mutex.try_lock &&
src_mutex.unlock == dst_mutex.unlock);
auto unlock_all = [&] {
if (!same_lock) file_mutex_unlock(&dst_mutex);
file_mutex_unlock(&src_mutex);
};
file_mutex_lock(&src_mutex);
if (!same_lock) file_mutex_lock(&dst_mutex);
FILE* in = fopen(src.c_str(), "rb"); FILE* in = fopen(src.c_str(), "rb");
if (in == nullptr) { if (in == nullptr) {
unlock_all();
return false; return false;
} }
FILE* out = fopen(dst.c_str(), "wb"); FILE* out = fopen(dst.c_str(), "wb");
if (out == nullptr) { if (out == nullptr) {
fclose(in); fclose(in);
unlock_all();
return false; return false;
} }
uint8_t buf[512]; uint8_t buf[512];
@@ -152,7 +134,6 @@ static bool copyFileContents(const std::string& src, const std::string& dst) {
if (!success) { if (!success) {
remove(dst.c_str()); remove(dst.c_str());
} }
unlock_all();
return success; return success;
} }
@@ -162,33 +143,23 @@ static bool copyRecursive(const std::string& src, const std::string& dst) {
return false; return false;
} }
// Process one entry at a time: release the device lock between iterations
// so other SPI bus users aren't starved, and stop immediately on failure.
FileMutex mutex;
file_mutex_get(&mutex, src.c_str());
file_mutex_lock(&mutex);
DIR* dir = opendir(src.c_str()); DIR* dir = opendir(src.c_str());
if (!dir) { if (!dir) {
file_mutex_unlock(&mutex);
file::deleteRecursively(dst); file::deleteRecursively(dst);
return false; return false;
} }
bool success = true; bool success = true;
while (success) { while (success) {
struct dirent* entry = readdir(dir); dirent* entry = readdir(dir);
if (!entry) break; if (!entry) break;
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
std::string name = entry->d_name; // copy before releasing lock std::string name = entry->d_name; // copy before releasing lock
file_mutex_unlock(&mutex);
success = copyRecursive(file::getChildPath(src, name), file::getChildPath(dst, name)); success = copyRecursive(file::getChildPath(src, name), file::getChildPath(dst, name));
file_mutex_lock(&mutex);
} }
closedir(dir); closedir(dir);
file_mutex_unlock(&mutex);
if (!success) { if (!success) {
file::deleteRecursively(dst); file::deleteRecursively(dst);
@@ -608,7 +579,6 @@ void View::onResult(uint32_t launchId, int32_t result) {
LOG_W(TAG, "Failed to delete %s", filepath.c_str()); LOG_W(TAG, "Failed to delete %s", filepath.c_str());
} }
} else if (file::isFile(filepath)) { } else if (file::isFile(filepath)) {
file::FileMutexGuard guard(filepath);
if (remove(filepath.c_str()) != 0) { if (remove(filepath.c_str()) != 0) {
LOG_W(TAG, "Failed to delete %s", filepath.c_str()); LOG_W(TAG, "Failed to delete %s", filepath.c_str());
} }
@@ -623,20 +593,17 @@ void View::onResult(uint32_t launchId, int32_t result) {
std::string new_name = resultText; std::string new_name = resultText;
if (!new_name.empty() && new_name != state->getSelectedChildEntry()) { if (!new_name.empty() && new_name != state->getSelectedChildEntry()) {
std::string rename_to = file::getChildPath(state->getCurrentPath(), new_name); std::string rename_to = file::getChildPath(state->getCurrentPath(), new_name);
{ struct stat st;
file::FileMutexGuard guard(filepath); if (stat(rename_to.c_str(), &st) == 0) {
struct stat st; LOG_W(TAG, "Rename: destination already exists: \"%s\"", rename_to.c_str());
if (stat(rename_to.c_str(), &st) == 0) { state->setPendingAction(State::ActionNone);
LOG_W(TAG, "Rename: destination already exists: \"%s\"", rename_to.c_str()); alertdialog::start(appInstanceId, "Rename failed", "\"" + new_name + "\" already exists.");
state->setPendingAction(State::ActionNone); break;
alertdialog::start(appInstanceId, "Rename failed", "\"" + new_name + "\" already exists."); }
break; if (rename(filepath.c_str(), rename_to.c_str()) == 0) {
} LOG_I(TAG, "Renamed \"%s\" to \"%s\"", filepath.c_str(), rename_to.c_str());
if (rename(filepath.c_str(), rename_to.c_str()) == 0) { } else {
LOG_I(TAG, "Renamed \"%s\" to \"%s\"", filepath.c_str(), rename_to.c_str()); LOG_E(TAG, "Failed to rename \"%s\" to \"%s\"", filepath.c_str(), rename_to.c_str());
} else {
LOG_E(TAG, "Failed to rename \"%s\" to \"%s\"", filepath.c_str(), rename_to.c_str());
}
} }
state->setEntriesForPath(state->getCurrentPath()); state->setEntriesForPath(state->getCurrentPath());
@@ -649,22 +616,21 @@ void View::onResult(uint32_t launchId, int32_t result) {
if (!filename.empty()) { if (!filename.empty()) {
std::string new_file_path = file::getChildPath(state->getCurrentPath(), filename); std::string new_file_path = file::getChildPath(state->getCurrentPath(), filename);
{ // O_CREAT | O_EXCL makes creation+existence-check one atomic operation, unlike a separate stat() before fopen()
file::FileMutexGuard guard(new_file_path); int fd = open(new_file_path.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd >= 0) {
struct stat st; FILE* new_file = fdopen(fd, "w");
if (stat(new_file_path.c_str(), &st) == 0) {
LOG_W(TAG, "File already exists: \"%s\"", new_file_path.c_str());
break;
}
FILE* new_file = fopen(new_file_path.c_str(), "w");
if (new_file) { if (new_file) {
fclose(new_file); fclose(new_file);
LOG_I(TAG, "Created file \"%s\"", new_file_path.c_str());
} else { } else {
LOG_E(TAG, "Failed to create file \"%s\"", new_file_path.c_str()); close(fd);
} }
LOG_I(TAG, "Created file \"%s\"", new_file_path.c_str());
} else if (errno == EEXIST) {
LOG_W(TAG, "File already exists: \"%s\"", new_file_path.c_str());
break;
} else {
LOG_E(TAG, "Failed to create file \"%s\"", new_file_path.c_str());
} }
state->setEntriesForPath(state->getCurrentPath()); state->setEntriesForPath(state->getCurrentPath());
@@ -677,20 +643,16 @@ void View::onResult(uint32_t launchId, int32_t result) {
if (!foldername.empty()) { if (!foldername.empty()) {
std::string new_folder_path = file::getChildPath(state->getCurrentPath(), foldername); std::string new_folder_path = file::getChildPath(state->getCurrentPath(), foldername);
{ struct stat st;
file::FileMutexGuard guard(new_folder_path); if (stat(new_folder_path.c_str(), &st) == 0) {
LOG_W(TAG, "Folder already exists: \"%s\"", new_folder_path.c_str());
break;
}
struct stat st; if (mkdir(new_folder_path.c_str(), 0755) == 0) {
if (stat(new_folder_path.c_str(), &st) == 0) { LOG_I(TAG, "Created folder \"%s\"", new_folder_path.c_str());
LOG_W(TAG, "Folder already exists: \"%s\"", new_folder_path.c_str()); } else {
break; LOG_E(TAG, "Failed to create folder \"%s\"", new_folder_path.c_str());
}
if (mkdir(new_folder_path.c_str(), 0755) == 0) {
LOG_I(TAG, "Created folder \"%s\"", new_folder_path.c_str());
} else {
LOG_E(TAG, "Failed to create folder \"%s\"", new_folder_path.c_str());
}
} }
state->setEntriesForPath(state->getCurrentPath()); state->setEntriesForPath(state->getCurrentPath());
@@ -709,12 +671,9 @@ void View::onResult(uint32_t launchId, int32_t result) {
// Revalidate right before the destructive delete so we only ever // Revalidate right before the destructive delete so we only ever
// remove the exact file the user agreed to overwrite. // remove the exact file the user agreed to overwrite.
bool dst_unchanged; bool dst_unchanged;
{ struct stat current_stat {};
file::FileMutexGuard guard(dst); dst_unchanged = (stat(dst.c_str(), &current_stat) == 0) &&
struct stat current_stat {}; state->pendingPasteDstMatches(current_stat);
dst_unchanged = (stat(dst.c_str(), &current_stat) == 0) &&
state->pendingPasteDstMatches(current_stat);
}
state->clearPendingPasteDstStat(); state->clearPendingPasteDstStat();
if (!dst_unchanged) { if (!dst_unchanged) {
@@ -780,13 +739,6 @@ void View::onPastePressed() {
std::string entry_name = file::getLastPathSegment(src); std::string entry_name = file::getLastPathSegment(src);
std::string dst = file::getChildPath(state->getCurrentPath(), entry_name); std::string dst = file::getChildPath(state->getCurrentPath(), entry_name);
// Note: FileMutexGuard(src) guards the source path; the existence check below is
// against dst, so there is a TOCTOU gap between this check and the write inside
// doPaste. When dst exists, the overwrite-confirm path below re-validates dst's
// stat immediately before the destructive delete (see ActionPaste in onResult),
// closing the window that matters (the dialog being open). When dst does not
// exist here, doPaste's write can still race a concurrent creator; acceptable on
// a single-user embedded device.
if (src == dst) { if (src == dst) {
LOG_I(TAG, "Paste: source and destination are the same path, skipping"); LOG_I(TAG, "Paste: source and destination are the same path, skipping");
return; return;
@@ -794,12 +746,9 @@ void View::onPastePressed() {
bool dst_exists; bool dst_exists;
struct stat dst_stat {}; struct stat dst_stat {};
{
file::FileMutexGuard guard(src);
dst_exists = (stat(dst.c_str(), &dst_stat) == 0);
}
if (dst_exists) { // If dst exists...
if (stat(dst.c_str(), &dst_stat) == 0) {
state->setPendingPasteDst(dst); state->setPendingPasteDst(dst);
state->setPendingPasteDstStat(dst_stat); state->setPendingPasteDstStat(dst_stat);
state->setPendingAction(State::ActionPaste); state->setPendingAction(State::ActionPaste);
@@ -815,11 +764,7 @@ void View::doPaste(const std::string& src, bool is_cut, const std::string& dst)
bool success = false; bool success = false;
bool src_delete_failed = false; bool src_delete_failed = false;
if (is_cut) { if (is_cut) {
{ if (rename(src.c_str(), dst.c_str()) != 0) {
file::FileMutexGuard guard(src);
success = (rename(src.c_str(), dst.c_str()) == 0);
}
if (!success) {
// Fallback for cross-filesystem moves: copy then delete. // Fallback for cross-filesystem moves: copy then delete.
// Only mark success if both halves succeed — if the source removal // Only mark success if both halves succeed — if the source removal
// fails we leave success=false so the clipboard is preserved and // fails we leave success=false so the clipboard is preserved and
+4 -10
View File
@@ -46,8 +46,6 @@ void resetFileContent(Context* ctx) {
} }
void openFile(Context* ctx, const std::string& path) { void openFile(Context* ctx, const std::string& path) {
// We might be reading from the SD card, which could share a SPI bus with other devices (display)
file::FileMutexGuard guard(path);
auto data = file::readString(path); auto data = file::readString(path);
if (data != nullptr) { if (data != nullptr) {
lvgl_lock(); lvgl_lock();
@@ -60,15 +58,11 @@ void openFile(Context* ctx, const std::string& path) {
} }
bool saveFile(Context* ctx, const std::string& path) { bool saveFile(Context* ctx, const std::string& path) {
// We might be writing to SD card, which could share a SPI bus with other devices (display)
bool result = false; bool result = false;
{ if (file::writeString(path, ctx->saveBuffer.c_str())) {
file::FileMutexGuard guard(path); LOG_I(TAG, "Saved to %s", path.c_str());
if (file::writeString(path, ctx->saveBuffer.c_str())) { ctx->filePath = path;
LOG_I(TAG, "Saved to %s", path.c_str()); result = true;
ctx->filePath = path;
result = true;
}
} }
return result; return result;
} }
-2
View File
@@ -58,7 +58,6 @@ bool isCompleted() {
LOG_E(TAG, "Setup path not found"); LOG_E(TAG, "Setup path not found");
return false; return false;
} }
file::FileMutexGuard guard(path);
return file::isFile(path); return file::isFile(path);
} }
@@ -69,7 +68,6 @@ void markCompleted() {
if (!getCompletedMarkerPath(path)) { if (!getCompletedMarkerPath(path)) {
return; return;
} }
file::FileMutexGuard guard(path);
file::writeString(path, ""); file::writeString(path, "");
} }
-12
View File
@@ -42,8 +42,6 @@ bool listDirectory(
const std::string& path, const std::string& path,
std::function<void(const dirent&)> onEntry std::function<void(const dirent&)> onEntry
) { ) {
FileMutexGuard guard(path);
LOG_I(TAG, "listDir start %s", path.c_str()); LOG_I(TAG, "listDir start %s", path.c_str());
DIR* dir = opendir(path.c_str()); DIR* dir = opendir(path.c_str());
if (dir == nullptr) { if (dir == nullptr) {
@@ -68,8 +66,6 @@ int scandir(
ScandirFilter filterMethod, ScandirFilter filterMethod,
ScandirSort sortMethod ScandirSort sortMethod
) { ) {
FileMutexGuard guard(path);
LOG_I(TAG, "scandir start"); LOG_I(TAG, "scandir start");
DIR* dir = opendir(path.c_str()); DIR* dir = opendir(path.c_str());
if (dir == nullptr) { if (dir == nullptr) {
@@ -193,8 +189,6 @@ bool writeString(const std::string& filepath, const std::string& content) {
} }
static bool findOrCreateDirectoryInternal(std::string path, mode_t mode) { static bool findOrCreateDirectoryInternal(std::string path, mode_t mode) {
FileMutexGuard guard(path);
struct stat dir_stat; struct stat dir_stat;
if (mkdir(path.c_str(), mode) == 0) { if (mkdir(path.c_str(), mode) == 0) {
return true; return true;
@@ -310,29 +304,23 @@ bool deleteRecursively(const std::string& path) {
} }
bool deleteFile(const std::string& path) { bool deleteFile(const std::string& path) {
FileMutexGuard guard(path);
return remove(path.c_str()) == 0; return remove(path.c_str()) == 0;
} }
bool deleteDirectory(const std::string& path) { bool deleteDirectory(const std::string& path) {
FileMutexGuard guard(path);
return rmdir(path.c_str()) == 0; return rmdir(path.c_str()) == 0;
} }
bool isFile(const std::string& path) { bool isFile(const std::string& path) {
FileMutexGuard guard(path);
return access(path.c_str(), F_OK) == 0; return access(path.c_str(), F_OK) == 0;
} }
bool isDirectory(const std::string& path) { bool isDirectory(const std::string& path) {
FileMutexGuard guard(path);
struct stat stat_result; struct stat stat_result;
return stat(path.c_str(), &stat_result) == 0 && S_ISDIR(stat_result.st_mode); return stat(path.c_str(), &stat_result) == 0 && S_ISDIR(stat_result.st_mode);
} }
bool readLines(const std::string& filePath, bool stripNewLine, std::function<void(const char* line)> callback) { bool readLines(const std::string& filePath, bool stripNewLine, std::function<void(const char* line)> callback) {
FileMutexGuard guard(filePath);
auto* file = fopen(filePath.c_str(), "r"); auto* file = fopen(filePath.c_str(), "r");
if (file == nullptr) { if (file == nullptr) {
return false; return false;
-135
View File
@@ -1,135 +0,0 @@
#include <tactility/device.h>
#include <tactility/drivers/display.h>
#include <tactility/drivers/sdcard.h>
#include <tactility/drivers/spi_controller.h>
#include <tactility/filesystem/file_mutex.h>
#include <tactility/filesystem/file_system.h>
#include <lvgl/lvgl.h>
#include <vector>
constexpr auto* TAG = "file_mutex_lvgl";
struct Device;
namespace {
std::vector<FileMutexId> registered_ids;
void wrapped_lvgl_lock() {
if (!lvgl_is_running()) return;
lvgl_lock();
}
bool wrapped_lvgl_try_lock(uint32_t timeout) {
// Return lock success, so the file operation can continue when LVGL is not running
// lvgl_try_lock() fails to lock if LVGL is not running
if (!lvgl_is_running()) return true;
return lvgl_try_lock(timeout);
}
void wrapped_lvgl_unlock() {
if (!lvgl_is_running()) return;
lvgl_unlock();
}
const FileMutex lvgl_mutex = {
.lock = wrapped_lvgl_lock,
.try_lock = wrapped_lvgl_try_lock,
.unlock = wrapped_lvgl_unlock,
};
}
namespace tt {
/**
* Finds file systems with a device (e.g. sd card) that is owned by a SPI controller.
* If the SPI controller has a display on the bus, we create an LVGL lock for the file system path.
*/
void initFileMutexForLvgl() {
file_system_for_each(&registered_ids, [](FileSystem* fs, void* context) {
char mount_path[64];
if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) {
return true;
}
LOG_D(TAG, "Mount path %s", mount_path);
// We only care about file system with a Device (owner)
auto* owner = file_system_get_owner(fs);
if (owner == nullptr) {
LOG_D(TAG, "Owner: none");
return true;
}
LOG_D(TAG, "Owner: %s", owner->name);
// Ignore devices without a parent (root)
auto* parent = device_get_parent(owner);
if (parent == nullptr) {
LOG_D(TAG, "Owner: no parent");
return true;
}
LOG_D(TAG, "Owner: parent %s", parent->name);
// If the FileSystem is on a SPI bus and there's more than 1 device, we assume the other one is the display.
auto* type = device_get_type(parent);
if (type != &SPI_CONTROLLER_TYPE || device_get_child_count(parent) <= 1) {
LOG_D(TAG, "Owner parent not SPI controller or not enough children");
return true;
}
struct Context {
const char* mountPath;
std::vector<FileMutexId>* registeredIds;
};
Context ctx = { .mountPath = mount_path, .registeredIds = static_cast<std::vector<FileMutexId>*>(context) };
device_for_each_child(parent, &ctx, [](Device* child, void* context) -> bool {
Context* ctx = static_cast<Context*>(context);
if (device_get_type(child) == &DISPLAY_TYPE) {
LOG_I(TAG, "Adding file mutex for %s as it shares a bus with a display", ctx->mountPath);
ctx->registeredIds->push_back(file_mutex_add(&lvgl_mutex, ctx->mountPath));
return false;
} else {
LOG_D(TAG, "child of parent, %s: not DISPLAY_TYPE", child->name);
}
return true;
});
return true;
});
// SDMMC-backed SD cards aren't parented under SPI_CONTROLLER_TYPE, so the pass above never
// sees them - but on some chips (classic ESP32) SDMMC and SPI still contend for DMA/bus
// access. Lock every SD card mount if a display exists anywhere, regardless of bus topology.
if (!device_exists_of_type(&DISPLAY_TYPE)) {
return;
}
file_system_for_each(&registered_ids, [](FileSystem* fs, void* context) {
char mount_path[64];
if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) {
return true;
}
auto* owner = file_system_get_owner(fs);
if (owner == nullptr || device_get_type(owner) != &SDCARD_TYPE) {
return true;
}
LOG_I(TAG, "Adding file mutex for %s (SD card) - a display is present and may contend for bus/DMA resources", mount_path);
auto* ids = static_cast<std::vector<FileMutexId>*>(context);
ids->push_back(file_mutex_add(&lvgl_mutex, mount_path));
return true;
});
}
void deinitFileMutexForLvgl() {
for (FileMutexId id : registered_ids) {
file_mutex_remove(id);
}
registered_ids.clear();
}
}
+1 -6
View File
@@ -4,12 +4,7 @@
namespace tt::lvgl { namespace tt::lvgl {
bool label_set_text_file(lv_obj_t* label, const char* filepath) { bool label_set_text_file(lv_obj_t* label, const char* filepath) {
std::unique_ptr<uint8_t[]> text; std::unique_ptr<uint8_t[]> text = file::readString(filepath);
{
file::FileMutexGuard guard(filepath);
text = file::readString(filepath);
}
if (text != nullptr) { if (text != nullptr) {
lv_label_set_text(label, reinterpret_cast<const char*>(text.get())); lv_label_set_text(label, reinterpret_cast<const char*>(text.get()));
return true; return true;
+1 -2
View File
@@ -67,8 +67,7 @@ void download(
auto bytes_left = client->getContentLength(); auto bytes_left = client->getContentLength();
file::FileMutexGuard guard(downloadFilePath); LOG_I(TAG, "Opening %s", downloadFilePath.c_str());
LOG_I(TAG, "opening %s", downloadFilePath.c_str());
auto* file = fopen(downloadFilePath.c_str(), "wb"); auto* file = fopen(downloadFilePath.c_str(), "wb");
if (file == nullptr) { if (file == nullptr) {
onError("Failed to open file"); onError("Failed to open file");
+1 -16
View File
@@ -2,7 +2,6 @@
#include <Tactility/StringUtils.h> #include <Tactility/StringUtils.h>
#include <Tactility/network/HttpdReq.h> #include <Tactility/network/HttpdReq.h>
#include <tactility/filesystem/file_mutex.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <memory> #include <memory>
@@ -186,16 +185,7 @@ size_t receiveFile(httpd_req_t* request, size_t length, const std::string& fileP
char buffer[BUFFER_SIZE]; char buffer[BUFFER_SIZE];
size_t bytes_received = 0; size_t bytes_received = 0;
// Locked only around each actual disk I/O call below, not across the httpd_req_recv() waits
// in between - this file's mutex may resolve to lvgl_lock() (see FileMutexLvgl.cpp), and
// holding that for the whole (potentially multi-second) network transfer starves LVGL's own
// task for the entire upload instead of just for each brief write.
FileMutex mutex {};
file_mutex_get(&mutex, filePath.c_str());
file_mutex_lock(&mutex);
auto* file = fopen(filePath.c_str(), "wb"); auto* file = fopen(filePath.c_str(), "wb");
file_mutex_unlock(&mutex);
if (file == nullptr) { if (file == nullptr) {
LOG_E(TAG, "Failed to open file for writing: %s", filePath.c_str()); LOG_E(TAG, "Failed to open file for writing: %s", filePath.c_str());
return 0; return 0;
@@ -226,19 +216,14 @@ size_t receiveFile(httpd_req_t* request, size_t length, const std::string& fileP
timeout_retries = 0; timeout_retries = 0;
size_t receive_chunk_size = (size_t)received; size_t receive_chunk_size = (size_t)received;
file_mutex_lock(&mutex); if (fwrite(buffer, 1, receive_chunk_size, file) != receive_chunk_size) {
bool write_ok = fwrite(buffer, 1, receive_chunk_size, file) == receive_chunk_size;
file_mutex_unlock(&mutex);
if (!write_ok) {
LOG_E(TAG, "Failed to write all bytes"); LOG_E(TAG, "Failed to write all bytes");
break; break;
} }
bytes_received += receive_chunk_size; bytes_received += receive_chunk_size;
} }
file_mutex_lock(&mutex);
fclose(file); fclose(file);
file_mutex_unlock(&mutex);
return bytes_received; return bytes_received;
} }
@@ -29,32 +29,28 @@ static bool loadVersionFromFile(const char* path, AssetVersion& version) {
// Read file content // Read file content
std::string content; std::string content;
{ FILE* fp = fopen(path, "r");
file::FileMutexGuard guard(path); if (!fp) {
LOG_E(TAG, "Failed to open version file: %s", path);
FILE* fp = fopen(path, "r"); return false;
if (!fp) {
LOG_E(TAG, "Failed to open version file: %s", path);
return false;
}
char buffer[256];
size_t bytesRead = fread(buffer, 1, sizeof(buffer) - 1, fp);
bool readError = ferror(fp) != 0;
fclose(fp);
if (readError) {
LOG_E(TAG, "Error reading version file: %s", path);
return false;
}
if (bytesRead == 0) {
LOG_E(TAG, "Version file is empty: %s", path);
return false;
}
buffer[bytesRead] = '\0';
content = buffer;
} }
char buffer[256];
size_t bytesRead = fread(buffer, 1, sizeof(buffer) - 1, fp);
bool readError = ferror(fp) != 0;
fclose(fp);
if (readError) {
LOG_E(TAG, "Error reading version file: %s", path);
return false;
}
if (bytesRead == 0) {
LOG_E(TAG, "Version file is empty: %s", path);
return false;
}
buffer[bytesRead] = '\0';
content = buffer;
// Parse JSON // Parse JSON
cJSON* json = cJSON_Parse(content.c_str()); cJSON* json = cJSON_Parse(content.c_str());
if (json == nullptr) { if (json == nullptr) {
@@ -113,30 +109,26 @@ static bool saveVersionToFile(const char* path, const AssetVersion& version) {
// Write to file // Write to file
bool success = false; bool success = false;
{ FILE* fp = fopen(path, "w");
file::FileMutexGuard guard(path); if (fp) {
size_t len = strlen(jsonString);
FILE* fp = fopen(path, "w"); size_t written = fwrite(jsonString, 1, len, fp);
if (fp) { success = (written == len);
size_t len = strlen(jsonString); if (success) {
size_t written = fwrite(jsonString, 1, len, fp); if (fflush(fp) != 0) {
success = (written == len); LOG_E(TAG, "Failed to flush version file: %s", path);
if (success) { success = false;
if (fflush(fp) != 0) { } else {
LOG_E(TAG, "Failed to flush version file: %s", path); int fd = fileno(fp);
if (fd >= 0 && fsync(fd) != 0) {
LOG_E(TAG, "Failed to fsync version file: %s", path);
success = false; success = false;
} else {
int fd = fileno(fp);
if (fd >= 0 && fsync(fd) != 0) {
LOG_E(TAG, "Failed to fsync version file: %s", path);
success = false;
}
} }
} }
fclose(fp);
} }
fclose(fp);
} }
cJSON_free(jsonString); cJSON_free(jsonString);
cJSON_Delete(json); cJSON_Delete(json);
@@ -1700,8 +1700,6 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
httpd_resp_set_type(request, "image/png"); httpd_resp_set_type(request, "image/png");
httpd_resp_set_hdr(request, "Cache-Control", "public, max-age=86400"); httpd_resp_set_hdr(request, "Cache-Control", "public, max-age=86400");
file::FileMutexGuard guard(faviconPath);
FILE* fp = fopen(faviconPath, "rb"); FILE* fp = fopen(faviconPath, "rb");
if (fp) { if (fp) {
char buffer[512]; char buffer[512];
@@ -1743,9 +1741,6 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
// Try to serve from Data partition first // Try to serve from Data partition first
if (file::isFile(dataPath.c_str())) { if (file::isFile(dataPath.c_str())) {
httpd_resp_set_type(request, getContentType(dataPath)); httpd_resp_set_type(request, getContentType(dataPath));
// Read and send file using standard C FILE* operations
file::FileMutexGuard guard(dataPath);
FILE* fp = fopen(dataPath.c_str(), "rb"); FILE* fp = fopen(dataPath.c_str(), "rb");
if (fp) { if (fp) {
@@ -1769,8 +1764,6 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
std::string sdPath = std::string("/sdcard/tactility/webserver") + requestedPath; std::string sdPath = std::string("/sdcard/tactility/webserver") + requestedPath;
if (file::isFile(sdPath.c_str())) { if (file::isFile(sdPath.c_str())) {
httpd_resp_set_type(request, getContentType(sdPath)); httpd_resp_set_type(request, getContentType(sdPath));
file::FileMutexGuard guard(sdPath);
FILE* fp = fopen(sdPath.c_str(), "rb"); FILE* fp = fopen(sdPath.c_str(), "rb");
if (fp) { if (fp) {
@@ -65,6 +65,19 @@ error_t spi_controller_try_lock(struct Device* device, TickType_t timeout);
*/ */
error_t spi_controller_unlock(struct Device* device); error_t spi_controller_unlock(struct Device* device);
/**
* @brief Locks device's parent bus when the parent is a SPI controller.
* @param[in] device the device whose parent may be a SPI controller
* @retval ERROR_NONE when the operation was successful, or the parent is not a SPI controller
*/
error_t spi_controller_lock_bus_of(struct Device* device);
/**
* @brief Unlocks a device's parent bus.
* @param[in] device the device whose parent may be a SPI controller
*/
void spi_controller_unlock_bus_of(struct Device* device);
extern const struct DeviceType SPI_CONTROLLER_TYPE; extern const struct DeviceType SPI_CONTROLLER_TYPE;
#ifdef __cplusplus #ifdef __cplusplus
@@ -1,63 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/freertos/freertos.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Set of lock/try_lock/unlock callbacks backing a filesystem mount's mutex.
* Any field left null is treated as a no-op by file_mutex_lock/try_lock/unlock.
*/
struct FileMutex {
void (*lock)();
bool (*try_lock)(uint32_t timeout);
void (*unlock)();
};
typedef uint32_t FileMutexId;
#define FILE_MUTEX_ID_INVALID ((FileMutexId)0)
/**
* @brief Registers a mutex for a mount path (e.g. "/sdcard") and its descendants.
* @param[in] mutex callbacks to associate with the path; a copy is stored
* @param[in] path mount path this mutex serializes access to
* @return the id of the new entry, or the id of the existing entry if path is already registered
* @note If a mutex is already registered for this exact path, no new entry is created and the
* existing entry's id is returned; the existing callbacks are left unchanged.
*/
FileMutexId file_mutex_add(const struct FileMutex* mutex, const char* path);
/**
* @brief Removes a previously added mutex registration.
* @param[in] id id returned by file_mutex_add(); a stale or unknown id is a no-op
*/
void file_mutex_remove(FileMutexId id);
/**
* @brief Looks up the mutex registered for path or one of its ancestor mount paths.
* @param[out] mutex receives the matching mutex, or an all-null (no-op) mutex if none matches
* @param[in] path file or directory path to look up
*/
void file_mutex_get(struct FileMutex* mutex, const char* path);
/** @brief Locks mutex. No-op if mutex->lock is null. */
void file_mutex_lock(const struct FileMutex* mutex);
/**
* @brief Attempts to lock mutex within timeout.
* @return true if locked (or mutex->try_lock is null), false on timeout
*/
bool file_mutex_try_lock(const struct FileMutex* mutex, TickType_t timeout);
/** @brief Unlocks mutex. No-op if mutex->unlock is null. */
void file_mutex_unlock(const struct FileMutex* mutex);
#ifdef __cplusplus
}
#endif
@@ -2,8 +2,6 @@
/** /**
* @brief Generic string key-value ".properties" file. * @brief Generic string key-value ".properties" file.
* @note Safely acquires/releases the filesystem mutex registered for the file's path (see
* tactility/filesystem/file_mutex.h) - manual locking isn't needed.
*/ */
#pragma once #pragma once
@@ -22,6 +22,22 @@ error_t spi_controller_unlock(Device* device) {
return SPI_DRIVER_API(driver)->unlock(device); return SPI_DRIVER_API(driver)->unlock(device);
} }
error_t spi_controller_lock_bus_of(Device* device) {
Device* parent = device_get_parent(device);
if (parent == nullptr || device_get_type(parent) != &SPI_CONTROLLER_TYPE) {
return ERROR_NONE;
}
return spi_controller_lock(parent);
}
void spi_controller_unlock_bus_of(Device* device) {
Device* parent = device_get_parent(device);
if (parent == nullptr || device_get_type(parent) != &SPI_CONTROLLER_TYPE) {
return;
}
spi_controller_unlock(parent);
}
const DeviceType SPI_CONTROLLER_TYPE { const DeviceType SPI_CONTROLLER_TYPE {
.name = "spi-controller" .name = "spi-controller"
}; };
@@ -1,123 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/filesystem/file_mutex.h>
#include <tactility/concurrent/mutex.h>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
static const FileMutex no_mutex = {
.lock = nullptr,
.try_lock = nullptr,
.unlock = nullptr,
};
struct FileMutexEntry {
FileMutexId id;
std::string path;
FileMutex mutex;
};
// Guards mutex_entries against concurrent add/get/remove; unrelated to whether a FileMutex's own
// lock/unlock is currently held (file_mutex_get() hands out a copy that stays valid regardless of
// later registry changes - see file_mutex_remove()).
struct FileMutexLedger {
std::vector<FileMutexEntry> entries;
FileMutexId next_id = 1;
Mutex mutex {};
FileMutexLedger() { mutex_construct(&mutex); }
~FileMutexLedger() { mutex_destruct(&mutex); }
void lock() { mutex_lock(&mutex); }
void unlock() { mutex_unlock(&mutex); }
};
static FileMutexLedger& get_ledger() {
static FileMutexLedger ledger;
return ledger;
}
extern "C" {
FileMutexId file_mutex_add(const FileMutex* mutex, const char* path) {
auto& ledger = get_ledger();
ledger.lock();
for (auto& entry : ledger.entries) {
if (entry.path == path) {
FileMutexId existing_id = entry.id;
ledger.unlock();
return existing_id;
}
}
FileMutexId new_id = ledger.next_id++;
ledger.entries.push_back({
.id = new_id,
.path = path,
.mutex = *mutex
});
ledger.unlock();
return new_id;
}
void file_mutex_remove(FileMutexId id) {
auto& ledger = get_ledger();
ledger.lock();
const auto iterator = std::ranges::find_if(ledger.entries, [id](const FileMutexEntry& entry) {
return entry.id == id;
});
if (iterator != ledger.entries.end()) {
// Plain erase, not swap-and-pop: file_mutex_get() matches first-registered-wins, so
// removal must preserve the relative order of the remaining entries.
ledger.entries.erase(iterator);
}
ledger.unlock();
}
void file_mutex_get(FileMutex* mutex, const char* path) {
auto& ledger = get_ledger();
std::string path_string = path;
ledger.lock();
for (auto& entry : ledger.entries) {
// Match the mount path itself, or a descendant (e.g. "/sdcard" registered, "/sdcard/config.json" requested).
bool is_match = path_string == entry.path ||
(entry.path == "/" && !path_string.empty() && path_string[0] == '/') ||
(path_string.rfind(entry.path, 0) == 0 && path_string[entry.path.size()] == '/');
if (is_match) {
memcpy(mutex, &entry.mutex, sizeof(FileMutex));
ledger.unlock();
return;
}
}
ledger.unlock();
*mutex = no_mutex;
}
void file_mutex_lock(const FileMutex* mutex) {
if (mutex->lock) {
mutex->lock();
}
}
bool file_mutex_try_lock(const FileMutex* mutex, TickType_t timeout) {
if (mutex->try_lock) {
return mutex->try_lock(timeout);
}
return true;
}
void file_mutex_unlock(const FileMutex* mutex) {
if (mutex->unlock) {
mutex->unlock();
}
}
}
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/properties_file.h> #include <tactility/properties_file.h>
#include <tactility/filesystem/file_mutex.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <cerrno> #include <cerrno>
@@ -53,14 +52,9 @@ namespace {
// (fgetc()'s EOF return doesn't by itself distinguish clean end-of-file from a read error - // (fgetc()'s EOF return doesn't by itself distinguish clean end-of-file from a read error -
// ferror() after the loop does); true otherwise, including for a missing file (ENOENT). // ferror() after the loop does); true otherwise, including for a missing file (ENOENT).
bool load_from_file(PropertiesFile* file) { bool load_from_file(PropertiesFile* file) {
FileMutex mutex {};
file_mutex_get(&mutex, file->path.c_str());
file_mutex_lock(&mutex);
FILE* handle = std::fopen(file->path.c_str(), "r"); FILE* handle = std::fopen(file->path.c_str(), "r");
if (handle == nullptr) { if (handle == nullptr) {
const int open_error = errno; const int open_error = errno;
file_mutex_unlock(&mutex);
if (open_error == ENOENT) { if (open_error == ENOENT) {
return true; return true;
} }
@@ -105,7 +99,6 @@ bool load_from_file(PropertiesFile* file) {
bool read_ok = std::ferror(handle) == 0; bool read_ok = std::ferror(handle) == 0;
std::fclose(handle); std::fclose(handle);
file_mutex_unlock(&mutex);
if (!read_ok) { if (!read_ok) {
LOG_E(TAG, "Failed to read %s", file->path.c_str()); LOG_E(TAG, "Failed to read %s", file->path.c_str());
@@ -121,16 +114,11 @@ bool load_from_file(PropertiesFile* file) {
// @return true if the backing file was fully replaced with the current entries; false (leaving // @return true if the backing file was fully replaced with the current entries; false (leaving
// the previous on-disk content untouched) if any step failed. // the previous on-disk content untouched) if any step failed.
bool save_to_file(const PropertiesFile* file) { bool save_to_file(const PropertiesFile* file) {
FileMutex mutex {};
file_mutex_get(&mutex, file->path.c_str());
file_mutex_lock(&mutex);
std::string temp_path = file->path + ".tmp"; std::string temp_path = file->path + ".tmp";
FILE* handle = std::fopen(temp_path.c_str(), "w"); FILE* handle = std::fopen(temp_path.c_str(), "w");
if (handle == nullptr) { if (handle == nullptr) {
LOG_E(TAG, "Failed to open %s", temp_path.c_str()); LOG_E(TAG, "Failed to open %s", temp_path.c_str());
file_mutex_unlock(&mutex);
return false; return false;
} }
@@ -146,7 +134,6 @@ bool save_to_file(const PropertiesFile* file) {
if (!write_ok || !flush_ok || !close_ok) { if (!write_ok || !flush_ok || !close_ok) {
LOG_E(TAG, "Failed to write %s", temp_path.c_str()); LOG_E(TAG, "Failed to write %s", temp_path.c_str());
std::remove(temp_path.c_str()); std::remove(temp_path.c_str());
file_mutex_unlock(&mutex);
return false; return false;
} }
@@ -157,11 +144,9 @@ bool save_to_file(const PropertiesFile* file) {
if (std::rename(temp_path.c_str(), file->path.c_str()) != 0) { if (std::rename(temp_path.c_str(), file->path.c_str()) != 0) {
LOG_E(TAG, "Failed to replace %s", file->path.c_str()); LOG_E(TAG, "Failed to replace %s", file->path.c_str());
std::remove(temp_path.c_str()); std::remove(temp_path.c_str());
file_mutex_unlock(&mutex);
return false; return false;
} }
file_mutex_unlock(&mutex);
return true; return true;
} }
+2 -8
View File
@@ -44,7 +44,6 @@
#include <tactility/drivers/usb_msc_device.h> #include <tactility/drivers/usb_msc_device.h>
#include <tactility/drivers/wifi.h> #include <tactility/drivers/wifi.h>
#include <tactility/error.h> #include <tactility/error.h>
#include <tactility/filesystem/file_mutex.h>
#include <tactility/filesystem/file_system.h> #include <tactility/filesystem/file_system.h>
#include <tactility/memory.h> #include <tactility/memory.h>
#include <tactility/module.h> #include <tactility/module.h>
@@ -170,13 +169,6 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
DEFINE_MODULE_SYMBOL(display_get_frame_buffer_count), DEFINE_MODULE_SYMBOL(display_get_frame_buffer_count),
DEFINE_MODULE_SYMBOL(display_get_backlight), DEFINE_MODULE_SYMBOL(display_get_backlight),
DEFINE_MODULE_SYMBOL(DISPLAY_TYPE), DEFINE_MODULE_SYMBOL(DISPLAY_TYPE),
// file_mutex
DEFINE_MODULE_SYMBOL(file_mutex_add),
DEFINE_MODULE_SYMBOL(file_mutex_remove),
DEFINE_MODULE_SYMBOL(file_mutex_get),
DEFINE_MODULE_SYMBOL(file_mutex_lock),
DEFINE_MODULE_SYMBOL(file_mutex_try_lock),
DEFINE_MODULE_SYMBOL(file_mutex_unlock),
// file system // file system
DEFINE_MODULE_SYMBOL(file_system_mount), DEFINE_MODULE_SYMBOL(file_system_mount),
DEFINE_MODULE_SYMBOL(file_system_unmount), DEFINE_MODULE_SYMBOL(file_system_unmount),
@@ -306,6 +298,8 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
DEFINE_MODULE_SYMBOL(spi_controller_lock), DEFINE_MODULE_SYMBOL(spi_controller_lock),
DEFINE_MODULE_SYMBOL(spi_controller_try_lock), DEFINE_MODULE_SYMBOL(spi_controller_try_lock),
DEFINE_MODULE_SYMBOL(spi_controller_unlock), DEFINE_MODULE_SYMBOL(spi_controller_unlock),
DEFINE_MODULE_SYMBOL(spi_controller_lock_bus_of),
DEFINE_MODULE_SYMBOL(spi_controller_unlock_bus_of),
DEFINE_MODULE_SYMBOL(SPI_CONTROLLER_TYPE), DEFINE_MODULE_SYMBOL(SPI_CONTROLLER_TYPE),
// drivers/trackball // drivers/trackball
DEFINE_MODULE_SYMBOL(trackball_read_delta), DEFINE_MODULE_SYMBOL(trackball_read_delta),
@@ -1,190 +0,0 @@
#include "doctest.h"
#include <tactility/filesystem/file_mutex.h>
namespace {
int lock_calls = 0;
int unlock_calls = 0;
int try_lock_calls = 0;
bool try_lock_result = true;
uint32_t try_lock_timeout_seen = 0;
void mock_lock() { lock_calls++; }
void mock_unlock() { unlock_calls++; }
bool mock_try_lock(uint32_t timeout) {
try_lock_calls++;
try_lock_timeout_seen = timeout;
return try_lock_result;
}
int lock_a_calls = 0;
int lock_b_calls = 0;
void mock_lock_a() { lock_a_calls++; }
void mock_lock_b() { lock_b_calls++; }
void reset_mocks() {
lock_calls = 0;
unlock_calls = 0;
try_lock_calls = 0;
try_lock_result = true;
try_lock_timeout_seen = 0;
lock_a_calls = 0;
lock_b_calls = 0;
}
} // namespace
TEST_CASE("file_mutex_get with zero registrations returns a no-op mutex") {
FileMutex mutex;
file_mutex_get(&mutex, "/nowhere/file.txt");
CHECK_EQ(mutex.lock, nullptr);
CHECK_EQ(mutex.try_lock, nullptr);
CHECK_EQ(mutex.unlock, nullptr);
// Calling through a no-op mutex must be safe, and try_lock must report success.
file_mutex_lock(&mutex);
CHECK_EQ(file_mutex_try_lock(&mutex, 123), true);
file_mutex_unlock(&mutex);
}
TEST_CASE("file_mutex_add/get with a single registration") {
reset_mocks();
FileMutex registered = { .lock = mock_lock, .try_lock = mock_try_lock, .unlock = mock_unlock };
file_mutex_add(&registered, "/mock1");
FileMutex mutex;
// Exact mount path match.
file_mutex_get(&mutex, "/mock1");
CHECK_EQ(mutex.lock, mock_lock);
CHECK_EQ(mutex.try_lock, mock_try_lock);
CHECK_EQ(mutex.unlock, mock_unlock);
// Descendant path match.
file_mutex_get(&mutex, "/mock1/nested/file.txt");
CHECK_EQ(mutex.lock, mock_lock);
// Unrelated path falls back to no-op.
FileMutex unrelated;
file_mutex_get(&unrelated, "/other/file.txt");
CHECK_EQ(unrelated.lock, nullptr);
// Prefix-but-not-descendant path (e.g. "/mock1x") must not match "/mock1".
FileMutex prefix_only;
file_mutex_get(&prefix_only, "/mock1x/file.txt");
CHECK_EQ(prefix_only.lock, nullptr);
// Exercise the resolved callbacks.
file_mutex_get(&mutex, "/mock1");
file_mutex_lock(&mutex);
CHECK_EQ(lock_calls, 1);
CHECK_EQ(file_mutex_try_lock(&mutex, 42), true);
CHECK_EQ(try_lock_calls, 1);
CHECK_EQ(try_lock_timeout_seen, 42);
file_mutex_unlock(&mutex);
CHECK_EQ(unlock_calls, 1);
// Re-registering the same path is a no-op: original callbacks remain in place.
FileMutex replacement = { .lock = nullptr, .try_lock = nullptr, .unlock = nullptr };
file_mutex_add(&replacement, "/mock1");
file_mutex_get(&mutex, "/mock1");
CHECK_EQ(mutex.lock, mock_lock);
}
TEST_CASE("file_mutex_add/get with two registrations resolves to the matching path") {
reset_mocks();
FileMutex mutex_a = { .lock = mock_lock_a, .try_lock = nullptr, .unlock = nullptr };
FileMutex mutex_b = { .lock = mock_lock_b, .try_lock = nullptr, .unlock = nullptr };
file_mutex_add(&mutex_a, "/mock2a");
file_mutex_add(&mutex_b, "/mock2b");
FileMutex resolved;
file_mutex_get(&resolved, "/mock2a/file.txt");
CHECK_EQ(resolved.lock, mock_lock_a);
file_mutex_get(&resolved, "/mock2b/file.txt");
CHECK_EQ(resolved.lock, mock_lock_b);
// Path matching neither registration falls back to no-op.
file_mutex_get(&resolved, "/mock2c/file.txt");
CHECK_EQ(resolved.lock, nullptr);
// Registration order matters: the first matching entry wins, not the longest
// prefix. A mount nested under an earlier one is shadowed by it.
FileMutex mutex_nested = { .lock = nullptr, .try_lock = nullptr, .unlock = nullptr };
file_mutex_add(&mutex_nested, "/mock2a/nested");
file_mutex_get(&resolved, "/mock2a/nested/file.txt");
CHECK_EQ(resolved.lock, mock_lock_a); // still /mock2a, registered first
}
TEST_CASE("file_mutex_add returns a valid id") {
reset_mocks();
FileMutex registered = { .lock = mock_lock, .try_lock = nullptr, .unlock = nullptr };
FileMutexId id = file_mutex_add(&registered, "/mockA");
CHECK_NE(id, FILE_MUTEX_ID_INVALID);
}
TEST_CASE("file_mutex_add with a duplicate path returns the existing id") {
reset_mocks();
FileMutex mutex_1 = { .lock = mock_lock, .try_lock = nullptr, .unlock = nullptr };
FileMutex mutex_2 = { .lock = mock_lock_a, .try_lock = nullptr, .unlock = nullptr };
FileMutexId id_1 = file_mutex_add(&mutex_1, "/mockB");
FileMutexId id_2 = file_mutex_add(&mutex_2, "/mockB");
CHECK_EQ(id_1, id_2);
FileMutex resolved;
file_mutex_get(&resolved, "/mockB");
CHECK_EQ(resolved.lock, mock_lock); // first registration's callbacks win, unchanged
}
TEST_CASE("file_mutex_remove removes a registration") {
reset_mocks();
FileMutex registered = { .lock = mock_lock, .try_lock = nullptr, .unlock = nullptr };
FileMutexId id = file_mutex_add(&registered, "/mockC");
FileMutex before;
file_mutex_get(&before, "/mockC");
CHECK_EQ(before.lock, mock_lock);
file_mutex_remove(id);
FileMutex after;
file_mutex_get(&after, "/mockC");
CHECK_EQ(after.lock, nullptr);
}
TEST_CASE("file_mutex_remove with an unknown id is a safe no-op") {
reset_mocks();
FileMutex registered = { .lock = mock_lock, .try_lock = nullptr, .unlock = nullptr };
file_mutex_add(&registered, "/mockD");
file_mutex_remove(999999); // never issued
file_mutex_remove(FILE_MUTEX_ID_INVALID);
FileMutex resolved;
file_mutex_get(&resolved, "/mockD");
CHECK_EQ(resolved.lock, mock_lock); // untouched
}
TEST_CASE("file_mutex_remove of one registration leaves others intact") {
reset_mocks();
FileMutex mutex_a = { .lock = mock_lock_a, .try_lock = nullptr, .unlock = nullptr };
FileMutex mutex_b = { .lock = mock_lock_b, .try_lock = nullptr, .unlock = nullptr };
FileMutexId id_a = file_mutex_add(&mutex_a, "/mockE1");
file_mutex_add(&mutex_b, "/mockE2");
file_mutex_remove(id_a);
FileMutex resolved_a;
file_mutex_get(&resolved_a, "/mockE1");
CHECK_EQ(resolved_a.lock, nullptr);
FileMutex resolved_b;
file_mutex_get(&resolved_b, "/mockE2");
CHECK_EQ(resolved_b.lock, mock_lock_b);
}