Power Management

Updated: April 19, 2023

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:

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:

  1. Populate the nto_power_parameter data structure with the power-management characteristics and policies you want to communicate to the kernel.
  2. 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.
Note: You can use the method described above to change the power-management policies for a CPU cluster at any time while your system is running. The exception is removing and, possibly, reinstating operating points (see 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.

Note that:
  • 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:

  1. 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.
  2. 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.

Note: Your power-management process may choose to ignore the sigevent from the kernel, though this is not usually a recommended course of action.

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:

  1. 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.
  2. Set nto_power_parameter.parameter_type to _NTO_PPT_DEFINE_CLUSTER.
  3. 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;
}