diff --git a/machine/api/src/lib.rs b/machine/api/src/lib.rs index 0fbf82f..af0a75a 100644 --- a/machine/api/src/lib.rs +++ b/machine/api/src/lib.rs @@ -23,6 +23,13 @@ pub trait Machinelike { fn monotonic_now() -> u64; fn monotonic_freq() -> u64; + fn rtc_raw() -> u64; + fn set_rtc_raw(time: u64) -> i32; + fn init_rtc() -> i32; + // index 0..32, 31 is used by the RTC + fn rtc_backup_register(index: u8) -> u32; + // index 0..32, 31 is used by the RTC + fn set_rtc_backup_register(index: u8, value: u32); // Returns the frequency of the machine's systick timer in Hz. fn systick_freq() -> u64; diff --git a/machine/cortex-m/src/native.rs b/machine/cortex-m/src/native.rs index bd10c1e..c1f1886 100644 --- a/machine/cortex-m/src/native.rs +++ b/machine/cortex-m/src/native.rs @@ -40,7 +40,10 @@ pub struct ArmMachine; impl hal_api::Machinelike for ArmMachine { fn init() { unsafe { - bindings::init_hal(); + let ret = bindings::init_hal(); + if ret != 0 { + panic!("init_hal failed: {}", ret); + } bindings::init_debug_uart(); bindings::dwt_init(); } @@ -84,6 +87,26 @@ impl hal_api::Machinelike for ArmMachine { unsafe { bindings::monotonic_freq() } } + fn rtc_raw() -> u64 { + unsafe { bindings::rtc_raw() } + } + + fn set_rtc_raw(time: u64) -> i32 { + unsafe { bindings::set_rtc_raw(time) } + } + + fn init_rtc() -> i32 { + unsafe { bindings::init_rtc() } + } + + fn rtc_backup_register(index: u8) -> u32 { + unsafe { bindings::rtc_backup_register(index) } + } + + fn set_rtc_backup_register(index: u8, value: u32) { + unsafe { bindings::set_rtc_backup_register(index, value) } + } + fn systick_freq() -> u64 { unsafe { bindings::systick_freq() } } diff --git a/machine/cortex-m/src/stub.rs b/machine/cortex-m/src/stub.rs index d37b00c..409962e 100644 --- a/machine/cortex-m/src/stub.rs +++ b/machine/cortex-m/src/stub.rs @@ -42,6 +42,25 @@ impl hal_api::Machinelike for StubMachine { 0 } + fn rtc_raw() -> u64 { + 0 + } + + fn set_rtc_raw(_time: u64) -> i32 { + 0 + } + + fn init_rtc() -> i32 { + 0 + } + + fn rtc_backup_register(index: u8) -> u32 { + 0 + } + + fn set_rtc_backup_register(index: u8, value: u32) { + } + fn systick_freq() -> u64 { 0 } diff --git a/machine/cortex-m/st/stm32l4/interface/clock.c b/machine/cortex-m/st/stm32l4/interface/clock.c index a593fa2..ef6f8b2 100644 --- a/machine/cortex-m/st/stm32l4/interface/clock.c +++ b/machine/cortex-m/st/stm32l4/interface/clock.c @@ -1,10 +1,148 @@ #include "lib.h" #include +#include #include +#include "stm32l4xx_hal_rcc.h" +#include "stm32l4xx_hal_rcc_ex.h" +#include static volatile uint64_t monotonic_hi = 0; static volatile uint32_t tick = 0; +#define RTC_BKP_MAGIC 0x4F534952U + +// use msb for the error type +// lower byte(s) contain hal status +enum ErrorTypes : uint64_t { + ERROR_CONTROL_VOLTAGE_SCALING = 0x01U << 56U, + ERROR_RCC_OSC_CONFIG = 0x02U << 56U, + ERROR_RCC_CLOCK_CONFIG = 0x03U << 56U, + ERROR_RTC_INIT_CLOCK_SOURCE = 0x04U << 56U, + ERROR_RTC_INIT = 0x05U << 56U, + ERROR_RTC_GET_TIME = 0x06U << 56U, + ERROR_RTC_GET_DATE = 0x07U << 56U, + ERROR_RTC_SET_TIME = 0x08U << 56U, + ERROR_RTC_SET_DATE = 0x09U << 56U +}; + +static RTC_HandleTypeDef rtc_handle; + +/** +* Try to use LSE, fall back to LSI and enable CSS if both are available. +* @retval HAL_StatusTypeDef codes: +* bit 0-1: selecting LSE clock source +* bit 2-3: selecting LSI clock source +* bit 4-5: HAL_TIMEOUT from waiting for LSI ready + */ +static int init_rtc_clock_source(void) +{ + HAL_PWR_EnableBkUpAccess(); + int error = 0; + + __HAL_RCC_LSI_ENABLE(); + + __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_HIGH); + __HAL_RCC_LSE_CONFIG(RCC_LSE_ON); + + RCC_PeriphCLKInitTypeDef periph = {0}; + periph.PeriphClockSelection = RCC_PERIPHCLK_RTC; + + periph.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; + HAL_StatusTypeDef status = HAL_RCCEx_PeriphCLKConfig(&periph); + + if (status != HAL_OK) { + error = status; + // fallback to LSI + periph.RTCClockSelection = RCC_RTCCLKSOURCE_LSI; + status = HAL_RCCEx_PeriphCLKConfig(&periph); + // if LSI selection also fails, return both errors + if (status != HAL_OK) { + error |= status << 2; + return error; + } + } + + // ensure LSI is ready + uint32_t tickstart = HAL_GetTick(); + while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) == RESET) { + if ((HAL_GetTick() - tickstart) > RCC_LSE_TIMEOUT_VALUE) { + error |= HAL_TIMEOUT << 4; + break; + } + } + + __HAL_RCC_RTC_ENABLE(); + + // clock security system requires both LSE and LSI to be enabled. + if (!error) { + HAL_RCCEx_EnableLSECSS(); + __HAL_RCC_ENABLE_IT(RCC_IT_LSECSS); + } + HAL_PWR_DisableBkUpAccess(); + + return error; +} + +void handle_css_lse_interrupt() +{ + HAL_PWR_EnableBkUpAccess(); + __HAL_RCC_CLEAR_IT(RCC_IT_LSECSS); + + // The software MUST then disable the LSECSSON bit + HAL_RCCEx_DisableLSECSS(); + // stop the defective 32 kHz oscillator (disabling LSEON) + __HAL_RCC_LSE_CONFIG(RCC_LSE_OFF); + + // and change the RTC clock source (no clock or LSI or HSE, with RTCSEL) + RCC_PeriphCLKInitTypeDef periph = {0}; + periph.PeriphClockSelection = RCC_PERIPHCLK_RTC; + periph.RTCClockSelection = RCC_RTCCLKSOURCE_LSI; + HAL_StatusTypeDef status = HAL_RCCEx_PeriphCLKConfig(&periph); + if (status != HAL_OK) { + // internal clock failed, try again later? + __HAL_RCC_RTC_DISABLE(); + } + HAL_PWR_DisableBkUpAccess(); +} + +uint64_t set_rtc_raw(uint64_t raw); +uint64_t init_rtc(void) +{ + __HAL_RCC_PWR_CLK_ENABLE(); + + int ret = init_rtc_clock_source(); + if (ret) { + return ERROR_RTC_INIT_CLOCK_SOURCE | ret; + } + + rtc_handle.Instance = RTC; + rtc_handle.Init.HourFormat = RTC_HOURFORMAT_24; + rtc_handle.Init.AsynchPrediv = 0x7FU; + rtc_handle.Init.SynchPrediv = 0x00FFU; + rtc_handle.Init.OutPut = RTC_OUTPUT_DISABLE; + rtc_handle.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE; + rtc_handle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; + rtc_handle.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; + + ret = HAL_RTC_Init(&rtc_handle); + if (ret != HAL_OK) { + return ERROR_RTC_INIT | ret; + } + + if (HAL_RTCEx_BKUPRead(&rtc_handle, RTC_BKP_DR31) != RTC_BKP_MAGIC) { + // Sat 01.01.2000 + unsigned long long time = ((uint64_t)0) | + ((uint64_t)0 << 8U) | + ((uint64_t)0 << 16U) | + ((uint64_t)RTC_WEEKDAY_SATURDAY << 32U) | + ((uint64_t)RTC_MONTH_JANUARY << 40U) | + ((uint64_t)1 << 48U) | + ((uint64_t)0 << 56U); + return set_rtc_raw(time); + } + return 0; +} + static void init_monotonic_timer(void) { const uint32_t target_hz = 1000000U; @@ -55,7 +193,7 @@ void tim2_hndlr(void) } } -void init_clock_cfg(void) +uint64_t init_clock_cfg(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; @@ -63,8 +201,9 @@ void init_clock_cfg(void) /* 80 MHz on STM32L4+ => Range 1 normal mode, not boost */ __HAL_RCC_PWR_CLK_ENABLE(); - if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK) { - while (1) {} + int ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); + if (ret != HAL_OK) { + return ERROR_CONTROL_VOLTAGE_SCALING | ret; } /* HSI16 -> PLL -> 80 MHz SYSCLK */ @@ -80,8 +219,9 @@ void init_clock_cfg(void) RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; // arbitrary unless you use PLLP RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // arbitrary unless you use PLLQ - if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { - while (1) {} + ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); + if (ret != HAL_OK) { + return ERROR_RCC_OSC_CONFIG | ret; } RCC_ClkInitStruct.ClockType = @@ -95,12 +235,14 @@ void init_clock_cfg(void) RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; - if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) { - while (1) {} + ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); + if (ret != HAL_OK) { + return ERROR_RCC_CLOCK_CONFIG | ret; } SystemCoreClockUpdate(); init_monotonic_timer(); + return 0; } unsigned long long monotonic_now(void) @@ -164,3 +306,69 @@ void do_tick(void) { tick++; } + + +uint32_t get_rtc_backup_register(uint32_t index) +{ + return HAL_RTCEx_BKUPRead(&rtc_handle, RTC_BKP_DR0 + index); +} + +void set_rtc_backup_register(uint32_t index, uint32_t value) +{ + HAL_PWR_EnableBkUpAccess(); + HAL_RTCEx_BKUPWrite(&rtc_handle, RTC_BKP_DR0 + index, value); + HAL_PWR_DisableBkUpAccess(); +} + +uint64_t rtc_raw(void) +{ + RTC_TimeTypeDef time = {0}; + RTC_DateTypeDef date = {0}; + + int ret = HAL_RTC_GetTime(&rtc_handle, &time, RTC_FORMAT_BCD); + if (ret != HAL_OK) { + return ERROR_RTC_GET_TIME | ret; + } + + ret = HAL_RTC_GetDate(&rtc_handle, &date, RTC_FORMAT_BCD); + if (ret != HAL_OK) { + return ERROR_RTC_GET_DATE | ret; + } + + return ((uint64_t)time.Hours) | + ((uint64_t)time.Minutes << 8U) | + ((uint64_t)time.Seconds << 16U) | + ((uint64_t)date.WeekDay << 24U) | + ((uint64_t)date.Month << 32U) | + ((uint64_t)date.Date << 40U) | + ((uint64_t)date.Year << 48U); +} + +uint64_t set_rtc_raw(uint64_t raw) +{ + RTC_TimeTypeDef rtc_time = {0}; + RTC_DateTypeDef rtc_date = {0}; + + rtc_time.Hours = (uint8_t)(raw & 0xFFU); + rtc_time.Minutes = (uint8_t)((raw >> 8U) & 0xFFU); + rtc_time.Seconds = (uint8_t)((raw >> 16U) & 0xFFU); + rtc_time.TimeFormat = RTC_HOURFORMAT_24; + + rtc_date.WeekDay = (uint8_t)((raw >> 32U) & 0xFFU); + rtc_date.Month = (uint8_t)((raw >> 40U) & 0xFFU); + rtc_date.Date = (uint8_t)((raw >> 48U) & 0xFFU); + rtc_date.Year = (uint8_t)((raw >> 56U) & 0xFFU); + + int ret = HAL_RTC_SetTime(&rtc_handle, &rtc_time, RTC_FORMAT_BCD); + if (ret != HAL_OK) { + return ERROR_RTC_SET_TIME | ret; + } + + ret = HAL_RTC_SetDate(&rtc_handle, &rtc_date, RTC_FORMAT_BCD); + if (ret != HAL_OK) { + return ERROR_RTC_SET_DATE | ret; + } + + HAL_RTCEx_BKUPWrite(&rtc_handle, RTC_BKP_DR31, RTC_BKP_MAGIC); + return 0; +} \ No newline at end of file diff --git a/machine/cortex-m/st/stm32l4/interface/export.h b/machine/cortex-m/st/stm32l4/interface/export.h index 552b463..81d2e3d 100644 --- a/machine/cortex-m/st/stm32l4/interface/export.h +++ b/machine/cortex-m/st/stm32l4/interface/export.h @@ -4,7 +4,7 @@ // lib.c unsigned long long systick_freq(void); -void init_hal(void); +int init_hal(void); __attribute__((noreturn)) void system_reset(void); // uart.c @@ -229,3 +229,9 @@ unsigned long long monotonic_now(void); unsigned long long monotonic_freq(void); void delay_us(uint32_t delay_us); void do_tick(void); +int init_rtc(void); +unsigned long long rtc_raw(void); +int set_rtc_raw(unsigned long long time); + +unsigned long rtc_backup_register(unsigned char index); +void set_rtc_backup_register(unsigned char index, unsigned long value); diff --git a/machine/cortex-m/st/stm32l4/interface/lib.c b/machine/cortex-m/st/stm32l4/interface/lib.c index 058c079..7f3ffed 100644 --- a/machine/cortex-m/st/stm32l4/interface/lib.c +++ b/machine/cortex-m/st/stm32l4/interface/lib.c @@ -14,16 +14,18 @@ static void enable_faults(void) { __DSB(); } -static void init_systick(void) { - HAL_SYSTICK_Config(SystemCoreClock / 1000); // Configure SysTick to interrupt every 1 ms +static int init_systick(void) { + if (HAL_SYSTICK_Config(SystemCoreClock / 1000)) // Configure SysTick to interrupt every 1 ms + return -1; HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); + return 0; } unsigned long long systick_freq(void) { return 1000; } -void init_hal(void) { +int init_hal(void) { #if OSIRIS_TUNING_ENABLEFPU init_fpu(); #endif @@ -31,8 +33,17 @@ void init_hal(void) { enable_faults(); - init_clock_cfg(); - init_systick(); + int ret = init_clock_cfg(); + if (ret != 0) { + return ret; + } + + ret = init_systick(); + if (ret != 0) { + return ret; + } + + return 0; } void HAL_MspInit(void) { diff --git a/machine/cortex-m/st/stm32l4/interface/lib.h b/machine/cortex-m/st/stm32l4/interface/lib.h index 3a4c6f0..9ddd3d0 100644 --- a/machine/cortex-m/st/stm32l4/interface/lib.h +++ b/machine/cortex-m/st/stm32l4/interface/lib.h @@ -2,7 +2,7 @@ #include -void init_clock_cfg(void); +int init_clock_cfg(void); unsigned long long monotonic_now(void); unsigned long long monotonic_freq(void); void delay_us(uint32_t delay_us); diff --git a/src/drivers.rs b/src/drivers.rs index fe3e561..d8b3a65 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -1,8 +1,10 @@ pub mod can; +pub mod clock; pub mod i2c; pub mod spi; pub fn init() { + clock::init(); i2c::init(); spi::init(); can::init(); diff --git a/src/drivers/clock.rs b/src/drivers/clock.rs new file mode 100644 index 0000000..3abd348 --- /dev/null +++ b/src/drivers/clock.rs @@ -0,0 +1,143 @@ +use hal_api::PosixError; + +use crate::hal; +use crate::hal::Machinelike; + +/// The monotonic clock is brought up by [hal::Machine::init()] +pub fn init() { + match hal::Machine::init_rtc() { + 0 => (), + -4 => { + kprintln!("failed to initialize RTC: init clock source"); + } + -5 => { + kprintln!("failed to initialize RTC: init RTC"); + } + ret => { + kprintln!("failed to initialize RTC: {ret}"); + } + } +} + +pub fn rtc_backup_register(index: u8) -> u32 { + assert!(index < 32, "RTC backup register index out of bounds"); + assert!(index != 31, "RTC uses this register for restart continuity"); + hal::Machine::rtc_backup_register(index) +} + +pub fn set_rtc_backup_register(index: u8, value: u32) { + assert!(index < 32, "RTC backup register index out of bounds"); + assert!(index != 31, "RTC uses this register for restart continuity"); + hal::Machine::set_rtc_backup_register(index, value) +} + +pub fn walltime() -> Result { + let raw = hal::Machine::rtc_raw(); + if raw == -1i64 as u64 { + kprintln!("failed to read RTC time"); + return Err(PosixError::EIO); + } + if raw == -2i64 as u64 { + kprintln!("failed to read RTC date"); + return Err(PosixError::EIO); + } + Ok(rtc_raw_to_unix(raw)) +} + +pub fn set_walltime(time: u64) -> Result<(), PosixError> { + let raw = unix_to_rtc_raw(time); + match hal::Machine::set_rtc_raw(raw) { + 0 => Ok(()), + -1 => { + kprintln!("failed to set RTC time"); + return Err(PosixError::EINVAL); + } + -2 => { + kprintln!("failed to set RTC date"); + return Err(PosixError::EINVAL); + } + _ => { + kprintln!("unknown error setting RTC time"); + return Err(PosixError::Unknown); + } + } +} + +const fn bcd_to_bin(value: u8) -> u8 { + ((value >> 4) * 10) + (value & 0x0f) +} + +const fn bin_to_bcd(value: u8) -> u8 { + ((value / 10) << 4) | (value % 10) +} + +const fn is_leap_year(year: u32) -> bool { + year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) +} + +const fn days_in_month(year: u32, month: u32) -> u32 { + match month { + 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, + 4 | 6 | 9 | 11 => 30, + 2 if is_leap_year(year) => 29, + 2 => 28, + _ => 0, + } +} + +fn days_since_unix_epoch(year: u32, month: u32, day: u32) -> u64 { + let mut days = 0u64; + for y in 1970..year { + days += if is_leap_year(y) { 366 } else { 365 }; + } + for m in 1..month { + days += days_in_month(year, m) as u64; + } + days + u64::from(day.saturating_sub(1)) +} + +fn rtc_raw_to_unix(raw: u64) -> u64 { + let hours = bcd_to_bin((raw & 0xff) as u8) as u64; + let minutes = bcd_to_bin(((raw >> 8) & 0xff) as u8) as u64; + let seconds = bcd_to_bin(((raw >> 16) & 0xff) as u8) as u64; + let year = 2000 + u32::from(bcd_to_bin(((raw >> 56) & 0xff) as u8)); + let month = u32::from(bcd_to_bin(((raw >> 40) & 0xff) as u8)); + let day = u32::from(bcd_to_bin(((raw >> 48) & 0xff) as u8)); + + days_since_unix_epoch(year, month, day) * 86_400 + hours * 3_600 + minutes * 60 + seconds +} + +fn unix_to_rtc_raw(unix: u64) -> u64 { + let epoch_2000 = 946_684_800u64; + let mut seconds = unix.saturating_sub(epoch_2000); + let mut days = seconds / 86_400; + seconds %= 86_400; + + let mut year = 2000u32; + while days >= if is_leap_year(year) { 366 } else { 365 } { + days -= if is_leap_year(year) { 366 } else { 365 }; + year += 1; + } + + let mut month = 1u32; + while days >= u64::from(days_in_month(year, month)) { + days -= u64::from(days_in_month(year, month)); + month += 1; + } + + let day = (days + 1) as u32; + let weekday = (((days_since_unix_epoch(year, month, day) + 4) % 7) + 1) as u8; + + let hours = (seconds / 3_600) as u8; + seconds %= 3_600; + let minutes = (seconds / 60) as u8; + let seconds = (seconds % 60) as u8; + + (u64::from(bin_to_bcd(hours))) + | (u64::from(bin_to_bcd(minutes)) << 8) + | (u64::from(bin_to_bcd(seconds)) << 16) + | (u64::from(weekday) << 32) + | (u64::from(bin_to_bcd(month as u8)) << 40) + | (u64::from(bin_to_bcd(day as u8)) << 48) + | (u64::from(bin_to_bcd((year - 2000) as u8)) << 56) +} diff --git a/src/lib.rs b/src/lib.rs index e8088e7..bd7dd69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,10 @@ pub unsafe extern "C" fn kernel_init() -> ! { kprint!("Scheduler initialized.\n"); idle::init(); - kprint!("Idle thread initialized.\n"); + kprintln!("Idle thread initialized.\n"); + + time::init(); + kprintln!("Time thread initialized.\n"); let (cyc, _ns) = hal::Machine::bench_end(); kprint!("Kernel init took {} cycles.\n", cyc); diff --git a/src/time.rs b/src/time.rs index e5379b2..d3d58fa 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,9 +1,45 @@ +use crate::drivers::clock; use crate::hal::{self, Machinelike}; use crate::{sched, sync}; static TICKS: sync::atomic::AtomicU64 = sync::atomic::AtomicU64::new(0); +extern "C" fn update_time(_ctx: *mut core::ffi::c_void) { + let interval: u64 = 100_000; // ~100 seconds in ticks + kprintln!( + "Time thread started with tick interval {} at {:?}", + interval, + clock::walltime() + ); + loop { + let tick = tick(); + sched::with(|sched| { + let _ = sched.sleep_until(tick + interval, tick); + }); + kprintln!("time is now {:?}", clock::walltime()); + } +} + +pub fn init() { + let attrs = sched::thread::Attributes { + entry: update_time, + ctx: core::ptr::null_mut(), + fin: None, + attrs: None, + }; + + sched::with(|sched| { + if let Ok(uid) = sched.create_thread(Some(sched::task::KERNEL_TASK), &attrs) { + if sched.enqueue(tick(), uid).is_err() { + panic!("failed to enqueue time thread."); + } + } else { + panic!("failed to create time task."); + } + }) +} + pub fn tick() -> u64 { TICKS.load(sync::atomic::Ordering::Acquire) } diff --git a/src/uapi/time.rs b/src/uapi/time.rs index c05f72f..c1b39dd 100644 --- a/src/uapi/time.rs +++ b/src/uapi/time.rs @@ -1,4 +1,6 @@ -use crate::time; +use hal_api::PosixError; + +use crate::{drivers::clock, time}; pub fn mono_now() -> u64 { time::mono_now() @@ -11,3 +13,11 @@ pub fn mono_freq() -> u64 { pub fn tick() -> u64 { time::tick() } + +pub fn walltime() -> Result { + clock::walltime() +} + +pub fn set_walltime(time: u64) -> Result<(), PosixError> { + clock::set_walltime(time) +}