Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson
2024-12-12 16:43:03 -08:00
committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
/*
* 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 "delay.h"
#include "util/attributes.h"
#include "util/units.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
#include <inttypes.h>
#if MICRO_FAMILY_STM32F7
# define INSTRUCTIONS_PER_LOOP (1)
#elif MICRO_FAMILY_STM32F2 || MICRO_FAMILY_STM32F4
# define INSTRUCTIONS_PER_LOOP (3)
#else
# error "Unexpected micro family"
#endif
static uint32_t s_loops_per_us = 0;
void NOINLINE delay_us(uint32_t us) {
uint32_t delay_loops = us * s_loops_per_us;
__asm volatile (
"spinloop: \n"
" subs %[delay_loops], #1 \n"
" bne spinloop \n"
: [delay_loops] "+r" (delay_loops) // read-write operand
:
: "cc"
);
}
void delay_init(void) {
// The loop above consists of 2 instructions (output of arm-none-eabi-objdump -d
// delay.X.o):
//
// subs r0, #1
// bne.w 4 <spinloop>
//
// Subtract consumes 1 cycle & the conditional branch consumes 1 + P (pipeline fill delay,
// 1-3 cycles) if the branch is taken, or 1 if not taken. For this situation, it appears that P=1
// on the STM32F2/F4, so the loop takes 3 and 2 cycles respectively. The Cortex-M7 (STM32F7) has a
// superscalar dual-issue architecture which allows for 1-cycle loops (including the subtract).
//
// @ 64MHz 1 instructions is ~15.6ns which translates to the previously measured 47ns for one loop
// Thus we can derive that to get a duration of 1µs from an arbitrary clock frequency the count
// value needs to be:
// count = 1e-6 / (1/F * 3) where F is the core clock frequency
//
// An additional note is that delay_us is always executed from flash. The
// instruction cache in the cortex M3 & M4 cores is pretty good at saving
// instructions with simple branches which means we don't stall on flash
// reads after the first loop. Counterintuitively, executing from SRAM
// actually adds a extra delay cycle on instruction fetches and can be
// stalled if peripherals are doing DMAs. (See PBL-22265 for more details)
RCC_ClocksTypeDef clocks;
RCC_GetClocksFreq(&clocks);
// Get the frequency in MHz so we don't overflow a uint32_t.
const uint32_t frequency_mhz = clocks.HCLK_Frequency / MHZ_TO_HZ(1);
const uint32_t clock_period_ps = PS_PER_US / frequency_mhz;
s_loops_per_us = PS_PER_US / (clock_period_ps * INSTRUCTIONS_PER_LOOP);
// we always want to delay for more than the time specified so round up
// if the numbers don't divide evenly
if ((PS_PER_US % (clock_period_ps * INSTRUCTIONS_PER_LOOP)) != 0) {
s_loops_per_us += 1;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
#pragma once
#include <inttypes.h>
//! Carefully timed spinloop that allows one to delay at a microsecond
//! granularity.
void delay_us(uint32_t us);
void delay_init(void);

View File

@@ -0,0 +1,132 @@
/*
* 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 "kernel/util/factory_reset.h"
#include "drivers/rtc.h"
#include "drivers/task_watchdog.h"
#include "flash_region/filesystem_regions.h"
#include "kernel/event_loop.h"
#include "kernel/util/standby.h"
#include "process_management/worker_manager.h"
#include "services/common/event_service.h"
#include "services/common/shared_prf_storage/shared_prf_storage.h"
#include "services/common/system_task.h"
#include "services/runlevel.h"
#include "shell/normal/app_idle_timeout.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/reboot_reason.h"
#include "system/reset.h"
#include "kernel/util/sleep.h"
#if !RECOVERY_FW
#include "services/normal/blob_db/pin_db.h"
#include "services/normal/blob_db/reminder_db.h"
#include "services/normal/filesystem/pfs.h"
#include "services/normal/timeline/event.h"
#endif
static bool s_in_factory_reset = false;
static void prv_factory_reset_non_pfs_data() {
PBL_LOG_SYNC(LOG_LEVEL_INFO, "Factory resetting...");
// This function can block the system task for a long time.
// Prevent callbacks being added to the system task so it doesn't overflow.
system_task_block_callbacks(true /* block callbacks */);
launcher_block_popups(true);
worker_manager_disable();
event_service_clear_process_subscriptions(PebbleTask_App);
shared_prf_storage_wipe_all();
services_set_runlevel(RunLevel_BareMinimum);
app_idle_timeout_stop();
while (worker_manager_get_current_worker_md()) {
// busy loop until the worker is killed
psleep(3);
}
rtc_timezone_clear();
}
void factory_reset_set_reason_and_reset(void) {
RebootReason reason = { RebootReasonCode_FactoryResetReset, 0 };
reboot_reason_set(&reason);
system_reset();
}
static void prv_factory_reset_post(bool should_shutdown) {
if (should_shutdown) {
enter_standby(RebootReasonCode_FactoryResetShutdown);
} else {
factory_reset_set_reason_and_reset();
}
}
void factory_reset(bool should_shutdown) {
s_in_factory_reset = true;
prv_factory_reset_non_pfs_data();
// TODO: wipe the registry on tintin?
filesystem_regions_erase_all();
#if !defined(RECOVERY_FW)
// "First use" is part of the PRF image for Snowy
boot_bit_set(BOOT_BIT_FORCE_PRF);
#endif
prv_factory_reset_post(should_shutdown);
}
#if !RECOVERY_FW
void close_db_files() {
// Deinit the databases and any clients
timeline_event_deinit();
reminder_db_deinit();
pin_db_deinit();
}
void factory_reset_fast(void *unused) {
s_in_factory_reset = true;
// disable the watchdog... we've got lots to do before we reset
task_watchdog_mask_clear(pebble_task_get_current());
close_db_files();
prv_factory_reset_non_pfs_data();
pfs_remove_files(NULL);
prv_factory_reset_post(false /* should_shutdown */);
}
#endif // !RECOVERY_FW
//! Used by the mfg flow to kick us out the MFG firmware and into the conumer PRF that's stored
//! on the external flash.
void command_enter_consumer_mode(void) {
boot_bit_set(BOOT_BIT_FORCE_PRF);
factory_reset(true /* should_shutdown */);
}
bool factory_reset_ongoing(void) {
return s_in_factory_reset;
}

View File

@@ -0,0 +1,29 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
//! Factory resets the device by wiping the flash
//! @param should_shutdown If true, shutdown after factory resetting, otherwise reboot.
void factory_reset(bool should_shutdown);
//! Factory resets the device by deleting all files
void factory_reset_fast(void *unused);
//! Returns true if a factory reset is in progress.
bool factory_reset_ongoing(void);

View File

@@ -0,0 +1,93 @@
/*
* 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 "kernel/util/fw_reset.h"
#include "console/pulse_internal.h"
#include "kernel/core_dump.h"
#include "kernel/util/factory_reset.h"
#include "services/common/comm_session/session.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"
static void prv_reset_into_prf(void) {
RebootReason reason = { RebootReasonCode_PrfReset, 0 };
reboot_reason_set(&reason);
boot_bit_set(BOOT_BIT_FORCE_PRF);
services_set_runlevel(RunLevel_BareMinimum);
system_reset();
}
void fw_reset_into_prf(void) {
prv_reset_into_prf();
}
static const uint8_t s_prf_reset_cmd = 0xff;
typedef enum {
ResetCmdNormal = 0x00,
ResetCmdCoreDump = 0x01,
ResetCmdFactoryReset = 0xfe,
ResetCmdIntoRecovery = 0xff,
} ResetCmd;
void reset_protocol_msg_callback(CommSession *session, const uint8_t* data, unsigned int length) {
PBL_ASSERT_RUNNING_FROM_EXPECTED_TASK(PebbleTask_KernelBackground);
const uint8_t cmd = data[0];
switch (cmd) {
case ResetCmdNormal:
PBL_LOG(LOG_LEVEL_WARNING, "Rebooting");
system_reset();
break;
case ResetCmdCoreDump:
PBL_LOG(LOG_LEVEL_INFO, "Core dump + Reboot triggered");
core_dump_reset(true /* force overwrite any existing core dump */);
break;
case ResetCmdIntoRecovery:
PBL_LOG(LOG_LEVEL_WARNING, "Rebooting into PRF");
prv_reset_into_prf();
break;
case ResetCmdFactoryReset:
factory_reset(false /* should_shutdown */);
break;
default:
PBL_LOG(LOG_LEVEL_ERROR, "Invalid reset msg, data[0] %u", data[0]);
break;
}
}
void fw_prepare_for_reset(bool unsafe_reset) {
if (!unsafe_reset) {
// Tear down Bluetooth, to avoid confusing the phone:
services_set_runlevel(RunLevel_BareMinimum);
#if PULSE_EVERYWHERE
pulse_end();
#endif
} else {
pulse_prepare_to_crash();
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
void fw_prepare_for_reset(bool skip_bt_teardown);
void fw_reset_into_prf(void);
typedef enum RemoteResetType {
RemoteResetRegular = 0x00,
RemoteResetPrf = 0xff,
} RemoteResetType;

View File

@@ -0,0 +1,95 @@
/*
* 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 "interval_timer.h"
#include "drivers/rtc.h"
#include "FreeRTOS.h"
static uint64_t prv_get_curr_system_time_ms(void) {
time_t time_s;
uint16_t time_ms;
rtc_get_time_ms(&time_s, &time_ms);
return (((uint64_t)time_s) * 1000 + time_ms);
}
void interval_timer_init(IntervalTimer *timer, uint32_t min_expected_ms, uint32_t max_expected_ms,
uint32_t weighting_factor_inverted) {
PBL_ASSERTN(weighting_factor_inverted != 0); // Divide by zero is not awesome
*timer = (IntervalTimer) {
.min_expected_ms = min_expected_ms,
.max_expected_ms = max_expected_ms,
.weighting_factor_inverted = weighting_factor_inverted
};
}
//! Record a sample that marks the start/end of an interval.
//! Safe to call from an ISR.
void interval_timer_take_sample(IntervalTimer *timer) {
portENTER_CRITICAL();
{
const uint64_t current_time = prv_get_curr_system_time_ms();
// Handle the first sample specially. We don't have an interval until we have
// 2 samples.
if (timer->num_samples == 0) {
timer->num_samples++;
} else {
const int64_t last_interval = current_time - timer->last_sample_timestamp_ms;
// Make sure this interval is valid
if (last_interval >= timer->min_expected_ms &&
last_interval <= timer->max_expected_ms) {
// It's valid! Let's roll it into our moving average
// This is an exponential moving average.
// https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
// average_now = average_previous + (weighting_factor * (new_value - average_previous))
// Where alpha is between 0 and 1. The closer to 1 the more responsive to recent changes
// the average is.
if (timer->num_samples == 1) {
// Initialize the average to the first sample we have
timer->average_ms = last_interval;
} else {
timer->average_ms = timer->average_ms +
((last_interval - timer->average_ms) / timer->weighting_factor_inverted);
}
timer->num_samples++;
}
}
timer->last_sample_timestamp_ms = current_time;
}
portEXIT_CRITICAL();
}
uint32_t interval_timer_get(IntervalTimer *timer, uint32_t *average_ms_out) {
uint32_t num_intervals;
portENTER_CRITICAL();
{
num_intervals = timer->num_samples ? timer->num_samples - 1 : 0;
*average_ms_out = timer->average_ms;
}
portEXIT_CRITICAL();
return num_intervals;
}

View File

@@ -0,0 +1,74 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
//! @file interval_timer.h
//!
//! Times the average number of milliseconds between samples. Taking a sample is ISR-safe. Uses
//! the RTC time domain which should be fairly accurate for real clock time. Note that because
//! we use the RTC we may occasionally have to discard intervals because the wall clock time was
//! changed. This is not ideal, but it's still a better source of time than our SysTick time source
//! as that is not necessarily synced to real time.
//!
//! The resolution of the recorded timer will be same as the configured resolution of our RTC
//! peripheral, which at the time of writing is 1/256 of a second.
//!
//! The average is calculated as an exponential moving average.
//! https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
//! The weighting factor (how responsive to recent changes) is configurable.
typedef struct {
uint64_t last_sample_timestamp_ms;
// The minimum and maximum values for an interval for it to be included into the average.
uint32_t min_expected_ms;
uint32_t max_expected_ms;
uint32_t weighting_factor_inverted;
//! The moving average we've calculated based on the samples we have so far.
uint32_t average_ms;
//! The number of samples we've taken.
uint32_t num_samples;
} IntervalTimer;
//! Initialize an interval timer.
//!
//! Allows the specification of an acceptable range of intervals. This is used to discard invalid
//! intervals.
//!
//! Intervals are averaged together in a moving average that includes the last n intervals. The
//! number of intervals to average over is configurable.
//!
//! @param min_expected_ms The minimum number of milliseconds between samples
//! @param min_expected_ms The maximum number of milliseconds between samples
//! @param weighting_factor_inverted
//! 1 / alpha. Specified as a inverted number to avoid dealing with floats. The higher the
//! number the less responsive to recent changes our average is.
void interval_timer_init(IntervalTimer *timer, uint32_t min_expected_ms, uint32_t max_expected_ms,
uint32_t weighting_factory_inverted);
//! Record a sample that marks the start/end of an interval.
//! Safe to call from an ISR.
void interval_timer_take_sample(IntervalTimer *timer);
//! @param[out] average_ms_out The average ms for the interval.
//! @return The number of valid intervals that are in our moving average. Note that this value
//! will never be larger than num_intervals_in_average.
uint32_t interval_timer_get(IntervalTimer *timer, uint32_t *average_ms_out);

View File

@@ -0,0 +1,74 @@
/*
* 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 "segment.h"
#include "system/passert.h"
#include <stdalign.h>
// Remove once the Bamboo build agents build unit tests with a more
// C11-compliant compiler (i.e. clang >= 3.5)
#if !defined(__CLANG_MAX_ALIGN_T_DEFINED) && !defined(_GCC_MAX_ALIGN_T)
typedef long double max_align_t;
#endif
static void prv_assert_sane_segment(MemorySegment *segment) {
PBL_ASSERT(segment->start <= segment->end,
"Segment end points before segment start");
}
static void * prv_align(void *ptr) {
uintptr_t c = (uintptr_t)ptr;
// Advance the pointer to the next alignment boundary.
return (void *)((c + alignof(max_align_t) - 1) & ~(alignof(max_align_t) - 1));
}
size_t memory_segment_get_size(MemorySegment *segment) {
prv_assert_sane_segment(segment);
return (size_t)((uintptr_t)segment->end - (uintptr_t)segment->start);
}
void memory_segment_align(MemorySegment *segment) {
segment->start = prv_align(segment->start);
prv_assert_sane_segment(segment);
}
void * memory_segment_split(MemorySegment * restrict parent,
MemorySegment * restrict child, size_t size) {
prv_assert_sane_segment(parent);
char *child_start = prv_align(parent->start);
void *child_end = child_start + size;
if (child_end > parent->end) {
// Requested size is too big to fit in the parent segment.
return NULL;
}
void *adjusted_parent_start = prv_align(child_end);
if (adjusted_parent_start > parent->end) {
// The child has left no room for the adjusted parent.
return NULL;
}
parent->start = adjusted_parent_start;
if (child) {
*child = (MemorySegment) {
.start = child_start,
.end = child_end,
};
}
return child_start;
}

View File

@@ -0,0 +1,59 @@
/*
* 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.
*/
//! Memory Segments
//!
//! A memory segment is a representation of a contiguous chunk of memory.
//! Memory segments can be split, dividing the segment in two. This API
//! is designed to simplify tasks such as process loading where a chunk
//! of memory must be allocated into a bunch of smaller chunks of
//! various fixed and dynamic sizes.
#pragma once
#include <stddef.h>
typedef struct MemorySegment {
void *start; //!< The lowest address of the segment
void *end; //!< One past the highest address of the segment
} MemorySegment;
//! Returns the size of the largest object that the segment can contain.
size_t memory_segment_get_size(MemorySegment *segment);
//! Align the start pointer of a segment such that it is suitably
//! aligned for any object.
void memory_segment_align(MemorySegment *segment);
//! Split a memory segment into two.
//!
//! The child memory segment is allocated from the start of the parent,
//! and the start of the parent is moved to the end of the child.
//!
//! After the split, the start addresses of both the parent and the
//! child are guaranteed to be suitably aligned for any object.
//!
//! @param parent the memory segment to split.
//! @param child the memory segment created from the split. May be NULL.
//! @param size size of the new memory segment.
//!
//! @return start of child memory segment if successful, or NULL if
//! there is not enough space in the parent segment to split
//! with the requested size.
void * memory_segment_split(MemorySegment * restrict parent,
MemorySegment * restrict child, size_t size);

View File

@@ -0,0 +1,22 @@
/*
* 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 "syscall/syscall.h"
void psleep(int millis) {
sys_psleep(millis);
}

View File

@@ -0,0 +1,34 @@
/*
* 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.
*/
#pragma once
//! @internal
//!
//! Waits for a certain amount of milliseconds by suspending the thread in firmware or by just busy
//! waiting in the bootloader.
//!
//! Note that the thread is slept until the current tick + millis of ticks, so that means that if
//! we're currently part way through a tick, we'll actually wait for a time that's less than
//! expected. For example, if we're at tick n and we want to wait for 1 millisecond, we'll sleep
//! the thread until tick n+1, which will result in a sleep of less than a millisecond, as we're
//! probably halfway through the n-th tick at this time. Also note that your thread isn't
//! guaranteed to be scheduled immediately after you're done sleeping, so you may sleep for longer
//! than you expect.
//!
//! @param millis The number of milliseconds to wait for
void psleep(int millis);

View File

@@ -0,0 +1,43 @@
/*
* 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 "FreeRTOS.h"
#include "task.h"
#include "mcu/interrupts.h"
extern uint32_t __isr_stack_start__[];
uint32_t stack_free_bytes(void) {
// Get the current SP
register uint32_t SP __asm ("sp");
uint32_t cur_sp = SP;
// Default stack
uint32_t start = (uint32_t) __isr_stack_start__;
// On ISR stack?
if (!mcu_state_is_isr()) {
TaskHandle_t task_handle = xTaskGetCurrentTaskHandle();
if (task_handle != NULL) {
// task_handle is NULL before we start the first task
start = (uint32_t)ulTaskGetStackStart(task_handle);
}
}
return cur_sp - start;
}

View File

@@ -0,0 +1,23 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
//! Return the number of free bytes left in the current stack. Returns 0 if stack space could
//! not be determined.
uint32_t stack_free_bytes(void);

View File

@@ -0,0 +1,111 @@
/*
* 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 "kernel/util/standby.h"
#include "drivers/imu.h"
#include "drivers/rtc.h"
#include "drivers/flash.h"
#include "drivers/pmic.h"
#include "drivers/pwr.h"
#include "drivers/periph_config.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/reset.h"
#include "drivers/display/display.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
#include "FreeRTOS.h"
#include "task.h"
//! If we don't have a PMIC entering standby is a little more complicated.
//! See platform/tintin/boot/src/standby.c.
//! We set a bootbit and reboot, and then the bootloader is responsible for really winding us down.
//! This is necessary because:
//! - When entering standby, a system reset is the only way to disable the IWDG
//! - When shutting down, it simplifies waiting on the charger to be removed,
//! and allows for us to handle other boot bits (eg. Force PRF) before powering down.
//!
//! @param boot_bit Boot bit to set. It should cause the bootloader to shut down or enter standby.
static NORETURN prv_enter_standby_non_pmic(BootBitValue boot_bit) {
// The I2C bus is not initialized in the bootloader.
// Put the Accelerometer into low power mode before resetting
imu_power_down();
boot_bit_set(boot_bit);
PBL_LOG(LOG_LEVEL_ALWAYS, "Rebooting to enter Standby mode.");
reboot_reason_set_restarted_safely();
system_hard_reset();
}
static NORETURN prv_enter_standby_pmic(void) {
reboot_reason_set_restarted_safely();
#if defined(TARGET_QEMU)
#if MICRO_FAMILY_STM32F7
WTF; // Unsupported
#else
// QEMU does not implement i2c devices, like the PMIC, yet. Let's turn off instead
// by going into standby mode using the power control of the STM32. We can't use
// prv_enter_standby_non_pmic() because PMIC based boards don't support that feature in their
// bootloader.
periph_config_enable(PWR, RCC_APB1Periph_PWR);
pwr_enable_wakeup(true);
PWR_EnterSTANDBYMode();
#endif
#endif
PBL_LOG(LOG_LEVEL_ALWAYS, "Using the PMIC to enter standby mode.");
pmic_power_off();
PBL_CROAK("PMIC didn't shut us down!");
}
NORETURN enter_standby(RebootReasonCode reason) {
PBL_LOG(LOG_LEVEL_ALWAYS, "Preparing to enter standby mode.");
RebootReason reboot_reason = { reason, 0 };
reboot_reason_set(&reboot_reason);
// Wipe display
display_clear();
/* skip BT teardown if BT isn't working */
system_reset_prepare(reason == RebootReasonCode_DialogBootFault);
#if PLATFORM_SILK && RECOVERY_FW
// For Silk PRF & MFG firmwares, fully shutdown the watch using the bootloader.
// Always entering full shutdown in these two situations will guarantee a much
// better shelf-life, and ensure that watches are shipped in full shutdown mode.
//
// Request the bootloader to completely power down as the last thing it does,
// rather than jumping into the fw. The bootloader may spin on the charger
// connection status, as we cannot shutdown while the charger is plugged.
// Luckily, we never try to power down the watch while plugged when in PRF.
prv_enter_standby_non_pmic(BOOT_BIT_SHUTDOWN_REQUESTED);
#elif CAPABILITY_HAS_PMIC
prv_enter_standby_pmic();
#else
// Request the bootloader to enter standby mode immediately after the system is reset.
prv_enter_standby_non_pmic(BOOT_BIT_STANDBY_MODE_REQUESTED);
#endif
}

View File

@@ -0,0 +1,27 @@
/*
* 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.
*/
#pragma once
#include "system/reboot_reason.h"
#include "util/attributes.h"
#if !UNITTEST
NORETURN
#else
void
#endif
enter_standby(RebootReasonCode reason);

215
src/fw/kernel/util/stop.c Normal file
View File

@@ -0,0 +1,215 @@
/*
* 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 "console/dbgserial.h"
#include "console/dbgserial_input.h"
#include "drivers/flash.h"
#include "drivers/periph_config.h"
#include "drivers/rtc.h"
#include "drivers/task_watchdog.h"
#include "os/tick.h"
#include "kernel/util/stop.h"
#include "kernel/util/wfi.h"
#include "mcu/interrupts.h"
#include "services/common/analytics/analytics.h"
#include "system/passert.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
#include <stdbool.h>
#include <inttypes.h>
static int s_num_items_disallowing_stop_mode = 0;
#ifdef PBL_NOSLEEP
static bool s_sleep_mode_allowed = false;
#else
static bool s_sleep_mode_allowed = true;
#endif
typedef struct {
uint32_t active_count;
RtcTicks ticks_when_stop_mode_disabled;
RtcTicks total_ticks_while_disabled;
} InhibitorTickProfile;
// Note: These variables should be protected within a critical section since
// they are read and modified by multiple threads
static InhibitorTickProfile s_inhibitor_profile[InhibitorNumItems];
void enter_stop_mode(void) {
// enable the interrupt on the debug RX line so that we can use the serial
// console even when we are in stop mode.
dbgserial_enable_rx_exti();
flash_power_down_for_stop_mode();
// Turn on the power control peripheral so that we can put the regulator into low-power mode
periph_config_enable(PWR, RCC_APB1Periph_PWR);
if (mcu_state_are_interrupts_enabled()) {
// If INTs aren't disabled here, we would wind up servicing INTs
// immediately after the WFI (while running at the wrong clock speed) which
// can confuse peripherals in subtle ways
WTF;
}
// Enter stop mode.
//PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
// We don't use ^^ the above function because of a silicon bug which
// causes the processor to skip some instructions upon wake from STOP
// in certain sitations. See the STM32F20x and STM32F21x Errata sheet
// section 2.1.3 "Debugging Stop mode with WFE entry", or the erratum
// of the same name in section 2.1.2 of the STM32F42x and STM32F43x
// Errata sheet, for (misleading) details.
// http://www.st.com/web/en/resource/technical/document/errata_sheet/DM00027213.pdf
// http://www.st.com/web/en/resource/technical/document/errata_sheet/DM00068628.pdf
// Configure the PWR peripheral to put us in low-power STOP mode when
// the processor enters deepsleep.
#if defined(MICRO_FAMILY_STM32F7)
uint32_t temp = PWR->CR1;
temp &= ~PWR_CR1_PDDS;
temp |= PWR_CR1_LPDS;
PWR->CR1 = temp;
#else
uint32_t temp = PWR->CR;
temp &= ~PWR_CR_PDDS;
temp |= PWR_CR_LPDS;
#if STM32F412xG
// STM32F412xG suports a new "low-power regulator low voltage in deep sleep" mode.
temp |= PWR_CR_LPLVDS;
#endif
PWR->CR = temp;
#endif
// Configure the processor core to enter deepsleep mode when we
// execute a WFI or WFE instruction.
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
// Go stop now.
__DSB(); // Drain any pending memory writes before entering sleep.
do_wfi(); // Wait for Interrupt (enter sleep mode). Work around F2/F4 errata.
__ISB(); // Let the pipeline catch up (force the WFI to activate before moving on).
// Tell the processor not to emter deepsleep mode for future WFIs.
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
// Stop mode will change our system clock to the HSI. Move it back to the PLL.
// Enable the PLL and wait until it's ready
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {}
// Select PLL as system clock source and wait until it's being used
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while (RCC_GetSYSCLKSource() != 0x08) {}
// No longer need the power control peripheral
periph_config_disable(PWR, RCC_APB1Periph_PWR);
flash_power_up_after_stop_mode();
}
void stop_mode_disable( StopModeInhibitor inhibitor ) {
portENTER_CRITICAL();
++s_num_items_disallowing_stop_mode;
++s_inhibitor_profile[inhibitor].active_count;
// TODO: We should probably check if s_inhibitor_profile.active_count == 1
// before doing this assignment. We don't seem to ever run into this case
// yet (i.e. active_count is never > 1), but when we do, this code would
// report the wrong number of nostop ticks.
s_inhibitor_profile[inhibitor].ticks_when_stop_mode_disabled = rtc_get_ticks();
portEXIT_CRITICAL();
}
void stop_mode_enable( StopModeInhibitor inhibitor ) {
portENTER_CRITICAL();
PBL_ASSERTN(s_num_items_disallowing_stop_mode != 0);
PBL_ASSERTN(s_inhibitor_profile[inhibitor].active_count != 0);
--s_num_items_disallowing_stop_mode;
--s_inhibitor_profile[inhibitor].active_count;
if (s_inhibitor_profile[inhibitor].active_count == 0) {
s_inhibitor_profile[inhibitor].total_ticks_while_disabled += rtc_get_ticks() -
s_inhibitor_profile[inhibitor].ticks_when_stop_mode_disabled;
}
portEXIT_CRITICAL();
}
bool stop_mode_is_allowed(void) {
#if PBL_NOSTOP
return false;
#else
return s_num_items_disallowing_stop_mode == 0;
#endif
}
void sleep_mode_enable(bool enable) {
s_sleep_mode_allowed = enable;
}
bool sleep_mode_is_allowed(void) {
#ifdef PBL_NOSLEEP
return false;
#endif
return s_sleep_mode_allowed;
}
static RtcTicks prv_get_nostop_ticks(StopModeInhibitor inhibitor, RtcTicks now_ticks) {
RtcTicks total_ticks = s_inhibitor_profile[inhibitor].total_ticks_while_disabled;
if (s_inhibitor_profile[inhibitor].active_count != 0) {
total_ticks += (now_ticks - s_inhibitor_profile[inhibitor].ticks_when_stop_mode_disabled);
}
return total_ticks;
}
static void prv_collect(AnalyticsMetric metric, StopModeInhibitor inhibitor, RtcTicks now_ticks) {
// operating on 64 bit values so the load/stores will _not_ be atomic
portENTER_CRITICAL();
RtcTicks ticks = prv_get_nostop_ticks(inhibitor, now_ticks);
s_inhibitor_profile[inhibitor].total_ticks_while_disabled = 0;
portEXIT_CRITICAL();
analytics_set(metric, ticks_to_milliseconds(ticks), AnalyticsClient_System);
}
void analytics_external_collect_stop_inhibitor_stats(RtcTicks now_ticks) {
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_MAIN_TIME, InhibitorMain, now_ticks);
// We don't care about the serial console nostop time, it should always
// be zero on watches in the field anyway. (InhibitorDbgSerial skipped)
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_BUTTON_TIME, InhibitorButton, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_BLUETOOTH_TIME, InhibitorBluetooth, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_DISPLAY_TIME, InhibitorDisplay, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_BACKLIGHT_TIME, InhibitorBacklight, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_COMM_TIME, InhibitorCommMode, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_FLASH_TIME, InhibitorFlash, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_I2C1_TIME, InhibitorI2C1, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_ACCESSORY, InhibitorAccessory, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_MIC, InhibitorMic, now_ticks);
// TODO PBL-37941: Add analytics for InhibitorDMA
}
void command_scheduler_force_active(void) {
sleep_mode_enable(false);
}
void command_scheduler_resume_normal(void) {
sleep_mode_enable(true);
}

76
src/fw/kernel/util/stop.h Normal file
View File

@@ -0,0 +1,76 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
typedef enum {
InhibitorMain,
InhibitorDbgSerial,
InhibitorButton,
InhibitorBluetooth,
InhibitorDisplay,
InhibitorBacklight,
InhibitorCommMode,
InhibitorFlash,
InhibitorI2C1,
InhibitorI2C2,
InhibitorMic,
InhibitorAccessory,
InhibitorVibes,
InhibitorCompositor,
InhibitorI2C3,
InhibitorI2C4,
InhibitorBluetoothWatchdog,
InhibitorNumItems
} StopModeInhibitor;
/** Enter stop mode.
*
* \note Probably no good reason to call this function from most application
* code. Let the FreeRTOS scheduler do its job.
*/
void enter_stop_mode(void);
/** Prevent the scheduler from entering stop mode in idle. Usually called when
* we know that there is some resource or peripheral being used that does not
* require the use of the CPU, but that going into stop mode would interrupt.
* \note Internally this is implemented as a reference counter, so it is
* necessary to balance each call to disallow_stop_mode with a matching call to
* allow_stop_mode.
* CAUTION: This function cannot be called at priorities > Systick
*/
void stop_mode_disable(StopModeInhibitor inhibitor);
/** Allow the scheduler to enter stop mode in idle again.
* CAUTION: This function cannot be called at priorities > Systick
*/
void stop_mode_enable(StopModeInhibitor inhibitor);
//! Check whether we are permitted to go into stop mode
bool stop_mode_is_allowed(void);
//! Enable or disable sleep mode.
//! Note: When sleep mode is disabled so is stop mode. When sleep mode is enabled, stop mode is
//! controlled by stop_mode_is_allowed.
void sleep_mode_enable(bool enable);
//! Check whether we are permitted to go into sleep mode.
bool sleep_mode_is_allowed(void);

View File

@@ -0,0 +1,31 @@
/*
* 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 "task_init.h"
#include "drivers/rng.h"
#include "drivers/rtc.h"
#include <stdlib.h>
void task_init(void) {
uint32_t seed;
if (!rng_rand(&seed)) {
// Fallback, time XOR'd with an approximation of the current stack pointer:
seed = rtc_get_time() ^ (uintptr_t) &seed;
}
srand(seed);
}

View File

@@ -0,0 +1,22 @@
/*
* 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.
*/
#pragma once
//! Performs initialization for the current FreeRTOS task. Currently, all it does is it
//! attempts to seed the current task's REENT random seed with a value from the
//! hardware random number generator.
void task_init(void);

View File

@@ -0,0 +1,76 @@
/*
* 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 "console/prompt.h"
#include "kernel/pbl_malloc.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdlib.h>
#if 0
void command_print_task_list(void) {
#if ( configUSE_TRACE_FACILITY == 1 )
char str_buffer[100];
prompt_send_response(
"name state pri fstk num stk_beg stk_ptr");
int num_tasks = uxTaskGetNumberOfTasks();
TaskStatus_t *task_info = kernel_malloc(num_tasks * sizeof( TaskStatus_t ));
if (!task_info) {
return;
}
num_tasks = uxTaskGetSystemState( task_info, num_tasks, NULL );
// Print info on each task
char status;
for (int i=0; i<num_tasks; i++) {
switch(task_info[i].eCurrentState) {
case eReady:
status = 'R';
break;
case eBlocked:
status = 'B';
break;
case eSuspended:
status = 'S';
break;
case eDeleted:
status = 'D';
break;
default:
status = '?';
break;
}
prompt_send_response_fmt(str_buffer, sizeof(str_buffer), "%-16s %6c %8u %8u %8u %p %p",
task_info[i].pcTaskName,
status,
(unsigned int) task_info[i].uxCurrentPriority,
(unsigned int)(sizeof(StackType_t) * task_info[i].usStackHighWaterMark),
(unsigned int)task_info[i].xTaskNumber,
(void *)task_info[i].pxStack,
(void *)task_info[i].pxTopOfStack);
}
kernel_free(task_info);
#else
prompt_send_response("Not available");
#endif
}
#endif

36
src/fw/kernel/util/wfi.c Normal file
View File

@@ -0,0 +1,36 @@
/*
* 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 "util/attributes.h"
#include "wfi.h"
void NOINLINE NAKED_FUNC do_wfi(void) {
// Work around a very strange bug in the STM32F where, upon waking from
// STOP or SLEEP mode, the processor begins acting strangely depending on the
// contents of the bytes following the "bx lr" instruction.
__asm volatile (
".align 4 \n" // Force 16-byte alignment
"wfi \n" // This instruction cannot be placed at 0xnnnnnnn4
"nop \n"
"bx lr \n"
"nop \n" // Fill the rest of the cache line with NOPs as the bytes
"nop \n" // following the bx affect the processor for some reason.
"nop \n"
"nop \n"
"nop \n"
);
}

19
src/fw/kernel/util/wfi.h Normal file
View File

@@ -0,0 +1,19 @@
/*
* 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.
*/
#pragma once
void do_wfi(void);