added upload uboot
This commit is contained in:
1 parent
febecf4dd9
commit
844f839c51
5 files changed
+215
-27
No files matched your search
@@ -8,10 +8,14 @@
|
|||||||
- **Обновление ядра**: Загрузка `Image.gz` в boot раздел
|
- **Обновление ядра**: Загрузка `Image.gz` в boot раздел
|
||||||
- **Обновление Device-Tree**: Загрузка `sun50i-h618-orangepi-zero2w.dtb`
|
- **Обновление Device-Tree**: Загрузка `sun50i-h618-orangepi-zero2w.dtb`
|
||||||
- **U-Boot Environment**: Загрузка `uboot.env`
|
- **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**: Просмотр и редактирование конфигурации загрузчика
|
- **Редактор extlinux.conf**: Просмотр и редактирование конфигурации загрузчика
|
||||||
- **Индикатор прогресса**: Отображение прогресса загрузки в реальном времени
|
- **Индикатор прогресса**: Отображение прогресса загрузки в реальном времени
|
||||||
- **Авто-монтирование**: Автоматически монтирует `/dev/mmcblk1p1` в `/media`
|
- **Авто-монтирование**: Автоматически монтирует `/dev/mmcblk1p1` в `/media`
|
||||||
- **Поддержка больших файлов**: Загрузка файлов до 200 MB
|
- **Поддержка больших файлов**: Загрузка файлов до 200 MB (до 1 MB для U-Boot Binary)
|
||||||
- **Перезагрузка системы**: Кнопка безопасной перезагрузки с корректным отмонтированием
|
- **Перезагрузка системы**: Кнопка безопасной перезагрузки с корректным отмонтированием
|
||||||
- **Мониторинг температуры**: Отображение температуры CPU с цветовой индикацией
|
- **Мониторинг температуры**: Отображение температуры CPU с цветовой индикацией
|
||||||
|
|
||||||
@@ -103,6 +107,7 @@ sudo ./up
|
|||||||
| Ядро | `/media/Image.gz` |
|
| Ядро | `/media/Image.gz` |
|
||||||
| Device-Tree | `/media/sun50i-h618-orangepi-zero2w.dtb` |
|
| Device-Tree | `/media/sun50i-h618-orangepi-zero2w.dtb` |
|
||||||
| U-Boot Env | `/media/uboot.env` |
|
| 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` |
|
| Extlinux | `/media/extlinux/extlinux.conf` |
|
||||||
|
|
||||||
## Конфигурация
|
## Конфигурация
|
||||||
|
|||||||
+178
-26
@@ -4,6 +4,8 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/mount.h>
|
#include <sys/mount.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <mtd/mtd-user.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -18,9 +20,11 @@
|
|||||||
#define KERNEL_FILE "Image.gz"
|
#define KERNEL_FILE "Image.gz"
|
||||||
#define DTB_FILE "sun50i-h618-orangepi-zero2w.dtb"
|
#define DTB_FILE "sun50i-h618-orangepi-zero2w.dtb"
|
||||||
#define UBOOT_FILE "uboot.env"
|
#define UBOOT_FILE "uboot.env"
|
||||||
|
#define UBOOT_BIN_FILE "u-boot-sunxi-with-spl.bin"
|
||||||
#define EXTLINUX_FILE "extlinux/extlinux.conf"
|
#define EXTLINUX_FILE "extlinux/extlinux.conf"
|
||||||
|
|
||||||
#define MAX_FILE_SIZE (200 * 1024 * 1024) // 200 MB
|
#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 const char *s_http_port = "http://0.0.0.0:43243";
|
||||||
static struct mg_mgr mgr;
|
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, "kernel") == 0) return KERNEL_FILE;
|
||||||
if (strcmp(type, "dtb") == 0) return DTB_FILE;
|
if (strcmp(type, "dtb") == 0) return DTB_FILE;
|
||||||
if (strcmp(type, "uboot") == 0) return UBOOT_FILE;
|
if (strcmp(type, "uboot") == 0) return UBOOT_FILE;
|
||||||
|
if (strcmp(type, "uboot_bin") == 0) return UBOOT_BIN_FILE;
|
||||||
return NULL;
|
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
|
// Get MIME type based on file extension
|
||||||
static const char* get_mime_type(const char *path) {
|
static const char* get_mime_type(const char *path) {
|
||||||
const char *ext = strrchr(path, '.');
|
const char *ext = strrchr(path, '.');
|
||||||
@@ -301,10 +431,10 @@ static void handle_temperature(struct mg_connection *c) {
|
|||||||
char type[32] = {0};
|
char type[32] = {0};
|
||||||
if (fscanf(type_f, "%31s", type) == 1) {
|
if (fscanf(type_f, "%31s", type) == 1) {
|
||||||
// Prefer CPU or soc zones
|
// 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, "soc") != NULL ||
|
||||||
strstr(type, "CPU") != NULL);
|
strstr(type, "CPU") != NULL);
|
||||||
|
|
||||||
int temp_raw;
|
int temp_raw;
|
||||||
if (fscanf(f, "%d", &temp_raw) == 1) {
|
if (fscanf(f, "%d", &temp_raw) == 1) {
|
||||||
temperature = temp_raw / 1000.0f;
|
temperature = temp_raw / 1000.0f;
|
||||||
@@ -337,11 +467,11 @@ static void handle_temperature(struct mg_connection *c) {
|
|||||||
char name[32] = {0};
|
char name[32] = {0};
|
||||||
if (fscanf(name_f, "%31s", name) == 1) {
|
if (fscanf(name_f, "%31s", name) == 1) {
|
||||||
// Prefer CPU or sunxi sensors
|
// 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, "sunxi") != NULL ||
|
||||||
strstr(name, "CPU") != NULL ||
|
strstr(name, "CPU") != NULL ||
|
||||||
strstr(name, "acpitz") != NULL);
|
strstr(name, "acpitz") != NULL);
|
||||||
|
|
||||||
int temp_raw;
|
int temp_raw;
|
||||||
if (fscanf(f, "%d", &temp_raw) == 1) {
|
if (fscanf(f, "%d", &temp_raw) == 1) {
|
||||||
temperature = temp_raw / 1000.0f;
|
temperature = temp_raw / 1000.0f;
|
||||||
@@ -369,7 +499,7 @@ static void handle_temperature(struct mg_connection *c) {
|
|||||||
"/sys/class/hwmon/hwmon3/temp1_input",
|
"/sys/class/hwmon/hwmon3/temp1_input",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; fallback_paths[i] != NULL && !success; i++) {
|
for (int i = 0; fallback_paths[i] != NULL && !success; i++) {
|
||||||
FILE *f = fopen(fallback_paths[i], "r");
|
FILE *f = fopen(fallback_paths[i], "r");
|
||||||
if (f) {
|
if (f) {
|
||||||
@@ -456,8 +586,14 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_data.len > MAX_FILE_SIZE) {
|
// Check file size limits
|
||||||
mg_http_reply(c, 400, "", "{\"error\":\"File too large (max 200MB)\"}\n");
|
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;
|
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);
|
printf("Upload: %s (%zu bytes) -> %s\n", type, file_data.len, filepath);
|
||||||
|
|
||||||
// Write file in chunks
|
// Update upload state
|
||||||
FILE *fp = fopen(filepath, "wb");
|
memcpy(g_upload.type, type, strlen(type) + 1);
|
||||||
if (!fp) {
|
g_upload.received = 0;
|
||||||
mg_http_reply(c, 500, "", "{\"error\":\"Failed to create file\"}\n");
|
g_upload.progress = 0;
|
||||||
return;
|
snprintf(g_upload.status, sizeof(g_upload.status), "Uploading...");
|
||||||
}
|
|
||||||
|
|
||||||
size_t written = 0;
|
// Special handling for U-Boot binary
|
||||||
size_t chunk_size = 65536;
|
if (strcmp(type, "uboot_bin") == 0) {
|
||||||
while (written < file_data.len) {
|
if (write_uboot_to_devices(file_data.buf, file_data.len, filepath) < 0) {
|
||||||
size_t to_write = (file_data.len - written < chunk_size) ?
|
snprintf(g_upload.status, sizeof(g_upload.status), "Failed");
|
||||||
(file_data.len - written) : chunk_size;
|
mg_http_reply(c, 500, "", "{\"error\":\"Failed to write U-Boot\"}\n");
|
||||||
if (fwrite(file_data.buf + written, 1, to_write, fp) != to_write) {
|
return;
|
||||||
fclose(fp);
|
}
|
||||||
mg_http_reply(c, 500, "", "{\"error\":\"Failed to write file\"}\n");
|
} 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;
|
return;
|
||||||
}
|
}
|
||||||
written += to_write;
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(fp);
|
size_t written = 0;
|
||||||
sync();
|
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);
|
printf("Upload complete: %s\n", filepath);
|
||||||
|
|
||||||
memcpy(g_upload.type, type, strlen(type) + 1);
|
|
||||||
g_upload.received = file_data.len;
|
g_upload.received = file_data.len;
|
||||||
g_upload.progress = 100;
|
g_upload.progress = 100;
|
||||||
snprintf(g_upload.status, sizeof(g_upload.status), "Complete");
|
snprintf(g_upload.status, sizeof(g_upload.status), "Complete");
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
const mountStatus = document.getElementById("mount-status");
|
const mountStatus = document.getElementById("mount-status");
|
||||||
const extlinuxEditor = document.getElementById("extlinux-editor");
|
const extlinuxEditor = document.getElementById("extlinux-editor");
|
||||||
const editorStatus = document.getElementById("editor-status");
|
const editorStatus = document.getElementById("editor-status");
|
||||||
|
const uploadHint = document.getElementById("upload-hint");
|
||||||
|
|
||||||
// Tab buttons
|
// Tab buttons
|
||||||
const tabBtns = document.querySelectorAll(".tab-btn");
|
const tabBtns = document.querySelectorAll(".tab-btn");
|
||||||
@@ -32,6 +33,13 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
this.classList.add("active");
|
this.classList.add("active");
|
||||||
uploadType.value = this.dataset.type;
|
uploadType.value = this.dataset.type;
|
||||||
hideResult();
|
hideResult();
|
||||||
|
|
||||||
|
// Show/hide U-Boot warning
|
||||||
|
if (this.dataset.type === "uboot_bin") {
|
||||||
|
uploadHint.classList.add("visible");
|
||||||
|
} else {
|
||||||
|
uploadHint.classList.remove("visible");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
<button class="tab-btn" data-type="uboot">
|
<button class="tab-btn" data-type="uboot">
|
||||||
U-Boot Environment
|
U-Boot Environment
|
||||||
</button>
|
</button>
|
||||||
|
<button class="tab-btn" data-type="uboot_bin">
|
||||||
|
U-Boot Binary (SPL)
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="upload-area">
|
<div class="upload-area">
|
||||||
@@ -81,6 +84,11 @@
|
|||||||
<span class="file-name" id="file-name"></span>
|
<span class="file-name" id="file-name"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="upload-hint" id="upload-hint">
|
||||||
|
⚠️ U-Boot Binary will be written to SPI NOR
|
||||||
|
(/dev/mtd0) and SD card at 8KB offset (/dev/mmcblk1
|
||||||
|
+8KB)
|
||||||
|
</div>
|
||||||
<button type="submit" class="upload-btn">Upload</button>
|
<button type="submit" class="upload-btn">Upload</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,21 @@ h2 {
|
|||||||
text-align: center;
|
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 {
|
.file-input-wrapper {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user