diff --git a/README.md b/README.md index 85ec68b..8d47ac8 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,14 @@ - **Обновление ядра**: Загрузка `Image.gz` в boot раздел - **Обновление Device-Tree**: Загрузка `sun50i-h618-orangepi-zero2w.dtb` - **U-Boot Environment**: Загрузка `uboot.env` +- **U-Boot Binary**: Загрузка `u-boot-sunxi-with-spl.bin` с записью в: + - `/media/u-boot-sunxi-with-spl.bin` (файл) + - `/dev/mtd0` (SPI NOR flash) + - `/dev/mmcblk1` со смещением 8KB (SD карта, аналог `dd seek=8`) - **Редактор extlinux.conf**: Просмотр и редактирование конфигурации загрузчика - **Индикатор прогресса**: Отображение прогресса загрузки в реальном времени - **Авто-монтирование**: Автоматически монтирует `/dev/mmcblk1p1` в `/media` -- **Поддержка больших файлов**: Загрузка файлов до 200 MB +- **Поддержка больших файлов**: Загрузка файлов до 200 MB (до 1 MB для U-Boot Binary) - **Перезагрузка системы**: Кнопка безопасной перезагрузки с корректным отмонтированием - **Мониторинг температуры**: Отображение температуры CPU с цветовой индикацией @@ -103,6 +107,7 @@ sudo ./up | Ядро | `/media/Image.gz` | | Device-Tree | `/media/sun50i-h618-orangepi-zero2w.dtb` | | U-Boot Env | `/media/uboot.env` | +| U-Boot Binary | `/media/u-boot-sunxi-with-spl.bin` + `/dev/mtd0` + `/dev/mmcblk1` (+8KB offset) | | Extlinux | `/media/extlinux/extlinux.conf` | ## Конфигурация diff --git a/src/main.c b/src/main.c index 2985eff..08d31cf 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -18,9 +20,11 @@ #define KERNEL_FILE "Image.gz" #define DTB_FILE "sun50i-h618-orangepi-zero2w.dtb" #define UBOOT_FILE "uboot.env" +#define UBOOT_BIN_FILE "u-boot-sunxi-with-spl.bin" #define EXTLINUX_FILE "extlinux/extlinux.conf" #define MAX_FILE_SIZE (200 * 1024 * 1024) // 200 MB +#define UBOOT_MAX_SIZE (1 * 1024 * 1024) // 1 MB for U-Boot static const char *s_http_port = "http://0.0.0.0:43243"; static struct mg_mgr mgr; @@ -100,9 +104,135 @@ static const char* get_target_filename(const char *type) { if (strcmp(type, "kernel") == 0) return KERNEL_FILE; if (strcmp(type, "dtb") == 0) return DTB_FILE; if (strcmp(type, "uboot") == 0) return UBOOT_FILE; + if (strcmp(type, "uboot_bin") == 0) return UBOOT_BIN_FILE; return NULL; } +// Write U-Boot to SPI NOR (mtd0) and MMC (mmcblk1) +static int write_uboot_to_devices(const char *data, size_t len, const char *filepath) { + int success = 0; + size_t chunk_size = 4096; + + // Allwinner H618 boot offset: 8KB (same as "bs=1024 seek=8" in dd) + #define MMC_BOOT_OFFSET 8192 + + printf("Writing U-Boot to devices (%zu bytes)...\n", len); + + // Check size limit for SPI NOR (safe limit to avoid overwriting partitions) + if (len > UBOOT_MAX_SIZE) { + printf("U-Boot image too large for SPI NOR (%zu > %d bytes)\n", len, UBOOT_MAX_SIZE); + return -1; + } + + // First, save to file in /media + FILE *f = fopen(filepath, "wb"); + if (!f) { + perror("fopen uboot file"); + return -1; + } + + size_t written = 0; + while (written < len) { + size_t to_write = (len - written < chunk_size) ? (len - written) : chunk_size; + if (fwrite(data + written, 1, to_write, f) != to_write) { + fclose(f); + return -1; + } + written += to_write; + g_upload.progress = (int)(written * 30 / len); // First 30% for file write + } + fclose(f); + sync(); + + // Write to /dev/mtd0 (SPI NOR flash) + printf("Writing to /dev/mtd0 (SPI NOR)...\n"); + int mtd_fd = open("/dev/mtd0", O_RDWR | O_SYNC); + if (mtd_fd >= 0) { + // Get erase block size for SPI NOR + struct mtd_info_user mtd_info; + memset(&mtd_info, 0, sizeof(mtd_info)); + if (ioctl(mtd_fd, MEMGETINFO, &mtd_info) == 0) { + printf("SPI NOR info: type=%s, size=%u bytes, erasesize=%u bytes\n", + mtd_info.type == MTD_NORFLASH ? "NOR" : "UNKNOWN", + mtd_info.size, mtd_info.erasesize); + + // Erase SPI NOR before writing + printf("Erasing SPI NOR...\n"); + struct erase_info_user erase_info; + erase_info.start = 0; + erase_info.length = mtd_info.size; // Erase entire chip + + if (ioctl(mtd_fd, MEMERASE, &erase_info) == 0) { + printf("SPI NOR erased successfully\n"); + } else { + perror("MEMERASE failed"); + printf("Continuing anyway (some systems auto-erase)...\n"); + } + } else { + perror("MEMGETINFO failed"); + printf("Could not get SPI NOR info, proceeding with write...\n"); + } + + // Write to SPI NOR + written = 0; + while (written < len) { + size_t to_write = (len - written < chunk_size) ? (len - written) : chunk_size; + ssize_t w = write(mtd_fd, data + written, to_write); + if (w < 0) { + perror("write mtd0 (SPI NOR)"); + close(mtd_fd); + break; + } + written += w; + g_upload.progress = 30 + (int)(written * 35 / len); // 30-65% for SPI NOR + } + close(mtd_fd); + printf("Written %zu bytes to /dev/mtd0 (SPI NOR)\n", written); + success = 1; + } else { + perror("open /dev/mtd0"); + printf("/dev/mtd0 (SPI NOR) not available, skipping...\n"); + } + + // Write to /dev/mmcblk1 at offset 8KB (Allwinner H618 boot location) + // This matches: dd if=u-boot.bin of=/dev/mmcblk1 bs=1024 seek=8 + printf("Writing to /dev/mmcblk1 at offset %d (SD card boot area)...\n", MMC_BOOT_OFFSET); + int mmc_fd = open("/dev/mmcblk1", O_RDWR | O_SYNC); + if (mmc_fd >= 0) { + // Seek to 8KB offset (sector 16 for 512-byte sectors) + if (lseek(mmc_fd, MMC_BOOT_OFFSET, SEEK_SET) != MMC_BOOT_OFFSET) { + perror("lseek mmcblk1"); + close(mmc_fd); + } else { + written = 0; + while (written < len) { + size_t to_write = (len - written < chunk_size) ? (len - written) : chunk_size; + ssize_t w = write(mmc_fd, data + written, to_write); + if (w < 0) { + perror("write mmcblk1"); + close(mmc_fd); + break; + } + written += w; + g_upload.progress = 65 + (int)(written * 35 / len); // 65-100% for MMC + } + close(mmc_fd); + printf("Written %zu bytes to /dev/mmcblk1 at offset %d\n", written, MMC_BOOT_OFFSET); + success = 1; + } + } else { + perror("open /dev/mmcblk1"); + printf("/dev/mmcblk1 not available, skipping...\n"); + } + + // Final sync to ensure all data is flushed + sync(); + + g_upload.progress = 100; + + return success ? 0 : -1; +} + // Get MIME type based on file extension static const char* get_mime_type(const char *path) { const char *ext = strrchr(path, '.'); @@ -301,10 +431,10 @@ static void handle_temperature(struct mg_connection *c) { char type[32] = {0}; if (fscanf(type_f, "%31s", type) == 1) { // Prefer CPU or soc zones - int is_cpu = (strstr(type, "cpu") != NULL || + int is_cpu = (strstr(type, "cpu") != NULL || strstr(type, "soc") != NULL || strstr(type, "CPU") != NULL); - + int temp_raw; if (fscanf(f, "%d", &temp_raw) == 1) { temperature = temp_raw / 1000.0f; @@ -337,11 +467,11 @@ static void handle_temperature(struct mg_connection *c) { char name[32] = {0}; if (fscanf(name_f, "%31s", name) == 1) { // Prefer CPU or sunxi sensors - int is_cpu = (strstr(name, "cpu") != NULL || + int is_cpu = (strstr(name, "cpu") != NULL || strstr(name, "sunxi") != NULL || strstr(name, "CPU") != NULL || strstr(name, "acpitz") != NULL); - + int temp_raw; if (fscanf(f, "%d", &temp_raw) == 1) { temperature = temp_raw / 1000.0f; @@ -369,7 +499,7 @@ static void handle_temperature(struct mg_connection *c) { "/sys/class/hwmon/hwmon3/temp1_input", NULL }; - + for (int i = 0; fallback_paths[i] != NULL && !success; i++) { FILE *f = fopen(fallback_paths[i], "r"); if (f) { @@ -456,8 +586,14 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { return; } - if (file_data.len > MAX_FILE_SIZE) { - mg_http_reply(c, 400, "", "{\"error\":\"File too large (max 200MB)\"}\n"); + // Check file size limits + size_t max_size = MAX_FILE_SIZE; + if (strcmp(type, "uboot_bin") == 0) { + max_size = UBOOT_MAX_SIZE; // 1MB limit for U-Boot (safe for boot area) + } + + if (file_data.len > max_size) { + mg_http_reply(c, 400, "", "{\"error\":\"File too large\"}\n"); return; } @@ -477,32 +613,48 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { printf("Upload: %s (%zu bytes) -> %s\n", type, file_data.len, filepath); - // Write file in chunks - FILE *fp = fopen(filepath, "wb"); - if (!fp) { - mg_http_reply(c, 500, "", "{\"error\":\"Failed to create file\"}\n"); - return; - } + // Update upload state + memcpy(g_upload.type, type, strlen(type) + 1); + g_upload.received = 0; + g_upload.progress = 0; + snprintf(g_upload.status, sizeof(g_upload.status), "Uploading..."); - size_t written = 0; - size_t chunk_size = 65536; - while (written < file_data.len) { - size_t to_write = (file_data.len - written < chunk_size) ? - (file_data.len - written) : chunk_size; - if (fwrite(file_data.buf + written, 1, to_write, fp) != to_write) { - fclose(fp); - mg_http_reply(c, 500, "", "{\"error\":\"Failed to write file\"}\n"); + // Special handling for U-Boot binary + if (strcmp(type, "uboot_bin") == 0) { + if (write_uboot_to_devices(file_data.buf, file_data.len, filepath) < 0) { + snprintf(g_upload.status, sizeof(g_upload.status), "Failed"); + mg_http_reply(c, 500, "", "{\"error\":\"Failed to write U-Boot\"}\n"); + return; + } + } else { + // Write file in chunks for other types + FILE *fp = fopen(filepath, "wb"); + if (!fp) { + mg_http_reply(c, 500, "", "{\"error\":\"Failed to create file\"}\n"); return; } - written += to_write; - } - fclose(fp); - sync(); + size_t written = 0; + size_t chunk_size = 65536; + while (written < file_data.len) { + size_t to_write = (file_data.len - written < chunk_size) ? + (file_data.len - written) : chunk_size; + if (fwrite(file_data.buf + written, 1, to_write, fp) != to_write) { + fclose(fp); + mg_http_reply(c, 500, "", "{\"error\":\"Failed to write file\"}\n"); + return; + } + written += to_write; + g_upload.progress = (int)(written * 100 / file_data.len); + } + + fclose(fp); + sync(); + g_upload.progress = 100; + } printf("Upload complete: %s\n", filepath); - memcpy(g_upload.type, type, strlen(type) + 1); g_upload.received = file_data.len; g_upload.progress = 100; snprintf(g_upload.status, sizeof(g_upload.status), "Complete"); diff --git a/web/app.js b/web/app.js index 7dc107d..195f098 100644 --- a/web/app.js +++ b/web/app.js @@ -15,6 +15,7 @@ document.addEventListener("DOMContentLoaded", function () { const mountStatus = document.getElementById("mount-status"); const extlinuxEditor = document.getElementById("extlinux-editor"); const editorStatus = document.getElementById("editor-status"); + const uploadHint = document.getElementById("upload-hint"); // Tab buttons const tabBtns = document.querySelectorAll(".tab-btn"); @@ -32,6 +33,13 @@ document.addEventListener("DOMContentLoaded", function () { this.classList.add("active"); uploadType.value = this.dataset.type; hideResult(); + + // Show/hide U-Boot warning + if (this.dataset.type === "uboot_bin") { + uploadHint.classList.add("visible"); + } else { + uploadHint.classList.remove("visible"); + } }); }); diff --git a/web/index.html b/web/index.html index cedc67f..623e9a3 100644 --- a/web/index.html +++ b/web/index.html @@ -56,6 +56,9 @@ +
@@ -81,6 +84,11 @@
+
+ ⚠️ U-Boot Binary will be written to SPI NOR + (/dev/mtd0) and SD card at 8KB offset (/dev/mmcblk1 + +8KB) +
diff --git a/web/style.css b/web/style.css index dc83b1a..eecd3e2 100644 --- a/web/style.css +++ b/web/style.css @@ -146,6 +146,21 @@ h2 { text-align: center; } +.upload-hint { + margin: 15px 0; + padding: 12px; + background: rgba(255, 165, 0, 0.15); + border: 1px solid rgba(255, 165, 0, 0.5); + border-radius: 8px; + color: #ffa500; + font-size: 0.9rem; + display: none; +} + +.upload-hint.visible { + display: block; +} + .file-input-wrapper { margin-bottom: 20px; }