diff options
author | Randall Spangler <rspangler@chromium.org> | 2017-07-12 12:56:48 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2017-07-19 14:41:21 -0700 |
commit | a6060f53ee2c2736031b5fea80884886c6e08816 (patch) | |
tree | c412e882d29a0e65fb90ad29e66ac98798fc69e2 | |
parent | 1082d99ca629c88a46996f3911f0a5af98dd3ba8 (diff) | |
download | chrome-ec-a6060f53ee2c2736031b5fea80884886c6e08816.tar.gz |
cr50: Add physical presence state machine
This will be used as part of case closed debugging configuration.
Currently, this only adds the state machine, and a test command which
is available when CR50_DEV=1. It also adds a new sleep mask flag for
physical presence detect, instead of oveloading FORCE_NO_DSLEEP.
The physical presence state machine supports both short (tap 5 times
in 15 seconds) and long (short, followed by 4 more taps 1-5 minutes
apart) sequences. It will call a callback function at the end of the
sequence, so that multiple things can each request physical presence
in turn. This will be used by ccdopen / ccdunlock / testlab commands
for CCD V1.
Eventually, CCD config will replace the unlock sequence handling in
wp.c. But for now, we don't touch that.
See go/cr50-ccd-wp for more information.
BUG=b:62537474
BRANCH=none
TEST=manual
Short sequence:
pp short
pp
pp
pp # See "Physical presence good"
Timeout
pp short
pp # 15 sec later, get timeout error
Long sequence:
pp long
pp
pp
pp
(wait 2 sec)
pp
(wait 2 sec)
pp # See "PP good"
Long sequence, presses too fast:
pp long
pp
pp
pp
pp # See "PP L too soon"
(wait 2 sec)
pp
(wait 2 sec)
pp # See "Physical presence good"
Abort:
pp short
pp abort # See "Physical presence aborted"
Change-Id: I09da81ad11d328e7d7740c3fe838a5f67d7b8708
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/568796
Reviewed-by: Aseda Aboagye <aaboagye@chromium.org>
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-by: Mary Ruthven <mruthven@chromium.org>
-rw-r--r-- | board/cr50/board.h | 6 | ||||
-rw-r--r-- | board/cr50/wp.c | 22 | ||||
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/physical_presence.c | 303 | ||||
-rw-r--r-- | include/config.h | 6 | ||||
-rw-r--r-- | include/console_channel.inc | 3 | ||||
-rw-r--r-- | include/physical_presence.h | 64 | ||||
-rw-r--r-- | include/system.h | 2 |
8 files changed, 403 insertions, 4 deletions
diff --git a/board/cr50/board.h b/board/cr50/board.h index 9d0ebbce43..b6ebedd7b0 100644 --- a/board/cr50/board.h +++ b/board/cr50/board.h @@ -102,6 +102,12 @@ /* Enable Case Closed Debugging */ #define CONFIG_CASE_CLOSED_DEBUG +#define CONFIG_PHYSICAL_PRESENCE + +#ifdef CR50_DEV +/* Enable unsafe dev features for physical presence in dev builds */ +#define CONFIG_PHYSICAL_PRESENCE_DEBUG_UNSAFE +#endif #define CONFIG_USB_PID 0x5014 #define CONFIG_USB_SELF_POWERED diff --git a/board/cr50/wp.c b/board/cr50/wp.c index 3c25036647..4d074d9cbd 100644 --- a/board/cr50/wp.c +++ b/board/cr50/wp.c @@ -11,6 +11,7 @@ #include "hooks.h" #include "nvmem.h" #include "nvmem_vars.h" +#include "physical_presence.h" #include "registers.h" #include "scratch_reg1.h" #include "system.h" @@ -377,7 +378,7 @@ static void unlock_sequence_is_over(void) unlock_in_progress = 0; /* Allow sleeping again */ - enable_sleep(SLEEP_MASK_FORCE_NO_DSLEEP); + enable_sleep(SLEEP_MASK_PHYSICAL_PRESENCE); } DECLARE_DEFERRED(unlock_sequence_is_over); @@ -397,12 +398,16 @@ static void power_button_poked(void) static void power_button_handler(void) { - if (unlock_in_progress) + CPRINTS("power button pressed"); + if (physical_detect_press() == EC_SUCCESS) { + /* Consumed by physical detect */ + } else if (unlock_in_progress) { power_button_poked(); #ifdef CONFIG_U2F - else + } else { power_button_record(); #endif + } GWRITE_FIELD(RBOX, INT_STATE, INTR_PWRB_IN_FED, 1); } @@ -420,7 +425,7 @@ static void start_unlock_process(int total_poking_time, int max_poke_interval) unlock_deadline.val += total_poking_time; /* Stay awake while we're doing this, just in case. */ - disable_sleep(SLEEP_MASK_FORCE_NO_DSLEEP); + disable_sleep(SLEEP_MASK_PHYSICAL_PRESENCE); /* Check progress after waiting long enough for one button press */ hook_call_deferred(&unlock_sequence_is_over_data, unlock_beat); @@ -437,6 +442,15 @@ static void power_button_init(void) } DECLARE_HOOK(HOOK_INIT, power_button_init, HOOK_PRIO_DEFAULT); +void board_physical_presence_enable(int enable) +{ + /* Stay awake while we're doing this, just in case. */ + if (enable) + disable_sleep(SLEEP_MASK_PHYSICAL_PRESENCE); + else + enable_sleep(SLEEP_MASK_PHYSICAL_PRESENCE); +} + /****************************************************************************/ /* TPM vendor-specific commands */ diff --git a/common/build.mk b/common/build.mk index 9ceef96bc1..5f6a8954bd 100644 --- a/common/build.mk +++ b/common/build.mk @@ -79,6 +79,7 @@ common-$(CONFIG_LPC)+=acpi.o port80.o common-$(CONFIG_MAG_CALIBRATE)+= mag_cal.o math_util.o vec3.o mat33.o mat44.o common-$(CONFIG_MKBP_EVENT)+=mkbp_event.o common-$(CONFIG_ONEWIRE)+=onewire.o +common-$(CONFIG_PHYSICAL_PRESENCE)+=physical_presence.o common-$(CONFIG_POWER_BUTTON)+=power_button.o common-$(CONFIG_POWER_BUTTON_X86)+=power_button_x86.o common-$(CONFIG_PSTORE)+=pstore_commands.o diff --git a/common/physical_presence.c b/common/physical_presence.c new file mode 100644 index 0000000000..0f4bbeb0f3 --- /dev/null +++ b/common/physical_presence.c @@ -0,0 +1,303 @@ +/* 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_IN_PROGRESS, + 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; + +/******************************************************************************/ +/* + * 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_state != 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) +{ + 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\n", dt); + + /* If we no longer care about presses, ignore them */ + if (pp_detect_state != 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_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_IN_PROGRESS; + 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_state == 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; +} + +#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 diff --git a/include/config.h b/include/config.h index 6f8cf1bf77..411fc30f6a 100644 --- a/include/config.h +++ b/include/config.h @@ -1819,6 +1819,12 @@ */ #undef CONFIG_PECI_TJMAX +/* Support physical presence detection (via a physical button) */ +#undef CONFIG_PHYSICAL_PRESENCE + +/* Enable (unsafe!) developer debug features for physical presence */ +#undef CONFIG_PHYSICAL_PRESENCE_DEBUG_UNSAFE + /*****************************************************************************/ /* PMU config */ diff --git a/include/console_channel.inc b/include/console_channel.inc index 145ef53729..dc72d665fb 100644 --- a/include/console_channel.inc +++ b/include/console_channel.inc @@ -19,6 +19,9 @@ CONSOLE_CHANNEL(CC_BLUETOOTH_HCI,"bluetooth_hci") #ifdef CONFIG_EXTENSION_COMMAND CONSOLE_CHANNEL(CC_EXTENSION, "extension") #endif +#if defined(CONFIG_CASE_CLOSED_DEBUG) || defined(CONFIG_PHYSICAL_PRESENCE) +CONSOLE_CHANNEL(CC_CCD, "ccd") +#endif CONSOLE_CHANNEL(CC_CHARGER, "charger") CONSOLE_CHANNEL(CC_CHIPSET, "chipset") CONSOLE_CHANNEL(CC_CLOCK, "clock") diff --git a/include/physical_presence.h b/include/physical_presence.h new file mode 100644 index 0000000000..f2678993fc --- /dev/null +++ b/include/physical_presence.h @@ -0,0 +1,64 @@ +/* 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 detection + */ +#ifndef __CROS_EC_PHYSICAL_PRESENCE_H +#define __CROS_EC_PHYSICAL_PRESENCE_H + +/** + * Start physical presence detect. + * + * If the physical presence sequence is successful, callback() will be called + * from the hook task context as a deferred function. + * + * On failure or abort, callback() will not be called. + * + * @param is_long Use long (!=0) or short (0) sequence) + * @param callback Function to call when successful + * @return EC_SUCCESS, EC_BUSY if detect already in progress, or other + * non-zero error code if error. + */ +int physical_detect_start(int is_long, void (*callback)(void)); + +/** + * Check if a physical detect attempt is in progress + * + * @return non-zero if in progress + */ +int physical_detect_busy(void); + +/** + * Abort a currently-running physical presence detect. + * + * Note there is a race condition between stopping detect and a running + * detect finishing and calling its callback. The intent of this function + * is not to prevent that, but instead to avoid an aborted physical detect + * tying up the button for long periods when we no longer care. + */ +void physical_detect_abort(void); + +/** + * Handle a physical detect button press. + * + * This may be called from interrupt level. + * + * Returns EC_SUCCESS if the press was consumed, or EC_ERROR_NOT_HANDLED if + * physical detect was idle (so the press is for someone else). + */ +int physical_detect_press(void); + +/** + * Start/stop capturing the button for physical presence. + * + * When enabled, a debounced button press+release should call + * physical_detect_press(). + * + * This should be implemented by the board. + * + * @param enable Enable (!=0) or disable (==0) capturing button. + */ +void board_physical_presence_enable(int enable); + +#endif /* __CROS_EC_PHYSICAL_PRESENCE_H */ diff --git a/include/system.h b/include/system.h index cd7f42817e..a4595b3651 100644 --- a/include/system.h +++ b/include/system.h @@ -389,6 +389,8 @@ enum { SLEEP_MASK_FAN = (1 << 8), /* Fan control loop ongoing */ SLEEP_MASK_USB_DEVICE = (1 << 9), /* Generic USB device in use */ SLEEP_MASK_PWM = (1 << 10), /* PWM output is enabled */ + SLEEP_MASK_PHYSICAL_PRESENCE = (1 << 11), /* Physical presence + * detection ongoing */ SLEEP_MASK_FORCE_NO_DSLEEP = (1 << 15), /* Force disable. */ |