TimerDelegate(), TimerDelegate_r()
Delegate timers between CPUs
Synopsis:
#include <sys/neutrino.h>
int TimerDelegate( uint32_t action,
int32_t owner_cpu,
int32_t delegate_cpu );
int TimerDelegate_r( uint32_t action,
int32_t owner_cpu,
int32_t delegate_cpu );
Arguments:
- action
- The action to perform. One of the following:
- _NTO_TIMER_DELEGATE — Delegate a CPU's timers to a delegate CPU.
- _NTO_TIMER_RECLAIM — Reclaim a CPU's timers from a delegate CPU.
- _NTO_TIMER_QUERY_DELEGATE — Query the kernel to determine a CPU's current delegate CPU.
- owner_cpu
- The CPU the call originated from, or -1 to use the current CPU. The owner_cpu can't be a CPU that's acting on the behalf of another CPU.
- delegate_cpu
- The delegate CPU. The acceptable values depend on the
action you specified:
- DELEGATE — You can't specify a CPU that has timers delegated to another CPU.
- RECLAIM — Either specify the CPU that's taking care of the timers from owner_cpu, or set this field to -1 to let the kernel decide.
- QUERY — Ignored.
Library:
libc
Use the -l c option to qcc to link against this library. This library is usually included automatically.
Description:
The TimerDelegate() and TimerDelegate_r() functions move the handling of timers between processors. This allows one processor to temporarily delegate (i.e., give ownership of) its timers to another processor, and then reclaim (i.e., take back the ownership of) these timers. Software timer queues are associated with a CPU in the QNX OS; the purpose of these functions is to delegate a CPU's timers to another CPU allowing you to take a CPU offline and keep its assigned timers firing.
The TimerDelegate() and TimerDelegate_r() functions are identical, except in the way they indicate errors. Refer to the "Returns" section for details. Privileged applications responsible for offlining and onlining CPUs, such as power managers, should call these functions.
Only one thread can call TimerDelegate() at a time. The function returns EBUSY if multiple threads attempt to call the function concurrently. The thread calling this function can loop on EBUSY until the function returns another return code.
Delegating a CPU's timer to another CPU
You should delegate a CPU's timers before taking the CPU offline. To do so, call TimerDelegate() with the _NTO_TIMER_DELEGATE action to delegate a CPU's timers. The application then proposes a delegate CPU candidate. The kernel can refuse the proposed CPU if the CPU isn't handling its own timers at that moment (i.e., the proposed CPU already delegated its timers to another CPU).
Reclaiming a CPU's timer from another CPU
Once a CPU is back in operation, you should ensure that it reclaims its timers before the CPU notifies the kernel that it's ready to take part in scheduling. To do so, call TimerDelegate() with the _NTO_TIMER_RECLAIM action to reclaim a CPU's timers. If the application is responsible for tracking the delegate CPU, the application passes on this information to the kernel. Otherwise, the kernel must find the delegate CPU itself (set delegate_cpu to -1), but this is costly.
The CPU only reclaims its own timers, which excludes any timers that it had previously been delegated with as a delegate CPU (refer to the section below on "Transitive Delegation" for more information).
Transitive Delegation
When a CPU delegates its timers with a delegate CPU, all timers in its care go to the delegate CPU. When the CPU comes back online, it only reclaims its own timers. The delegate CPU takes care of the timers the CPU was previously delegated with before going offline.
For example, imagine a system with four CPUs. CPU3 needs to be taken offline and has previously been delegated with CPU2's timers. CPU3 delegates its timers to CPU1. CPU1 is then responsible for the timers of both CPU2 and CPU3.
If CPU3 comes back online before CPU2, then CPU3 is only responsible for its own timers from that point on. Subsequently, CPU1 is responsible for its own timers and CPU2's timers, until CPU2 reclaims its timers.
Requirements
The TimerDelegate*() functions only succeed if the following requirements are met:
- The privileged application can run a thread at a higher priority that the
clock IST (254 by default) and a lower priority than the IPI IST (always
255). Therefore, you must configure the system so that the clock IST runs at
a maximum priority of 253, by passing the -C option to
procnto as follows:
procnto-smp-instr -C0,253
- The process must have the PROCMGR_AID_RUNSTATE ability enabled. For more information, go to procmgr_ability().
- The thread that calls the function must:
- be pinned to the CPU delegating its timers.
- have its scheduling policy set to SCHED_OFFLINING. This policy is a FIFO scheduling policy that allows a thread to be of higher priority than the clock IST.
- have a higher priority than the clock IST.
Returns:
- TimerDelegate() returns -1 and sets errno.
- TimerDelegate_r() returns the negative of a value from the Errors section and doesn't set errno.
Errors:
- EAGAIN
- The kernel refused the proposed delegate_cpu during a
DELEGATE operation. For more information, refer to the
Delegating a CPU's timer to another CPU
section. - EBUSY
- Multiple threads tried to call TimerDelegate() concurrently.
- EINVAL
- An invalid CPU was provided.
- ENOEXEC
- The calling thread doesn't have its scheduling policy set to SCHED_OFFLINING.
- ENOSYS
- An invalid action was provided, or the CPU provided isn't the current CPU.
- ENOTSUP
- The call was performed on a uniprocessor system.
- EPERM
- The application process didn't have the PROCMGR_AID_RUNSTATE ability enabled.
- ERANGE
- The calling thread's priority is less than or equal to that of the clock IST.
- ESRCH
- The kernel couldn't find the current delegate CPU (i.e., the current CPU hasn't delegated its timers).
Examples:
#include <sys/neutrino.h>
#include <sys/syspage.h>
#include <sys/resource.h>
#include <sys/cpuinline.h>
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <pthread.h>
#define cps SYSPAGE_ENTRY(qtime)->cycles_per_sec
#define cc ClockCycles()
static unsigned ncpus;
static void burn(uint64_t const sec)
{
uint64_t now_cc = ClockCycles(), end_cc = now_cc + sec * cps;
while(ClockCycles() < end_cc) {
// Burn CPU cycles.
__cpu_bus_backoff();
}
}
static void pin(unsigned const cpu)
{
unsigned const runmask = (1U << cpu);
void *threadctl_arg = (void *)(uintptr_t)runmask;
assert(ThreadCtl(_NTO_TCTL_RUNMASK, threadctl_arg) != -1);
}
static void unpin(void)
{
unsigned const runmask = ~0U;
void *threadctl_arg = (void *)(uintptr_t)runmask;
assert(ThreadCtl(_NTO_TCTL_RUNMASK, threadctl_arg) != -1);
}
static void powerdown(unsigned const delegate_cpu)
{
for (;;) {
int const rc = TimerDelegate_r(_NTO_TIMER_DELEGATE,
-1, delegate_cpu);
if (rc == 0) { break; }
if (rc == -EBUSY) { __cpu_bus_backoff(); continue; }
// A real power manager would do some handling because it did something
// wrong if it gets here.
exit(3);
}
}
static void powerup(unsigned const delegate_cpu)
{
for (;;) {
int const rc = TimerDelegate_r(_NTO_TIMER_RECLAIM,
-1, delegate_cpu);
if (rc == 0) { break; }
if (rc == -EBUSY) { __cpu_bus_backoff(); continue; }
// A real power manager would do some handling because it did something
// wrong if it gets here.
exit(3);
}
}
static unsigned find_delegate(void)
{
unsigned delegate_cpu = -1;
for (;;) {
int const rc = TimerDelegate_r(_NTO_TIMER_QUERY_DELEGATE,
-1, delegate_cpu);
if (rc >= 0) { delegate_cpu = (unsigned)rc; break; }
if (rc == -EBUSY) { __cpu_bus_backoff(); continue; }
// A real power manager would do some handling because it did something
// wrong if it gets here.
exit(3);
}
return delegate_cpu;
}
static void *powerman(void * const arg)
{
assert(arg == (void *)0UL);
// Thread will be pinned on CPU0 and will delegate CPU0's timers to CPU1
int const owner_cpu = (int)(intptr_t)arg;
int const delegate_cpu = owner_cpu + 1;
pin(owner_cpu);
struct sched_param const param = { .sched_priority = 254, };
pthread_t const self = pthread_self();
int const rc = pthread_setschedparam(self, SCHED_OFFLINING, ¶m);
assert(rc == 0);
// Powering down CPU0, the timer associated with CPU0 will keep ticking on
// CPU1.
powerdown(delegate_cpu);
// Burn CPU cycles for two seconds, we should see 10 more ticks from the
// timer at 200ms interval.
burn(2);
// Reclaim timers.
powerup(find_delegate());
return NULL;
}
static void SIGALRM_handler(int const signo)
{
printf("%lu ms got signo %d\n", clock_gettime_mon_ns()/1000000UL, signo);
}
#define MS(x) ((uint64_t)(x) * 1000000UL)
int main(int argc, char *argv[])
{
ncpus = _syspage_ptr->num_cpu;
assert(ncpus > 1U);
int rc;
// Pin current thread on CPU0, that will force the timer to be associated
// with CPU0, starts after 500ms, ticks every 200ms
pin(0);
signal(SIGALRM, SIGALRM_handler);
int t = TimerCreate(CLOCK_MONOTONIC, NULL); // SIGALRM
assert(t != -1);
struct _itimer itime = { MS(500), MS(200), };
rc = TimerSettime(t, 0, &itime, NULL);
assert(rc != -1);
unpin();
// let timer fire for a few times
burn(1);
// until end point, CPU0 is down, timer keeps firing on CPU1
printf("start @ %lu ms\n", clock_gettime_mon_ns()/1000000UL);
// spawn thread that simulates a powerdown of CPU0
pthread_t powerman_tid;
rc = pthread_create(&powerman_tid, NULL, powerman, (void *)(uintptr_t)0);
assert(rc == 0);
rc = pthread_join(powerman_tid, NULL);
// until end point, CPU0 has reclaimed its timers.
printf("end @ %lu ms\n", clock_gettime_mon_ns()/1000000UL);
burn(1); // run timer for an extra second
return 0;
}
Classification:
Safety: | |
---|---|
Cancellation point | No |
Signal handler | No |
Thread | Yes |