/* Copyright (C) 2018 RDA Technologies Limited and/or its affiliates("RDA").
 * All rights reserved.
 *
 * This software is supplied "AS IS" without any warranties.
 * RDA assumes no responsibility or liability for the use of the software,
 * conveys no license or title under any patent, copyright, or mask work
 * right to the product. RDA reserves the right to make changes in the
 * software without notification.  RDA also make no representation or
 * warranty that such application will be suitable for the specified use
 * without further testing or modification.
 */

#ifndef _OSI_API_H_
#define _OSI_API_H_

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifndef _MSC_VER
#include "kernel_config.h"
#endif
#include "osi_compiler.h"
#include "osi_vsmap.h"
#include "osi_clock.h"

#include "quec_proj_config.h"

#ifdef __cplusplus
extern "C" {
#endif

#define OSI_WAIT_FOREVER (-1U)
#define OSI_DELAY_MAX (-1U)

/**
 * Special value to indicate timer callback will be invoked in
 * timer ISR.
 */
#define OSI_TIMER_IN_ISR ((osiThread_t *)NULL)

/**
 * Special value to indicate timer callback will be invoked in
 * timer service thread.
 */
#define OSI_TIMER_IN_SERVICE ((osiThread_t *)0xffffffff)

/**
 * reserved event id to indicate nop
 */
#define OSI_EVENT_ID_NONE (0)

/**
 * reserved event id to indicate quit event loop
 */
#define OSI_EVENT_ID_QUIT (8)

/**
 * elapsed timer for counting elapsed time
 */
typedef uint32_t osiElapsedTimer_t;

/**
 * opaque data structure for timer
 */
typedef struct osiTimer osiTimer_t;

/**
 * opaque data structure for timer pool
 */
typedef struct osiTimerPool osiTimerPool_t;

/**
 * opaque data structure for thread
 */
typedef struct osiThread osiThread_t;

/**
 * opaque data structure for message queue
 */
typedef struct osiMessageQueue osiMessageQueue_t;

/**
 * opaque data structure for event queue
 *
 * Event queue is just a message queue, and the message is \p osiEvent_t
 * (event itself rather than pointer).
 */
typedef struct osiEventQueue osiEventQueue_t;

/**
 * opaque data structure for mutex
 */
typedef struct osiMutex osiMutex_t;

/**
 * opaque data structure for semaphore
 */
typedef struct osiSemaphore osiSemaphore_t;

/**
 * opaque data structure for work
 */
typedef struct osiWork osiWork_t;

/**
 * opaque data structure for work queue
 */
typedef struct osiWorkQueue osiWorkQueue_t;

/**
 * opaque data structure for thread notify
 */
typedef struct osiNotify osiNotify_t;

/**
 * function type of callback
 */
typedef void (*osiCallback_t)(void *ctx);

/**
 * function type of thread entry
 */
typedef void (*osiThreadEntry_t)(void *argument);

/**
 * function type of interrupt handler
 */
typedef void (*osiIrqHandler_t)(void *ctx);

/**
 * event, with ID and 3 parameters
 */
typedef struct osiEvent
{
    uint32_t id;     ///< event identifier
    uint32_t param1; ///< 1st parameter
    uint32_t param2; ///< 2nd parameter
    uint32_t param3; ///< 3rd parameter
} osiEvent_t;

/**
 * thread priority
 *
 * The definition is independent of implementation. Though some
 * implementation will use larger value for higher priority and
 * others will use smaller value for highe priority, this enum will
 * use larger value for higher priority.
 *
 * \p OSI_PRIORITY_IDLE and \p OSI_PRIORITY_HISR are reserved, can't
 * be used.
 *
 * The definition is the same as CMSIS-RTOS.
 */
typedef enum
{
    OSI_PRIORITY_IDLE = 1, // reserved
    OSI_PRIORITY_LOW = 8,
    OSI_PRIORITY_BELOW_NORMAL = 16,
#ifdef CONFIG_QUEC_PROJECT_FEATURE
	OSI_PRIORITY_NORMAL_LOWER = 23,
#endif	
    OSI_PRIORITY_NORMAL = 24,
    OSI_PRIORITY_ABOVE_NORMAL = 32,
    OSI_PRIORITY_HIGH = 40,
    OSI_PRIORITY_REALTIME = 48,
    OSI_PRIORITY_HISR = 56, // reserved
} osiThreadPriority_t;

/**
 * suspend mode
 *
 * System behavior of suspend modes will be diffrent among underlay
 * platform. Driver shall take care the difference, and most likely
 * application won't take care it.
 */
typedef enum osiSuspendMode
{
    OSI_SUSPEND_PM0, ///< light sleep
    OSI_SUSPEND_PM1, ///< 1st level suspend mode
    OSI_SUSPEND_PM2  ///< 2nd level suspend mode
} osiSuspendMode_t;

/**
 * resume wakeup source
 *
 * Resume wakeup source depends on platform. They should be defined in
 * \p hal_chip.h. One bit indicates one source, and multiple sources are
 * possible. \p OSI_RESUME_ABORT is reserved to indicate suspend is
 * aborted.
 */
typedef enum osiResumeSource
{
    OSI_RESUME_ABORT = (1 << 31), ///< resume by suspend aborted
} osiResumeSource_t;

/**
 * \brief boot cause
 *
 * This list is for cold boot cause. Though it is rare, it is possible
 * there exist multiple boot causes simultanuously.
 *
 * Usually boot cause is determined from hardware status registers.
 */
typedef enum osiBootCause
{
    OSI_BOOTCAUSE_UNKNOWN = 0,            ///< placeholder for unknown reason
    OSI_BOOTCAUSE_PWRKEY = (1 << 0),      ///< boot by power key
    OSI_BOOTCAUSE_PIN_RESET = (1 << 1),   ///< boot by pin reset
    OSI_BOOTCAUSE_ALARM = (1 << 2),       ///< boot by alarm
    OSI_BOOTCAUSE_CHARGE = (1 << 3),      ///< boot by charge in
    OSI_BOOTCAUSE_WDG = (1 << 4),         ///< boot by watchdog
    OSI_BOOTCAUSE_PIN_WAKEUP = (1 << 5),  ///< boot by gpio wakeup
    OSI_BOOTCAUSE_SMPL_WAKEUP = (1 << 6), ///< boot from SMPL
    OSI_BOOTCAUSE_GPT_WAKEUP = (1 << 7),  ///< boot from GPT
    OSI_BOOTCAUSE_WDG_IN_PSM = (1 << 8),  ///< boot by watchdog rst
    OSI_BOOTCAUSE_PANIC = (1 << 9),       ///< boot by panic reset
    OSI_BOOTCAUSE_PM2BOOT = (1 << 10),    ///< boot from PM2 cold boot
} osiBootCause_t;

/**
 * boot mode
 *
 * Besides normal boot, there are several other boot modes. For each
 * platform, not all boot modes are supported.
 *
 * Usually, boot mode can be determined by hardware (for example, some
 * GPIO) or software (for example, by flags written at \p osiShutdown).
 */
typedef enum osiBootMode
{
    OSI_BOOTMODE_NORMAL = 0,           ///< normal boot
    OSI_BOOTMODE_DOWNLOAD = 0x444e,    ///< 'DN' boot to download mode
    OSI_BOOTMODE_CALIB = 0x434c,       ///< 'CL' boot to calibration mode
    OSI_BOOTMODE_CALIB_POST = 0x4350,  ///< 'CP' boot to calibration post mode
    OSI_BOOTMODE_NB_CALIB = 0x4e43,    ///< 'NC' boot to NB calibration mode
    OSI_BOOTMODE_BBAT = 0x4241,        ///< 'BA' boot to BBAT mode
    OSI_BOOTMODE_UPGRADE = 0x4654,     ///< 'FT' boot to bootloader upgrade
    OSI_BOOTMODE_PSM_RESTORE = 0x5053, ///< 'PS' boot to PSM restore
    OSI_SHUTDOWN_OPENCPU_PM2_RESTORE = 0x6045, ///< 'OPM' boot to opencpu PSM restore
    OSI_BOOTMODE_OPENCPU_PSM_RESTORE = 0x6056, ///< 'OPS' boot to opencpu PSM restore
} osiBootMode_t;

/**
 * shudown mode
 *
 * For each platform, not all shutdown modes are supported.
 *
 * \p OSI_SHUTDOWN_PANIC is a special case. Though it is a *shutdown mode*,
 * it will appear as \p OSI_BOOTCAUSE_PANIC after reset.
 *
 * \p OSI_SHUTDOWN_PSM_PM2BOOT is a special case. The bootmode will be
 * \p OSI_BOOTMODE_PSM_RESTORE and there is cause \p OSI_BOOTCAUSE_PM2BOOT.
 */
typedef enum osiShutdownMode
{
    OSI_SHUTDOWN_RESET = 0,               ///< normal reset
    OSI_SHUTDOWN_FORCE_DOWNLOAD = 0x5244, ///< 'RD' reset to force download mode
    OSI_SHUTDOWN_DOWNLOAD = 0x444e,       ///< 'DN' reset to download mode
    OSI_SHUTDOWN_BL_DOWNLOAD = 0x4244,    ///< 'BD' reset to bootloader download mode
    OSI_SHUTDOWN_CALIB_MODE = 0x434c,     ///< 'CL' reset to calibration mode
    OSI_SHUTDOWN_NB_CALIB_MODE = 0x4e43,  ///< 'NC' reset to NB calibration mode
    OSI_SHUTDOWN_BBAT_MODE = 0x4241,      ///< 'BA' reset to BBAT mode
    OSI_SHUTDOWN_UPGRADE = 0x4654,        ///< 'FT' reset to upgrade mode
    OSI_SHUTDOWN_POWER_OFF = 0x4f46,      ///< 'OF' power off
    OSI_SHUTDOWN_PSM_SLEEP = 0x5053,      ///< 'PS' power saving mode
    OSI_SHUTDOWN_PSM_PM2BOOT = 0x5042,    ///< 'PB' power saving mode with PM2
    OSI_SHUTDOWN_PANIC = 0x504e,          ///< 'PN' panic reset
    OSI_SHUTDOWN_OPENCPU_PM2_SLEEP = 0x6045, ///< 'OPM' opencpu power mode 2
    OSI_SHUTDOWN_OPENCPU_PSM_SLEEP = 0x6056, ///< 'OPS' opencpu power saving mode
    OSI_SHUTDOWN_OPENCPU_SWITCH = 0x5357,    ///< 'SW' opencpu switch system
    OSI_SHUTDOWN_CANCEL = 0xffff,         ///< special value for shutdown callback
} osiShutdownMode_t;

/**
 * PSM data owner
 */
typedef enum osiPsmDataOwner
{
    OSI_PSMDATA_OWNER_KERNEL,     ///< kernel
    OSI_PSMDATA_OWNER_STACK,      ///< stack
    OSI_PSMDATA_OWNER_AT,         ///< AT engine
    OSI_PSMDATA_OWNER_CFW,        ///< CFW
    OSI_PSMDATA_OWNER_NBTIMER,    ///< NBIOT timers
    OSI_PSMDATA_OWNER_NBSDB,      ///< NBIOT sdb
    OSI_PSMDATA_OWNER_NBLPS,      ///< NBIOT LPS
    OSI_PSMDATA_OWNER_LWIP,       ///< LWIP
    OSI_PSMDATA_OWNER_USER = 100, ///< start owner for user application
} osiPsmDataOwner_t;

/**
 * shuwdown callback function type
 *
 * Before shutdown, the registered callbacks will be invokes. The callbacks
 * are executed in system high priority work queue, and with interrupt
 * disabled. So, the callbacks shouldn't rely on system high priority work
 * queue and interrupts. However, thread schedule is still working and
 * multi-thread can work still.
 */
typedef void (*osiShutdownCallback_t)(void *ctx, osiShutdownMode_t mode);

/**
 * invoke global constructors
 *
 * Global constructors are not called during boot. Rather, they will be called
 * in \p osiInvokeGlobalCtors, and it shall be called in \p osiAppStart.
 *
 * At \p osiAppStart, RTOS is ready. So, it is permitted to call more OSI
 * APIs at global constructors.
 *
 * Though global constructors are supported. It is not encouraged to use this
 * feature. Only use this feature when you really know what you are doing.
 */
void osiInvokeGlobalCtors(void);

/**
 * kernel start
 *
 * Start the kernel. This API will create system threads (at least,
 * idle thread) and start thread scheduler. So, it won't return.
 *
 * Before \p osiKernelStart is called, kernel data structure may be
 * uninitialized, and many osi APIs can be called. Including
 *
 * - timer
 * - IRQ
 * - power management
 */
OSI_NO_RETURN void osiKernelStart(void);

/**
 * suspend thread scheduler
 *
 * After scheduler is suspended, there are no thread context switch.
 * However, interrupt handlers will be executed.
 *
 * The meaning of return flag depends on underlay RTOS. Don't assume the
 * meaning of the return value.
 *
 * \return  scheduler suspend flag.
 */
uint32_t osiSchedulerSuspend(void);

/**
 * resume thread scheduler
 *
 * Scheduler suspend and resume is not *recursive*. That is, after
 * \p osiSchedulerResume is called, scheduler is resumed no matter
 * how many times \p osiSchedulerSuspend are called.
 *
 * \param [in] flag     scheduler suspend flag returned by the latest
 *                      \p osiSchedulerSuspend
 */
void osiSchedulerResume(uint32_t flag);

/**
 * \brief enter critical section
 *
 * The underlay RTOS may have different implementation for critical
 * section. It may manipulate CPU IRQ enable bit(s), or manipulate
 * IRQ mask.
 *
 * This can be called in ISR.
 *
 * \return  critical section flags
 */
uint32_t osiEnterCritical(void);

/**
 * \brief exit critical section
 *
 * Critical section flags is implementation depend. It should be the value
 * returned by \p osiEnterCritical, and don't change the value manually.
 *
 * Critical section is *recursive*. That is, after \p osiExitCritical is
 * called, it doesn't mean system will enter *unprotected* state. Rather,
 * it will return to state before last call of \p osiEnterCritical.
 * For example:
 *
 * \code{.cpp}
 * uint32_t critical1 = osiEnterCritical();
 * uint32_t critical2 = osiEnterCritical();
 * // ...
 * osiExitCritical(critical2);
 * osiExitCritical(critical1);
 * \endcode
 *
 * After the first call of \p osiExitCritical, system is in *protected*
 * state still.
 *
 * In recursive, the exit order must be the reverse order of enter. For
 * example, the following codes are wrong:
 *
 * \code{.cpp}
 * uint32_t critical1 = osiEnterCritical();
 * uint32_t critical2 = osiEnterCritical();
 * // ...
 * osiExitCritical(critical1);
 * osiExitCritical(critical2);
 * \endcode
 *
 * This can be called in ISR.
 *
 * \param critical  critical section flags
 */
void osiExitCritical(uint32_t critical);

/**
 * \brief get IRQ flags and disable IRQ
 *
 * This will always manipulate CPU IRQ enable bit(s).
 *
 * After this call, \p osiEnterCritical can't be called. When
 * \p osiEnterCritical manipulates IRQ mask, it is very possible that
 * it will change CPU IRQ enable bit(s) without protection.
 *
 * In most cases, \p osiIrqSave and \p osiIrqRestore shouldn't be
 * used, unless you really know what you are doing.
 *
 * \return  IRQ flags before disable IRQ
 */
uint32_t osiIrqSave(void);

/**
 * \brief restore IRQ flags
 *
 * IRQ flags is arch and implementation depend. It should be the value
 * returned by \p osiIrqSave, and don't change the value manually.
 *
 * \param flags IRQ flags
 */
void osiIrqRestore(uint32_t flags);

/**
 * set interrupt handler
 *
 * When interrupt arrived, the registered handler will be called with
 * the registered context pointer.
 *
 * For each interrupt, only one handler can be registered. When a handler
 * is already registered for an interrupt, \p osiIrqSetHandler will
 * replace the old one.
 *
 * \p irqn depends on interrupt controller in system. When GIC is used,
 * it is the number in GIC.
 *
 * \param irqn      IRQ number
 * \param handler   IRQ handler
 * \param ctx       IRQ handler context pointer
 * \return
 *      - true on success
 *      - false on invalid parameters
 */
bool osiIrqSetHandler(uint32_t irqn, osiIrqHandler_t handler, void *ctx);

/**
 * enable interrupt
 *
 * \param irqn      IRQ number
 * \return
 *      - true on success
 *      - false on invalid parameters
 */
bool osiIrqEnable(uint32_t irqn);

/**
 * disable interrupt
 *
 * \param irqn      IRQ number
 * \return
 *      - true on success
 *      - false on invalid parameters
 */
bool osiIrqDisable(uint32_t irqn);

/**
 * whether interrupt is enabled
 *
 * \param irqn      IRQ number
 * \return
 *      - true if interrupt is enabled
 *      - false if interrupt is disabled
 */
bool osiIrqEnabled(uint32_t irqn);

/**
 * set interrupt priority
 *
 * \p priority depends on interrupt controller used on system. When GIC
 * is used, \p priority should follow GIC requirement and meaning.
 *
 * \param irqn      IRQ number
 * \param priority  IRQ priority
 * \return
 *      - true on success
 *      - false on invalid parameters
 */
bool osiIrqSetPriority(uint32_t irqn, uint32_t priority);

/**
 * get interrupt priority
 *
 * When \p irqn is invalid, 0x80000000U will be returned.
 *
 * \param irqn      IRQ number
 * \return      IRQ priority
 */
uint32_t osiIrqGetPriority(uint32_t irqn);

/**
 * set interrupt group
 *
 * This is only valid when GIC is used.
 *
 * \param irqn      IRQ number
 * \param group     GIC group
 * \return
 *      - true on success
 *      - false on invalid parameters
 */
bool osiIrqSetGroup(uint32_t irqn, uint32_t group);

/**
 * check whether there are pending interrupt
 *
 * This is for special purpose. It shall be called with interrupt disabled
 * (not masked off).
 *
 * This may be unimplemented in some chips.
 *
 * \return
 *      - true if there are interrupt pending.
 */
bool osiIrqPending(void);

/**
 * enable D-cache
 *
 * Usually, it shouldn't be called in application. It will only be called
 * once in boot code.
 *
 * Not all platforms implement this API.
 */
void osiDCacheEnable(void);

/**
 * disable D-cache
 *
 * Usually, it shouldn't be called in application. And most likely it will
 * never be called. It is provided for completeness.
 *
 * Not all platforms implement this API.
 */
void osiDCacheDisable(void);

/**
 * enable I-cache
 *
 * Usually, it shouldn't be called in application. It will only be called
 * once in boot code.
 *
 * Not all platforms implement this API.
 */
void osiICacheEnable(void);

/**
 * disable I-cache
 *
 * Usually, it shouldn't be called in application. And most likely it will
 * never be called. It is provided for completeness.
 *
 * Not all platforms implement this API.
 */
void osiICacheDisable(void);

/**
 * clean (write back) D-cache
 *
 * Usually it shall be called **before** the cachable memory will be read by
 * not cache coherent hardware.
 *
 * D-cache clean will operate by cache line. So, is the memory range is
 * not cache line aligned, other memory on the cache line will be cleaned
 * also. For example, assuming D-cache line size is 32 byte,
 * \p osiDCacheClean((void *)8, 32) will clean [0-8], [40-64] also.
 *
 * When D-cache coherence is needed to be considered, it is recommended to
 * declare or allocate memory by the following:
 *
 * \code{.cpp}
 * char mem1[SIZE] OSI_CACHE_LINE_ALIGNED;
 * void *mem2 = memalign(CONFIG_CACHE_LINE_SIZE, size);
 * \endcode
 *
 * \param address   starting address to be cleaned
 * \param size      size to be cleaned
 */
void osiDCacheClean(const void *address, size_t size);

/**
 * invalidate D-cache
 *
 * Usually it shall be called **before** the cachable memory will be write by
 * not cache coherent hardware.
 *
 * D-cache line alignment is very important for \p osiDCacheInvalidate.
 * Otherwise, other memory on the cache line will be changed randomly.
 *
 * \param address   starting address to be cleaned
 * \param size      size to be cleaned
 */
void osiDCacheInvalidate(const void *address, size_t size);

/**
 * clean and invalidate D-cache
 *
 * \param address   starting address to be cleaned
 * \param size      size to be cleaned
 */
void osiDCacheCleanInvalidate(const void *address, size_t size);

/**
 * invalidate D-cache range
 *
 * \param address   starting address to be cleaned
 * \param size      size to be cleaned
 */
void osiICacheInvalidate(const void *address, size_t size);

/**
 * sync D-cahce with I-cache
 *
 * It shall be called after code memory is operated as data (such as,
 * copy codes from flash to RAM).
 *
 * \param address   starting address to be cleaned
 * \param size      size to be cleaned
 */
void osiICacheSync(const void *address, size_t size);

/**
 * clean all D-cache
 */
void osiDCacheCleanAll(void);

/**
 * invalidate all D-cache
 */
void osiDCacheInvalidateAll(void);

/**
 * clean and invalidate all D-cache
 */
void osiDCacheCleanInvalidateAll(void);

/**
 * invalidate all I-cache
 */
void osiICacheInvalidateAll(void);

/**
 * sync D-cahce with I-cache for all
 */
void osiICacheSyncAll(void);

/**
 * create a thread
 *
 * Each \p osiThread_t will have an event queue. So, the event queue
 * depth should be specified at creation.
 *
 * \p name will be copied to thread control block. So, \p name can be dynamic
 * memory.
 *
 * After a thread is created, it will be executed immediately. So, it is
 * possible that \p entry will be executed before the return value is
 * assigned to some variable, if the new thread priority is higher than
 * current thread priority. Pay attention to use thread pointer in
 * \p entry.
 *
 * \code{.cpp}
 * void entry(void *argument) {
 *     osiThread_t *thread = osiThreadCurrent();
 *     for (;;) {
 *         osiEvent_t event = {};
 *         osiEventWait(thread, &event);
 *         ......
 *     }
 * }
 * \endcode
 *
 * In some underlay RTOS, there are limitation on maximum stack size.
 * For example, when \p configSTACK_DEPTH_TYPE is defined as \p uint16_t,
 * the stack size must be less than 64KB*4.
 *
 * \param name      thread name
 * \param entry     thread entry function
 * \param argument  thread entry function argument
 * \param priority  thread priority
 * \param stack_size    thread stack size in byte
 * \param event_count   thread event queue depth (count of events can be hold)
 * \return
 *      - thread pointer
 *      - NULL if failed
 */
osiThread_t *osiThreadCreate(const char *name, osiThreadEntry_t entry, void *argument,
                             uint32_t priority, uint32_t stack_size,
                             uint32_t event_count);

#ifdef CONFIG_QUEC_PROJECT_FEATURE
/**
 * create a thread
 *
 * Each \p osiThread_t will have an event queue. So, the event queue
 * depth should be specified at creation.
 *
 * \p name will be copied to thread control block. So, \p name can be dynamic
 * memory.
 *
 * After a thread is created, it will be executed immediately. So, it is
 * possible that \p entry will be executed before the return value is
 * assigned to some variable, if the new thread priority is higher than
 * current thread priority. Pay attention to use thread pointer in
 * \p entry.
 *
 * \code{.cpp}
 * void entry(void *argument) {
 *     osiThread_t *thread = osiThreadCurrent();
 *     for (;;) {
 *         osiEvent_t event = {};
 *         osiEventWait(thread, &event);
 *         ......
 *     }
 * }
 * \endcode
 *
 * In some underlay RTOS, there are limitation on maximum stack size.
 * For example, when \p configSTACK_DEPTH_TYPE is defined as \p uint16_t,
 * the stack size must be less than 64KB*4.
 *
 * \param name      thread name
 * \param entry     thread entry function
 * \param argument  thread entry function argument
 * \param priority  thread priority
 * \param stack_size    thread stack size in byte
 * \param event_count   thread event queue depth (count of events can be hold)
 * \return
 *      - thread pointer
 *      - NULL if failed
 */
osiThread_t *quec_osiThreadCreate(const char *name, osiThreadEntry_t entry, void *argument,
                             uint32_t priority, uint32_t stack_size,
                             uint32_t event_count);
#endif

/**
 * create a thread with specified stack
 *
 * It is similar to \p osiThreadCreate, and the stack won't be dynamic created.
 * Rather, the specified buffer \p stack will be used at the stack of the
 * thread. Typical usage is to create performance sensitive thread, and set stack
 * in SRAM to improve performance.
 *
 * Application should always use \p osiThreadCreate. It may be unimplemented on
 * some platforms.
 *
 * \param name      thread name
 * \param entry     thread entry function
 * \param argument  thread entry function argument
 * \param priority  thread priority
 * \param stack     thread stack buffer, must be valid
 * \param stack_size    thread stack size in byte
 * \param event_count   thread event queue depth (count of events can be hold)
 * \return
 *      - thread pointer
 *      - NULL if failed
 */
osiThread_t *osiThreadCreateWithStack(const char *name, osiThreadEntry_t entry, void *argument,
                                      uint32_t priority, void *stack, uint32_t stack_size,
                                      uint32_t event_count);

/**
 * get event queue of thread
 *
 * When \p thread is NULL, return the event queue of current thread.
 *
 * \param thread    thread pointer
 * \return      event queue of the thread
 */
osiEventQueue_t *osiThreadEventQueue(osiThread_t *thread);

/**
 * get current thread pointer
 *
 * \return      current thread pointer
 */
osiThread_t *osiThreadCurrent(void);

/**
 * set whether current thread need FPU
 *
 * Obsoleted. Floating point can be used in all threads, and ISR. This API
 * is just for compatible, and implemented as empty.
 *
 * \param enabled   true for enable FPU, false for disable FPU.
 */
void osiThreadSetFPUEnabled(bool enabled);

/**
 * get thread priority
 *
 * When \p thread is NULL, return the priotity of current thread.
 *
 * \param thread    thread pointer
 * \return      priotity of the thread
 */
uint32_t osiThreadPriority(osiThread_t *thread);

/**
 * set thread priority
 *
 * When \p thread is NULL, set the priotity of current thread.
 *
 * After the priority changed, it is possible thread context switch
 * will occur. However, don't depend on this feature.
 *
 * \param thread    thread pointer
 * \param priority  priority to be set
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiThreadSetPriority(osiThread_t *thread, uint32_t priority);

/**
 * suspend a thread
 *
 * When \p thread is NULL, suspend current thread.
 *
 * \param thread    thread pointer
 */
void osiThreadSuspend(osiThread_t *thread);

/**
 * resume a thread
 *
 * \p thread can't be NULL.
 *
 * \param thread    thread pointer
 */
void osiThreadResume(osiThread_t *thread);

/**
 * current thread yield
 *
 * When there are threads with the same priority, other threads will
 * be scheduled.
 */
void osiThreadYield(void);

/**
 * current thread sleep
 *
 * Change current thread into sleep mode, and will be rescheduled
 * after the specified period.
 *
 * This will use the underlay RTOS mechanism. It is possible the sleep
 * time precision is the tick of underlay RTOS.
 *
 * \param ms        sleep time in milliseconds
 */
void osiThreadSleep(uint32_t ms);

/**
 * current thread sleep with high precision timer
 *
 * It won't use the underlay RTOS mechanism, rather it will use high
 * precision timer.
 *
 * It will be used when high precision sleep time is needed.
 *
 * \param us        sleep time in microseconds
 */
void osiThreadSleepUS(uint32_t us);

/**
 * current thread sleep with relaxed timeout
 *
 * It is a power optimization version of \p osiThreadSleep. Due to power
 * saving, it is possible that current thread will be wakeup later then
 * *normal timeout*, but it will wakeup no later than *relaxed timeout*
 * even system will enter suspend.
 *
 * When \p relax_ms is \p OSI_DELAY_MAX, it means it can be ignored
 * completed for power saving. However, after system is awoken, the thread
 * will be wakeup still if the *normal timeout* is expired.
 *
 * \param ms        sleep time in milliseconds
 * \param relax_ms  relaxed sleep time in milliseconds
 */
void osiThreadSleepRelaxed(uint32_t ms, uint32_t relax_ms);

/**
 * thread stack unused space
 *
 * It needs underlay RTOS support. When the underlay RTOS doesn't support
 * this feature, it returns 0.
 *
 * The typical method to support this feature in underlay RTOS is to check
 * stack content, and comparing with preset magic byte or word.
 *
 * It is recommended to use it only for debug.
 *
 * \param thread    thread pointer, NULL for current thread
 * \return
 *      - thread stack unused space in bytes
 *      - 0 if the underlay RTOS doesn't support this feature
 */
uint32_t osiThreadStackUnused(osiThread_t *thread);

/**
 * space from current stack pointer to current thread stack end
 *
 * When \p refill is true, the current stack space will be fill with magic
 * byte or word for measuring unused stack. When underlay RTOS doesn't
 * support feature to measure stack remain, \p refill will be ignored.
 *
 * It is recommended to use it only for debug.
 *
 * \return
 *      - space to current thread stack end, in bytes
 */
uint32_t osiThreadStackCurrentSpace(bool refill);

/**
 * current thread exit
 *
 * When a thread is finished, \p osiThreadExit must be called. And
 * kernel will release thread resources at appropriate time.
 */
OSI_NO_RETURN void osiThreadExit(void);

/**
 * show thread information through trace
 *
 * It is for debug purpose only.
 */
void osiShowThreadState(void);

/**
 * send an event to a thread
 *
 * At send, the body of \p event will be copied to event queue
 * rather then send the pointer of \p event.
 *
 * When event queue of the target thread is full, the caller thread
 * will be block until there are rooms in target thread event queue.
 *
 * This can be called in ISR. And in ISR, it will return false when event
 * queue is full, and not wait.
 *
 * \param thread    thread pointer, can't be NULL
 * \param event     event to be sent
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiEventSend(osiThread_t *thread, const osiEvent_t *event);

/**
 * send event loop quit event to a thread
 *
 * This is the normalized method to notify a thread to quit. When \p thread
 * is the current thread, \p wait can't be true.
 *
 * \code{.cpp}
 *     // caller thread
 *     osiSendQuitEvent(thread);
 *
 *     // thread to quit
 *     for (;;) {
 *         osiEvent_t event = {};
 *         osiEventWait(thread, &event);
 *         if (event.id == OSI_EVENT_ID_QUIT)
 *             break;
 *
 *         // ......
 *     }
 *     osiThreadExit();
 * \endcode
 *
 * \param thread    thread pointer, can't be NULL
 * \param wait      whether to wait thread exit
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiSendQuitEvent(osiThread_t *thread, bool wait);

/**
 * send an event to a thread with timeout
 *
 * When \p timeout is 0, this will return false immediately. When \p timeout
 * is \p OSI_WAIT_FOREVER, this will wait forever until there are
 * rooms in target thread event queue.
 *
 * This can be called in ISR. And in ISR, \p timeout will be ignored.
 *
 * \param thread    thread pointer, can't be NULL
 * \param event     event to be sent
 * \param timeout   timeout in milliseconds
 * \return
 *      - true on success
 *      - false on invalid parameter, or timeout
 */
bool osiEventTrySend(osiThread_t *thread, const osiEvent_t *event, uint32_t timeout);

/**
 * wait an event
 *
 * Wait an event from current thread event queue. When current thread
 * event queue is empty, it will be blocked forever until there are
 * event in event queue.
 *
 * The event body will be copied to \p event.
 *
 * There are some event ID used by system. After system event is received
 * and process, this will return an event with ID of 0. Application can
 * ignore event ID 0 safely.
 *
 * It **can't** be called in ISR. And in ISR, it will return false directly.
 *
 * \param thread    thread pointer, can't be NULL
 * \param event     event pointer
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiEventWait(osiThread_t *thread, osiEvent_t *event);

/**
 * wait an event with timeout
 *
 * When \p timeout is 0, this will return false immediately. When \p timeout
 * is \p OSI_WAIT_FOREVER, this will wait forever until there are
 * events in target thread event queue.
 *
 * It **can't** be called in ISR. And in ISR, it will return false directly.
 *
 * \param thread    thread pointer, can't be NULL
 * \param event     event pointer
 * \param timeout   timeout in milliseconds
 * \return
 *      - true on success
 *      - false on invalid parameter, or timeout
 */
bool osiEventTryWait(osiThread_t *thread, osiEvent_t *event, uint32_t timeout);

/**
 * whether there are pending event in event queue
 *
 * It can be called in both thread context and ISR context.
 *
 * \param thread    thread pointer, can't be NULL
 * \return
 *      - true if there are pending event
 *      - false if not
 */
bool osiEventPending(osiThread_t *thread);

/**
 * get the pending event count in event queue
 *
 * It can be called in both thread context and ISR context.
 *
 * \param thread    thread pointer, can't be NULL
 * \return
 *      - the pending event count in event queue
 *      - 0 on invalid parameter
 */
uint32_t osiEventPendingCount(osiThread_t *thread);

/**
 * get the space of event count in event queue
 *
 * It can be called in both thread context and ISR context.
 *
 * \param thread    thread pointer, can't be NULL
 * \return
 *      - the space of event count in event queue
 *      - 0 on invalid parameter
 */
uint32_t osiEventSpaceCount(osiThread_t *thread);

/**
 * set callback to be executed on thread
 *
 * Thread callback is implemented by \p osiEvent_t.
 *
 * This can be called in ISR. In ISR, the callback event will be lost when
 * the event queue of target thread is full.
 *
 * \param thread    thread pointer, can't be NULL
 * \param cb        callback to be executed
 * \param cb_ctx    callback context
 * \return
 *      - true on success
 *      - false on invalid parameter, or event queue is full in ISR
 */
bool osiThreadCallback(osiThread_t *thread, osiCallback_t cb, void *cb_ctx);

/**
 * \brief create a work
 *
 * \p run can't be NULL, and \p complete can be NULL.
 *
 * \param run       execute function of the work
 * \param complete  callback to be invoked after the work is finished
 * \param ctx       context of \p run and \p complete
 * \return
 *      - the created work
 *      - NULL if invalid parameter or out of memory
 */
osiWork_t *osiWorkCreate(osiCallback_t run, osiCallback_t complete, void *ctx);

/**
 * \brief delete the work
 *
 * When \p work is running when it is called, it will be deleted after the
 * current run finished.
 *
 * \param work      the work to be deleted
 */
void osiWorkDelete(osiWork_t *work);

/**
 * \brief reset callback of existed work
 *
 * This can change the callbacks of existed work. It can be used to setup
 * a kind of *work flow*. An example:
 *
 * \code{.cpp}
 * static void prvPrepare(void *param) {
 *     // ....
 *     osiWorkResetCallback(work, prvRealJob, NULL, ctx);
 * }
 *
 * static void prvRealJob(void *param) {
 *     // ...
 * }
 *
 * void init(void) {
 *     work = osiWorkCreate(prvPrepare, NULL, ctx);
 *     osiWorkEnqueue(work, wa);
 * }
 * \endcode
 *
 * This is clearer than creating multiple work instances.
 *
 * Though it is not typical application, the callbacks can be changed when
 * the work is already queued. In this case, there may exist corner cases
 * about whether the previous or the new callbacks will be invoked, depends
 * on thread priorities. Caller should take care about this.
 *
 * When it is called inside work callback, the new callbacks will be invoked
 * at the next timer when work is invoked.
 *
 * \param work      the work pointer, must be valid
 * \param run       execute function of the work
 * \param complete  callback to be invoked after the work is finished
 * \param ctx       context of \p run and \p complete
 * \return
 *      - true on success
 *      - false on fail, invalid parameters
 */
bool osiWorkResetCallback(osiWork_t *work, osiCallback_t run, osiCallback_t complete, void *ctx);

/**
 * \brief enqueue a work in specified work queue
 *
 * When \p work is running, it will be queued to \p wq again and then it
 * will be invoked again.
 *
 * When \p work is queued, and \p wq is the same as original work queue,
 * nothing will be done. When \p wq is not the same as the original
 * work queue, it will be removed from the original work queue, and
 * queue the work into the specified work queue.
 *
 * This can be called in ISR.
 *
 * \param work      the work pointer, must be valid
 * \param wq        work queue to run the work, must be valid
 * \return
 *      - true on success
 *      - false for invalid parameter, or work is running
 */
bool osiWorkEnqueue(osiWork_t *work, osiWorkQueue_t *wq);

/**
 * \brief enqueue a work in the last of specified work queue
 *
 * It is similar to \p osiWorkEnqueue, except it will consider work order.
 * For example:
 *
 * \code{.cpp}
 *      osiWorkEnqueue(work1, wq);
 *      osiWorkEnqueue(work2, wq);
 *      osiWorkEnqueue(work1, wq);
 * \endcode
 *
 * If work queue is busy on another work during these calls, and when work
 * queue processing these works, it will:
 * - execute work1->callback
 * - execute work2->callback
 *
 * So, even the order of works aren't preserved. \p work1 is the last queued
 * work, and the real last executed work is \p work2.
 *
 * With \p osiWorkEnqueueLast, it will ensure that the last queued work will
 * be executed at the last.
 *
 * \param work      the work pointer, must be valid
 * \param wq        work queue to run the work, must be valid
 * \return
 *      - true on success
 *      - false for invalid parameter, or work is running
 */
bool osiWorkEnqueueLast(osiWork_t *work, osiWorkQueue_t *wq);

/**
 * \brief cancel a work
 *
 * When \p work is running, the current execution won't be interrupted.
 *
 * \param work      the work pointer, must be valid
 */
void osiWorkCancel(osiWork_t *work);

/**
 * \brief wait a work finish
 *
 * When \p work is running or enqued, this will wait the work finish.
 * When \p timeout is 0, it will return immediately. When \p timeout
 * is \p OSI_WAIT_FOREVER, it will wait infinitely until the work is
 * finished.
 *
 * \param work      the work pointer, must be valid
 * \param timeout   wait timeout
 * \return
 *      - true if the work is finished
 *      - false on invalid parameter, or wait timeout
 */
bool osiWorkWaitFinish(osiWork_t *work, unsigned timeout);

/**
 * \brief create work queue
 *
 * Multiple threads can be created to reduce work execution latency. For
 * example, when one thread in a work queue is blocked, other threads of
 * the work queue can execute other works queued to the work queue.
 *
 * The maximum thread count is implementation dependent. So, it is possible
 * that the created thread count is less than \p thread_count. And also it is
 * possible that \p thread_count is ignored.
 *
 * The created threads have the same priority and stack size.
 *
 * The work queue thread entry function can't be customized
 *
 * \param name      work queue name
 * \param thread_count  thread count to be created for the work queue
 * \param priority  work queue thread priority
 * \param stack_size    thread stack size in byte
 * \return
 *      - the work queue pointer
 *      - NULL if failed
 */
osiWorkQueue_t *osiWorkQueueCreate(const char *name, size_t thread_count, uint32_t priority, uint32_t stack_size);

/**
 * \brief delete work queue
 *
 * All resources of the work queue will be deleted.
 *
 * The works in running will continue, and \p complete will be invoked as
 * normal. However, queued work in the work queue won't be executed any more.
 *
 * \param wq    work queue to be deleted
 */
void osiWorkQueueDelete(osiWorkQueue_t *wq);

/**
 * \brief get the system high priority work queue
 *
 * A work queue with priority \p OSI_PRIORITY_HIGH will be created at kernel
 * start.
 *
 * \return  the system high priority work queue
 */
osiWorkQueue_t *osiSysWorkQueueHighPriority(void);

/**
 * \brief get the system low priority work queue
 *
 * A work queue with priority \p OSI_PRIORITY_LOW will be created at kernel
 * start.
 *
 * \return  the system low priority work queue
 */
osiWorkQueue_t *osiSysWorkQueueLowPriority(void);

/**
 * \brief get the system work queue for asynchronuous file system write
 *
 * A work queue with priority \p OSI_PRIORITY_BELOW_NORMAL will be created
 * at kernel start. This work queue shall be used for asynchronuous file
 * system write.
 *
 * Usually file write is slow, especially for file system on NOR flash.
 * When faster response is needed, file write can be deferred to this
 * work queue. Also, the work queue will be flushed before system shutdown.
 *
 * \return  the system file write work queue
 */
osiWorkQueue_t *osiSysWorkQueueFileWrite(void);

/**
 * \brief create a thread notify
 *
 * Thread notify is thread callback with state to avoid duplicated event
 * to be sent to thread event queue.
 *
 * \param thread    thread to execute the callback, can't be NULL
 * \param cb        callback to be executed, can't be NULL
 * \param ctx       callback context
 * \return
 *      - created notify
 *      - NULL if parameters are invalid or out of memory
 */
osiNotify_t *osiNotifyCreate(osiThread_t *thread, osiCallback_t cb, void *ctx);

/**
 * \brief delete a thread notify
 *
 * The memory of the thread notify will be released.
 *
 * When the thread notify is already in thread event queue, the callback
 * won't be invoked, and the memory may be released delayed.
 *
 * \param notify    thread notify pointer, must be valid
 */
void osiNotifyDelete(osiNotify_t *notify);

/**
 * \brief trigger a thread notify
 *
 * When the thread notify event isn't in thread event queue, an event for
 * thread notify will be queued to the tail of thread event queue.
 *
 * \param notify    thread notify pointer, must be valid
 */
void osiNotifyTrigger(osiNotify_t *notify);

/**
 * \brief cancel a thread notify
 *
 * When the thread notify event has already sent to thread event queue,
 * the invocation of the callback will be cancelled.
 *
 * \param notify    thread notify pointer, must be valid
 */
void osiNotifyCancel(osiNotify_t *notify);

/**
 * \brief create a timer
 *
 * Create a timer with specified callback and callback context. After create,
 * the timer is in stop state. Application can start it in various mode.
 *
 * When \p thread is \p OSI_TIMER_IN_ISR, the callback will be executed in
 * timer ISR. So, the callback should follow ISR programming guide. This is
 * **not** recommended unless it is absolutely needed.
 *
 * When \p thread is \p OSI_TIMER_IN_SERVICE, the callback will be executed
 * in timer service thread.
 *
 * Otherwise, the callback will be executed in the specified thread through
 * *event* mechanism.
 *
 * It is needed to call \p osiTimerDelete to free resources.
 *
 * \param thread    thread to execute the callback
 * \param cb        callback to be executed after timer expire
 * \param ctx       callback context
 * \return
 *      - the created timer instance
 *      - NULL at out of memory, or invalid parameter
 */
osiTimer_t *osiTimerCreate(osiThread_t *thread, osiCallback_t cb, void *ctx);

/**
 * \brief create a timer, enqueue a work on expiration
 *
 * Create a timer with specified callback and callback context. After create,
 * the timer is in stop state. Application can start it in various mode.
 *
 * At expiration, \p work will be enqueued to \p wq.
 *
 * It is needed to call \p osiTimerDelete to free resources.
 *
 * \param work      work to be enqueued at expiration
 * \param wq        work queue to be enqueued at expiration
 * \return
 *      - the created timer instance
 *      - NULL at out of memory, or invalid parameter
 */
osiTimer_t *osiTimerCreateWork(osiWork_t *work, osiWorkQueue_t *wq);

/**
 * \brief create a timer, EV_TIMER on expiration
 *
 * This is for legacy codes only. **Don't** use it except at porting
 * legacy codes.
 *
 * Create a timer with specified timerid. After the timer expired, the
 * specified thread will receive an event { EV_TIMER, timerid, 0, 0}.
 *
 * It is needed to call \p osiTimerDelete to free resources.
 *
 * \param thread    thread to execute the callback, it can't be NULL
 * \param timerid   timerid in expiration event
 * \return
 *      - the created timer instance
 *      - NULL at out of memory, or invalid parameter
 */
osiTimer_t *osiTimerEventCreate(osiThread_t *thread, uint32_t timerid);

/**
 * \brief set/change callback of timer
 *
 * In most cases, timer callback set at create is not needed to be changed.
 *
 * \p timer should be created by \p osiTimerCreate. Special values of
 * \p thread follow \p osiTimerCreate.
 *
 * It is permitted to change \p thread of timer, including special values.
 * For example, it is permitted to change a timer executing in timer service
 * to timer executing in specified thread. However, it is not recommended.
 *
 * This can only be called when \p timer is stopped. Otherwise, it will
 * return false.
 *
 * \param timer     timer to be changed
 * \param thread    thread to execute the callback
 * \param cb        callback to be executed after timer expire
 * \param ctx       callback context
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerSetCallback(osiTimer_t *timer, osiThread_t *thread, osiCallback_t cb, void *ctx);

/**
 * \brief set/change work and work queue of timer
 *
 * In most cases, timer work and work queue set at create is not needed to be changed.
 *
 * \p timer should be created by \p osiTimerCreateWork, to enqueue work to work
 * queue at expiration.
 *
 * \param timer     timer to be changed
 * \param work      work to be enqueued at expiration
 * \param wq        work queue to be enqueued at expiration
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerSetWork(osiTimer_t *timer, osiWork_t *work, osiWorkQueue_t *wq);

/**
 * \brief set/change timer event
 *
 * In most cases, timer thread and id set at create is not needed to be changed.
 *
 * \p timer should be created by \p osiTimerEventCreate.
 *
 * \param timer     timer to be changed
 * \param thread    thread to execute the callback, it can't be NULL
 * \param timerid   timerid in expiration event
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerSetEvent(osiTimer_t *timer, osiThread_t *thread, uint32_t timerid);

/**
 * \brief delete a timer
 *
 * Delete the timer, and free associated resources.
 *
 * When the timer callback will be executed in thread rather than ISR, and the
 * event for timer expiration has been sent, some resources won't be freed
 * immediately. Rather, they will be freed after the timer expiration event
 * is popped out from thread event queue. However, the callback won't be
 * executed even delete is delayed.
 *
 * Refer to document about corner case of timer thread callback.
 *
 * \param timer     the timer to be deleted
 */
void osiTimerDelete(osiTimer_t *timer);

/**
 * \brief change timer relaxed timeout for running timer
 *
 * When the timer is not running, it will return false.
 *
 * When the timer is started, this will change the relaxed timeout
 * specified at start.
 *
 * This can be called for both one shot timer and periodic timer.
 *
 * \p relaxed_ms is the additional time based on normal timeout. It will
 * only be used for sleep. When \p relaxed_ms is 0, this timer will wakeup
 * system even system enter sleep mode. When \p relaxed_ms is
 * \p OSI_DELAY_MAX, this timer will not wakeup system when system enter
 * sleep mode. Otherwise, system will process the timer no later than the
 * normal timeout plus additional timeout.
 *
 * For example, when the period of a timer is 100ms, and relaxed timeout
 * is 500ms, system will process the timer no later than 600ms. This can
 * make system sleep more time, and reduce power consumption.
 *
 * For periodic timer, the additional timeout is add to normal timeout of
 * earch run. For example, a periodic timer period is 100ms, and relaxed
 * timeout is 50ms, then system will process the timer no later than 150ms.
 * And even the timer is delayed to 150ms due to system sleep, the next
 * timeout is 200ms unchanged. Another example, a periodic timer period
 * is 100ms, and relaxed timeout is 500ms, then system will process the
 * timer no later than 600ms. When the timer is delayed to 600ms due to
 * system sleep, the callback will be invoked only once even 6 periods are
 * elapsed. The next timeout is at 700ms.
 *
 * \param timer         the timer to be set
 * \param relaxed_ms    relaxed timeout in milliseconds
 * \return
 *      - true on success
 *      - false on invalid parameter, or timer is not running
 */
bool osiTimerChangeRelaxed(osiTimer_t *timer, uint32_t relaxed_ms);

/**
 * \brief set timer period for next call
 *
 * The properties will be used by next \p osiTimerStartLast. It can only be
 * called when the timer is not running.
 *
 * It is the same as \p osiTimerSetPeriodRelaxed, with \p relaxed_ms
 * as 0.
 *
 * \param timer     the timer to be set
 * \param ms        period in milliseconds
 * \param periodic  true for periodic, false for one shot
 * \return
 *      - true on success
 *      - false on invalid parameter, or timer is started
 */
bool osiTimerSetPeriod(osiTimer_t *timer, uint32_t ms, bool periodic);

/**
 * \brief set timer period for next call, with relaxed timeout
 *
 * The properties will be used by next \p osiTimerStartLast. It can only be
 * called when the timer is not running.
 *
 * \param timer     the timer to be set
 * \param ms        period in milliseconds
 * \param relaxed_ms    relaxed timeout in milliseconds
 * \param periodic  true for periodic, false for one shot
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerSetPeriodRelaxed(osiTimer_t *timer, uint32_t ms, uint32_t relaxed_ms, bool periodic);

/**
 * \brief whether timer is started
 *
 * \param timer     the timer to be set
 * \return
 *      - true if the timer is started
 *      - false if not started, or invalid parameter
 */
bool osiTimerIsRunning(osiTimer_t *timer);

/**
 * \brief get remaining time of timer in milliseconds
 *
 * Even the timer is already timed out, it will return 0 rather then negative
 * value. For example, thread callback timer is already timed out, but the
 * callback hasn't invoked in the specified thread.
 *
 * \param timer     the timer to be set
 * \return
 *      - remaining time
 *      - timer is not started, or invalid parameter
 */
int64_t osiTimerRemaining(osiTimer_t *timer);

/**
 * \brief timer expiration time in milliseconds
 *
 * The expiration time is in the same coordinate of \p osiUpTime.
 *
 * When the timer is not running, it will return -1. For already timeed out
 * timer, the return value may be smaller than \p osiUpTime.
 *
 * \param timer     the timer to be checked
 * \return
 *      - expiration time
 *      - -1 if the timer is not running
 */
int64_t osiTimerExpiration(osiTimer_t *timer);

/**
 * \brief start a timer with last period
 *
 * The last period is the period set by \p osiTimerSetPeriod, or other start
 * APIs with period parameter.
 *
 * It is recommended that not to mixing start API with period parameter, and
 * start API without period parameter. Though the behavior is determinstic,
 * it is harder to be understood.
 *
 * \param timer     the timer to be started
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerStartLast(osiTimer_t *timer);

/**
 * \brief start a timer
 *
 * Start a timer, and the timer will be expired in specified period from
 * now. After the callback is executed, the timer will come to stopped state
 * automatically.
 *
 * When the timer is in stated state before this function, the previous
 * expiration won't be executed.
 *
 * Refer to document about corner case of timer thread callback.
 *
 * It is valid that the timeout period is 0. In this case, the timer will
 * expire very soon.
 *
 * Due to timeout is 32bits of milliseconds, The maximum timeout period is
 * ~50 days.
 *
 * It is the same as \p osiTimerStartRelaxed, with \p relaxed_ms is 0.
 *
 * \param timer     the timer to be started
 * \param ms        timeout period
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerStart(osiTimer_t *timer, uint32_t ms);

/**
 * \brief start a timer with relaxed timeout
 *
 * It is a power optimization version of \p osiTimerStart.
 *
 * Refer to \p osiTimerChangeRelaxed for explaination of \p relaxed_ms.
 *
 * \param timer     the timer to be started
 * \param ms        normal timeout period
 * \param relax_ms  relaxed timeout period
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerStartRelaxed(osiTimer_t *timer, uint32_t ms, uint32_t relax_ms);

/**
 * \brief start a timer, timeout in unit of microseconds
 *
 * Timeout in microseconds can support higher precision. However, the maximum
 * timeout is shorter, ~1.2 hours.
 *
 * The real precision depends on hardware.

 * \param timer     the timer to be started
 * \param us        timeout period in microseconds
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerStartMicrosecond(osiTimer_t *timer, uint32_t us);

/**
 * \brief start a periodic timer
 *
 * Internally, the period may be aligned to hardware tick (larger than
 * 16384Hz), there may exist accumulated error in long run. So, don't use
 * periodic timer count for long time time, \p osiUpTime is a better choice.
 *
 * When the period of periodic timer is too small, it will have serious impact
 * on system. Internally, the period taking effect will no less than
 * \p CONFIG_KERNEL_PERIODIC_TIMER_MIN_PERIOD.
 *
 * It is the same as \p osiTimerStartPeriodicRelaxed, with \p relaxed_ms is 0.
 *
 * \param timer     the timer to be started
 * \param ms        interval in milliseconds
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerStartPeriodic(osiTimer_t *timer, uint32_t ms);

/**
 * \brief start a periodic timer with relaxed timeout
 *
 * It is a power optimization version of \p osiTimerStartPeriodic.
 *
 * Refer to \p osiTimerChangeRelaxed for explaination of \p relaxed_ms.
 *
 * \param timer     the timer to be started
 * \param ms        interval in milliseconds
 * \param relaxed_ms    relaxed timeout period
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerStartPeriodicRelaxed(osiTimer_t *timer, uint32_t ms, uint32_t relaxed_ms);

/**
 * \brief stop a time
 *
 * Stop a not-started or stopped timer is valid, just do nothing.
 *
 * Refer to document about corner case of timer thread callback.
 *
 * \param timer     the timer to be stopped
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiTimerStop(osiTimer_t *timer);

/**
 * create a message queue
 *
 * \param msg_count maximum message count can be hold in queue
 * \param msg_size  size of each message in bytes
 * \return
 *      - message queue pointer
 *      - NULL on invalid parameter or out of memory
 */
osiMessageQueue_t *osiMessageQueueCreate(uint32_t msg_count, uint32_t msg_size);

/**
 * delete a message queue
 *
 * When \p mq is NULL, nothing will be done, just as \p free.
 *
 * \param mq    message queue pointer
 */
void osiMessageQueueDelete(osiMessageQueue_t *mq);

/**
 * put a message to message queue
 *
 * \p msg should hold content size the same as \p msg_size specified at
 * \p osiMessageQueueCreate.
 *
 * After put, the content of \p msg will be copied to message queue.
 *
 * When \p mq is full, it will be blocked until there are rooms.
 *
 * This can be called in ISR. And in ISR, it will return false when queue
 * is full, and not wait.
 *
 * \param mq    message queue pointer
 * \param msg   mesage pointer
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiMessageQueuePut(osiMessageQueue_t *mq, const void *msg);

/**
 * put a message to message queue with timeout
 *
 * This can be called in ISR. And in ISR, \p timeout will be ignored.
 *
 * \param mq        message queue pointer
 * \param msg       mesage pointer
 * \param timeout   timeout in milliseconds
 * \return
 *      - true on success
 *      - false on invalid parameter or timeout
 */
bool osiMessageQueueTryPut(osiMessageQueue_t *mq, const void *msg, uint32_t timeout);

/**
 * get a message to message queue
 *
 * \p msg should be able tp hold content size of \p msg_size specified at
 * \p osiMessageQueueCreate.
 *
 * After get, the content of message will be copied to \p msg.
 *
 * When \p mq is empty, it will be blocked until there are messages.
 *
 * This can be called in ISR. And in ISR, it will return false when queue
 * is empty, and not wait.
 *
 * \param mq    message queue pointer
 * \param msg   mesage pointer
 * \return
 *      - true on success
 *      - false on invalid parameter
 */
bool osiMessageQueueGet(osiMessageQueue_t *mq, void *msg);

/**
 * get a message to message queue with timeout
 *
 * This can be called in ISR. And in ISR, \p timeout will be ignored.
 *
 * \param mq        message queue pointer
 * \param msg       mesage pointer
 * \param timeout   timeout in milliseconds
 * \return
 *      - true on success
 *      - false on invalid parameter or timeout
 */
bool osiMessageQueueTryGet(osiMessageQueue_t *mq, void *msg, uint32_t timeout);

/**
 * get the pending message count in message queue
 *
 * It can be called in both thread context and ISR context.
 *
 * \param mq        message queue pointer
 * \return
 *      - the pending message count in message queue
 *      - 0 on invalid parameter
 */
uint32_t osiMessageQueuePendingCount(osiMessageQueue_t *mq);

/**
 * get the space of message count in message queue
 *
 * It can be called in both thread context and ISR context.
 *
 * \param mq        message queue pointer
 * \return
 *      - the space of message count in message queue
 *      - 0 on invalid parameter
 */
uint32_t osiMessageQueueSpaceCount(osiMessageQueue_t *mq);

/**
 * create a semaphore
 *
 * When \p max_count is 1, it is a binary semaphore. Otherwise, it is
 * counting semaphore.
 *
 * \param max_count     maximum count of the semaphore
 * \param init_count    initial count of the semaphore
 * \return
 *      - semaphore pointer
 *      - NULL on invalid parameter or out of memory
 */
osiSemaphore_t *osiSemaphoreCreate(uint32_t max_count, uint32_t init_count);

/**
 * delete the semaphore
 *
 * When there are blocked thread on the semaphore, the behavior is undefined
 * for \p osiSemaphoreDelete.
 *
 * \param semaphore     semaphore pointer
 */
void osiSemaphoreDelete(osiSemaphore_t *semaphore);

/**
 * acquire from semaphore
 *
 * After acquire, the count of semaphore will be decreased by 1.
 *
 * When the count of semaphore is 0, it will be blocked until the count
 * becomes non-zero (increased by \p osiSemaphoreRelease).
 *
 * This can be called in ISR. And in ISR, it will return false when
 * semaphore is unavailable, and not wait.
 *
 * \param semaphore     semaphore pointer
 * \return
 *      - true on success
 *      - false on invalid parameter, or unavailable in ISR
 */
bool osiSemaphoreAcquire(osiSemaphore_t *semaphore);

/**
 * acquire from semaphore with timeout
 *
 * This can be called in ISR. And in ISR, \p timeout will be ignored.
 *
 * \param semaphore     semaphore pointer
 * \param timeout       timeout in milliseconds
 * \return
 *      - true on success
 *      - false on timeout
 */
bool osiSemaphoreTryAcquire(osiSemaphore_t *semaphore, uint32_t timeout);

/**
 * release to semaphore
 *
 * After release, the count of semaphore will be increased by 1.
 * When there are blocked thread on the semaphore, one of the blocked
 * thread will be unblocked.
 *
 * This can be called in ISR.
 *
 * \param semaphore     semaphore pointer
 */
void osiSemaphoreRelease(osiSemaphore_t *semaphore);

/**
 * create a mutex
 *
 * After creation, the mutex is in *open* state.
 *
 * \return
 *      - mutex pointer
 *      - NULL on out of memory
 */
osiMutex_t *osiMutexCreate(void);

/**
 * delete the mutex
 *
 * When \p mutex is NULL, nothing will be done, just as \p free.
 *
 * \param mutex     mutex pointer to be deleted
 */
void osiMutexDelete(osiMutex_t *mutex);

/**
 * lock the mutex
 *
 * When \p mutex is locked by another thread, it will wait forever
 * until the mutex is unlocked.
 *
 * It **can't** be called in ISR. And in ISR, it will return directly.
 *
 * \param mutex     mutex pointer to be locked
 */
void osiMutexLock(osiMutex_t *mutex);

/**
 * lock the mutex with timeout
 *
 * It **can't** be called in ISR. And in ISR, it will return false directly.
 *
 * \param mutex     mutex pointer to be locked
 * \param timeout   timeout in milliseconds
 * \return
 *      - true on success
 *      - false on timeout
 */
bool osiMutexTryLock(osiMutex_t *mutex, uint32_t timeout);

/**
 * unlock the mutex
 *
 * It **can't** be called in ISR. And in ISR, it will return directly.
 *
 * \param mutex     mutex pointer to be unlocked
 */
void osiMutexUnlock(osiMutex_t *mutex);

/**
 * \brief monoclinic system time
 *
 * It is a relative time from system boot. Even after suspend and resume,
 * the monoclinic system time will be contiguous.
 *
 * At PSM wakeup (if supported), it is not contiguous.
 *
 * \return      monoclinic system time in milliseconds
 */
int64_t osiUpTime(void);

/**
 * \brief monoclinic system time in microsecond
 *
 * The monoclinic system time in unit of microsecond.
 *
 * \return      monoclinic system time in microseconds
 */
int64_t osiUpTimeUS(void);

/**
 * \brief set monoclinic system time
 *
 * When it is known that the hardware resource for monoclinic system time
 * is discontinued, such as power off during deep sleep, this is called to
 * set monoclinic system time.
 *
 * When up time is changed, epoch time and local time aren't be changed.
 *
 * It should only be called in system integration.
 *
 * \param ms        target monoclinic system time in milliseconds
 */
void osiSetUpTime(int64_t ms);

/**
 * \brief set monoclinic system time
 *
 * When it is known that the hardware resource for monoclinic system time
 * is discontinued, such as power off during deep sleep, this is called to
 * set monoclinic system time.
 *
 * When up time is changed, epoch time and local time aren't be changed.
 *
 * It should only be called in system integration.
 *
 * \param us        target monoclinic system time in microseconds
 */
void osiSetUpTimeUS(int64_t us);

/**
 * \brief get the epoch time
 *
 * The time is millisecond (1/1000 second) from 1970-01-01 UTC. To avoid
 * overflow, the data type is 64 bits.
 *
 * Epoch time is not monoclinic time. For example, when system time is
 * synchronized with network, there may exist a jump (forward or backward)
 * of epoch time.
 *
 * In 2 cases, this system time may be not reliable:
 * - During boot, and before RTC is initialized.
 * - During wakeup, and the elapsed sleep time hasn't compensated.
 *
 * \return      epoch time in milliseconds
 */
int64_t osiEpochTime(void);

/**
 * \brief get the epoch time in second
 *
 * The time is seconds from 1970-01-01 UTC. To avoid overflow, the data
 * type is 64 bits.
 * - signed 32bits will overflow at year 2038
 * - unsigned 32bits will overflow at year 2106
 *
 * Epoch time is not monoclinic time. For example, when system time is
 * synchronized with network, there may exist a jump (forward or backward)
 * of epoch time.
 *
 * In 2 cases, this system time may be not reliable:
 * - During boot, and before RTC is initialized.
 * - During wakeup, and the elapsed sleep time hasn't compensated.
 *
 * \return      epoch time in seconds
 */
int64_t osiEpochSecond(void);

/**
 * \brief set the monoclinic system time
 *
 * After the system time is changed, RTC won't be updated automatically.
 * It is needed to call RTC API to sync system time to RTC.
 *
 * When epoch time is changed, the monoclilinc system up time isn't changed,
 * and local time is changed correspondingly. The delta bewteen epoch time
 * and local time is only affected by timezone.
 *
 * \param ms    epoch time in milliseconds
 *
 * \return      true on succeed else fail
 */
bool osiSetEpochTime(int64_t ms);

/**
 * \brief set the monoclinic system time
 *
 * After the system time is changed, RTC won't be updated automatically.
 * It is needed to call RTC API to sync system time to RTC.
 *
 * When epoch time is changed, the monoclilinc system up time isn't changed,
 * and local time is changed correspondingly. The delta bewteen epoch time
 * and local time is only affected by timezone.
 *
 * \param us    epoch time in microseconds
 *
 * \return      true on succeed else fail
 */
bool osiSetEpochTimeUS(int64_t us);

/**
 * \brief get time zone in second
 *
 * Time zone is the offset between local time and epoch time:
 *      local_time = epoch_time + time_zone
 *
 * OSI won't keep time zone at power off. Other module should store
 * time zone in NVRAM, and set to OSI at boot.
 *
 * \return  time zone in second
 */
int osiTimeZoneOffset(void);

/**
 * \brief set time zone in second
 *
 * Time zone is the offset between local time and epoch time:
 *      local_time = epoch_time + time_zone
 *
 * Time zone should be in [-12*3600, 12*3600]. However, it is not checked
 * inside this. The caller should make sure the \p offset is reasonable.
 *
 * \param offset    time zone in second
 */
void osiSetTimeZoneOffset(int offset);

/**
 * \brief get the local time
 *
 * It is just: epoch_time + time_zone
 *
 * \return      local time in milliseconds
 */
int64_t osiLocalTime(void);

/**
 * \brief get the local time in seconds
 *
 * It is just: epoch_time + time_zone
 *
 * \return      local time in seconds
 */
int64_t osiLocalSecond(void);

/**
 * \brief convert epoch time to up time
 *
 * When some values has special meanings, such as INT64_MAX means invalid or not
 * exist, caller should check special values before call this.
 *
 * \param epoch     epoch time in milliseconds
 * \return  up time in milliseconds
 */
int64_t osiEpochToUpTime(int64_t epoch);

/**
 * \brief start counting of elsapsed timer
 *
 * \param timer     elapsed timer, must be valid
 */
void osiElapsedTimerStart(osiElapsedTimer_t *timer);

/**
 * \brief elapsed milliseconds after start
 *
 * \param timer     elapsed timer, must be valid
 * \return
 *      - elapsed milliseconds after start
 */
uint32_t osiElapsedTime(osiElapsedTimer_t *timer);

/**
 * \brief elapsed microseconds after start
 *
 * \param timer     elapsed timer, must be valid
 * \return
 *      - elapsed microseconds after start
 */
uint32_t osiElapsedTimeUS(osiElapsedTimer_t *timer);

/**
 * \brief convert uptime to epoch time
 *
 * When some values has special meanings, such as INT64_MAX means invalid or not
 * exist, caller should check special values before call this.
 *
 * \param uptime     up time in milliseconds
 * \return  epoch time in milliseconds
 */
int64_t osiUpTimeToEpoch(int64_t uptime);

/**
 * \brief PM source callbacks
 *
 * All the callbacks are called with interrupt disabled. So, it is not
 * needed to call \p osiEnterCritical or \p osiIrqSave for protection.
 * And it is not error to call them.
 *
 * Don't call blocking API in the callback.
 */
typedef struct osiPmSourceOps
{
    void (*suspend)(void *ctx, osiSuspendMode_t mode);                 ///< callback to be called before suspend
    void (*resume)(void *ctx, osiSuspendMode_t mode, uint32_t source); ///< callback to be called after resume
    bool (*prepare)(void *ctx);                                        ///< callback to be called at suspend check
    void (*prepare_abort)(void *ctx);                                  ///< callback to be called at suspend check fail
} osiPmSourceOps_t;

/**
 * PM source opaque data struct
 */
typedef struct osiPmSource osiPmSource_t;

/**
 * \brief power management module initialization
 *
 * It should be called in early stage of boot, due to many other modules
 * may register suspend and resume callback at initialization.
 */
void osiPmInit(void);

/**
 * \brief power management core start
 *
 * To avoid suspend too early, PM core won't be started at initialization
 * automatically. After system is initialized, and necessary PM sources
 * are created, \p osiPmStart shall be called.
 *
 * Only after \p osiPmStart is called, PM core will check and enter suspend.
 */
void osiPmStart(void);

/**
 * \brief power management core stop
 *
 * Stop PM core, and systen will never suspend.
 *
 * It is only for debug. And it shouldn't be used in real application.
 */
void osiPmStop(void);

/**
 * \brief create PM source
 *
 * Modules are distinguished by FOURCC *tag*. So, tag should be unique
 * system wise.
 *
 * When the *tag* is already registered, it will return existed PM source.
 *
 * \p ops is permitted to be NULL, and all callbacks inside it are permitted
 * to be NULL.
 *
 * Resume callbacks are called in create order, suspend callbacks are called
 * in revered order. When resume order does matter, call \p osiPmResumeReorder
 * or \p osiPmResumeFirst to change resume order.
 *
 * The returned pointer should be destroyed and freed by \p osiPmSourceDelete.
 *
 * PM source lock and unlock is binary. That is, there is no *counter* inside.
 * When \p osiPmWakeUnlock is called, the PM source won't prevent system
 * suspend, no matter how many times of \p osiPmWakeLock is called.
 *
 * \param tag       module tag
 * \param ops       callbacks during suspend and resume
 * \param ctx       callback context, shared by all callbacks
 * \return
 *      - PM source pointer
 *      - NULL on out of memory
 */
osiPmSource_t *osiPmSourceCreate(uint32_t tag, const osiPmSourceOps_t *ops, void *ctx);

/**
 * \brief destroy PM source
 *
 * \param ps        PM source
 */
void osiPmSourceDelete(osiPmSource_t *ps);

/**
 * \brief ensure resume callback order
 *
 * In cases that the resume callback order is important, this API will check
 * and change resume callback order if needed. After the call, the resumed
 * order is ensured.
 *
 * When resume callback order is changed, suspend callback order is changed
 * also.
 *
 * It won't be checked whether the tags are valid.
 *
 * \param tag_later     module tag to be resumed laster
 * \param tag_earlier   module tag to be resumed earlier
 */
void osiPmResumeReorder(uint32_t tag_later, uint32_t tag_earlier);

/**
 * \brief move PM source to the first in resume order
 *
 * Find the PM source, and move it to the head of resume list. When PM source
 * with \p tag is not found in resume list, nothing will be done.
 *
 * \param tag       module tag to be moved
 */
void osiPmResumeFirst(uint32_t tag);

/**
 * \brief wake lock to prevent suspend
 *
 * Indicate the PM source will prevent system suspend. PM source is *NOT*
 * counting, \p osiPmWakeLock will just set the PM source state. Multiple
 * calls of \p osiPmWakeLock is equivalent to a single call.
 *
 * When \p ops.prepare is not NULL, the internal state of PM source is
 * *suspend possible* rather than *active*. When system wants to
 * suspend, \p ops.prepare will be called to double check whether
 * suspend is permitted.
 *
 * \param ps        PM source
 */
void osiPmWakeLock(osiPmSource_t *ps);

/**
 * \brief wake unlock to permit suspend
 *
 * Indicate the PM source won't prevent system suspend. PM source is *NOT*
 * counting. No matter how many calls of \p osiPmWakeLock are called,
 * \p osiPmWakeUnlock will set the PM source to unlock state.
 *
 * \param ps        PM source
 */
void osiPmWakeUnlock(osiPmSource_t *ps);

/**
 * \brief set 32K sleep flag
 *
 * For platforms support both 32K sleep and suspend, the default sleep
 * mode is suspend. When it is needed to use 32K sleep forcedly, it is
 * needed to call this API.
 *
 * Each bit of \p flag represents a request source. When there are any
 * 32K sleep requests, system will use 32K sleep rather than suspend.
 *
 * The 32K sleep request sources depend on platform. And they are
 * defined in platform dependent hal_chip.h.
 *
 * \param flag      32K sleep request flag
 */
void osiSet32KSleepFlag(uint32_t flag);

/**
 * \brief clear 32K sleep flag
 *
 * \param flag      32K sleep request flag
 */
void osiClear32KSleepFlag(uint32_t flag);

/**
 * \brief register a shutdown callback
 *
 * When the callback and context is already registered, it will return
 * false.
 *
 * The order to invoke callbacks is undefined.
 *
 * *Don't* call \p osiRegisterShutdownCallback or
 * \p osiUnregisterShutdownCallback inside the callbacks.
 *
 * \param cb        shutdown callback to be registered, can't be NULL
 * \param ctx       shutdown callback context
 * \return
 *      - true if callback registered
 *      - false if callback is already registered, or callback is NULL
 */
bool osiRegisterShutdownCallback(osiShutdownCallback_t cb, void *ctx);

/**
 * \brief unregister a shutdown callback
 *
 * \param cb        shutdown callback to be unregistered, can't be NULL
 * \param ctx       shutdown callback context
 * \return
 *      - true if callback unregistered
 *      - false if callback isn't found
 */
bool osiUnregisterShutdownCallback(osiShutdownCallback_t cb, void *ctx);

/**
 * \brief set PSM sleep ready
 *
 * \param start_time    the start time of psm request, in uptime
 * \param sleep_time    psm sleep time
 */
void osiSetPsmSleepReady(int64_t start_time, int64_t sleep_time);

/**
 * \brief set PSM sleep not ready
 */
void osiSetPsmSleepNotReady(void);

/**
 * \brief whether PSM sleep is ready
 *
 * \return
 *      - true if PSM sleep is ready
 *      - false if PSM sleep is not ready
 */
bool osiIsPsmSleepReady(void);

/**
 * \brief get PSM elapsed time in milliseconds
 *
 * The elapsed time is starting from \p start_time parameter of
 * \p osiSetPsmSleepReady.
 *
 * \return
 *      - PSM elsapsed time
 *      - 0 if PSM is not ready
 */
int64_t osiGetPsmElapsedTime(void);

/**
 * \brief get cp deep sleep time in milliseconds
 *
 * \return      cp deep sleep time
 */
uint64_t osiCpDeepSleepTime(void);

/**
 * \brief shutdown to specified mode
 *
 * When parameter is wrong, it will return false:
 * - When \p mode is OSI_SHUTDOWN_PSM_SLEEP, and PSM isn't enabled.
 *   Caller should check whether PSM is enabled before calling
 *   \p osiShutdown.
 * - When \p mode is not supported in the platform.
 *
 * It never return true.
 *
 * \param mode      shutdown mode
 * \return
 *      - false on parameter error
 */
bool osiShutdown(osiShutdownMode_t mode);

/**
 * \brief get boot causes
 *
 * It is possible there are multiple boot causes. The returned value is
 * bit or of all boot causes.
 *
 * It is possible hardware registers will be cleared after accessed. So,
 * always call \p osiGetBootCauses to get the boot causes, rather than
 * accessing hardware registers directly.
 *
 * \return  boot causes
 */
uint32_t osiGetBootCauses(void);

/**
 * \brief set a boot cause
 *
 * It is intended only for system integration.
 *
 * \param cause     boot cause
 */
void osiSetBootCause(osiBootCause_t cause);

/**
 * \brief clear a boot cause
 *
 * It is intended only for system integration.
 *
 * \param cause     boot cause
 */
void osiClearBootCause(osiBootCause_t cause);

/**
 * \brief get boot mode
 *
 * \return  boot mode
 */
osiBootMode_t osiGetBootMode(void);

/**
 * \brief set boot mode
 *
 * It is intended only for system integration. At calling, caller should
 * take care conflict of other boot mode detection.
 *
 * \param mode      boot mode
 */
void osiSetBootMode(osiBootMode_t mode);

/**
 * \brief PSM restore
 *
 * It should be called in system initialization, after file system
 * initialization.
 */
void osiPsmRestore(void);

/**
 * \brief save PSM data
 *
 * PSM data save is implemented in platform. PSM data should be saved in
 * persistent storage, which can be flash or aon memory. Caller shouldn't
 * assume the storage type.
 *
 * It should only be called in \p osiShutdown callbacks.
 *
 * For each owner, \p osiPsmDataSave shall be called only once at most.
 *
 * It is recommended to use fixed size buffer, which is the same at each
 * PSM shutdown. However, variable size buffer is also supported.
 *
 * As a special case, \p size of 0 is permitted. It just keep record that
 * the owner is saved without real data.
 *
 * \param owner     PSM data owner
 * \param buf       owner's PSM data buffer
 * \param size      owner's PSM data buffer size
 * \return
 *      - true on success
 *      - false on fail
 *          - duplicated owner
 *          - out of memory
 */
bool osiPsmDataSave(osiPsmDataOwner_t owner, const void *buf, uint32_t size);

/**
 * \brief restore PSM data
 *
 * Restore PSM data. At PSM resume boot, PSM data owner calls this to get
 * the data saved by \p osiPsmDataSave
 *
 * The buffer size should be equal or larger than the saved size. If it is
 * smaller than saved size, return -1.
 *
 * When \p buf is NULL, it will return PSM data size without copy. It can
 * be used to get the PSM data size.
 *
 * \param owner     PSM data owner
 * \param buf       buffer for owner's PSM data
 * \param size      buffer size
 * \return
 *      - PSM data size on success
 *      - 0 if there are no PSM data
 *      - -1 on error
 */
int osiPsmDataRestore(osiPsmDataOwner_t owner, void *buf, uint32_t size);

/**
 * send out debug event to trace tool
 *
 * If system and trace tool support debug event, this will send
 * a word to trace tool.
 *
 * It should be only used in quick debug. It is possible that platform
 * can't support debug event, and debug event output may be turned off
 * by compiling option.
 *
 * It is possible that debughost may be blocked. And then debughost event
 * sent will timeout.
 *
 * NOTE: Debug event output will take times. For example, when debug host
 * baud rate is 921600, it may take up to 109us. And when debughost is
 * blocked, it may take 200us for timeout.
 *
 * \param event     word which will appear on trace tool
 * \return
 *      - true if the event is sent
 *      - false if the event sent timeout
 */
bool osiDebugEvent(uint32_t event);

/**
 * panic
 *
 * Called on fatal error, and system can't go on.
 *
 * It is different from \p assert. It will always cause system panic,
 * and there are no compiling option to ignore it.
 */
void osiPanic(void);

/**
 * panic with specified address
 *
 * It is the same as \p osiPanic, except \p address is shown in log,
 * rather than the return address of caller.
 */
void osiPanicAt(void *address);

/**
 * panic with no_return keyword
 *
 * Called on fatal error, and system can't go on.
 *
 * It is used for posix API, eg exit and abort
 */
OSI_NO_RETURN void osiPanicPosix(void);

/**
 * whether system is in panic mode
 *
 * In panic mode, system will still run a small daemon. And there is no
 * interrupt in panic mode. So, features used in panic daemon shall take
 * care of this.
 *
 * \return
 *      - true if system is in panic mode
 *      - false if system is not in panic mode
 */
bool osiIsPanic(void);

/**
 * register enter and polling callback at blue screen mode
 *
 * The maximum panic handler is pre-configured.
 *
 * \param enter         callback to be caled when entering panic mode
 * \param poll          callback to be caled when during panic mode
 * \param param         callback parameter
 * \return
 *      - true on success
 *      - false on fail, invalid paramters or too many handlers
 */
bool osiRegisterBlueScreenHandler(osiCallback_t enter, osiCallback_t poll, void *param);

/**
 * optional assert
 *
 * It is a macro. When \p CONFIG_KERNEL_ASSERT_ENABLED is defined, it will
 * panic when \p expect_true is not true. When \p CONFIG_KERNEL_ASSERT_ENABLED
 * is not defined, it will be expanded as empty.
 *
 * \p info is just a remainder in source code. It won't be used.
 */
#ifdef CONFIG_KERNEL_ASSERT_ENABLED
#define OSI_ASSERT(expect_true, info) OSI_DO_WHILE0(if (!(expect_true)) osiPanic();)
#else
#define OSI_ASSERT(expect_true, info)
#endif

/**
 * busy loop delay
 *
 * The precision of delay depends on platform. And it will ensure to delay
 * at least the specified time.
 *
 * \param us    delay time in microseconds
 */
OSI_NO_MIPS16 void osiDelayUS(uint32_t us);

/**
 * delay by CPU loop
 *
 * The absolute delay time depends on CPU frequency. \p count is the loop
 * count, not the delayed instruction count.
 *
 * \param count     delay loop count
 */
void osiDelayLoops(uint32_t count);

/**
 * call function with specified stack
 *
 * This will only be called with low level codes, and most likely, it is not
 * needed for application.
 *
 * This function is located in SRAM. So, it can be called even the external
 * RAM in unavailable.
 *
 * \p function shall have 2 parameters at most. It can return one word. When
 * \p function doesn't return value, the return value of this wrapper is
 * undefined.
 *
 * \p sp should be 8 bytes aligned.
 *
 * \param sp        the stack to be used for \p function
 * \param function  the real function
 * \param param0    the first parameter for \p function
 * \param param1    the second parameter for \p function
 * \return
 *      - the return value of \p function
 */
uint32_t osiCallWithStack(void *sp, void *function, uint32_t param0, uint32_t param1);

#ifdef __cplusplus
}
#endif
#endif
