/* SPDX-License-Identifier: GPL-3.0-only */ #include "web_httpd_idle.h" #include "web_httpd_adapter.h" #include #include #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #define IDLE_POLL_US INT64_C(1000000) #define IDLE_FENCE_US INT64_C(1000000) static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED; static httpd_handle_t s_server; static esp_timer_handle_t s_timer; static uintptr_t s_generation; static bool s_accepting, s_queued, s_submitting; /* Only HTTPD touches rows while alive; prepare runs before SSL startup. */ static web_httpd_idle_row_t s_rows[WEB_HTTPD_IDLE_SOCKETS]; static void idle_work(void *argument) { uintptr_t generation = (uintptr_t)argument; taskENTER_CRITICAL(&s_lock); bool current = s_queued && generation == s_generation; httpd_handle_t server = current && s_accepting ? s_server : NULL; taskEXIT_CRITICAL(&s_lock); if (server) web_httpd_idle_sweep(server, s_rows, esp_timer_get_time()); taskENTER_CRITICAL(&s_lock); if (current && generation == s_generation) s_queued = false; taskEXIT_CRITICAL(&s_lock); } static void idle_timer(void *argument) { (void)argument; taskENTER_CRITICAL(&s_lock); httpd_handle_t server = NULL; uintptr_t generation = s_generation; if (s_accepting && !s_queued && !s_submitting) { server = s_server; s_queued = s_submitting = true; } taskEXIT_CRITICAL(&s_lock); if (!server) return; esp_err_t error = httpd_queue_work(server, idle_work, (void *)generation); taskENTER_CRITICAL(&s_lock); /* A callback may finish before queue_work returns. Keep the submission * reservation until here so it cannot clear a newer probe's queued flag. */ if (error != ESP_OK) s_queued = false; s_submitting = false; taskEXIT_CRITICAL(&s_lock); } esp_err_t web_httpd_idle_prepare(void) { #if defined(CONFIG_HTTPD_QUEUE_WORK_BLOCKING) && CONFIG_HTTPD_QUEUE_WORK_BLOCKING return ESP_ERR_NOT_SUPPORTED; #else taskENTER_CRITICAL(&s_lock); bool allowed = !s_server && !s_queued && !s_submitting && s_generation != UINTPTR_MAX; taskEXIT_CRITICAL(&s_lock); if (!allowed) return ESP_ERR_INVALID_STATE; if (!s_timer) { esp_timer_handle_t timer = NULL; const esp_timer_create_args_t args = { .callback = idle_timer, .name = "web_idle", .skip_unhandled_events = true, }; esp_err_t error = esp_timer_create(&args, &timer); if (error == ESP_OK) error = esp_timer_start_periodic(timer, IDLE_POLL_US); if (error != ESP_OK) { if (timer) (void)esp_timer_delete(timer); return error; } s_timer = timer; } memset(s_rows, 0, sizeof(s_rows)); return ESP_OK; #endif } esp_err_t web_httpd_idle_attach(httpd_handle_t server) { taskENTER_CRITICAL(&s_lock); bool allowed = server && s_timer && !s_server && !s_queued && !s_submitting && s_generation != UINTPTR_MAX; if (allowed) { ++s_generation; /* Never reused, including when HTTPD's handle is reused. */ s_server = server; s_accepting = true; } taskEXIT_CRITICAL(&s_lock); return allowed ? ESP_OK : ESP_ERR_INVALID_STATE; } esp_err_t web_httpd_idle_detach(httpd_handle_t server) { taskENTER_CRITICAL(&s_lock); bool owned = server && s_server == server; bool absent = !s_server; if (owned) s_accepting = false; taskEXIT_CRITICAL(&s_lock); /* Partial startup may never have attached. */ if (!owned) return absent ? ESP_OK : ESP_ERR_INVALID_STATE; int64_t deadline = esp_timer_get_time() + IDLE_FENCE_US; for (;;) { taskENTER_CRITICAL(&s_lock); bool submitting = s_submitting; taskEXIT_CRITICAL(&s_lock); if (!submitting) return ESP_OK; if (esp_timer_get_time() >= deadline) return ESP_ERR_TIMEOUT; vTaskDelay(1); } } void web_httpd_idle_stopped(httpd_handle_t server) { taskENTER_CRITICAL(&s_lock); if (server && s_server == server && !s_accepting && !s_submitting) { s_server = NULL; s_queued = false; /* Successful HTTPD stop joined owner and destroyed queue. */ } taskEXIT_CRITICAL(&s_lock); } void web_httpd_idle_tls(esp_https_server_user_cb_arg_t *arg) { if (!arg || !arg->tls || arg->user_cb_state != HTTPD_SSL_USER_CB_SESS_CREATE) return; int fd = -1; if (esp_tls_get_conn_sockfd(arg->tls, &fd) != ESP_OK || fd < 0) { /* Identity unavailable: conservatively restart every idle observation. */ memset(s_rows, 0, sizeof(s_rows)); return; } for (unsigned i = 0; i < WEB_HTTPD_IDLE_SOCKETS; ++i) if (s_rows[i].fd == fd) memset(&s_rows[i], 0, sizeof(s_rows[i])); }