| /* Copyright 2017 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. |
| * |
| * Physical presence detect state machine |
| */ |
| |
| #include "common.h" |
| #include "console.h" |
| #include "hooks.h" |
| #include "physical_presence.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| #define CPRINTS(format, args...) cprints(CC_CCD, format, ## args) |
| #define CPRINTF(format, args...) cprintf(CC_CCD, format, ## args) |
| |
| #ifdef CONFIG_PHYSICAL_PRESENCE_DEBUG_UNSAFE |
| /* More lenient physical presence for dev builds */ |
| #define PP_SHORT_PRESS_COUNT 3 |
| #define PP_SHORT_PRESS_MIN_INTERVAL_US (100 * MSEC) |
| #define PP_SHORT_PRESS_MAX_INTERVAL_US (15 * SECOND) |
| #define PP_LONG_PRESS_COUNT (PP_SHORT_PRESS_COUNT + 2) |
| #define PP_LONG_PRESS_MIN_INTERVAL_US (2 * SECOND) |
| #define PP_LONG_PRESS_MAX_INTERVAL_US (300 * SECOND) |
| #else |
| /* Stricter physical presence for non-dev builds */ |
| #define PP_SHORT_PRESS_COUNT 5 |
| #define PP_SHORT_PRESS_MIN_INTERVAL_US (100 * MSEC) |
| #define PP_SHORT_PRESS_MAX_INTERVAL_US (5 * SECOND) |
| #define PP_LONG_PRESS_COUNT (PP_SHORT_PRESS_COUNT + 4) |
| #define PP_LONG_PRESS_MIN_INTERVAL_US (60 * SECOND) |
| #define PP_LONG_PRESS_MAX_INTERVAL_US (300 * SECOND) |
| #endif |
| |
| enum pp_detect_state { |
| PP_DETECT_IDLE = 0, |
| PP_DETECT_AWAITING_PRESS, |
| PP_DETECT_BETWEEN_PRESSES, |
| PP_DETECT_FINISHING, |
| PP_DETECT_ABORT |
| }; |
| |
| /* Physical presence state machine data */ |
| static enum pp_detect_state pp_detect_state; |
| static void (*pp_detect_callback)(void); |
| static uint8_t pp_press_count; |
| static uint8_t pp_press_count_needed; |
| static uint64_t pp_last_press; /* Time of last press */ |
| |
| /* |
| * We need a mutex because physical_detect_start() and physical_detect_abort() |
| * could be called from multiple threads (TPM or console). And either of those |
| * could preempt the deferred functions for the state machine which run in the |
| * hook task. |
| */ |
| static struct mutex pp_mutex; |
| |
| static int pp_detect_in_progress(void) |
| { |
| return ((pp_detect_state == PP_DETECT_AWAITING_PRESS) || |
| (pp_detect_state == PP_DETECT_BETWEEN_PRESSES)); |
| } |
| |
| /******************************************************************************/ |
| /* |
| * Deferred functions |
| * |
| * These are called by the hook task, so can't preempt each other. But they |
| * could be preempted by calls to physical_presence_start() or |
| * physical_presence_abort(). |
| */ |
| |
| /** |
| * Clean up at end of physical detect sequence. |
| */ |
| static void physical_detect_done(void) |
| { |
| /* |
| * Note that calling physical_detect_abort() from another thread after |
| * the start of physical_detect_done() but before mutex_lock() will |
| * result in another call to physical_detect_done() being queued up. |
| * That's harmless, because we go back to PP_DETECT_IDLE at the end of |
| * this call, so the second call will simply drop through without |
| * calling pp_detect_callback(). |
| */ |
| mutex_lock(&pp_mutex); |
| |
| if (!pp_detect_in_progress()) { |
| CPRINTF("\nPhysical presence check aborted.\n"); |
| pp_detect_callback = NULL; |
| } else if (pp_press_count < pp_press_count_needed) { |
| CPRINTF("\nPhysical presence check timeout.\n"); |
| pp_detect_callback = NULL; |
| } |
| |
| pp_detect_state = PP_DETECT_FINISHING; |
| mutex_unlock(&pp_mutex); |
| |
| /* No longer care about button presses */ |
| board_physical_presence_enable(0); |
| |
| /* |
| * Call the callback function. Do this outside the mutex, because the |
| * callback may take a while. If we kept holding the mutex, then calls |
| * to physical_detect_abort() or physical_detect_start() during the |
| * callback would block instead of simply failing. |
| */ |
| if (pp_detect_callback) { |
| CPRINTS("PP callback"); |
| pp_detect_callback(); |
| pp_detect_callback = NULL; |
| } |
| |
| /* Now go to idle */ |
| mutex_lock(&pp_mutex); |
| pp_detect_state = PP_DETECT_IDLE; |
| mutex_unlock(&pp_mutex); |
| } |
| DECLARE_DEFERRED(physical_detect_done); |
| |
| /** |
| * Print a prompt when we've hit the minimum wait time |
| */ |
| static void physical_detect_prompt(void) |
| { |
| pp_detect_state = PP_DETECT_AWAITING_PRESS; |
| CPRINTF("\n\nPress the physical button now!\n\n"); |
| } |
| DECLARE_DEFERRED(physical_detect_prompt); |
| |
| /** |
| * Handle a physical present button press |
| * |
| * This is implemented as a deferred function so it can use the mutex. |
| */ |
| static void physical_detect_check_press(void) |
| { |
| uint64_t now = get_time().val; |
| uint64_t dt = now - pp_last_press; |
| |
| mutex_lock(&pp_mutex); |
| |
| CPRINTS("PP press dt=%.6lld", dt); |
| |
| /* If we no longer care about presses, ignore them */ |
| if (!pp_detect_in_progress()) |
| goto pdpress_exit; |
| |
| /* Ignore extra presses we don't need */ |
| if (pp_press_count >= pp_press_count_needed) |
| goto pdpress_exit; |
| |
| /* Ignore presses outside the expected interval */ |
| if (pp_press_count < PP_SHORT_PRESS_COUNT) { |
| if (dt < PP_SHORT_PRESS_MIN_INTERVAL_US) { |
| CPRINTS("PP S too soon"); |
| goto pdpress_exit; |
| } |
| if (dt > PP_SHORT_PRESS_MAX_INTERVAL_US) { |
| CPRINTS("PP S too late"); |
| goto pdpress_exit; |
| } |
| } else { |
| if (dt < PP_LONG_PRESS_MIN_INTERVAL_US) { |
| CPRINTS("PP L too soon"); |
| goto pdpress_exit; |
| } |
| if (dt > PP_LONG_PRESS_MAX_INTERVAL_US) { |
| CPRINTS("PP L too late"); |
| goto pdpress_exit; |
| } |
| } |
| |
| /* Ok, we need this press */ |
| CPRINTS("PP press counted!"); |
| pp_detect_state = PP_DETECT_BETWEEN_PRESSES; |
| pp_last_press = now; |
| pp_press_count++; |
| |
| /* Set up call to done handler for timeout or actually done */ |
| if (pp_press_count == pp_press_count_needed) { |
| /* Done, so call right away */ |
| hook_call_deferred(&physical_detect_done_data, 0); |
| } else if (pp_press_count < PP_SHORT_PRESS_COUNT) { |
| hook_call_deferred(&physical_detect_prompt_data, |
| PP_SHORT_PRESS_MIN_INTERVAL_US); |
| hook_call_deferred(&physical_detect_done_data, |
| PP_SHORT_PRESS_MAX_INTERVAL_US); |
| } else { |
| CPRINTF("Another press will be required soon.\n"); |
| dt = PP_LONG_PRESS_MAX_INTERVAL_US; |
| hook_call_deferred(&physical_detect_prompt_data, |
| PP_LONG_PRESS_MIN_INTERVAL_US); |
| hook_call_deferred(&physical_detect_done_data, |
| PP_LONG_PRESS_MAX_INTERVAL_US); |
| } |
| |
| pdpress_exit: |
| mutex_unlock(&pp_mutex); |
| } |
| DECLARE_DEFERRED(physical_detect_check_press); |
| |
| /******************************************************************************/ |
| /* Interface */ |
| |
| int physical_detect_start(int is_long, void (*callback)(void)) |
| { |
| mutex_lock(&pp_mutex); |
| |
| /* Fail if detection is already in progress */ |
| if (pp_detect_state != PP_DETECT_IDLE) { |
| mutex_unlock(&pp_mutex); |
| return EC_ERROR_BUSY; |
| } |
| |
| pp_press_count_needed = is_long ? PP_LONG_PRESS_COUNT : |
| PP_SHORT_PRESS_COUNT; |
| pp_press_count = 0; |
| pp_last_press = get_time().val; |
| pp_detect_callback = callback; |
| pp_detect_state = PP_DETECT_BETWEEN_PRESSES; |
| mutex_unlock(&pp_mutex); |
| |
| /* Start capturing button presses */ |
| hook_call_deferred(&physical_detect_check_press_data, -1); |
| board_physical_presence_enable(1); |
| |
| CPRINTS("PP start %s", is_long ? "long" : "short"); |
| |
| /* Initial timeout is for a short press */ |
| hook_call_deferred(&physical_detect_prompt_data, |
| PP_SHORT_PRESS_MIN_INTERVAL_US); |
| hook_call_deferred(&physical_detect_done_data, |
| PP_SHORT_PRESS_MAX_INTERVAL_US); |
| |
| return EC_SUCCESS; |
| } |
| |
| int physical_detect_busy(void) |
| { |
| return pp_detect_state != PP_DETECT_IDLE; |
| } |
| |
| void physical_detect_abort(void) |
| { |
| mutex_lock(&pp_mutex); |
| if (pp_detect_in_progress()) { |
| CPRINTS("PP abort"); |
| pp_detect_state = PP_DETECT_ABORT; |
| /* Speed up call to done */ |
| hook_call_deferred(&physical_detect_prompt_data, -1); |
| hook_call_deferred(&physical_detect_check_press_data, -1); |
| hook_call_deferred(&physical_detect_done_data, 0); |
| } |
| mutex_unlock(&pp_mutex); |
| } |
| |
| int physical_detect_press(void) |
| { |
| /* Ignore presses if we're idle */ |
| if (pp_detect_state == PP_DETECT_IDLE) |
| return EC_ERROR_NOT_HANDLED; |
| |
| /* Call the deferred function to do the work */ |
| hook_call_deferred(&physical_detect_check_press_data, 0); |
| return EC_SUCCESS; |
| } |
| |
| enum pp_fsm_state physical_presense_fsm_state(void) |
| { |
| switch (pp_detect_state) { |
| case PP_DETECT_AWAITING_PRESS: |
| return PP_AWAITING_PRESS; |
| case PP_DETECT_BETWEEN_PRESSES: |
| return PP_BETWEEN_PRESSES; |
| default: |
| break; |
| } |
| |
| return PP_OTHER; |
| } |
| |
| #ifdef CONFIG_PHYSICAL_PRESENCE_DEBUG_UNSAFE |
| |
| /** |
| * Test callback function |
| */ |
| static void pp_test_callback(void) |
| { |
| ccprintf("\nPhysical presence good\n"); |
| } |
| |
| /** |
| * Test physical presence. |
| */ |
| static int command_ppresence(int argc, char **argv) |
| { |
| /* Print current status */ |
| ccprintf("PP state: %d, %d/%d, dt=%.6lld\n", |
| pp_detect_state, pp_press_count, pp_press_count_needed, |
| get_time().val - pp_last_press); |
| |
| /* With no args, simulate a button press */ |
| if (argc < 2) { |
| physical_detect_press(); |
| return EC_SUCCESS; |
| } |
| |
| if (!strcasecmp(argv[1], "short")) { |
| return physical_detect_start(0, pp_test_callback); |
| } else if (!strcasecmp(argv[1], "long")) { |
| return physical_detect_start(1, pp_test_callback); |
| } else if (!strcasecmp(argv[1], "abort")) { |
| physical_detect_abort(); |
| return EC_SUCCESS; |
| } else { |
| return EC_ERROR_PARAM1; |
| } |
| } |
| DECLARE_SAFE_CONSOLE_COMMAND(ppresence, command_ppresence, |
| "[short | long | abort]", |
| "Test physical presence press or sequence"); |
| |
| #endif |