mirror of
https://github.com/Ai-Thinker-Open/Ai-Thinker-Open_RTL8710BX_ALIOS_SDK.git
synced 2026-07-13 21:45:38 +00:00
rel_1.6.0 init
This commit is contained in:
commit
27b3e2883d
19359 changed files with 8093121 additions and 0 deletions
24
Living_SDK/kernel/pwrmgmt/Config.in
Executable file
24
Living_SDK/kernel/pwrmgmt/Config.in
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
menuconfig AOS_COMP_PWRMGMT
|
||||
bool "pwrmgmt"
|
||||
default n
|
||||
help
|
||||
|
||||
if AOS_COMP_PWRMGMT
|
||||
# Configurations for component pwrmgmt
|
||||
config PWRMGMT_CONFIG_SHOW
|
||||
int "PWRMGMT_CONFIG_SHOW"
|
||||
range 0 1
|
||||
default 0
|
||||
help
|
||||
set to 1 if you want to call state show function:enable cpu_pwr_c_support_print,
|
||||
cpu_pwr_info_show,cpu_pwr_state_show.
|
||||
default 0
|
||||
|
||||
config PWRMGMT_CONFIG_DEBUG
|
||||
int "PWRMGMT_CONFIG_DEBUG"
|
||||
range 0 1
|
||||
default 0
|
||||
help
|
||||
set to 1 if you want to show debug infomation.
|
||||
default 0
|
||||
endif
|
||||
20
Living_SDK/kernel/pwrmgmt/README.md
Executable file
20
Living_SDK/kernel/pwrmgmt/README.md
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
## Contents
|
||||
|
||||
## Introduction
|
||||
**pwrmgmt** power management module of AliOS Things
|
||||
|
||||
### Features
|
||||
- power management feature
|
||||
|
||||
### Directories
|
||||
|
||||
```sh
|
||||
pwrmgmt
|
||||
├─ cpu_pwr_lib.c # API implementation of power management module
|
||||
├─ cpu_tickless.c # tickless strategy
|
||||
├─ cpu_pwr_hal_lib.c # hal layer of power management module
|
||||
├─ cpu_pwr_show.c # used for infomation show
|
||||
```
|
||||
### Dependencies
|
||||
|
||||
## Reference
|
||||
935
Living_SDK/kernel/pwrmgmt/cpu_pwr_hal_lib.c
Executable file
935
Living_SDK/kernel/pwrmgmt/cpu_pwr_hal_lib.c
Executable file
|
|
@ -0,0 +1,935 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
/*
|
||||
This file provides Hardware Abstraction Layer of CPU power management support.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cpu_pwr_lib.h"
|
||||
#include "cpu_pwr_hal_lib.h"
|
||||
#include "pwrmgmt_api.h"
|
||||
#include "pwrmgmt_debug.h"
|
||||
|
||||
#define CPU_TREE_MUX_LOCK() \
|
||||
(void)krhino_mutex_lock(&cpu_pwr_hal_mux, RHINO_WAIT_FOREVER)
|
||||
#define CPU_TREE_MUX_UNLOCK() (void)krhino_mutex_unlock(&cpu_pwr_hal_mux)
|
||||
|
||||
/* the index of this p_cpu_node_array implies the cpu logic id */
|
||||
cpu_pwr_t *p_cpu_node_array[RHINO_CONFIG_CPU_NUM];
|
||||
static kmutex_t cpu_pwr_hal_mux;
|
||||
static kspinlock_t cpu_pwr_lock;
|
||||
|
||||
/*
|
||||
board_cpu_pwr_init() must be provided by board or platform,
|
||||
different board/platform may has different cpu topology.
|
||||
*/
|
||||
extern pwr_status_t board_cpu_pwr_init(void);
|
||||
static int num_of_bit_one_get(uint32_t n);
|
||||
|
||||
cpu_pwr_t *cpu_pwr_node_find_by_name(char *p_name, uint32_t index)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (i = 0; i < RHINO_CONFIG_CPU_NUM; i++) {
|
||||
if (p_cpu_node_array[i]->name == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((!strncmp(p_cpu_node_array[i]->name, p_name, CPU_PWR_NODE_NAME_LEN)) &&
|
||||
(p_cpu_node_array[i]->unit == index)) {
|
||||
/* find it, and return */
|
||||
return p_cpu_node_array[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pwr_status_t cpu_pwr_node_init_(char *name, uint32_t unit, cpu_pwr_t *p_cpu_node)
|
||||
{
|
||||
if (name == NULL || p_cpu_node == NULL) {
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
memset(p_cpu_node, 0, sizeof(cpu_pwr_t));
|
||||
|
||||
p_cpu_node->name = name;
|
||||
p_cpu_node->unit = unit;
|
||||
|
||||
p_cpu_node->current_c_state = CPU_CSTATE_NONE;
|
||||
p_cpu_node->support_bitset_c = 0;
|
||||
|
||||
PWR_DBG(DBG_INFO, "init %s%d done\n", name, unit);
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_node_init_static will create and initialize a cpu power management
|
||||
* node according the given level number, node name and node unit.
|
||||
*
|
||||
* This routine is normally used to create cpu power management topology tree by
|
||||
* boards/platform.
|
||||
*
|
||||
* For example, cpu_pwr_node_init_static(2, "core", 0, p_cpu_node) will init a
|
||||
* cpu power management node named "core0" in level2 of the cpu power management
|
||||
* topology tree, and return the struction point address, but it do not belong
|
||||
* to any other node and do not has any child at this time,
|
||||
* cpu_pwr_child_add(p_parent, p_child) could be used to create relationship
|
||||
* with other node in the topology tree.
|
||||
*
|
||||
* the space of p_cpu_node is provided by caller.
|
||||
*/
|
||||
pwr_status_t cpu_pwr_node_init_static(char *name, uint32_t unit, cpu_pwr_t *p_cpu_node)
|
||||
{
|
||||
if (name == NULL || p_cpu_node == NULL) {
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
return cpu_pwr_node_init_(name, unit, p_cpu_node);
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_node_init_dyn() is same as cpu_pwr_node_init_static(),
|
||||
* the difference is the space of pp_cpu_node is mallocced in
|
||||
* running time.
|
||||
*/
|
||||
pwr_status_t cpu_pwr_node_init_dyn(char *name, uint32_t unit, cpu_pwr_t **pp_cpu_node)
|
||||
{
|
||||
cpu_pwr_t *p_cpu_node;
|
||||
|
||||
if (name == NULL || pp_cpu_node == NULL) {
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
p_cpu_node = (cpu_pwr_t *)malloc(sizeof(cpu_pwr_t));
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "malloc failed\n");
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
*pp_cpu_node = p_cpu_node;
|
||||
|
||||
return cpu_pwr_node_init_(name, unit, *pp_cpu_node);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* cpu_pwr_node_record - record the cpu power management node.
|
||||
* The node information is setup by this way:
|
||||
* a) a array named p_cpu_node_array[32] with 32 point(type is cpu_pwr_t *) is
|
||||
* created in cpu_pwr_hal_lib_init();
|
||||
* b) all the 32 point is set to NULL in cpu_pwr_hal_lib_init();
|
||||
* c) when BSP/platform init the cpu pwr, it knows the cpu index information of
|
||||
* that cpu, BSP/platform should call cpu_pwr_node_record(p_cpu_node, cpu_idx)
|
||||
* to save the cpu_pwr_t * information of cpu indicated by cpu_idx into
|
||||
* p_cpu_node_array[cpu_idx];
|
||||
*
|
||||
* When there is requirement to find the cpu_pwr_t * according the cpu_idx,
|
||||
* just return the p_cpu_node_array[cpu_idx] is fine and save much time.
|
||||
*
|
||||
* @return PWR_OK or PWR_ERR when failed.
|
||||
*/
|
||||
pwr_status_t cpu_pwr_node_record(cpu_pwr_t *p_cpu_node, uint32_t cpu_idx)
|
||||
{
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "p_cpu_node == NULL\n");
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
if (cpu_idx >= RHINO_CONFIG_CPU_NUM) {
|
||||
PWR_DBG(DBG_ERR, "cpu_idx(%d) error, it should be 0 ~ %d\n", cpu_idx,
|
||||
RHINO_CONFIG_CPU_NUM - 1);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
if (p_cpu_node_array[cpu_idx] != NULL) {
|
||||
PWR_DBG(DBG_ERR, "p_cpu_node_array[%d] != NULL\n", cpu_idx);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* set the flag to a default value */
|
||||
p_cpu_node->cstate_updating = FALSE;
|
||||
|
||||
p_cpu_node_array[cpu_idx] = p_cpu_node;
|
||||
|
||||
PWR_DBG(DBG_INFO, "%s%d connect to logic id %d done\n", p_cpu_node->name,
|
||||
p_cpu_node->unit, cpu_idx);
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_c_method_set() set the method of controlling C state,
|
||||
*normally it will be called by platform in board_cpu_pwr_init().
|
||||
*
|
||||
* @renturn PWR_OK or PWR_ERR when failed.
|
||||
*/
|
||||
pwr_status_t cpu_pwr_c_method_set(uint32_t cpu_idx, cpu_cstate_set_t cpu_cstate_set_func)
|
||||
{
|
||||
cpu_pwr_t *p_cpu_node;
|
||||
pwr_status_t ret = PWR_OK;
|
||||
|
||||
if (cpu_idx >= RHINO_CONFIG_CPU_NUM) {
|
||||
PWR_DBG(DBG_ERR, "cpu_idx(%d) error, it should be 0 ~ %d\n", cpu_idx,
|
||||
RHINO_CONFIG_CPU_NUM - 1);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* fetch the p_cpu_node directly from p_cpu_node_array according cpu_idx */
|
||||
p_cpu_node = p_cpu_node_array[cpu_idx];
|
||||
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "did not find p_cpu_node in p_cpu_node_array "
|
||||
"with cpu_idx[%d]\n", cpu_idx);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
CPU_TREE_MUX_LOCK();
|
||||
|
||||
/* bind P state set/get method into this node */
|
||||
p_cpu_node->cpu_cstate_set_func = cpu_cstate_set_func;
|
||||
|
||||
/* set C state to C0 by default, is here the right place ?*/
|
||||
p_cpu_node->current_c_state = CPU_CSTATE_C0;
|
||||
|
||||
PWR_DBG(DBG_INFO, "set CPU(%s%d) C-state-set(function) to 0x%08x\n",
|
||||
p_cpu_node->name, p_cpu_node->unit, cpu_cstate_set_func);
|
||||
PWR_DBG(DBG_INFO, "set CPU(%s%d) current_c_state to C%d\n",
|
||||
p_cpu_node->name, p_cpu_node->unit, p_cpu_node->current_c_state);
|
||||
CPU_TREE_MUX_UNLOCK();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static pwr_status_t cpu_pwr_c_state_set_(cpu_pwr_t *p_cpu_node,
|
||||
cpu_cstate_t cpu_c_state, int master,
|
||||
int all_cores_need_sync)
|
||||
{
|
||||
pwr_status_t ret = PWR_OK;
|
||||
uint32_t state_c_match = 0;
|
||||
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "PWR_ERR: p_cpu_node == NULL\n");
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
state_c_match = (CPU_STATE_BIT(cpu_c_state) & p_cpu_node->support_bitset_c);
|
||||
|
||||
if ((p_cpu_node->cpu_cstate_set_func != NULL) && (state_c_match == 0)) {
|
||||
PWR_DBG(DBG_INFO,
|
||||
"%s%d(with supportCBitset[0x%08x]) "
|
||||
"do not support C state P%d\n",
|
||||
p_cpu_node->name, p_cpu_node->unit,
|
||||
p_cpu_node->support_bitset_c, cpu_c_state);
|
||||
}
|
||||
|
||||
/*
|
||||
* If current p_cpu_node support cpu_cstate_set_func() and match the C state
|
||||
* which it support, use it directly and update C state of all the children
|
||||
* at same time.
|
||||
*/
|
||||
if ((p_cpu_node->cpu_cstate_set_func != NULL) && (state_c_match != 0)) {
|
||||
|
||||
/*
|
||||
* The new C state should be recorded before calling the real C
|
||||
* control method, the reason is when C0->C1~Cn, the CPU will stop
|
||||
* to execute the instruction.
|
||||
*/
|
||||
|
||||
/* update the current C state */
|
||||
p_cpu_node->current_c_state = cpu_c_state;
|
||||
|
||||
/* hold input C state into desire_c_state */
|
||||
p_cpu_node->desire_c_state = cpu_c_state;
|
||||
|
||||
/*
|
||||
* cpu_c_state == CPU_CSTATE_C1, C1 state must a independent
|
||||
* state that each core support, each core could enter/exit
|
||||
* C1 state independently, so each core could update it's C1
|
||||
* state independently;
|
||||
*
|
||||
* C2/C3 or higher is dependent on platfrom/arch, some platform
|
||||
* support C2/C3 per-core, but some platform do not support.
|
||||
* all_cores_need_sync is assert here to check this information,
|
||||
* 1) all_cores_need_sync == TRUE, means that this Cx state is not
|
||||
* per-core independent, it need all cores enter/exit this Cx
|
||||
* state at same time.
|
||||
* 2) all_cores_need_sync == FALSE, means that this Cx state is
|
||||
* pre-core independent, it could enter/exit this Cx state
|
||||
* independently.
|
||||
*
|
||||
* When all_cores_need_sync is TRUE, we leave the responsibility
|
||||
* to master(means the last core enter idle mode) to update
|
||||
* all cores C state of Cx(C2/C3 or higher).
|
||||
*/
|
||||
|
||||
/* call the real C idle method */
|
||||
ret = p_cpu_node->cpu_cstate_set_func(cpu_c_state, master);
|
||||
|
||||
if (ret != PWR_OK) {
|
||||
PWR_DBG(DBG_ERR, "%s%d -> cpu_cstate_set_func(P%d) failed\n",
|
||||
p_cpu_node->name, p_cpu_node->unit, cpu_c_state);
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* if we
|
||||
* 1) - do not support cpu_cstate_set_func()
|
||||
* or
|
||||
* 2) - the request C state is not support
|
||||
*/
|
||||
|
||||
PWR_DBG(DBG_INFO, "p_cpu_node(%s%d) do not support cpu_cstate_set_func or the request C state is not support\n",
|
||||
p_cpu_node->name, p_cpu_node->unit);
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_c_state_get - get C state of CPU indicated by cpu_idx
|
||||
*
|
||||
* This routine will get C state of CPU indicated by cpu_idx, the C state will
|
||||
* return in pCpuCStatus.
|
||||
*
|
||||
* @return PWR_OK or PWR_ERR when failed.
|
||||
*/
|
||||
static pwr_status_t _cpu_pwr_c_state_get(uint32_t cpu_idx,
|
||||
cpu_cstate_t *p_cpu_c_state)
|
||||
{
|
||||
cpu_pwr_t *p_cpu_node;
|
||||
|
||||
if (cpu_idx >= RHINO_CONFIG_CPU_NUM) {
|
||||
PWR_DBG(DBG_ERR, "cpu_idx(%d) error, it should be 0 ~ %d\n", cpu_idx,
|
||||
RHINO_CONFIG_CPU_NUM - 1);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* fetch the p_cpu_node directly from p_cpu_node_array according cpu_idx */
|
||||
p_cpu_node = p_cpu_node_array[cpu_idx];
|
||||
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "did not find p_cpu_node in p_cpu_node_array "
|
||||
"with cpu_idx[%d]\n", cpu_idx);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
*p_cpu_c_state = p_cpu_node->current_c_state;
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
pwr_status_t cpu_pwr_c_state_get(uint32_t cpu_idx, cpu_cstate_t *p_cpu_c_state)
|
||||
{
|
||||
pwr_status_t ret;
|
||||
|
||||
krhino_spin_lock_irq_save(&cpu_pwr_lock);
|
||||
ret = _cpu_pwr_c_state_get(cpu_idx, p_cpu_c_state);
|
||||
krhino_spin_unlock_irq_restore(&cpu_pwr_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* cpu_pwr_c_state_set - put CPU into given C state
|
||||
*
|
||||
* This routine will put CPU into given C state, under SMP environment,
|
||||
* cpu_idx is fetched locally from cpu_index_get which implies that
|
||||
* this function is always applied to the current core.
|
||||
* This function has to be called on the CPU for which the C state is being set.
|
||||
* the caller has to ensure that the code will be executed on the same core
|
||||
* (such as called by task with CPU affinity or from ISR context).
|
||||
*
|
||||
* @return PWR_OK or PWR_ERR when failed.
|
||||
*/
|
||||
static pwr_status_t _cpu_pwr_c_state_set(cpu_cstate_t target_c_state)
|
||||
{
|
||||
uint32_t cpu_idx;
|
||||
cpu_pwr_t * p_cpu_node;
|
||||
pwr_status_t ret = PWR_OK;
|
||||
int master;
|
||||
|
||||
cpu_idx = 0;
|
||||
|
||||
/* fetch the p_cpu_node directly from p_cpu_node_array according cpu_idx */
|
||||
p_cpu_node = p_cpu_node_array[cpu_idx];
|
||||
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "did not find p_cpu_node in p_cpu_node_array with cpu_idx[%d]\n",
|
||||
cpu_idx);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
if (p_cpu_node->current_c_state == target_c_state) {
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* at any time for one CPU, only one C state updating process is allowed,
|
||||
* when a C state updating is going on, it set cstate_updating to 1,
|
||||
* if the other thread(a task or interrupt) try to start other C state
|
||||
* updating, it will encount cstate_updating == 1, and should
|
||||
* return immediately.
|
||||
*
|
||||
* a exception should be consider, idle task()->cpu_pwr_down()->
|
||||
* cpu_pwr_c_state_set(CPU_CSTATE_C1(C2,C3,...)) will make
|
||||
* CPU stopping execute instruction and never return, which will
|
||||
* leave cstate_updating as TRUE and never be cleaned to FALSE.
|
||||
*
|
||||
* when cpu_pwr_c_state_set(CPU_CSTATE_C0) is called again
|
||||
* to make C state changed from C1(C2,C3,...) to C0, we check
|
||||
* the value of cstate_updating, if it is TURE, which implies that
|
||||
* cstate_updating is not cleaned in previous state change(from C0
|
||||
* to C1,C2,C3,...), cstate_updating will be forced to FALSE.
|
||||
*/
|
||||
|
||||
if (target_c_state == CPU_CSTATE_C0 && p_cpu_node->current_c_state != CPU_CSTATE_C0
|
||||
&& p_cpu_node->cstate_updating == TRUE) {
|
||||
|
||||
p_cpu_node->cstate_updating = FALSE;
|
||||
}
|
||||
|
||||
if (p_cpu_node->cstate_updating == FALSE) {
|
||||
p_cpu_node->cstate_updating = TRUE;
|
||||
|
||||
if (target_c_state == CPU_CSTATE_C0) {
|
||||
|
||||
/*
|
||||
* cpu_c_state == CPU_CSTATE_C0, which means current call
|
||||
* stack is:
|
||||
* krhino_intrpt_enter() -> cpu_pwr_up() -> cpu_pwr_c_state_set(C0)
|
||||
* what we need to do here is set current_c_state/desire_c_state
|
||||
* to C0.
|
||||
* For the first core which wakeup there maybe need some
|
||||
* prepare work need to do, so the master(the first one
|
||||
* open door) should be distinguished here.
|
||||
*/
|
||||
|
||||
/*
|
||||
* for UP, we are always the master, because there is
|
||||
* only one core in system.
|
||||
*/
|
||||
master = TRUE;
|
||||
|
||||
/*
|
||||
* master == TRUE, means we are the first one wakeup,
|
||||
* a liite more work need to do than other cores here
|
||||
* is update all the parent to C0 state. And send IPI
|
||||
* to other cores to update it's self Cx state when
|
||||
* wake up from C2 or high Cx state(C3/C4...).
|
||||
*/
|
||||
|
||||
if (master == TRUE) {
|
||||
/* call the real C control method */
|
||||
|
||||
if (p_cpu_node->cpu_cstate_set_func != NULL) {
|
||||
|
||||
/*
|
||||
* Note, the master value is TRUE here,
|
||||
* cpu_cstate_set_func() could take proper action
|
||||
* based on this value, because we are the first
|
||||
* core wake up.
|
||||
*/
|
||||
ret = p_cpu_node->cpu_cstate_set_func(target_c_state, master);
|
||||
if (ret != PWR_OK) {
|
||||
PWR_DBG(DBG_ERR, "%s%d -> cpu_cstate_set_func(P%d) failed\n",
|
||||
p_cpu_node->name, p_cpu_node->unit, target_c_state);
|
||||
ret = PWR_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* update C state to C0 */
|
||||
p_cpu_node->current_c_state = CPU_CSTATE_C0;
|
||||
p_cpu_node->desire_c_state = CPU_CSTATE_C0;
|
||||
|
||||
} else {
|
||||
/*
|
||||
* If come here, we must be subsequent core wake up
|
||||
* after first core, what we need to do is updating
|
||||
* self state to C0 and call function if need,
|
||||
* normmally cpu_cstate_set_func() will do nothing here.
|
||||
*/
|
||||
if (p_cpu_node->cpu_cstate_set_func != NULL) {
|
||||
ret = p_cpu_node->cpu_cstate_set_func(target_c_state, master);
|
||||
if (ret != PWR_OK) {
|
||||
PWR_DBG(DBG_ERR, "%s%d -> cpu_cstate_set_func(P%d) failed\n",
|
||||
p_cpu_node->name, p_cpu_node->unit, target_c_state);
|
||||
|
||||
ret = PWR_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
p_cpu_node->current_c_state = CPU_CSTATE_C0;
|
||||
p_cpu_node->desire_c_state = CPU_CSTATE_C0;
|
||||
}
|
||||
} else {
|
||||
|
||||
/*
|
||||
* if come here, cpu_c_state must is not CPU_CSTATE_C0,
|
||||
* it must be C1 or high C2/C3..., it must be going
|
||||
* to idle Cx mode.
|
||||
*/
|
||||
|
||||
/*
|
||||
* for UP, we are always the master, because there is
|
||||
* only one core in system.
|
||||
*/
|
||||
master = TRUE;
|
||||
|
||||
ret = cpu_pwr_c_state_set_(p_cpu_node, target_c_state, master, FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* after C state change completed, it should set cstate_updating
|
||||
* to 0 to allow C state updating again.
|
||||
*
|
||||
* a exception should be consider, idletask()->cpu_pwr_down()->
|
||||
* cpu_pwr_c_state_set(CPU_CSTATE_C1(C2,C3,...)) will make
|
||||
* CPU stopping execute instruction and never return, which will
|
||||
* lead this line never be executed and leave cstate_updating as TRUE.
|
||||
*
|
||||
* when cpu_pwr_c_state_set(CPU_CSTATE_C0) is called again
|
||||
* to make C state changed from C1(C2,C3,...) to C0, we check
|
||||
* the value of cstate_updating, if it is TURE, which implies that
|
||||
* cstate_updating is not cleaned in previous state change(from C0
|
||||
* to C1,C2,C3,...), cstate_updating will be forced to FALSE.
|
||||
*/
|
||||
p_cpu_node->cstate_updating = FALSE;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
pwr_status_t cpu_pwr_c_state_set(cpu_cstate_t target_c_state)
|
||||
{
|
||||
pwr_status_t ret;
|
||||
|
||||
krhino_spin_lock_irq_save(&cpu_pwr_lock);
|
||||
ret = _cpu_pwr_c_state_set(target_c_state);
|
||||
krhino_spin_unlock_irq_restore(&cpu_pwr_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static pwr_status_t cpu_pwr_c_state_capability_set_(cpu_pwr_t *p_cpu_node,
|
||||
int cpu_idx, int32_t support_bitset_c)
|
||||
{
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "p_cpu_node == NULL\n");
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* if this level support setting the P state, it implies that it
|
||||
maintain the support_bitset_p value. */
|
||||
if (p_cpu_node->cpu_cstate_set_func != NULL) {
|
||||
p_cpu_node->support_bitset_c = support_bitset_c;
|
||||
|
||||
PWR_DBG(DBG_INFO, "p_cpu_node(%s%d) support_bitset_c = 0x%016lx\n",
|
||||
p_cpu_node->name, p_cpu_node->unit, p_cpu_node->support_bitset_c);
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
PWR_DBG(DBG_ERR, "PWR_ERR: p_cpu_node(%s%d) support_bitset_c = 0x%016lx\n",
|
||||
p_cpu_node->name, p_cpu_node->unit, p_cpu_node->support_bitset_c);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
static pwr_status_t cpu_pwr_c_state_capability_get_(cpu_pwr_t *p_cpu_node, int cpu_idx,
|
||||
uint32_t *p_support_bitset_c)
|
||||
{
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "p_cpu_node == NULL\n");
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/*
|
||||
* if this level support setting the C state, it implies that it
|
||||
* maintain the support_bitset_c value.
|
||||
*/
|
||||
if (p_cpu_node->cpu_cstate_set_func != NULL) {
|
||||
*p_support_bitset_c |= p_cpu_node->support_bitset_c;
|
||||
|
||||
PWR_DBG(DBG_INFO, "p_cpu_node(%s%d) support_bitset_c = 0x%016lx\n",
|
||||
p_cpu_node->name, p_cpu_node->unit, p_cpu_node->support_bitset_c);
|
||||
|
||||
/* do not return, continue check if any support C state from parent */
|
||||
}
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_c_state_capability_set() will set the C state supported of given CPU.
|
||||
* There is two way to set the C state of CPU which is supplied by this library,
|
||||
* one is according cpu index, other is according cpu level.
|
||||
*
|
||||
* If all the cpu node in same level support same C state, user can use
|
||||
* cpu_pwr_c_state_capability_set_by_level(level, support_bitset_c) to set all
|
||||
* nodes in same level at one time.
|
||||
* If different CPU support different C state, user can use this routine to set
|
||||
* support C state for each cpu individually.
|
||||
*
|
||||
* @return PWR_OK or PWR_ERR when failed.
|
||||
*/
|
||||
pwr_status_t cpu_pwr_c_state_capability_set(uint32_t cpu_idx, uint32_t support_bitset_c)
|
||||
{
|
||||
cpu_pwr_t *p_cpu_node;
|
||||
pwr_status_t ret = PWR_OK;
|
||||
|
||||
if (cpu_idx >= RHINO_CONFIG_CPU_NUM) {
|
||||
PWR_DBG(DBG_ERR, "cpu_idx(%d) error, it should be 0 ~ %d\n", cpu_idx,
|
||||
RHINO_CONFIG_CPU_NUM - 1);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* fetch the p_cpu_node directly from p_cpu_node_array according cpu_idx */
|
||||
p_cpu_node = p_cpu_node_array[cpu_idx];
|
||||
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "did not find p_cpu_node in p_cpu_node_array with cpu_idx[%d]\n",
|
||||
cpu_idx);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
CPU_TREE_MUX_LOCK();
|
||||
|
||||
ret = cpu_pwr_c_state_capability_set_(p_cpu_node, cpu_idx, support_bitset_c);
|
||||
|
||||
CPU_TREE_MUX_UNLOCK();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_c_state_capability_get() will return the all C state supported of
|
||||
* given CPU.
|
||||
*
|
||||
* @return PWR_OK or PWR_ERR when failed.
|
||||
*/
|
||||
pwr_status_t cpu_pwr_c_state_capability_get(uint32_t cpu_idx,
|
||||
uint32_t *p_support_bitset_c)
|
||||
{
|
||||
cpu_pwr_t *p_cpu_node;
|
||||
pwr_status_t ret = PWR_OK;
|
||||
|
||||
if (cpu_idx >= RHINO_CONFIG_CPU_NUM) {
|
||||
PWR_DBG(DBG_ERR, "cpu_idx(%d) error, it should be 0 ~ %d\n", cpu_idx,
|
||||
RHINO_CONFIG_CPU_NUM - 1);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* fetch the p_cpu_node directly from p_cpu_node_array according cpu_idx */
|
||||
p_cpu_node = p_cpu_node_array[cpu_idx];
|
||||
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "did not find p_cpu_node in p_cpu_node_array with cpu_idx[%d]\n",
|
||||
cpu_idx);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* bzero return value first */
|
||||
*p_support_bitset_c = 0;
|
||||
|
||||
CPU_TREE_MUX_LOCK();
|
||||
|
||||
ret = cpu_pwr_c_state_capability_get_(p_cpu_node, cpu_idx, p_support_bitset_c);
|
||||
|
||||
CPU_TREE_MUX_UNLOCK();
|
||||
|
||||
PWR_DBG(DBG_INFO, "%s%d C support bit set 0x%08x\n", p_cpu_node->name,
|
||||
p_cpu_node->unit, *p_support_bitset_c);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static pwr_status_t cpu_pwr_c_state_latency_save_(cpu_pwr_t *p_cpu_node,
|
||||
cpu_cstate_t cpu_c_state,
|
||||
uint32_t latency)
|
||||
{
|
||||
int i;
|
||||
size_t spaceSize = 0;
|
||||
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "PWR_ERR: p_cpu_node == NULL\n");
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that comparing enums like this is technically
|
||||
* not valid. There is some ambiguity because while enums are
|
||||
* intended to be represented by integers, it's up to the
|
||||
* compiler to decide whether they should be signed or unsigned.
|
||||
* In this case, we happen to know that the cpu_cstate_t is defined
|
||||
* to only use unsigned integer values, so we can temporarily
|
||||
* get away with using a cast here to silence the compiler
|
||||
* warning, but this test should probably be removed.
|
||||
*/
|
||||
|
||||
for (i = 0; i <= CPU_CSTATE_MAX; i++) {
|
||||
if (cpu_c_state == CPU_CSTATE_C0 + i) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > CPU_CSTATE_MAX) {
|
||||
PWR_DBG(DBG_ERR, "PWR_ERR: cpu_c_state(%d) > CPU_CSTATE_MAX(%d)\n",
|
||||
cpu_c_state, CPU_PSTATE_MAX);
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
if (p_cpu_node->cpu_cstate_set_func != NULL) {
|
||||
/* how many C state is supportted */
|
||||
if (p_cpu_node->cstate_nums == 0) {
|
||||
p_cpu_node->cstate_nums = num_of_bit_one_get(p_cpu_node->support_bitset_c);
|
||||
|
||||
if (p_cpu_node->cstate_nums == 0) {
|
||||
PWR_DBG(DBG_ERR, "%s%d->support_bitset_c(%d), cstate_nums() = 0\n",
|
||||
p_cpu_node->name, p_cpu_node->unit, p_cpu_node->support_bitset_c);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* malloc the space to save frequency info */
|
||||
if (p_cpu_node->p_pair_latency == NULL) {
|
||||
spaceSize = sizeof(state_val_pair_t) *p_cpu_node->cstate_nums;
|
||||
p_cpu_node->p_pair_latency = (state_val_pair_t *)malloc(spaceSize);
|
||||
|
||||
if (p_cpu_node->p_pair_latency == NULL) {
|
||||
PWR_DBG(DBG_ERR, "zalloc failed\n");
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* clean space */
|
||||
memset(p_cpu_node->p_pair_latency, 0, spaceSize);
|
||||
|
||||
/* init value to init value CPU_LATENCY_UNKNOW */
|
||||
for (i = 0; i < p_cpu_node->cstate_nums; i++) {
|
||||
p_cpu_node->p_pair_latency[i].value =
|
||||
(uint32_t)CPU_LATENCY_UNKNOW;
|
||||
}
|
||||
}
|
||||
|
||||
/* if we support this C state, save the corresponding latency info */
|
||||
if (CPU_STATE_BIT(cpu_c_state) & p_cpu_node->support_bitset_c) {
|
||||
|
||||
/* check if info has been saved before */
|
||||
for (i = 0; i < p_cpu_node->cstate_nums; i++) {
|
||||
if ((p_cpu_node->p_pair_latency[i].state == cpu_c_state) &&
|
||||
(p_cpu_node->p_pair_latency[i].value == latency)) {
|
||||
return PWR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < p_cpu_node->cstate_nums; i++) {
|
||||
/* search a free space on p_pair_latency to save info */
|
||||
if (p_cpu_node->p_pair_latency[i].value == (uint32_t)CPU_LATENCY_UNKNOW) {
|
||||
p_cpu_node->p_pair_latency[i].state = cpu_c_state;
|
||||
p_cpu_node->p_pair_latency[i].value = latency;
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* if search free space failed */
|
||||
if (i == p_cpu_node->cstate_nums) {
|
||||
PWR_DBG(DBG_ERR, "p_cpu_node(%s%d) search free space failed\n",
|
||||
p_cpu_node->name, p_cpu_node->unit);
|
||||
return PWR_ERR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if come here, there is two possible case:
|
||||
* 1) this node do not support cpu_cstate_set_func
|
||||
* or
|
||||
* 2) this node support cpu_cstate_set_func, but the given C state
|
||||
* cpu_c_state is not supported
|
||||
*/
|
||||
PWR_DBG(DBG_INFO, "p_cpu_node(%s%d) do not support cpu_cstate_set_func "
|
||||
"or the given C state cpu_c_state is not supported\n",
|
||||
p_cpu_node->name, p_cpu_node->unit);
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_c_state_latency_save() save the cpu latency information of C state,
|
||||
* it will let cpu topology system to know how long when the CPU wake up from
|
||||
* C state, normally it will be called by platform in
|
||||
* board_cpu_pwr_topo_create().
|
||||
*
|
||||
* @return PWR_OK or PWR_ERR when failed.
|
||||
*/
|
||||
pwr_status_t cpu_pwr_c_state_latency_save(uint32_t cpu_idx, cpu_cstate_t cpu_c_state,
|
||||
uint32_t latency)
|
||||
{
|
||||
cpu_pwr_t *p_cpu_node;
|
||||
pwr_status_t ret = PWR_OK;
|
||||
|
||||
if (cpu_idx >= RHINO_CONFIG_CPU_NUM) {
|
||||
PWR_DBG(DBG_ERR, "cpu_idx(%d) error, it should be 0 ~ %d\n", cpu_idx,
|
||||
RHINO_CONFIG_CPU_NUM - 1);
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* fetch the p_cpu_node directly from p_cpu_node_array according cpu_idx */
|
||||
p_cpu_node = p_cpu_node_array[cpu_idx];
|
||||
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "did not find p_cpu_node in p_cpu_node_array with cpu_idx[%d]\n",
|
||||
cpu_idx);
|
||||
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
CPU_TREE_MUX_LOCK();
|
||||
|
||||
ret = cpu_pwr_c_state_latency_save_(p_cpu_node, cpu_c_state, latency);
|
||||
|
||||
CPU_TREE_MUX_UNLOCK();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t cpu_pwr_c_state_latency_get_(cpu_pwr_t *p_cpu_node,
|
||||
cpu_cstate_t cpu_c_state)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (p_cpu_node == NULL) {
|
||||
PWR_DBG(DBG_ERR, "p_cpu_node is NULL\n", p_cpu_node);
|
||||
|
||||
return (uint32_t)CPU_LATENCY_UNKNOW;
|
||||
}
|
||||
|
||||
/* if we support cpu_cstate_set_func, then we should save the CPU latency
|
||||
in initialization stage already. */
|
||||
if (p_cpu_node->cpu_cstate_set_func != NULL) {
|
||||
|
||||
/*
|
||||
* if the query C state is supported, then we must has the CPU
|
||||
* latency info, or it is a bug which is something such as
|
||||
* p_pair_latency is is not initialized in the boot stage.
|
||||
*/
|
||||
if (CPU_STATE_BIT(cpu_c_state) & p_cpu_node->support_bitset_c) {
|
||||
if (p_cpu_node->p_pair_latency != NULL) {
|
||||
for (i = 0; i < p_cpu_node->cstate_nums; i++) {
|
||||
if (cpu_c_state == p_cpu_node->p_pair_latency[i].state) {
|
||||
return p_cpu_node->p_pair_latency[i].value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PWR_DBG(DBG_ERR, "p_cpu_node(%s%d)->pPStateAttrArray is not initialized\n",
|
||||
p_cpu_node->name, p_cpu_node->unit);
|
||||
|
||||
return (uint32_t)CPU_LATENCY_UNKNOW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (uint32_t)CPU_LATENCY_UNKNOW;
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine gets the Cstate latency of a logical CPU for the specified
|
||||
* Cstate. Cstate latency is the time when CPU wake up from idle/sleep state,
|
||||
* normally this time is a fixed value which is decided by hardware design and
|
||||
* electrical characteristics. The deeper idle/sleep state CPU enter, the longer
|
||||
* latency time is need when CPU wakeup.
|
||||
*
|
||||
* Tickless module will use latency info to evaluate which C state should enter,
|
||||
* for example, if latency C1 is 5 ticks, latency C2 is 10 ticks, than if there
|
||||
* is nothing to do for the system in the feature 10 ticks, system would enter
|
||||
* C2 state and wake up after 10 ticks.
|
||||
*
|
||||
* @return Cstate latency(microsecond) of a logical CPU for specified Cstate
|
||||
* or CPU_LATENCY_UNKNOW when failed.
|
||||
*/
|
||||
uint32_t cpu_pwr_c_state_latency_get(uint32_t cpu_idx, cpu_cstate_t cpu_c_state)
|
||||
{
|
||||
cpu_pwr_t *p_cpu_node;
|
||||
|
||||
if (cpu_idx >= RHINO_CONFIG_CPU_NUM) {
|
||||
PWR_DBG(DBG_ERR, "cpu_idx(%d) error, it should be 0 ~ %d\n", cpu_idx,
|
||||
RHINO_CONFIG_CPU_NUM - 1);
|
||||
|
||||
return (uint32_t)CPU_LATENCY_UNKNOW;
|
||||
}
|
||||
|
||||
p_cpu_node = p_cpu_node_array[cpu_idx];
|
||||
|
||||
return cpu_pwr_c_state_latency_get_(p_cpu_node, cpu_c_state);
|
||||
}
|
||||
|
||||
/*
|
||||
* cpu_pwr_hal_lib_init() is called to initialize the cpu hardware
|
||||
* abstraction layer infrastructure, such as constructing the semphore.
|
||||
*
|
||||
* In the end of this routine, it will execute a function named
|
||||
* board_cpu_pwr_init() which is provided by board/platform
|
||||
*/
|
||||
void cpu_pwr_hal_lib_init(void)
|
||||
{
|
||||
/* clean the space */
|
||||
memset(p_cpu_node_array, 0, sizeof(cpu_pwr_t *) *RHINO_CONFIG_CPU_NUM);
|
||||
|
||||
krhino_spin_lock_init(&cpu_pwr_lock);
|
||||
|
||||
if (krhino_mutex_create(&cpu_pwr_hal_mux, "cpu_pwr_hal_mux") != PWR_OK) {
|
||||
PWR_DBG(DBG_ERR, "krhino_mutex_create(cpu_pwr_hal_mux) failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* call board function to create CPU power management topology tree */
|
||||
board_cpu_pwr_init();
|
||||
|
||||
/*
|
||||
* set CPU default idle mode to sleep mode,
|
||||
* if tickless configuration is enabled,
|
||||
* the idle mode will be updated to tickless mode by tickless module.
|
||||
*/
|
||||
(void)cpu_pwr_idle_mode_set(CPU_IDLE_MODE_SLEEP);
|
||||
|
||||
PWR_DBG(DBG_INFO, "cpu_pwr_hal_lib_init() done!\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* num_of_bit_one_get() return the number of 1 for the given binary value.
|
||||
*/
|
||||
static int num_of_bit_one_get(uint32_t n)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while (n != 0) {
|
||||
n = n & (n - 1);
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
122
Living_SDK/kernel/pwrmgmt/cpu_pwr_lib.c
Executable file
122
Living_SDK/kernel/pwrmgmt/cpu_pwr_lib.c
Executable file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
#include "pwrmgmt_api.h"
|
||||
#include "cpu_pwr_hal_lib.h"
|
||||
#include "cpu_pwr_lib.h"
|
||||
#include "cpu_tickless.h"
|
||||
|
||||
static CPU_IDLE_MODE cpu_pwr_idle_mode = CPU_IDLE_MODE_RUN;
|
||||
static int cpu_pwr_init_flag = 0;
|
||||
|
||||
static kspinlock_t cpu_pwr_spin;
|
||||
|
||||
void (*_func_cpu_tickless_down)(void);
|
||||
void (*_func_cpu_tickless_up)(void);
|
||||
|
||||
/**
|
||||
* cpu_pwrmgmt_init() is the entry of whole cpu power manamgement system.
|
||||
*/
|
||||
int cpu_pwrmgmt_init(void)
|
||||
{
|
||||
/* avoid reentry */
|
||||
if (cpu_pwr_init_flag) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
krhino_spin_lock_init(&cpu_pwr_spin);
|
||||
|
||||
/* create cpu power management framework and cpu topology structure */
|
||||
cpu_pwr_hal_lib_init();
|
||||
|
||||
/* init cpu tickless framework */
|
||||
tickless_init();
|
||||
|
||||
/* save the job complete flag */
|
||||
cpu_pwr_init_flag = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_down() is invoked from idle task when it detects that the kernel
|
||||
* can go idle. cpu_pwr_down() checks the current cpu idle mode and put
|
||||
* cpu into lower power mode according different idle mode.
|
||||
*/
|
||||
void cpu_pwr_down(void)
|
||||
{
|
||||
if (cpu_pwr_init_flag == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((cpu_pwr_idle_mode == CPU_IDLE_MODE_TICKLESS) &&
|
||||
(_func_cpu_tickless_down != NULL)) {
|
||||
_func_cpu_tickless_down();
|
||||
} else if (cpu_pwr_idle_mode == CPU_IDLE_MODE_SLEEP) {
|
||||
(void)cpu_pwr_c_state_set(CPU_CSTATE_C1);
|
||||
} else {
|
||||
/* CPU_IDLE_MODE_RUN */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_up() is invoked from all interrupt and exception stubs to wakeup
|
||||
* the CPU in case it was in lower power mode. This is necessary so that
|
||||
* the kernel can adjust its tick count to reflect the length of time that
|
||||
* it was in lower power mode.
|
||||
*/
|
||||
void cpu_pwr_up(void)
|
||||
{
|
||||
if (cpu_pwr_init_flag == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)cpu_pwr_c_state_set(CPU_CSTATE_C0);
|
||||
|
||||
/* execute tickless up hook if existed */
|
||||
if ((cpu_pwr_idle_mode == CPU_IDLE_MODE_TICKLESS) &&
|
||||
(_func_cpu_tickless_up != NULL)) {
|
||||
_func_cpu_tickless_up();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_idle_mode_set() sets the power mode used when CPUs are idle. The
|
||||
* following <mode> settings are supported: CPU_IDLE_MODE_RUN - CPUs are
|
||||
* continuously running when idle CPU_IDLE_MODE_SLEEP - system clock interupts
|
||||
* occur at every tick. CPU_IDLE_MODE_TICKLESS- CPUs sleep for multiple tick
|
||||
* durations, system tick interupts occur only when kernel event expirations
|
||||
* occur (timout, delays, etc).
|
||||
* @return PWR_OK, or PWR_ERR in case of failure
|
||||
*/
|
||||
pwr_status_t cpu_pwr_idle_mode_set(CPU_IDLE_MODE request_mode)
|
||||
{
|
||||
if (request_mode > CPU_IDLE_MODE_TICKLESS) {
|
||||
return (PWR_ERR);
|
||||
}
|
||||
|
||||
if ((request_mode == CPU_IDLE_MODE_TICKLESS) &&
|
||||
(_func_cpu_tickless_down == NULL)) {
|
||||
return (PWR_ERR);
|
||||
}
|
||||
|
||||
if (request_mode == cpu_pwr_idle_mode) {
|
||||
return (PWR_OK);
|
||||
}
|
||||
|
||||
cpu_pwr_idle_mode = request_mode;
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_idle_mode_get() returns the current idle CPU power mode
|
||||
*
|
||||
* @return current CPU idle mode
|
||||
*/
|
||||
CPU_IDLE_MODE cpu_pwr_idle_mode_get(void)
|
||||
{
|
||||
return cpu_pwr_idle_mode;
|
||||
}
|
||||
113
Living_SDK/kernel/pwrmgmt/cpu_pwr_show.c
Executable file
113
Living_SDK/kernel/pwrmgmt/cpu_pwr_show.c
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file provides cpu_pwr_topology_show()/cpu_pwr_state_show() to show
|
||||
* information for CPU power management.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pwrmgmt_api.h"
|
||||
#include "cpu_pwr_lib.h"
|
||||
#include "cpu_pwr_hal_lib.h"
|
||||
#include "pwrmgmt_debug.h"
|
||||
|
||||
#if (PWRMGMT_CONFIG_SHOW > 0)
|
||||
extern cpu_pwr_t *p_cpu_pwr_root_node;
|
||||
extern cpu_pwr_t *p_cpu_node_array[];
|
||||
|
||||
static void cpu_pwr_c_support_print(cpu_pwr_t *p_cpu_node, char *prefix)
|
||||
{
|
||||
uint32_t state_set;
|
||||
uint32_t latency = 0;
|
||||
int state;
|
||||
int ix;
|
||||
uint32_t tmp;
|
||||
|
||||
(void)printf("%s C-States:\n", prefix);
|
||||
|
||||
state_set = p_cpu_node->support_bitset_c;
|
||||
|
||||
while (state_set != 0) {
|
||||
/* find first bit set */
|
||||
if (state_set == 0) {
|
||||
state = 0;
|
||||
} else {
|
||||
tmp = state_set & (-state_set);
|
||||
state = 31 - krhino_find_first_bit(&tmp);
|
||||
}
|
||||
|
||||
for (ix = 0; ix < p_cpu_node->cstate_nums; ix++) {
|
||||
if (state == p_cpu_node->p_pair_latency[ix].state) {
|
||||
latency = p_cpu_node->p_pair_latency[ix].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(void)printf("%s C%d: %4d us latency\n", prefix, state, latency);
|
||||
|
||||
state_set &= ~(1 << state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_info_show() will show information for all nodes of the cpu power
|
||||
* management. For each node the C-state and P-state capability
|
||||
* is provided, as applicable, depending on hardware capabilities and
|
||||
* configuration.
|
||||
*
|
||||
* For P-state capability the CPU core frequency and voltage setting is
|
||||
* displayed. For C-state capabilities the latency information (wake-up time)
|
||||
* is displayed.
|
||||
*/
|
||||
void cpu_pwr_info_show(void)
|
||||
{
|
||||
cpu_pwr_t *p_cpu_node;
|
||||
int cpu_idx;
|
||||
|
||||
(void)printf("\n CPU NODE CAPABILITIES\n"
|
||||
"--------------- ---------------\n");
|
||||
|
||||
for (cpu_idx = 0; cpu_idx < RHINO_CONFIG_CPU_NUM; cpu_idx++) {
|
||||
p_cpu_node = p_cpu_node_array[cpu_idx];
|
||||
(void)printf("%s%u (CPU%d)\n", p_cpu_node->name, p_cpu_node->unit, cpu_idx);
|
||||
cpu_pwr_c_support_print(p_cpu_node, " ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_pwr_state_show() display the CPU power/performance states.
|
||||
*/
|
||||
void cpu_pwr_state_show(void)
|
||||
{
|
||||
uint32_t cpu_idx;
|
||||
cpu_cstate_t cState;
|
||||
|
||||
printf("\n CPU C-STATE\n"
|
||||
"---------- ----------\n");
|
||||
|
||||
for (cpu_idx = 0; cpu_idx < RHINO_CONFIG_CPU_NUM; cpu_idx++) {
|
||||
cpu_pwr_c_state_get(cpu_idx, &cState);
|
||||
printf("CPU%-5u C%-2u\n", cpu_idx, cState);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#endif /* RHINO_CONFIG_CPU_PWR_SHOW */
|
||||
|
||||
#if (PWRMGMT_CONFIG_DEBUG > 0)
|
||||
void pwr_debug(pwr_debug_level_t debug_level, const char *fmt_str, ...)
|
||||
{
|
||||
va_list param;
|
||||
|
||||
if (debug_level >= PWR_DEBUG_LEVEL) {
|
||||
va_start(param,fmt_str);
|
||||
vprintf(fmt_str,param);
|
||||
va_end(param);
|
||||
}
|
||||
}
|
||||
#endif /* RHINO_CONFIG_CPU_PWR_DEBUG > 0 */
|
||||
438
Living_SDK/kernel/pwrmgmt/cpu_tickless.c
Executable file
438
Living_SDK/kernel/pwrmgmt/cpu_tickless.c
Executable file
|
|
@ -0,0 +1,438 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
/*
|
||||
This file provides support for cpu tickless-idle.
|
||||
|
||||
Tickless-idle eliminates unnecessary timer interrupts when the processor is
|
||||
otherwise idle. When the processor exits the idle state, it advances time by
|
||||
the number of ticks that elapsed during the idle period. For SMP, the tickless
|
||||
idle is entered when all CPUs are ready.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "pwrmgmt_api.h"
|
||||
#include "cpu_pwr_lib.h"
|
||||
#include "cpu_pwr_hal_lib.h"
|
||||
#include "pwrmgmt_debug.h"
|
||||
#include "cpu_tickless.h"
|
||||
|
||||
/* 100 * 365 * 24 * 3600 * 1000 * 1000 = 0xB342EB7C38000 */
|
||||
#define TIME_100_YEARS_IN_US 0xB342EB7C38000ULL
|
||||
|
||||
static uint32_t tickless_ctate_mask; /* C-states support set */
|
||||
static cpu_cstate_t c_state_entered;
|
||||
static int is_current_tickless = FALSE;
|
||||
|
||||
static uint32_t cStateConfig[RHINO_CONFIG_CPU_NUM];
|
||||
static uint32_t cStateLatency[RHINO_CONFIG_CPU_NUM][CPU_CSTATE_MAX + 1];
|
||||
static one_shot_timer_t *cStateOneShotTimer[CPU_CSTATE_MAX + 1];
|
||||
|
||||
/*
|
||||
* tickless_live_cpu_set, after tickless module is initialized,
|
||||
* all the cores will set bit in tickless_live_cpu_set, each core
|
||||
* will clear the relative bit in tickless_live_cpu_set before
|
||||
* it is going to enter tickless idle mode, when all the cores
|
||||
* clears the bit in tickless_live_cpu_set which means all cores
|
||||
* want to enter tickless idle mode, the last core will close
|
||||
* the system tick interrupt and start a one-shot plan, and
|
||||
* then enter tickless idle mode.
|
||||
*
|
||||
* On a big-little system, there are some big cores(higher
|
||||
* frequency) and some little cores(lower frequency), normally
|
||||
* all big cores belong same cluster and all little cores belong
|
||||
* same cluster, and these two clusters could has different
|
||||
* Cx state, say big cluster could in C2 while little cluster
|
||||
* could in C0. For tickless mode, only when all cores want
|
||||
* to enter tickless idle mode, the system could enter tickless
|
||||
* mode, all cores here means include both big cores and little
|
||||
* cores.
|
||||
*
|
||||
* In the struct cpu_pwr_t, it also has a value named child_live_cpu_set,
|
||||
* that is not same thing as tickless_live_cpu_set, different cluster
|
||||
* could has different Cx state in one CPU package, child_live_cpu_set
|
||||
* in cpu_pwr_t is used to track idle state of each cores under same
|
||||
* cluster in this example.
|
||||
*/
|
||||
|
||||
static unsigned int tickless_live_cpu_set = 0;
|
||||
static kspinlock_t ticklessSpin;
|
||||
|
||||
/* import */
|
||||
extern void systick_suspend(void);
|
||||
extern void systick_resume(void);
|
||||
|
||||
/* forward declare */
|
||||
static pwr_status_t tickless_timer_init(void);
|
||||
static void tickless_enter(void);
|
||||
static void tickless_exit(void);
|
||||
static void tickless_enter_check(uint32_t cpu_idx, uint32_t cStatesCfg,
|
||||
uint64_t *p_sleeptime, cpu_cstate_t *p_cstate_to_set);
|
||||
static pwr_status_t tickless_one_shot_start(uint64_t sleep_time, cpu_cstate_t c_state_to_enter);
|
||||
static tick_t tickless_one_shot_stop(cpu_cstate_t c_state_current);
|
||||
static void tickless_announce_n(tick_t n_ticks);
|
||||
static pwr_status_t tickless_c_state_latency_init(uint32_t cpu_idx, uint32_t *p_latency);
|
||||
|
||||
/**
|
||||
* This routine is supplied by this module for board/platform,
|
||||
* it is responsibility of board/platform to provide information
|
||||
* that which C state is supported with tickless mode.
|
||||
*/
|
||||
void tickless_c_states_add(uint32_t c_state_set)
|
||||
{
|
||||
tickless_ctate_mask |= c_state_set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine installs the static routines tickless_enter(),
|
||||
* tickless_exit(), into cpu power management facility.
|
||||
*/
|
||||
pwr_status_t tickless_init(void)
|
||||
{
|
||||
uint32_t cpu_idx;
|
||||
|
||||
if (tickless_ctate_mask == 0) {
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* initialize the spinlock */
|
||||
krhino_spin_lock_init(&ticklessSpin);
|
||||
|
||||
/* initialize the one-shot timers */
|
||||
if (tickless_timer_init() != PWR_OK) {
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
/* set cpu idle mode to run until initialization completes */
|
||||
cpu_pwr_idle_mode_set(CPU_IDLE_MODE_RUN);
|
||||
|
||||
for (cpu_idx = 0; cpu_idx < RHINO_CONFIG_CPU_NUM; cpu_idx++) {
|
||||
if (cpu_pwr_c_state_capability_get(cpu_idx, &cStateConfig[cpu_idx]) != PWR_OK) {
|
||||
return PWR_ERR;
|
||||
}
|
||||
|
||||
cStateConfig[cpu_idx] &= tickless_ctate_mask;
|
||||
|
||||
tickless_c_state_latency_init(cpu_idx, cStateLatency[cpu_idx]);
|
||||
|
||||
/* set live flag for all cores */
|
||||
tickless_live_cpu_set |= 1 << cpu_idx;
|
||||
}
|
||||
|
||||
_func_cpu_tickless_down = tickless_enter;
|
||||
_func_cpu_tickless_up = tickless_exit;
|
||||
|
||||
/*set cpu idle mode to tickless mode now */
|
||||
cpu_pwr_idle_mode_set(CPU_IDLE_MODE_TICKLESS);
|
||||
|
||||
return (PWR_OK);
|
||||
}
|
||||
|
||||
static pwr_status_t tickless_timer_init(void)
|
||||
{
|
||||
uint32_t idx;
|
||||
|
||||
for (idx = 0; idx < CPU_CSTATE_MAX; idx++) {
|
||||
if (cStateOneShotTimer[idx] != NULL) {
|
||||
if (cStateOneShotTimer[idx]->one_shot_init() != PWR_OK) {
|
||||
return PWR_ERR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PWR_OK;
|
||||
}
|
||||
|
||||
static pwr_status_t tickless_one_shot_start(uint64_t sleep_time, cpu_cstate_t c_state_to_enter)
|
||||
{
|
||||
if (cStateOneShotTimer[c_state_to_enter]->one_shot_start(sleep_time) != PWR_OK) {
|
||||
PWR_DBG(DBG_INFO, "start one shot(%lld ms) fail\n", sleep_time);
|
||||
|
||||
return (PWR_ERR);
|
||||
}
|
||||
|
||||
return (PWR_OK);
|
||||
}
|
||||
|
||||
static tick_t tickless_one_shot_stop(cpu_cstate_t c_state_current)
|
||||
{
|
||||
tick_t n_ticks;
|
||||
uint64_t passed_micro_sec;
|
||||
uint32_t us_per_tick = 1000000 / RHINO_CONFIG_TICKS_PER_SECOND;
|
||||
static uint64_t us_remain = 0;
|
||||
|
||||
if (cStateOneShotTimer[c_state_current]->one_shot_stop(&passed_micro_sec) !=
|
||||
PWR_OK) {
|
||||
PWR_DBG(DBG_ERR, "timerOneShotCancel fail\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
n_ticks = (tick_t)((passed_micro_sec * RHINO_CONFIG_TICKS_PER_SECOND) / (1000000));
|
||||
|
||||
/* fixs ticks drift issue */
|
||||
us_remain += passed_micro_sec % us_per_tick;
|
||||
if (us_remain >= us_per_tick) {
|
||||
n_ticks++;
|
||||
us_remain -= us_per_tick;
|
||||
}
|
||||
|
||||
return n_ticks;
|
||||
}
|
||||
|
||||
/**
|
||||
* tickless_c_state_latency_init() sets the latency times for entering and
|
||||
* exiting the various C states for idle power management. Latency table
|
||||
* is only used when CPU Power Manager is present.
|
||||
* @param[in] cpu_idx CPU index
|
||||
* @param[in] p_latency[] Latency array
|
||||
* @return PWR_OK or PWR_ERR when failed.
|
||||
*/
|
||||
static pwr_status_t tickless_c_state_latency_init(uint32_t cpu_idx, uint32_t p_latency[])
|
||||
{
|
||||
cpu_cstate_t cstate;
|
||||
uint32_t cstate_all;
|
||||
uint32_t latency;
|
||||
|
||||
if (cpu_pwr_c_state_capability_get(cpu_idx, &cstate_all) != PWR_OK) {
|
||||
return (PWR_ERR);
|
||||
}
|
||||
|
||||
for (cstate = CPU_CSTATE_C0; cstate <= CPU_CSTATE_MAX; cstate++) {
|
||||
p_latency[cstate] = 0;
|
||||
|
||||
if (cstate_all & (1 << cstate)) {
|
||||
latency = cpu_pwr_c_state_latency_get(cpu_idx, cstate);
|
||||
|
||||
if (latency == (uint32_t)CPU_LATENCY_UNKNOW) {
|
||||
p_latency[cstate] = 0;
|
||||
} else {
|
||||
p_latency[cstate] = latency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (PWR_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* tickless_enter_check() calculates the amount of time until the next kernel
|
||||
* time event, adjusts it based on configuration and CPU capabilities.
|
||||
* It also deteremines the CPU idle state (C-state) to enter.
|
||||
* @param[in] cpu_idx current CPU
|
||||
* @param[in] cstate_cfg, enabled C states
|
||||
* @param[out] p_sleeptime, time to next event in us
|
||||
* @param[out] p_cstate_to_set C state to set
|
||||
* @return N/A
|
||||
*/
|
||||
static void tickless_enter_check(uint32_t cpu_idx, uint32_t cstate_cfg,
|
||||
uint64_t *p_sleeptime, cpu_cstate_t *p_cstate_to_set)
|
||||
{
|
||||
uint32_t cpu_c_state;
|
||||
uint64_t sleep_time_us; /* sleep time in microseconds */
|
||||
uint64_t one_shot_max_us_support; /* max one shot time support */
|
||||
tick_t n_ticks;
|
||||
|
||||
n_ticks = krhino_next_sleep_ticks_get();
|
||||
|
||||
if (n_ticks == RHINO_WAIT_FOREVER) {
|
||||
sleep_time_us = TIME_100_YEARS_IN_US;
|
||||
} else {
|
||||
if (n_ticks > 1) {
|
||||
n_ticks = n_ticks -1;
|
||||
}
|
||||
sleep_time_us = 1000000ull * n_ticks / RHINO_CONFIG_TICKS_PER_SECOND;
|
||||
}
|
||||
|
||||
/* get max valid Cx from cstate_cfg */
|
||||
if (cstate_cfg == 0) {
|
||||
cpu_c_state = 0;
|
||||
} else {
|
||||
cpu_c_state = 31 - krhino_find_first_bit(&cstate_cfg);
|
||||
}
|
||||
|
||||
while (cpu_c_state > 0) {
|
||||
/* check if latency is less than idle time */
|
||||
if (cStateLatency[cpu_idx][cpu_c_state] < sleep_time_us) {
|
||||
/* hit! save cpu_c_state as the target C state */
|
||||
*p_cstate_to_set = (cpu_cstate_t)cpu_c_state;
|
||||
|
||||
/* find the target, break here */
|
||||
break;
|
||||
}
|
||||
|
||||
/* clear the current Cx from cstate_cfg */
|
||||
cstate_cfg &= ~(1 << cpu_c_state);
|
||||
|
||||
/* get max valid Cx from cstate_cfg */
|
||||
if (cstate_cfg == 0) {
|
||||
cpu_c_state = 0;
|
||||
} else {
|
||||
cpu_c_state = 31 - krhino_find_first_bit(&cstate_cfg);
|
||||
}
|
||||
}
|
||||
|
||||
one_shot_max_us_support = 0;
|
||||
|
||||
if ((cStateOneShotTimer[cpu_c_state] != NULL) &&
|
||||
(cStateOneShotTimer[cpu_c_state]->one_shot_seconds_max != NULL)) {
|
||||
one_shot_max_us_support =
|
||||
1000000 * (uint64_t)cStateOneShotTimer[cpu_c_state]->one_shot_seconds_max();
|
||||
}
|
||||
|
||||
/* if request sleeptime is longer than support, cut it down
|
||||
to max support value. */
|
||||
if (sleep_time_us > one_shot_max_us_support) {
|
||||
sleep_time_us = one_shot_max_us_support;
|
||||
}
|
||||
|
||||
/* real sleep time should be reduced by latency time. */
|
||||
sleep_time_us -= cStateLatency[cpu_idx][cpu_c_state];
|
||||
|
||||
/* save the real sleep time into p_sleeptime and return. */
|
||||
*p_sleeptime = sleep_time_us;
|
||||
}
|
||||
|
||||
/**
|
||||
* tickless_enter() is called when a CPU is going to enter idle state, a one
|
||||
* shot interrupt is planned at sametime which is used to wake up CPU.
|
||||
* @return N/A
|
||||
*/
|
||||
|
||||
static void tickless_enter(void)
|
||||
{
|
||||
uint64_t sleep_time;
|
||||
cpu_cstate_t cstate_to_enter = CPU_CSTATE_C1;
|
||||
uint32_t cpu_idx = 0;
|
||||
tick_t n_ticks = 0;
|
||||
CPSR_ALLOC();
|
||||
|
||||
krhino_spin_lock_irq_save(&ticklessSpin);
|
||||
|
||||
/* Check if tickless can be entered now */
|
||||
tickless_enter_check(cpu_idx, cStateConfig[cpu_idx], &sleep_time,
|
||||
&cstate_to_enter);
|
||||
|
||||
if ((sleep_time > 0) && (is_current_tickless == FALSE)) {
|
||||
/*
|
||||
* Enable a one shot timer to wake up the system from an idle power
|
||||
* management state. This one shot timer will wakeup the system
|
||||
* unless another asynchronous event has woken up the CPU already.
|
||||
*/
|
||||
if (tickless_one_shot_start(sleep_time, cstate_to_enter) == PWR_OK) {
|
||||
is_current_tickless = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
c_state_entered = cstate_to_enter;
|
||||
|
||||
if (is_current_tickless == TRUE) {
|
||||
/* suspend system tick interrupt */
|
||||
systick_suspend();
|
||||
|
||||
/*
|
||||
* take CPU into relative C idle state which is decided by
|
||||
* tickless_enter_check().
|
||||
*
|
||||
* NOTE, the CPU interrupt is still locked here, so CPU will be put
|
||||
* into Cx state with cpu interrupt locked, but it is safe to wake
|
||||
* up CPU throught interrupt.
|
||||
*
|
||||
* For ARM cortex-M, the processor ignores the value of PRIMASK in
|
||||
* determining whether an asynchronous exception is a WFI wakeup event.
|
||||
*
|
||||
* For ARM cortex-A, when processor is in WFI state, it will be waked
|
||||
* up by:
|
||||
* a physical IRQ interrupt regardless of the value of the CPSR.I bit,
|
||||
* a physical FIQ interrupt regardless of the value of the CPSR.F bit.
|
||||
*
|
||||
* For Xtensa, WAITI sets the interrupt level in PS.INTLEVEL to imm4
|
||||
* and then, on some Xtensa ISA implementations, suspends processor
|
||||
* operation until an interrupt occurs. The combination of setting
|
||||
* the interrupt level and suspending operation avoids a race condition
|
||||
* where an interrupt between the interrupt level setting and the
|
||||
* suspension of operation would be ignored until a second interrupt
|
||||
* occurred.
|
||||
*/
|
||||
(void)cpu_pwr_c_state_set(cstate_to_enter);
|
||||
}
|
||||
|
||||
#if 1
|
||||
if (is_current_tickless == TRUE) {
|
||||
|
||||
cpu_pwr_c_state_set(CPU_CSTATE_C0);
|
||||
|
||||
n_ticks = tickless_one_shot_stop(c_state_entered);
|
||||
|
||||
/* set is_current_tickless to FALSE */
|
||||
is_current_tickless = FALSE;
|
||||
|
||||
if (n_ticks > 0) {
|
||||
/* announces elapsed ticks to the kernel */
|
||||
tickless_announce_n(n_ticks);
|
||||
}
|
||||
|
||||
/* resume system tick interrupt */
|
||||
systick_resume();
|
||||
}
|
||||
#endif
|
||||
RHINO_CRITICAL_ENTER();
|
||||
RHINO_CRITICAL_EXIT_SCHED();
|
||||
|
||||
krhino_spin_unlock_irq_restore(&ticklessSpin);
|
||||
}
|
||||
|
||||
/**
|
||||
* tickless_exit() is called when a CPU gets interrupted. If it determeines
|
||||
* that the system is waking up from tickless idle, it re-enables tick
|
||||
* interrupts and stop the one shot interrupt plan, also announces elapsed
|
||||
* ticks to the kernel.
|
||||
*
|
||||
* @return N/A
|
||||
*/
|
||||
static void tickless_exit(void)
|
||||
{
|
||||
tick_t n_ticks = 0;
|
||||
|
||||
krhino_spin_lock_irq_save(&ticklessSpin);
|
||||
|
||||
if (!is_current_tickless) {
|
||||
krhino_spin_unlock_irq_restore(&ticklessSpin);
|
||||
return;
|
||||
}
|
||||
|
||||
n_ticks = tickless_one_shot_stop(c_state_entered);
|
||||
|
||||
/* set is_current_tickless to FALSE */
|
||||
is_current_tickless = FALSE;
|
||||
|
||||
krhino_spin_unlock_irq_restore(&ticklessSpin);
|
||||
|
||||
if (n_ticks > 0) {
|
||||
/* announces elapsed ticks to the kernel */
|
||||
tickless_announce_n(n_ticks);
|
||||
}
|
||||
|
||||
/* resume system tick interrupt */
|
||||
systick_resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* tickless_announce_n() is called to announces elapsed ticks to the kernel.
|
||||
*/
|
||||
static void tickless_announce_n(tick_t n_ticks)
|
||||
{
|
||||
tick_list_update((tick_i_t)n_ticks);
|
||||
}
|
||||
|
||||
/**
|
||||
* tickless_one_shot_timer_save() is used to build connection between
|
||||
* one shot timer and c state, different c state could has different
|
||||
* wake up timer.
|
||||
*/
|
||||
void tickless_one_shot_timer_save(cpu_cstate_t cstate, one_shot_timer_t *p_timer)
|
||||
{
|
||||
cStateOneShotTimer[cstate] = p_timer;
|
||||
}
|
||||
62
Living_SDK/kernel/pwrmgmt/include/cpu_pwr_hal_lib.h
Executable file
62
Living_SDK/kernel/pwrmgmt/include/cpu_pwr_hal_lib.h
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
#ifndef CPU_PWR_HAL_H
|
||||
#define CPU_PWR_HAL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include <k_err.h>
|
||||
|
||||
#include "pwrmgmt_state.h"
|
||||
|
||||
#define CPU_PWR_NODE_NAME_LEN 64
|
||||
|
||||
#define CPU_STATE_BIT(n) (1 << (n))
|
||||
#define CPU_FREQ_UNKNOW (-1)
|
||||
#define CPU_VOLT_UNKNOW (-1)
|
||||
#define CPU_LATENCY_UNKNOW (-1)
|
||||
|
||||
typedef struct {
|
||||
uint32_t state;
|
||||
uint32_t value;
|
||||
} state_val_pair_t;
|
||||
|
||||
struct cpu_pwr;
|
||||
|
||||
typedef pwr_status_t (*cpu_pstate_set_t)(struct cpu_pwr *p_cpu_node, uint32_t cpu_state);
|
||||
typedef pwr_status_t (*cpu_cstate_set_t)(uint32_t cpu_state, int master);
|
||||
typedef pwr_status_t (*cpu_cstate_get_t)(struct cpu_pwr *p_cpu_node, uint32_t *pCpuState);
|
||||
|
||||
typedef struct cpu_pwr {
|
||||
uint32_t support_bitset_c;
|
||||
int cstate_nums;
|
||||
state_val_pair_t *p_pair_latency;
|
||||
unsigned int cstate_updating;
|
||||
cpu_cstate_t current_c_state;
|
||||
cpu_cstate_t desire_c_state;
|
||||
cpu_cstate_set_t cpu_cstate_set_func;
|
||||
char * name;
|
||||
uint32_t unit;
|
||||
} cpu_pwr_t;
|
||||
|
||||
extern void cpu_pwr_hal_lib_init(void);
|
||||
extern pwr_status_t cpu_pwr_node_init_static(char *, uint32_t, cpu_pwr_t *);
|
||||
extern pwr_status_t cpu_pwr_node_init_dyn(char *, uint32_t, cpu_pwr_t **);
|
||||
extern cpu_pwr_t * cpu_pwr_node_find_by_name(char *pName, uint32_t index);
|
||||
extern pwr_status_t cpu_pwr_node_record(cpu_pwr_t *p_cpu_node, uint32_t cpuIndex);
|
||||
extern pwr_status_t cpu_pwr_c_state_capability_set(uint32_t cpuIndex, uint32_t supportBitsetC);
|
||||
extern pwr_status_t cpu_pwr_c_state_latency_save(uint32_t cpuIndex, cpu_cstate_t cpu_state,
|
||||
uint32_t latency);
|
||||
extern uint32_t cpu_pwr_c_state_latency_get(uint32_t cpuIndex, cpu_cstate_t CState);
|
||||
extern pwr_status_t cpu_pwr_c_method_set(uint32_t cpu_idx, cpu_cstate_set_t cpu_cstate_set_func);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CPU_PWR_HAL_H */
|
||||
33
Living_SDK/kernel/pwrmgmt/include/cpu_pwr_lib.h
Executable file
33
Living_SDK/kernel/pwrmgmt/include/cpu_pwr_lib.h
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
#ifndef CPU_PWR_LIB_H
|
||||
#define CPU_PWR_LIB_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include <k_api.h>
|
||||
|
||||
#include "pwrmgmt_state.h"
|
||||
|
||||
#ifndef container_of
|
||||
#define container_of(ptr, type, member) \
|
||||
((type *)((char *)(ptr) - offsetof(type, member)))
|
||||
#endif /* container_of */
|
||||
|
||||
int cpu_pwrmgmt_init(void);
|
||||
void cpu_pwr_down(void);
|
||||
void cpu_pwr_up(void);
|
||||
|
||||
extern void (*_func_cpu_tickless_up)(void);
|
||||
extern void (*_func_cpu_tickless_down)(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CPU_PWR_LIB_H */
|
||||
37
Living_SDK/kernel/pwrmgmt/include/cpu_tickless.h
Executable file
37
Living_SDK/kernel/pwrmgmt/include/cpu_tickless.h
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
#ifndef CPU_TICKLSEE_H
|
||||
#define CPU_TICKLSEE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include <k_api.h>
|
||||
|
||||
#include "pwrmgmt_state.h"
|
||||
|
||||
typedef pwr_status_t (*one_shot_init_t)(void);
|
||||
typedef uint32_t (*one_shot_seconds_max_t)(void);
|
||||
typedef pwr_status_t (*one_shot_start_t)(uint64_t planUse);
|
||||
typedef pwr_status_t (*one_shot_stop_t)(uint64_t *pPassedUs);
|
||||
|
||||
typedef struct {
|
||||
one_shot_init_t one_shot_init;
|
||||
one_shot_seconds_max_t one_shot_seconds_max;
|
||||
one_shot_start_t one_shot_start;
|
||||
one_shot_stop_t one_shot_stop;
|
||||
} one_shot_timer_t;
|
||||
|
||||
extern pwr_status_t tickless_init(void);
|
||||
extern void tickless_c_states_add(uint32_t c_state_set);
|
||||
extern void tickless_one_shot_timer_save(cpu_cstate_t c_state, one_shot_timer_t *p_timer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CPU_TICKLSEE_H */
|
||||
68
Living_SDK/kernel/pwrmgmt/include/pwrmgmt_api.h
Executable file
68
Living_SDK/kernel/pwrmgmt/include/pwrmgmt_api.h
Executable file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
/*
|
||||
DESCRIPTION
|
||||
This file provides APIs for management CPU power.
|
||||
*/
|
||||
|
||||
#ifndef PWRMGMT_API_H
|
||||
#define PWRMGMT_API_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include <k_api.h>
|
||||
|
||||
#include "pwrmgmt_default_config.h"
|
||||
#include "cpu_pwr_lib.h"
|
||||
#include "cpu_pwr_hal_lib.h"
|
||||
#include "pwrmgmt_state.h"
|
||||
|
||||
/*
|
||||
* CPU power management operates in one of three possible modes:
|
||||
*
|
||||
* CPU_IDLE_MODE_RUN:
|
||||
* In this CPU mode of operation, idle CPU power management is disabled.
|
||||
*
|
||||
* CPU_IDLE_MODE_SLEEP:
|
||||
* the CPUs sleep when idle, but system clock interupts wakes up the CPU
|
||||
* at every tick.
|
||||
*
|
||||
* CPU_IDLE_MODE_TICKLESS: CPUs sleep when idle, potentially for multiple
|
||||
* ticks once enter idle mode.
|
||||
*/
|
||||
typedef enum cpu_idle_mode {
|
||||
CPU_IDLE_MODE_RUN = 0,
|
||||
CPU_IDLE_MODE_SLEEP = 1,
|
||||
CPU_IDLE_MODE_TICKLESS = 2
|
||||
} CPU_IDLE_MODE;
|
||||
|
||||
extern pwr_status_t cpu_pwr_c_state_set(cpu_cstate_t target_c_state);
|
||||
extern pwr_status_t cpu_pwr_c_state_get(uint32_t cpu_idx, cpu_cstate_t *p_cstate);
|
||||
extern pwr_status_t cpu_pwr_c_state_capability_get(uint32_t cpu_idx, uint32_t *p_support_bitset_c);
|
||||
|
||||
extern pwr_status_t cpu_pwr_idle_mode_set(CPU_IDLE_MODE mode);
|
||||
extern CPU_IDLE_MODE cpu_pwr_idle_mode_get(void);
|
||||
|
||||
int pwrmgmt_suspend_lowpower();
|
||||
int pwrmgmt_resume_lowpower();
|
||||
|
||||
#if (PWRMGMT_CONFIG_SHOW > 0)
|
||||
extern void cpu_pwr_state_show(void);
|
||||
extern void cpu_pwr_info_show(void);
|
||||
#endif /* PWRMGMT_CONFIG_SHOW */
|
||||
|
||||
#if (WIFI_CONFIG_SUPPORT_LOWPOWER > 0)
|
||||
extern int wifi_enter_powersave();
|
||||
extern int wifi_exit_powersave();
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PWRMGMT_API_H */
|
||||
42
Living_SDK/kernel/pwrmgmt/include/pwrmgmt_debug.h
Executable file
42
Living_SDK/kernel/pwrmgmt/include/pwrmgmt_debug.h
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
#ifndef PWRMGMT_DEBUG_H
|
||||
#define PWRMGMT_DEBUG_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "pwrmgmt_default_config.h"
|
||||
|
||||
/*
|
||||
* when use debug, do not close uart when turn into pwr down state
|
||||
*/
|
||||
#define PWR_DEBUG_LEVEL DBG_INFO
|
||||
|
||||
typedef enum {
|
||||
DBG_OFF = 0x00000000,
|
||||
DBG_INFO = 0x00000001,
|
||||
DBG_WARN = 0x00000002,
|
||||
DBG_ERR = 0x00000003,
|
||||
} pwr_debug_level_t;
|
||||
|
||||
void pwr_debug(pwr_debug_level_t debug_level, const char *fmt_str, ...);
|
||||
|
||||
#if (PWRMGMT_CONFIG_DEBUG > 0)
|
||||
#define PWR_DBG pwr_debug
|
||||
#else
|
||||
#define PWR_DBG(lvl, ...)
|
||||
#endif /* PWRMGMT_CONFIG_DEBUG > DBG_OFF */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PWRMGMT_DEBUG_H */
|
||||
31
Living_SDK/kernel/pwrmgmt/include/pwrmgmt_default_config.h
Executable file
31
Living_SDK/kernel/pwrmgmt/include/pwrmgmt_default_config.h
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
#ifndef PWRMGMT_DEFAULT_CONFIG_H
|
||||
#define PWRMGMT_DEFAULT_CONFIG_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include "pwrmgmt_api.h"
|
||||
|
||||
#ifndef PWRMGMT_CONFIG_SHOW
|
||||
#define PWRMGMT_CONFIG_SHOW 0
|
||||
#endif
|
||||
|
||||
#ifndef PWRMGMT_CONFIG_DEBUG
|
||||
#define PWRMGMT_CONFIG_DEBUG 0
|
||||
#endif
|
||||
|
||||
#ifndef PWRMGMT_CONFIG_LOG_ENTERSLEEP
|
||||
#define PWRMGMT_CONFIG_LOG_ENTERSLEEP 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* PWRMGMT_DEFAULT_CONFIG_H */
|
||||
111
Living_SDK/kernel/pwrmgmt/include/pwrmgmt_state.h
Executable file
111
Living_SDK/kernel/pwrmgmt/include/pwrmgmt_state.h
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
/*
|
||||
DESCRIPTION
|
||||
This file provides base type define power state.
|
||||
*/
|
||||
|
||||
#ifndef PWRMGMT_STATE_H
|
||||
#define PWRMGMT_STATE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#endif
|
||||
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
PWR_ERR = (-1),
|
||||
PWR_OK = (0)
|
||||
} pwr_status_t;
|
||||
|
||||
/* CPU states */
|
||||
typedef enum {
|
||||
CPU_CSTATE_C0 = 0, /* Operating */
|
||||
CPU_CSTATE_C1 = 1, /* Halt -- not executing put powered on */
|
||||
CPU_CSTATE_C2 = 2, /* not execuing with lower power than C1 */
|
||||
CPU_CSTATE_C3 = 3, /* not execuing with lower power than C2 */
|
||||
CPU_CSTATE_C4 = 4, /* not execuing with lower power than C3 */
|
||||
CPU_CSTATE_C5 = 5,
|
||||
CPU_CSTATE_C6 = 6,
|
||||
CPU_CSTATE_C7 = 7,
|
||||
CPU_CSTATE_C8 = 8,
|
||||
CPU_CSTATE_C9 = 9,
|
||||
CPU_CSTATE_C10 = 10,
|
||||
CPU_CSTATE_C11 = 11,
|
||||
CPU_CSTATE_C12 = 12,
|
||||
CPU_CSTATE_C13 = 13,
|
||||
CPU_CSTATE_C14 = 14,
|
||||
CPU_CSTATE_C15 = 15,
|
||||
CPU_CSTATE_C16 = 16,
|
||||
CPU_CSTATE_C17 = 17,
|
||||
CPU_CSTATE_C18 = 18,
|
||||
CPU_CSTATE_C19 = 19,
|
||||
CPU_CSTATE_C20 = 20,
|
||||
CPU_CSTATE_C21 = 21,
|
||||
CPU_CSTATE_C22 = 22,
|
||||
CPU_CSTATE_C23 = 23,
|
||||
CPU_CSTATE_C24 = 24,
|
||||
CPU_CSTATE_C25 = 25,
|
||||
CPU_CSTATE_C26 = 26,
|
||||
CPU_CSTATE_C27 = 27,
|
||||
CPU_CSTATE_C28 = 28,
|
||||
CPU_CSTATE_C29 = 29,
|
||||
CPU_CSTATE_C30 = 30,
|
||||
CPU_CSTATE_C31 = 31,
|
||||
CPU_CSTATE_MAX = 31,
|
||||
CPU_CSTATE_NONE = 0xFF
|
||||
} cpu_cstate_t;
|
||||
|
||||
/* Performance states */
|
||||
typedef enum {
|
||||
CPU_PSTATE_P0 = 0, /* Max power and clock */
|
||||
CPU_PSTATE_P1 = 1,
|
||||
CPU_PSTATE_P2 = 2,
|
||||
CPU_PSTATE_P3 = 3,
|
||||
CPU_PSTATE_P4 = 4,
|
||||
CPU_PSTATE_P5 = 5,
|
||||
CPU_PSTATE_P6 = 6,
|
||||
CPU_PSTATE_P7 = 7,
|
||||
CPU_PSTATE_P8 = 8,
|
||||
CPU_PSTATE_P9 = 9,
|
||||
CPU_PSTATE_P10 = 10,
|
||||
CPU_PSTATE_P11 = 11,
|
||||
CPU_PSTATE_P12 = 12,
|
||||
CPU_PSTATE_P13 = 13,
|
||||
CPU_PSTATE_P14 = 14,
|
||||
CPU_PSTATE_P15 = 15,
|
||||
CPU_PSTATE_P16 = 16,
|
||||
CPU_PSTATE_P17 = 17,
|
||||
CPU_PSTATE_P18 = 18,
|
||||
CPU_PSTATE_P19 = 19,
|
||||
CPU_PSTATE_P20 = 20,
|
||||
CPU_PSTATE_P21 = 21,
|
||||
CPU_PSTATE_P22 = 22,
|
||||
CPU_PSTATE_P23 = 23,
|
||||
CPU_PSTATE_P24 = 24,
|
||||
CPU_PSTATE_P25 = 25,
|
||||
CPU_PSTATE_P26 = 26,
|
||||
CPU_PSTATE_P27 = 27,
|
||||
CPU_PSTATE_P28 = 28,
|
||||
CPU_PSTATE_P29 = 29,
|
||||
CPU_PSTATE_P30 = 30,
|
||||
CPU_PSTATE_P31 = 31,
|
||||
CPU_PSTATE_MAX = 32,
|
||||
CPU_PSTATE_NONE = 0xFF
|
||||
} cpu_pstate_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PWRMGMT_STATE_H */
|
||||
29
Living_SDK/kernel/pwrmgmt/pwrmgmt.mk
Executable file
29
Living_SDK/kernel/pwrmgmt/pwrmgmt.mk
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
NAME := pwrmgmt
|
||||
|
||||
$(NAME)_MBINS_TYPE := kernel
|
||||
$(NAME)_VERSION := 1.0.0
|
||||
$(NAME)_SUMMARY :=
|
||||
|
||||
$(NAME)_SOURCES := cpu_pwr_lib.c
|
||||
$(NAME)_SOURCES += cpu_pwr_hal_lib.c
|
||||
$(NAME)_SOURCES += cpu_pwr_show.c
|
||||
$(NAME)_SOURCES += cpu_tickless.c
|
||||
$(NAME)_SOURCES += wifi_pwr_lib.c
|
||||
|
||||
ifeq ($(HOST_ARCH),linux)
|
||||
$(NAME)_DEFINES += IO_NEED_TRAP
|
||||
endif
|
||||
|
||||
#default gcc
|
||||
ifeq ($(COMPILER),)
|
||||
$(NAME)_CFLAGS += -Wall -Werror
|
||||
else ifeq ($(COMPILER),gcc)
|
||||
$(NAME)_CFLAGS += -Wall -Werror
|
||||
else ifeq ($(COMPILER),armcc)
|
||||
GLOBAL_DEFINES += __BSD_VISIBLE
|
||||
endif
|
||||
|
||||
GLOBAL_INCLUDES += include
|
||||
GLOBAL_DEFINES += AOS_COMP_PWRMGMT
|
||||
GLOBAL_DEFINES += RHINO_CONFIG_CPU_PWR_MGMT=1
|
||||
AOS_COMP_PWRMGMT := y
|
||||
10
Living_SDK/kernel/pwrmgmt/ucube.py
Executable file
10
Living_SDK/kernel/pwrmgmt/ucube.py
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
src = Split('''
|
||||
cpu_pwr_hal_lib.c
|
||||
cpu_pwr_lib.c
|
||||
cpu_pwr_show.c
|
||||
cpu_tickless.c
|
||||
''')
|
||||
component = aos_component('pwrmgmt', src)
|
||||
|
||||
component.add_global_includes('include')
|
||||
component.add_global_macros('AOS_COMP_PWRMGMT')
|
||||
24
Living_SDK/kernel/pwrmgmt/wifi_pwr_lib.c
Executable file
24
Living_SDK/kernel/pwrmgmt/wifi_pwr_lib.c
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Alibaba Group Holding Limited
|
||||
*/
|
||||
|
||||
#include "pwrmgmt_api.h"
|
||||
#include <hal/hal.h>
|
||||
|
||||
#if (WIFI_CONFIG_SUPPORT_LOWPOWER > 0)
|
||||
|
||||
int wifi_enter_powersave()
|
||||
{
|
||||
hal_wifi_module_t *module;
|
||||
module = hal_wifi_get_default_module();
|
||||
return hal_wifi_enter_powersave(module, WIFI_CONFIG_RECEIVE_DTIM);
|
||||
}
|
||||
|
||||
int wifi_exit_powersave()
|
||||
{
|
||||
hal_wifi_module_t *module;
|
||||
module = hal_wifi_get_default_module();
|
||||
return hal_wifi_exit_powersave(module);
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue