mirror of
https://github.com/google/pebble
synced 2026-10-16 00:04:12 +02:00
309 lines
9.3 KiB
C
309 lines
9.3 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "services/common/firmware_update.h"
|
|
|
|
#include "apps/core_apps/progress_ui_app.h"
|
|
#include "flash_region/flash_region.h"
|
|
#include "kernel/event_loop.h"
|
|
#include "kernel/system_message.h"
|
|
#include "kernel/ui/modals/modal_manager.h"
|
|
#include "process_management/app_manager.h"
|
|
#include "process_management/app_manager.h"
|
|
#include "services/common/battery/battery_monitor.h"
|
|
#include "services/common/system_task.h"
|
|
#include "services/runlevel.h"
|
|
#include "system/bootbits.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "system/reset.h"
|
|
#include "util/math.h"
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "semphr.h"
|
|
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
// The legacy firmware UI breaks firmware and resources into 50% chunks. In reality since these
|
|
// parts are not of equal sizes, one of these '50%' blocks will take longer than the
|
|
// other. Additionally, iOS/Android and the watch are not in sync over what this should look like.
|
|
//
|
|
// The new UI allows the phone to give us more info up front about how much data will be
|
|
// transmitted and also cleanly drive a re-start of the UI to a non-0 percentage if the FW update
|
|
// is being resumed. Newer implementations should this! (See PBL-42130)
|
|
|
|
static SemaphoreHandle_t s_firmware_update_semaphore;
|
|
static bool s_is_recovery_fw = false;
|
|
static FirmwareUpdateStatus s_update_status = FirmwareUpdateStopped;
|
|
|
|
typedef struct LegacyFwUpdateCompletionStatus {
|
|
uint32_t recovery_percent_completion;
|
|
uint32_t resource_percent_completion;
|
|
uint32_t firmware_percent_completion;
|
|
} LegacyFwUpdateCompletionStatus;
|
|
|
|
typedef struct FwUpdateCompletionStatus {
|
|
uint32_t bytes_transferred;
|
|
uint32_t total_size;
|
|
} FwUpdateCompletionStatus;
|
|
|
|
typedef struct {
|
|
bool use_legacy_mode;
|
|
union {
|
|
FwUpdateCompletionStatus status;
|
|
LegacyFwUpdateCompletionStatus legacy_status;
|
|
};
|
|
} FwUpdateCurrentCompletionStatus;
|
|
|
|
static FwUpdateCurrentCompletionStatus s_current_completion_status = { 0 };
|
|
|
|
//
|
|
// Start handlers for legacy percentage status handling. Someday, we can hopefully
|
|
// remove them outright and it should just involve deleting these routines
|
|
//
|
|
|
|
static bool prv_legacy_firmware_update_get_percent_progress(uint32_t *progress) {
|
|
if (!s_current_completion_status.use_legacy_mode) {
|
|
return false;
|
|
}
|
|
|
|
LegacyFwUpdateCompletionStatus *status = &s_current_completion_status.legacy_status;
|
|
if (s_is_recovery_fw) {
|
|
*progress = MIN(100, status->recovery_percent_completion);
|
|
} else {
|
|
*progress =
|
|
MIN(100, (status->resource_percent_completion + status->firmware_percent_completion) / 2);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool prv_legacy_handle_progress(PebblePutBytesEvent *event) {
|
|
if (!s_current_completion_status.use_legacy_mode) {
|
|
return false;
|
|
}
|
|
|
|
LegacyFwUpdateCompletionStatus *status = &s_current_completion_status.legacy_status;
|
|
switch (event->object_type) {
|
|
case ObjectFirmware:
|
|
status->firmware_percent_completion = event->progress_percent;
|
|
break;
|
|
|
|
case ObjectSysResources:
|
|
status->resource_percent_completion = event->progress_percent;
|
|
break;
|
|
|
|
case ObjectRecovery:
|
|
status->recovery_percent_completion = event->progress_percent;
|
|
break;
|
|
|
|
default:
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Unexpected Object type %u", event->object_type);
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool prv_legacy_completion_status_init(PebbleSystemMessageEvent *event) {
|
|
if (event->type != PebbleSystemMessageFirmwareUpdateStartLegacy) {
|
|
return false;
|
|
}
|
|
|
|
s_current_completion_status.use_legacy_mode = true;
|
|
LegacyFwUpdateCompletionStatus *status =
|
|
&s_current_completion_status.legacy_status;
|
|
|
|
*status = (LegacyFwUpdateCompletionStatus) {
|
|
.recovery_percent_completion = 0,
|
|
.resource_percent_completion = 0,
|
|
.firmware_percent_completion = 0
|
|
};
|
|
|
|
return true;
|
|
}
|
|
|
|
// End Legacy completion handlers
|
|
|
|
bool firmware_update_is_in_progress(void) {
|
|
return s_update_status == FirmwareUpdateRunning;
|
|
}
|
|
|
|
FirmwareUpdateStatus firmware_update_current_status(void) {
|
|
return s_update_status;
|
|
}
|
|
|
|
void firmware_update_init(void) {
|
|
vSemaphoreCreateBinary(s_firmware_update_semaphore);
|
|
PBL_ASSERTN(s_firmware_update_semaphore != NULL);
|
|
}
|
|
|
|
static void prv_initialize_completion_status(PebbleSystemMessageEvent *event) {
|
|
if (prv_legacy_completion_status_init(event)) {
|
|
return;
|
|
}
|
|
|
|
s_current_completion_status.use_legacy_mode = false;
|
|
FwUpdateCompletionStatus *status = &s_current_completion_status.status;
|
|
*status = (FwUpdateCompletionStatus) {
|
|
.bytes_transferred = event->bytes_transferred,
|
|
.total_size = event->total_transfer_size
|
|
};
|
|
}
|
|
|
|
// Initialization for a firmware update could involve an erase of 8 flash
|
|
// sectors. Worst case timing for an erase is ~5s, so let's set our timeout to
|
|
// 40s to give us some headroom.
|
|
#define FIRMWARE_TIMEOUT_MS (1000 * 40)
|
|
|
|
static FirmwareUpdateStatus prv_firmware_update_start(PebbleSystemMessageEvent *event) {
|
|
if (battery_monitor_critical_lockout()) {
|
|
return FirmwareUpdateCancelled; // Disable firmware updates on low power
|
|
}
|
|
|
|
if (xSemaphoreTake(s_firmware_update_semaphore, 0) == pdFALSE) {
|
|
return FirmwareUpdateStopped;
|
|
}
|
|
|
|
FirmwareUpdateStatus result = s_update_status;
|
|
if (result != FirmwareUpdateRunning) {
|
|
prv_initialize_completion_status(event);
|
|
|
|
services_set_runlevel(RunLevel_FirmwareUpdate);
|
|
modal_manager_pop_all();
|
|
|
|
static const ProgressUIAppArgs s_update_args = {
|
|
.progress_source = PROGRESS_UI_SOURCE_FW_UPDATE,
|
|
};
|
|
app_manager_launch_new_app(&(AppLaunchConfig) {
|
|
.md = progress_ui_app_get_info(),
|
|
.common.args = &s_update_args,
|
|
.restart = true,
|
|
});
|
|
put_bytes_expect_init(FIRMWARE_TIMEOUT_MS);
|
|
result = FirmwareUpdateRunning;
|
|
}
|
|
|
|
xSemaphoreGive(s_firmware_update_semaphore);
|
|
return result;
|
|
}
|
|
|
|
static void prv_handle_firmware_update_start_msg(PebbleSystemMessageEvent *event) {
|
|
FirmwareUpdateStatus result = prv_firmware_update_start(event);
|
|
s_update_status = result;
|
|
PBL_ASSERTN((result == FirmwareUpdateRunning) ||
|
|
(result == FirmwareUpdateStopped) ||
|
|
(result == FirmwareUpdateCancelled));
|
|
system_message_send_firmware_start_response(result);
|
|
}
|
|
|
|
static void prv_firmware_update_finish(bool failed) {
|
|
if (xSemaphoreTake(s_firmware_update_semaphore, 0) == pdFALSE) {
|
|
return;
|
|
}
|
|
|
|
if (failed) {
|
|
// If we failed, we can set it back to normal. If we succeeded, we'll reboot shortly.
|
|
// We don't know the runlevel that was set before, so we assume it was Normal.
|
|
services_set_runlevel(RunLevel_Normal);
|
|
}
|
|
|
|
s_update_status = failed ? FirmwareUpdateFailed : FirmwareUpdateStopped;
|
|
|
|
xSemaphoreGive(s_firmware_update_semaphore);
|
|
}
|
|
|
|
unsigned int firmware_update_get_percent_progress(void) {
|
|
if (!firmware_update_is_in_progress()) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t progress = 0;
|
|
if (prv_legacy_firmware_update_get_percent_progress(&progress)) {
|
|
return progress;
|
|
}
|
|
|
|
FwUpdateCompletionStatus *status = &s_current_completion_status.status;
|
|
return (status->bytes_transferred * 100) / status->total_size;
|
|
}
|
|
|
|
void firmware_update_event_handler(PebbleSystemMessageEvent* event) {
|
|
switch (event->type) {
|
|
case PebbleSystemMessageFirmwareUpdateStartLegacy:
|
|
case PebbleSystemMessageFirmwareUpdateStart:
|
|
prv_handle_firmware_update_start_msg(event);
|
|
break;
|
|
|
|
case PebbleSystemMessageFirmwareUpdateFailed:
|
|
prv_firmware_update_finish(true /* failed */);
|
|
break;
|
|
|
|
case PebbleSystemMessageFirmwareUpdateComplete:
|
|
prv_firmware_update_finish(false /* failed */);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void prv_handle_progress(PebblePutBytesEvent *event) {
|
|
if (prv_legacy_handle_progress(event)) {
|
|
return;
|
|
}
|
|
|
|
if (event->type != PebblePutBytesEventTypeProgress) {
|
|
return; // Only progress events report bytes_transferred updates
|
|
}
|
|
|
|
FwUpdateCompletionStatus *status = &s_current_completion_status.status;
|
|
status->bytes_transferred += event->bytes_transferred;
|
|
}
|
|
|
|
void firmware_update_pb_event_handler(PebblePutBytesEvent *event) {
|
|
if (!firmware_update_is_in_progress()) {
|
|
return; // not my pb transfer
|
|
}
|
|
|
|
switch (event->type) {
|
|
case PebblePutBytesEventTypeStart:
|
|
s_is_recovery_fw = (event->object_type == ObjectRecovery);
|
|
prv_handle_progress(event);
|
|
break;
|
|
|
|
case PebblePutBytesEventTypeProgress:
|
|
prv_handle_progress(event);
|
|
break;
|
|
|
|
case PebblePutBytesEventTypeCleanup:
|
|
if (event->failed) {
|
|
// exit now in case the phone is gone
|
|
prv_firmware_update_finish(true /* failed */);
|
|
}
|
|
break;
|
|
|
|
case PebblePutBytesEventTypeInitTimeout:
|
|
PBL_LOG(LOG_LEVEL_WARNING, "Timed out waiting for putbytes request from phone");
|
|
prv_firmware_update_finish(true /* failed */);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|