summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2017-07-12 12:56:48 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-07-19 14:41:21 -0700
commita6060f53ee2c2736031b5fea80884886c6e08816 (patch)
treec412e882d29a0e65fb90ad29e66ac98798fc69e2
parent1082d99ca629c88a46996f3911f0a5af98dd3ba8 (diff)
downloadchrome-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.h6
-rw-r--r--board/cr50/wp.c22
-rw-r--r--common/build.mk1
-rw-r--r--common/physical_presence.c303
-rw-r--r--include/config.h6
-rw-r--r--include/console_channel.inc3
-rw-r--r--include/physical_presence.h64
-rw-r--r--include/system.h2
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. */