mirror of
https://github.com/google/pebble
synced 2026-10-16 16:24:12 +02:00
Import of the watch repository from Pebble
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user