/* 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=%.6ld", 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=%.6ld\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