450 lines
10 KiB
C
450 lines
10 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (C) 2022 MediaTek Inc. All Rights Reserved.
|
|
*
|
|
* Author: Weijie Gao <weijie.gao@mediatek.com>
|
|
*
|
|
* Failsafe Web UI
|
|
*/
|
|
|
|
#include <command.h>
|
|
#include <errno.h>
|
|
#include <env.h>
|
|
#include <malloc.h>
|
|
#include <net.h>
|
|
#include <net/mtk_tcp.h>
|
|
#include <net/mtk_httpd.h>
|
|
#include <u-boot/md5.h>
|
|
#include <linux/stringify.h>
|
|
#include <dm/ofnode.h>
|
|
#include <vsprintf.h>
|
|
#include <version_string.h>
|
|
#include <failsafe/fw_type.h>
|
|
|
|
#include "../board/mediatek/common/boot_helper.h"
|
|
#include "fs.h"
|
|
|
|
static u32 upload_data_id;
|
|
static const void *upload_data;
|
|
static size_t upload_size;
|
|
static int upgrade_success;
|
|
static failsafe_fw_t fw_type;
|
|
|
|
#ifdef CONFIG_MEDIATEK_MULTI_MTD_LAYOUT
|
|
static const char *mtd_layout_label;
|
|
const char *get_mtd_layout_label(void);
|
|
#define MTD_LAYOUTS_MAXLEN 128
|
|
#endif
|
|
|
|
int __weak failsafe_validate_image(const void *data, size_t size, failsafe_fw_t fw)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int __weak failsafe_write_image(const void *data, size_t size, failsafe_fw_t fw)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
static int output_plain_file(struct httpd_response *response,
|
|
const char *filename)
|
|
{
|
|
const struct fs_desc *file;
|
|
int ret = 0;
|
|
|
|
file = fs_find_file(filename);
|
|
|
|
response->status = HTTP_RESP_STD;
|
|
|
|
if (file) {
|
|
response->data = file->data;
|
|
response->size = file->size;
|
|
} else {
|
|
response->data = "Error: file not found";
|
|
response->size = strlen(response->data);
|
|
ret = 1;
|
|
}
|
|
|
|
response->info.code = 200;
|
|
response->info.connection_close = 1;
|
|
response->info.content_type = "text/html";
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void version_handler(enum httpd_uri_handler_status status,
|
|
struct httpd_request *request,
|
|
struct httpd_response *response)
|
|
{
|
|
if (status != HTTP_CB_NEW)
|
|
return;
|
|
|
|
response->status = HTTP_RESP_STD;
|
|
|
|
response->data = version_string;
|
|
response->size = strlen(response->data);
|
|
|
|
response->info.code = 200;
|
|
response->info.connection_close = 1;
|
|
response->info.content_type = "text/plain";
|
|
}
|
|
|
|
static void index_handler(enum httpd_uri_handler_status status,
|
|
struct httpd_request *request,
|
|
struct httpd_response *response)
|
|
{
|
|
if (status == HTTP_CB_NEW)
|
|
output_plain_file(response, "index.html");
|
|
}
|
|
|
|
|
|
static void upload_handler(enum httpd_uri_handler_status status,
|
|
struct httpd_request *request,
|
|
struct httpd_response *response)
|
|
{
|
|
static char md5_str[33] = "";
|
|
static char resp[128];
|
|
struct httpd_form_value *fw;
|
|
#ifdef CONFIG_MEDIATEK_MULTI_MTD_LAYOUT
|
|
struct httpd_form_value *mtd = NULL;
|
|
#endif
|
|
u8 md5_sum[16];
|
|
int i;
|
|
|
|
static char hexchars[] = "0123456789abcdef";
|
|
|
|
if (status != HTTP_CB_NEW)
|
|
return;
|
|
|
|
response->status = HTTP_RESP_STD;
|
|
response->info.code = 200;
|
|
response->info.connection_close = 1;
|
|
response->info.content_type = "text/plain";
|
|
|
|
#ifdef CONFIG_MTK_BOOTMENU_MMC
|
|
fw = httpd_request_find_value(request, "gpt");
|
|
if (fw) {
|
|
fw_type = FW_TYPE_GPT;
|
|
goto done;
|
|
}
|
|
#endif
|
|
|
|
fw = httpd_request_find_value(request, "fip");
|
|
if (fw) {
|
|
fw_type = FW_TYPE_FIP;
|
|
if (failsafe_validate_image(fw->data, fw->size, fw_type))
|
|
goto fail;
|
|
goto done;
|
|
}
|
|
|
|
fw = httpd_request_find_value(request, "bl2");
|
|
if (fw) {
|
|
fw_type = FW_TYPE_BL2;
|
|
if (failsafe_validate_image(fw->data, fw->size, fw_type))
|
|
goto fail;
|
|
goto done;
|
|
}
|
|
|
|
fw = httpd_request_find_value(request, "firmware");
|
|
if (fw) {
|
|
fw_type = FW_TYPE_FW;
|
|
if (failsafe_validate_image(fw->data, fw->size, fw_type))
|
|
goto fail;
|
|
#ifdef CONFIG_MEDIATEK_MULTI_MTD_LAYOUT
|
|
mtd = httpd_request_find_value(request, "mtd_layout");
|
|
#endif
|
|
goto done;
|
|
}
|
|
|
|
fw = httpd_request_find_value(request, "initramfs");
|
|
if (fw) {
|
|
fw_type = FW_TYPE_INITRD;
|
|
if (fdt_check_header(fw->data))
|
|
goto fail;
|
|
goto done;
|
|
}
|
|
|
|
fail:
|
|
response->data = "fail";
|
|
response->size = strlen(response->data);
|
|
return;
|
|
|
|
done:
|
|
upload_data_id = upload_id;
|
|
upload_data = fw->data;
|
|
upload_size = fw->size;
|
|
|
|
md5_wd((u8 *)fw->data, fw->size, md5_sum, 0);
|
|
for (i = 0; i < 16; i++) {
|
|
u8 hex = (md5_sum[i] >> 4) & 0xf;
|
|
md5_str[i * 2] = hexchars[hex];
|
|
hex = md5_sum[i] & 0xf;
|
|
md5_str[i * 2 + 1] = hexchars[hex];
|
|
}
|
|
|
|
#ifdef CONFIG_MEDIATEK_MULTI_MTD_LAYOUT
|
|
if (mtd) {
|
|
mtd_layout_label = mtd->data;
|
|
sprintf(resp, "%ld %s %s", fw->size, md5_str, mtd->data);
|
|
} else {
|
|
sprintf(resp, "%ld %s", fw->size, md5_str);
|
|
}
|
|
#else
|
|
sprintf(resp, "%ld %s", fw->size, md5_str);
|
|
#endif
|
|
|
|
response->data = resp;
|
|
response->size = strlen(response->data);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
struct flashing_status {
|
|
char buf[4096];
|
|
int ret;
|
|
int body_sent;
|
|
};
|
|
|
|
static void result_handler(enum httpd_uri_handler_status status,
|
|
struct httpd_request *request,
|
|
struct httpd_response *response)
|
|
{
|
|
struct flashing_status *st;
|
|
u32 size;
|
|
|
|
if (status == HTTP_CB_NEW) {
|
|
st = calloc(1, sizeof(*st));
|
|
if (!st) {
|
|
response->info.code = 500;
|
|
return;
|
|
}
|
|
|
|
st->ret = -1;
|
|
|
|
response->session_data = st;
|
|
|
|
response->status = HTTP_RESP_CUSTOM;
|
|
|
|
response->info.http_1_0 = 1;
|
|
response->info.content_length = -1;
|
|
response->info.connection_close = 1;
|
|
response->info.content_type = "text/html";
|
|
response->info.code = 200;
|
|
|
|
size = http_make_response_header(&response->info,
|
|
st->buf, sizeof(st->buf));
|
|
|
|
response->data = st->buf;
|
|
response->size = size;
|
|
|
|
return;
|
|
}
|
|
|
|
if (status == HTTP_CB_RESPONDING) {
|
|
st = response->session_data;
|
|
|
|
if (st->body_sent) {
|
|
response->status = HTTP_RESP_NONE;
|
|
return;
|
|
}
|
|
|
|
if (upload_data_id == upload_id) {
|
|
#ifdef CONFIG_MEDIATEK_MULTI_MTD_LAYOUT
|
|
if (mtd_layout_label &&
|
|
strcmp(get_mtd_layout_label(), mtd_layout_label) != 0) {
|
|
printf("httpd: saving mtd_layout_label: %s\n", mtd_layout_label);
|
|
env_set("mtd_layout_label", mtd_layout_label);
|
|
env_save();
|
|
}
|
|
#endif
|
|
if (fw_type == FW_TYPE_INITRD)
|
|
st->ret = 0;
|
|
else
|
|
st->ret = failsafe_write_image(upload_data,
|
|
upload_size, fw_type);
|
|
}
|
|
|
|
/* invalidate upload identifier */
|
|
upload_data_id = rand();
|
|
|
|
if (!st->ret)
|
|
response->data = "success";
|
|
else
|
|
response->data = "failed";
|
|
|
|
response->size = strlen(response->data);
|
|
|
|
st->body_sent = 1;
|
|
|
|
return;
|
|
}
|
|
|
|
if (status == HTTP_CB_CLOSED) {
|
|
st = response->session_data;
|
|
|
|
upgrade_success = !st->ret;
|
|
|
|
free(response->session_data);
|
|
|
|
if (upgrade_success)
|
|
mtk_tcp_close_all_conn();
|
|
}
|
|
}
|
|
|
|
static void style_handler(enum httpd_uri_handler_status status,
|
|
struct httpd_request *request,
|
|
struct httpd_response *response)
|
|
{
|
|
if (status == HTTP_CB_NEW) {
|
|
output_plain_file(response, "style.css");
|
|
response->info.content_type = "text/css";
|
|
}
|
|
}
|
|
|
|
static void js_handler(enum httpd_uri_handler_status status,
|
|
struct httpd_request *request,
|
|
struct httpd_response *response)
|
|
{
|
|
if (status == HTTP_CB_NEW) {
|
|
output_plain_file(response, "main.js");
|
|
response->info.content_type = "text/javascript";
|
|
}
|
|
}
|
|
|
|
static void not_found_handler(enum httpd_uri_handler_status status,
|
|
struct httpd_request *request,
|
|
struct httpd_response *response)
|
|
{
|
|
if (status == HTTP_CB_NEW) {
|
|
output_plain_file(response, "404.html");
|
|
response->info.code = 404;
|
|
}
|
|
}
|
|
|
|
static void html_handler(enum httpd_uri_handler_status status,
|
|
struct httpd_request *request,
|
|
struct httpd_response *response)
|
|
{
|
|
if (status != HTTP_CB_NEW)
|
|
return;
|
|
|
|
if (output_plain_file(response, request->urih->uri + 1))
|
|
not_found_handler(status, request, response);
|
|
}
|
|
|
|
#ifdef CONFIG_MEDIATEK_MULTI_MTD_LAYOUT
|
|
static const char *get_mtdlayout_str(void)
|
|
{
|
|
static char mtd_layout_str[MTD_LAYOUTS_MAXLEN];
|
|
ofnode node, layout;
|
|
|
|
sprintf(mtd_layout_str, "%s;", get_mtd_layout_label());
|
|
|
|
node = ofnode_path("/mtd-layout");
|
|
if (ofnode_valid(node) && ofnode_get_child_count(node)) {
|
|
ofnode_for_each_subnode(layout, node) {
|
|
strcat(mtd_layout_str, ofnode_read_string(layout, "label"));
|
|
strcat(mtd_layout_str, ";");
|
|
}
|
|
}
|
|
|
|
return mtd_layout_str;
|
|
}
|
|
#endif
|
|
|
|
static void mtd_layout_handler(enum httpd_uri_handler_status status,
|
|
struct httpd_request *request,
|
|
struct httpd_response *response)
|
|
{
|
|
if (status != HTTP_CB_NEW)
|
|
return;
|
|
|
|
response->status = HTTP_RESP_STD;
|
|
|
|
#ifdef CONFIG_MEDIATEK_MULTI_MTD_LAYOUT
|
|
response->data = get_mtdlayout_str();
|
|
#else
|
|
response->data = "error";
|
|
#endif
|
|
|
|
response->size = strlen(response->data);
|
|
|
|
response->info.code = 200;
|
|
response->info.connection_close = 1;
|
|
response->info.content_type = "text/plain";
|
|
}
|
|
|
|
int start_web_failsafe(void)
|
|
{
|
|
struct httpd_instance *inst;
|
|
|
|
inst = httpd_find_instance(80);
|
|
if (inst)
|
|
httpd_free_instance(inst);
|
|
|
|
inst = httpd_create_instance(80);
|
|
if (!inst) {
|
|
printf("Error: failed to create HTTP instance on port 80\n");
|
|
return -1;
|
|
}
|
|
|
|
httpd_register_uri_handler(inst, "/", &index_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/bl2.html", &html_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/booting.html", &html_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/cgi-bin/luci", &index_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/cgi-bin/luci/", &index_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/fail.html", &html_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/flashing.html", &html_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/getmtdlayout", &mtd_layout_handler, NULL);
|
|
#ifdef CONFIG_MTK_BOOTMENU_MMC
|
|
httpd_register_uri_handler(inst, "/gpt.html", &html_handler, NULL);
|
|
#endif
|
|
httpd_register_uri_handler(inst, "/initramfs.html", &html_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/main.js", &js_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/result", &result_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/style.css", &style_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/uboot.html", &html_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/upload", &upload_handler, NULL);
|
|
httpd_register_uri_handler(inst, "/version", &version_handler, NULL);
|
|
httpd_register_uri_handler(inst, "", ¬_found_handler, NULL);
|
|
|
|
net_loop(MTK_TCP);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_httpd(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
u32 local_ip;
|
|
int ret;
|
|
|
|
#ifdef CONFIG_NET_FORCE_IPADDR
|
|
net_ip = string_to_ip(CONFIG_IPADDR);
|
|
net_netmask = string_to_ip(CONFIG_NETMASK);
|
|
#endif
|
|
local_ip = ntohl(net_ip.s_addr);
|
|
|
|
printf("\nWeb failsafe UI started\n");
|
|
printf("URL: http://%u.%u.%u.%u/\n",
|
|
(local_ip >> 24) & 0xff, (local_ip >> 16) & 0xff,
|
|
(local_ip >> 8) & 0xff, local_ip & 0xff);
|
|
printf("\nPress Ctrl+C to exit\n");
|
|
|
|
ret = start_web_failsafe();
|
|
|
|
if (upgrade_success) {
|
|
if (fw_type == FW_TYPE_INITRD)
|
|
boot_from_mem((ulong)upload_data);
|
|
else
|
|
do_reset(NULL, 0, 0, NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
U_BOOT_CMD(httpd, 1, 0, do_httpd,
|
|
"Start failsafe HTTP server", ""
|
|
);
|