Power Management
A major contributor to the power consumption of a CPU is the frequency at which it is clocked. The power-management API allows you to manage this.
Power management can be divided into two phases:
Setting up power management
Generally, you set up power management early on in the life of your system. This setup doesn't have to be completed before you allow users to begin using the running system, however. That is, if you must meet some fast boot requirements, such as presenting a working camera to a user within n milliseconds, you can defer setting up power management until after you have met these requirements (see the Boot Optimization Guide).
When you set up your power management, you:
- inform the kernel about the power-management characteristics of the board on which it is running; specifically, the operating points the board supports
- define CPU clusters whose members will be managed together as a group (see
CPU clusters
) - specify the policies that the kernel should implement when determining if it should move CPU clusters to different operating points; that is, how long and at what usage level the kernel should allow a CPU cluster to run before changing the operating point at which its CPUs are running
To set up or change your power-management policies, in the process you use for your power management, call ChannelCreate() and then ConnectAttach() to create and attach to a channel. Then, for each CPU:
- Populate the nto_power_parameter data structure with the power-management characteristics and policies you want to communicate to the kernel.
- Call the PowerParameter() function as required to create CPU clusters, set their online and offline characteristics or their frequency characteristics, and communicate these to the kernel.
Managing operating points); this is generally not needed or recommended, however.
CPU clusters
For the purposes of power management (and, therefore, in this documentation) a CPU cluster isn't necessarily equivalent to a CPU cluster as defined in the SoC documentation for your board. It is simply a set of CPUs you create so you can manage their power characteristics together. A CPU cluster may include only a single CPU or multiple CPUs.
- The power-management API works with CPU clusters, so to manage a CPU's power consumption, you must include it in a cluster.
- When CPUs are in a cluster they change operating points together and only together. As long as a CPU is in cluster you can't change its operating points independently of the other CPUs in the cluster.
- The kernel reports an underflow only if all CPUs in the cluster are below the usage level specified by the power-management policies for that cluster.
- The kernel reports an overflow if any CPU in a cluster exceeds the usage level specified by the power-management policies for that cluster.
If your board's SoC supports setting the frequencies for its CPU clusters independently or supports other CPU clusters, then it may be useful to create your power-management CPU clusters to match the SoC CPU clusters.
Managing power consumption
After you have informed the kernel of the SoC's power-management characteristics and the policies you want to implement, the kernel will monitor CPU cluster loads at the operating points specified in the power-management policies.
When the kernel detects that a CPU cluster is overloaded or underused, it sends a sigevent to your power-management process. This process should then:
- Perform the firmware and/or hardware operations required to move the affected CPU cluster to a new operating point; that is, decrease or increase the CPU frequency, as required.
- Call PowerSetActive() to inform the kernel of the new operating point.
With the new information it receives through the call to PowerSetActive(), the kernel will continue to monitor CPU cluster loads and to send the power-management process new sigevents as required.
If it needs to know a CPU's current active frequency, your power-management process can call PowerGetActive().
Managing operating points
While the system is running, you may need to change the operating points you communicated to the kernel with your initial power management setup. Specifically:
- Overheating
- If a chip is overheating, you should remove from a CPU cluster's characteristics one or more of the highest frequency operating points. This will cause the kernel to prevent the CPUs in the cluster from running at these frequencies, and should reduce the heat they generate.
- Critical tasks
- If a user in the system is performing or about to perform a critical task that is CPU-intensive and, therefore, for which CPU performance must be maintained to prevent glitching, you should remove from a CPU cluster's characteristics one or more of the lowest frequency operating points. This will ensure that the CPUs in the cluster continue running at higher-frequency operating points.
To make these changes to CPU cluster characteristics:
- Set the bits in nto_power_parameter.u.cluster.frequency_mask to mask out the operating points you don't want to be available to the CPU cluster.
- Set nto_power_parameter.parameter_type to _NTO_PPT_DEFINE_CLUSTER.
- Call PowerParameter() to inform the kernel of the changes to the permissable operating points for the CPU cluster.
If you need to re-instate some permissable operating points, you can modify the frequency_mask bitmask and repeat the procedure.
Example
The program below illustrates how the power-management API can be used:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/siginfo.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
int
main() {
struct nto_power_parameter parm = {0};
int r;
int coid;
int chid;
int pp[3];
unsigned cluster;
unsigned i;
chid = ChannelCreate(0);
if(chid == -1) {
printf("chid failed %d\n", errno);
return 1;
}
coid = ConnectAttach(0, getpid(), chid, _NTO_SIDE_CHANNEL, 0);
if(coid == -1) {
printf("coid failed %d\n", errno);
return 1;
}
parm.parameter_type = 0;
parm.clusterid = -1;
parm.u.cluster.frequency_mask = 0;
cluster = PowerParameter(0, sizeof(parm), &parm, NULL);
printf("PowerParm (cluster) got %d, %d\n", cluster, errno);
for(i = 0; i < _syspage_ptr->num_cpu; ++i) {
parm.parameter_type = _NTO_PPT_DEFINE_CPU;
parm.clusterid = cluster;
parm.u.cpu.cpuid = i;
parm.u.cpu.unloaded = 75;
parm.u.cpu.loaded.nonburst = 97;
parm.u.cpu.loaded.burst = 20;
pp[0] = PowerParameter(0, sizeof(parm), &parm, NULL);
printf("PowerParm (cpu%d) got %d, %d\n", i, pp[0], errno);
}
parm.parameter_type = _NTO_PPT_DEFINE_FREQ;
parm.clusterid = cluster;
parm.u.freq.performance = 100;
parm.u.freq.low.load = 10;
parm.u.freq.low.duration = 100; // ms
parm.u.freq.high.load = 90;
parm.u.freq.high.duration = 200; // ms
parm.u.freq.max_cores_online_hint = 0;
SIGEV_PULSE_INIT(&parm.u.freq.ev, coid, 10, 1, 0);
SIGEV_MAKE_UPDATEABLE(&parm.u.freq.ev);
pp[0] = PowerParameter(0, sizeof(parm), &parm, NULL);
printf("PowerParm got %d, %d\n", pp[0], errno);
parm.u.freq.performance = 200;
parm.u.freq.low.load = 15;
parm.u.freq.low.duration = 150; // ms
parm.u.freq.high.load = 80;
parm.u.freq.high.duration = 50; // ms
SIGEV_PULSE_INIT(&parm.u.freq.ev, coid, 10, 1, 0);
SIGEV_MAKE_UPDATEABLE(&parm.u.freq.ev);
pp[1] = PowerParameter(0, sizeof(parm), &parm, NULL);
printf("PowerParm got %d, %d\n", pp[1], errno);
parm.u.freq.performance = 300;
parm.u.freq.low.load = 20;
parm.u.freq.low.duration = 400; // ms
parm.u.freq.high.load = 75;
parm.u.freq.high.duration = 500; // ms
SIGEV_PULSE_INIT(&parm.u.freq.ev, coid, 10, 1, 0);
SIGEV_MAKE_UPDATEABLE(&parm.u.freq.ev);
pp[2] = PowerParameter(0, sizeof(parm), &parm, NULL);
printf("PowerParm got %d, %d\n", pp[2], errno);
r = PowerSetActive(pp[0]);
printf("PowerSA 1 got r=%d, errno=%d\n", r, errno);
struct _pulse p;
for( ;; ) {
MsgReceivePulse(chid, &p, sizeof(p), NULL);
printf("EV code=%d val=0x%x, curr=%d: ", p.code, p.value.sival_int, PowerGetActive(0));
unsigned id = p.value.sival_int >> _NTO_PFR_PARM_ID_SHIFT;
r = PowerSetActive(id);
printf("PowerSA 2 (%d) got r=%d, errno=%d\n", id, r, errno);
}
return 0;
}
