311 lines
No EOL
9.3 KiB
C
311 lines
No EOL
9.3 KiB
C
/*
|
|
* This program source code file is part of the KTag project.
|
|
*
|
|
* 🛡️ <https://ktag.clubk.club> 🃞
|
|
*
|
|
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU Affero General Public License as published by the Free
|
|
* Software Foundation, either version 3 of the License, or (at your option) any
|
|
* later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
* details.
|
|
*
|
|
* There should be a copy of the GNU Affero General Public License in the LICENSE
|
|
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <SystemK.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <NVM.h>
|
|
#include "esp_log.h"
|
|
#include "esp_ota_ops.h"
|
|
#include "esp_spiffs.h"
|
|
#include "esp_partition.h"
|
|
|
|
static const char *TAG = "Reprogramming";
|
|
static const char *FIRMWARE_DIR = "/usb/esp/firmware";
|
|
static const char *APP_IMAGE_PREFIX = "APP";
|
|
static const char *SPIFFS_IMAGE_PREFIX = "SPIFFS";
|
|
|
|
#define REPROGRAMMING_TASK_PRIORITY 1
|
|
static TaskHandle_t xReprogrammingTask = NULL;
|
|
static StaticTask_t xReprogrammingTaskBuffer;
|
|
static StackType_t xReprogrammingTaskStack[4096];
|
|
|
|
#define BUFFER_SIZE 1024
|
|
|
|
typedef struct
|
|
{
|
|
unsigned long total_size;
|
|
unsigned long remaining_size;
|
|
void *data;
|
|
} data_manager_t;
|
|
|
|
typedef enum {
|
|
IMAGE_TYPE_APP,
|
|
IMAGE_TYPE_SPIFFS
|
|
} image_type_t;
|
|
|
|
static char *find_image_filename(const char *prefix)
|
|
{
|
|
DIR *dir = opendir(FIRMWARE_DIR);
|
|
if (dir == NULL)
|
|
{
|
|
KLOG_ERROR(TAG, "Failed to open directory '%s': %s!", FIRMWARE_DIR, strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
struct dirent *entry;
|
|
char *filename = NULL;
|
|
|
|
while ((entry = readdir(dir)) != NULL)
|
|
{
|
|
if (entry->d_type == DT_REG && strncmp(entry->d_name, prefix, strlen(prefix)) == 0)
|
|
{
|
|
filename = strdup(entry->d_name);
|
|
if (filename == NULL)
|
|
{
|
|
KLOG_ERROR(TAG, "Memory allocation failed!");
|
|
}
|
|
else
|
|
{
|
|
KLOG_DEBUG(TAG, "Found image: %s", filename);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
if (filename == NULL)
|
|
{
|
|
KLOG_WARN(TAG, "Couldn't find an image with prefix '%s' in directory '%s'!", prefix, FIRMWARE_DIR);
|
|
return NULL;
|
|
}
|
|
|
|
size_t needed = snprintf(NULL, 0, "%s/%s", FIRMWARE_DIR, filename) + 1;
|
|
char *full_path = malloc(needed);
|
|
if (full_path != NULL)
|
|
{
|
|
snprintf(full_path, needed, "%s/%s", FIRMWARE_DIR, filename);
|
|
}
|
|
|
|
free(filename);
|
|
return full_path;
|
|
}
|
|
|
|
static SystemKResult_T program_image(const char *filename, image_type_t image_type)
|
|
{
|
|
esp_err_t err;
|
|
FILE *file = fopen(filename, "rb");
|
|
if (file == NULL)
|
|
{
|
|
KLOG_ERROR(TAG, "Failed to open file %s", filename);
|
|
return SYSTEMK_RESULT_FILE_NOT_FOUND;
|
|
}
|
|
|
|
fseek(file, 0, SEEK_END);
|
|
long file_size = ftell(file);
|
|
fseek(file, 0, SEEK_SET);
|
|
|
|
KLOG_INFO(TAG, "File size: %ld bytes", file_size);
|
|
|
|
data_manager_t data_manager = {
|
|
.total_size = file_size,
|
|
.remaining_size = file_size,
|
|
.data = malloc(BUFFER_SIZE)
|
|
};
|
|
|
|
if (data_manager.data == NULL)
|
|
{
|
|
KLOG_ERROR(TAG, "Failed to allocate memory");
|
|
fclose(file);
|
|
return SYSTEMK_RESULT_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
if (image_type == IMAGE_TYPE_APP)
|
|
{
|
|
esp_ota_handle_t update_handle = 0;
|
|
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
|
|
KLOG_INFO(TAG, "Writing to partition subtype %d at offset 0x%" PRIx32, update_partition->subtype, update_partition->address);
|
|
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
|
|
if (err != ESP_OK)
|
|
{
|
|
KLOG_ERROR(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
|
|
free(data_manager.data);
|
|
fclose(file);
|
|
return SYSTEMK_RESULT_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
while (data_manager.remaining_size > 0)
|
|
{
|
|
size_t size = (data_manager.remaining_size <= BUFFER_SIZE) ? data_manager.remaining_size : BUFFER_SIZE;
|
|
if (fread(data_manager.data, 1, size, file) != size)
|
|
{
|
|
KLOG_ERROR(TAG, "fread failed");
|
|
err = ESP_FAIL;
|
|
break;
|
|
}
|
|
|
|
err = esp_ota_write(update_handle, data_manager.data, size);
|
|
if (err != ESP_OK)
|
|
{
|
|
KLOG_ERROR(TAG, "esp_ota_write failed (%s)", esp_err_to_name(err));
|
|
break;
|
|
}
|
|
|
|
data_manager.remaining_size -= size;
|
|
}
|
|
|
|
if (err == ESP_OK)
|
|
{
|
|
err = esp_ota_end(update_handle);
|
|
if (err != ESP_OK)
|
|
{
|
|
KLOG_ERROR(TAG, "esp_ota_end failed (%s)", esp_err_to_name(err));
|
|
}
|
|
else
|
|
{
|
|
err = esp_ota_set_boot_partition(update_partition);
|
|
if (err != ESP_OK)
|
|
{
|
|
KLOG_ERROR(TAG, "esp_ota_set_boot_partition failed (%s)", esp_err_to_name(err));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (image_type == IMAGE_TYPE_SPIFFS)
|
|
{
|
|
const esp_partition_t *spiffs_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL);
|
|
if (spiffs_partition == NULL)
|
|
{
|
|
KLOG_ERROR(TAG, "SPIFFS partition not found");
|
|
free(data_manager.data);
|
|
fclose(file);
|
|
return SYSTEMK_RESULT_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
err = esp_partition_erase_range(spiffs_partition, 0, spiffs_partition->size);
|
|
if (err != ESP_OK)
|
|
{
|
|
KLOG_ERROR(TAG, "Failed to erase SPIFFS partition (%s)", esp_err_to_name(err));
|
|
free(data_manager.data);
|
|
fclose(file);
|
|
return SYSTEMK_RESULT_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
while (data_manager.remaining_size > 0)
|
|
{
|
|
size_t size = (data_manager.remaining_size <= BUFFER_SIZE) ? data_manager.remaining_size : BUFFER_SIZE;
|
|
if (fread(data_manager.data, 1, size, file) != size)
|
|
{
|
|
KLOG_ERROR(TAG, "fread failed");
|
|
err = ESP_FAIL;
|
|
break;
|
|
}
|
|
|
|
err = esp_partition_write(spiffs_partition, spiffs_partition->size - data_manager.remaining_size, data_manager.data, size);
|
|
if (err != ESP_OK)
|
|
{
|
|
KLOG_ERROR(TAG, "esp_partition_write failed (%s)", esp_err_to_name(err));
|
|
break;
|
|
}
|
|
|
|
data_manager.remaining_size -= size;
|
|
}
|
|
}
|
|
|
|
free(data_manager.data);
|
|
fclose(file);
|
|
|
|
return (err == ESP_OK) ? SYSTEMK_RESULT_SUCCESS : SYSTEMK_RESULT_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
SystemKResult_T Maybe_Reprogram_From_USB(void)
|
|
{
|
|
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
|
|
char *app_image_filename = find_image_filename(APP_IMAGE_PREFIX);
|
|
char *spiffs_image_filename = find_image_filename(SPIFFS_IMAGE_PREFIX);
|
|
|
|
if (app_image_filename)
|
|
{
|
|
KLOG_INFO(TAG, "Application image found: %s", app_image_filename);
|
|
result = program_image(app_image_filename, IMAGE_TYPE_APP);
|
|
free(app_image_filename);
|
|
if (result != SYSTEMK_RESULT_SUCCESS)
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (spiffs_image_filename)
|
|
{
|
|
KLOG_INFO(TAG, "SPIFFS image found: %s", spiffs_image_filename);
|
|
result = program_image(spiffs_image_filename, IMAGE_TYPE_SPIFFS);
|
|
free(spiffs_image_filename);
|
|
}
|
|
|
|
if ((app_image_filename == NULL) && (spiffs_image_filename == NULL))
|
|
{
|
|
KLOG_ERROR(TAG, "Reprogramming failed--there's nothing to reprogram!");
|
|
result = SYSTEMK_RESULT_FILE_NOT_FOUND;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void Reprogramming_Task(void *pvParameters)
|
|
{
|
|
SystemKResult_T result = Maybe_Reprogram_From_USB();
|
|
static All_On_Data_T data;
|
|
|
|
if (result == SYSTEMK_RESULT_SUCCESS)
|
|
{
|
|
KLOG_INFO(TAG, "Reprogramming succeeded--rebooting...");
|
|
data.color = ApplyMask(COLOR_GREEN, 0x70);
|
|
data.style = DISPLAY_STYLE_SOLID;
|
|
}
|
|
else
|
|
{
|
|
KLOG_ERROR(TAG, "Reprogramming failed--rebooting...");
|
|
data.color = ApplyMask(COLOR_RED, 0x70);
|
|
data.style = DISPLAY_STYLE_SOLID;
|
|
}
|
|
|
|
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_ON, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&data};
|
|
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
|
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
|
esp_restart();
|
|
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
void Request_Reprogramming_From_USB(void)
|
|
{
|
|
if (xReprogrammingTask == NULL)
|
|
{
|
|
KLOG_INFO(TAG, "Attempting USB reprogramming...");
|
|
|
|
xReprogrammingTask = xTaskCreateStaticPinnedToCore(
|
|
Reprogramming_Task,
|
|
"Reprogramming",
|
|
sizeof(xReprogrammingTaskStack) / sizeof(StackType_t),
|
|
NULL,
|
|
REPROGRAMMING_TASK_PRIORITY,
|
|
xReprogrammingTaskStack,
|
|
&xReprogrammingTaskBuffer,
|
|
APP_CPU_NUM);
|
|
}
|
|
else
|
|
{
|
|
KLOG_WARN(TAG, "Reprogramming already in progress.");
|
|
}
|
|
} |