| /* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* System hooks for Chrome EC */ |
| |
| #include "atomic.h" |
| #include "console.h" |
| #include "hooks.h" |
| #include "link_defs.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| #ifdef HOOK_DEBUG |
| #define CPUTS(outstr) cputs(CC_HOOK, outstr) |
| #define CPRINTF(format, args...) cprintf(CC_HOOK, format, ## args) |
| #else |
| #define CPUTS(outstr) |
| #define CPRINTF(format, args...) |
| #endif |
| |
| #define DEFERRED_FUNCS_COUNT (__deferred_funcs_end - __deferred_funcs) |
| |
| struct hook_ptrs { |
| const struct hook_data *start; |
| const struct hook_data *end; |
| }; |
| |
| /* |
| * Hook data start and end pointers for each type of hook. Must be in same |
| * order as enum hook_type. |
| */ |
| static const struct hook_ptrs hook_list[] = { |
| {__hooks_init, __hooks_init_end}, |
| {__hooks_freq_change, __hooks_freq_change_end}, |
| {__hooks_sysjump, __hooks_sysjump_end}, |
| {__hooks_chipset_pre_init, __hooks_chipset_pre_init_end}, |
| {__hooks_chipset_startup, __hooks_chipset_startup_end}, |
| {__hooks_chipset_resume, __hooks_chipset_resume_end}, |
| {__hooks_chipset_suspend, __hooks_chipset_suspend_end}, |
| {__hooks_chipset_shutdown, __hooks_chipset_shutdown_end}, |
| {__hooks_ac_change, __hooks_ac_change_end}, |
| {__hooks_lid_change, __hooks_lid_change_end}, |
| {__hooks_pwrbtn_change, __hooks_pwrbtn_change_end}, |
| {__hooks_tick, __hooks_tick_end}, |
| {__hooks_second, __hooks_second_end}, |
| }; |
| |
| /* Times for deferrable functions */ |
| static uint64_t defer_until[DEFERRABLE_MAX_COUNT]; |
| static int defer_new_call; |
| |
| void hook_notify(enum hook_type type) |
| { |
| const struct hook_data *start, *end, *p; |
| int count, called = 0; |
| int last_prio = HOOK_PRIO_FIRST - 1, prio; |
| |
| CPRINTF("[%T hook notify %d]\n", type); |
| |
| start = hook_list[type].start; |
| end = hook_list[type].end; |
| count = ((uint32_t)end - (uint32_t)start) / sizeof(struct hook_data); |
| |
| /* Call all the hooks in priority order */ |
| while (called < count) { |
| /* Find the lowest remaining priority */ |
| for (p = start, prio = HOOK_PRIO_LAST + 1; p < end; p++) { |
| if (p->priority < prio && p->priority > last_prio) |
| prio = p->priority; |
| } |
| last_prio = prio; |
| |
| /* Call all the hooks with that priority */ |
| for (p = start; p < end; p++) { |
| if (p->priority == prio) { |
| called++; |
| p->routine(); |
| } |
| } |
| } |
| } |
| |
| void hook_init(void) |
| { |
| hook_notify(HOOK_INIT); |
| } |
| |
| int hook_call_deferred(void (*routine)(void), int us) |
| { |
| const struct deferred_data *p; |
| int i; |
| |
| /* Find the index of the routine */ |
| for (p = __deferred_funcs; p < __deferred_funcs_end; p++) { |
| if (p->routine == routine) |
| break; |
| } |
| if (p >= __deferred_funcs_end) |
| return EC_ERROR_INVAL; /* Routine not registered */ |
| |
| i = p - __deferred_funcs; |
| |
| if (us == -1) { |
| /* Cancel */ |
| defer_until[i] = 0; |
| } else { |
| /* Set alarm */ |
| defer_until[i] = get_time().val + us; |
| /* |
| * Flag that hook_call_deferred() has been called. If the hook |
| * task is already active, this will allow it to go through the |
| * loop one more time before sleeping. |
| */ |
| defer_new_call = 1; |
| /* Wake task so it can re-sleep for the proper time */ |
| task_wake(TASK_ID_HOOKS); |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| void hook_task(void) |
| { |
| /* Periodic hooks will be called first time through the loop */ |
| static uint64_t last_second = -SECOND; |
| static uint64_t last_tick = -HOOK_TICK_INTERVAL; |
| |
| while (1) { |
| uint64_t t = get_time().val; |
| int next = 0; |
| int i; |
| |
| /* Handle deferred routines */ |
| for (i = 0; i < DEFERRED_FUNCS_COUNT; i++) { |
| if (defer_until[i] && defer_until[i] < t) { |
| CPRINTF("[%T hook call deferred 0x%p]\n", |
| __deferred_funcs[i].routine); |
| /* |
| * Call deferred function. Clear timer first, |
| * so it can request itself be called later. |
| */ |
| defer_until[i] = 0; |
| __deferred_funcs[i].routine(); |
| } |
| } |
| |
| if (t - last_tick >= HOOK_TICK_INTERVAL) { |
| hook_notify(HOOK_TICK); |
| last_tick = t; |
| } |
| |
| if (t - last_second >= SECOND) { |
| hook_notify(HOOK_SECOND); |
| last_second = t; |
| } |
| |
| /* Calculate when next tick needs to occur */ |
| t = get_time().val; |
| if (last_tick + HOOK_TICK_INTERVAL > t) |
| next = last_tick + HOOK_TICK_INTERVAL - t; |
| |
| /* Wake earlier if needed by a deferred routine */ |
| defer_new_call = 0; |
| for (i = 0; i < DEFERRED_FUNCS_COUNT && next > 0; i++) { |
| if (!defer_until[i]) |
| continue; |
| |
| if (defer_until[i] < t) |
| next = 0; |
| else if (defer_until[i] - t < next) |
| next = defer_until[i] - t; |
| } |
| |
| /* |
| * If nothing is immediately pending, and hook_call_deferred() |
| * hasn't been called since we started calculating next, sleep |
| * until the next event. |
| */ |
| if (next > 0 && !defer_new_call) |
| task_wait_event(next); |
| } |
| } |