summaryrefslogtreecommitdiff
path: root/common/mkbp_event.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/mkbp_event.c')
-rw-r--r--common/mkbp_event.c290
1 files changed, 228 insertions, 62 deletions
diff --git a/common/mkbp_event.c b/common/mkbp_event.c
index 44cec336b5..18843ef6be 100644
--- a/common/mkbp_event.c
+++ b/common/mkbp_event.c
@@ -10,67 +10,125 @@
#include "gpio.h"
#include "host_command.h"
#include "hwtimer.h"
+#include "timer.h"
#include "link_defs.h"
#include "mkbp_event.h"
#include "power.h"
#include "util.h"
-static uint32_t events;
+#define CPUTS(outstr) cputs(CC_COMMAND, outstr)
+#define CPRINTS(format, args...) cprints(CC_COMMAND, format, ## args)
+#define CPRINTF(format, args...) cprintf(CC_COMMAND, format, ## args)
+
+/*
+ * Tracks the current state of the MKBP interrupt send from the EC to the AP.
+ *
+ * The inactive state is only valid when there are no events to set to the AP.
+ * If the AP is asleep, then some events are not worth waking the AP up, so the
+ * interrupt could remain in an inactive in that case.
+ *
+ * The transition state (INTERRUPT_INACTIVE_TO_ACTIVE) is used to track the
+ * sometimes lock transition for a "rising edge" for platforms that send the
+ * rising edge interrupt through a host communication layer
+ *
+ * The active state represents that a rising edge interrupt has already been
+ * sent to the AP, and the EC is waiting for the AP to call get next event
+ * host command to consume all of the events (at which point the state will
+ * move to inactive).
+ *
+ * The transition from ACTIVE -> INACTIVE is considerer to be simple meaning
+ * the operation can be performed within a blocking mutex (e.g. no-op or setting
+ * a gpio).
+ */
+enum interrupt_state {
+ INTERRUPT_INACTIVE,
+ INTERRUPT_INACTIVE_TO_ACTIVE, /* Transitioning */
+ INTERRUPT_ACTIVE,
+};
+
+struct mkbp_state {
+ struct mutex lock;
+ uint32_t events;
+ enum interrupt_state interrupt;
+ /*
+ * Tracks unique transitions to INTERRUPT_INACTIVE_TO_ACTIVE allowing
+ * only the most recent transition to finish the transition to a final
+ * state -- either active or inactive depending on the result of the
+ * operation.
+ */
+ uint8_t interrupt_id;
+ /*
+ * Tracks the number of consecutive failed attempts for the AP to poll
+ * get_next_events in order to limit the retry logic.
+ */
+ uint8_t failed_attempts;
+};
+
+static struct mkbp_state state;
uint32_t mkbp_last_event_time;
-static void set_event(uint8_t event_type)
+#ifdef CONFIG_MKBP_USE_GPIO
+static int mkbp_set_host_active_via_gpio(int active, uint32_t *timestamp)
{
- atomic_or(&events, 1 << event_type);
-}
+ /*
+ * If we want to take a timestamp, then disable interrupts temporarily
+ * to ensure that the timestamp is as close as possible to the setting
+ * of the GPIO pin in hardware (i.e. we aren't interrupted between
+ * taking the timestamp and setting the gpio)
+ */
+ if (timestamp) {
+ interrupt_disable();
+ *timestamp = __hw_clock_source_read();
+ }
-static void clear_event(uint8_t event_type)
-{
- atomic_clear(&events, 1 << event_type);
-}
+ gpio_set_level(GPIO_EC_INT_L, !active);
-static int event_is_set(uint8_t event_type)
-{
- return events & (1 << event_type);
-}
+ if (timestamp)
+ interrupt_enable();
-#ifndef CONFIG_MKBP_USE_HOST_EVENT
-void mkbp_set_host_active_via_gpio(int active)
-{
- gpio_set_level(GPIO_EC_INT_L, !active);
+ return EC_SUCCESS;
}
#endif
-void mkbp_set_host_active_via_event(int active)
+#ifdef CONFIG_MKBP_USE_HOST_EVENT
+static int mkbp_set_host_active_via_event(int active, uint32_t *timestamp)
{
+ /* This should be moved into host_set_single_event for more accuracy */
+ if (timestamp)
+ *timestamp = __hw_clock_source_read();
if (active)
host_set_single_event(EC_HOST_EVENT_MKBP);
+ return EC_SUCCESS;
}
-
-__attribute__((weak)) void mkbp_set_host_active(int active)
-{
-#ifdef CONFIG_MKBP_USE_HOST_EVENT
- mkbp_set_host_active_via_event(active);
-#else
- mkbp_set_host_active_via_gpio(active);
#endif
-}
-/**
- * Assert host keyboard interrupt line.
+/*
+ * This communicates to the AP whether an MKBP event is currently available
+ * for processing.
+ *
+ * NOTE: When active is 0 this function CANNOT de-schedule. It must be very
+ * simple like toggling a GPIO or no-op
+ *
+ * @param active 1 if there is an event, 0 otherwise
+ * @param timestamp, if non-null this variable will be written as close to the
+ * hardware interrupt from EC->AP as possible.
*/
-static void set_host_interrupt(int active)
+static int mkbp_set_host_active(int active, uint32_t *timestamp)
{
- static int old_active;
-
- interrupt_disable();
-
- if (old_active == 0 && active == 1)
- mkbp_last_event_time = __hw_clock_source_read();
-
- mkbp_set_host_active(active);
-
- old_active = active;
- interrupt_enable();
+#if defined(CONFIG_MKBP_USE_CUSTOM)
+ /*
+ * TODO change mkbp_set_host_active_via_custom declaration. Done in
+ * child CL to decouple changes
+ */
+ if (timestamp)
+ *timestamp = __hw_clock_source_read();
+ mkbp_set_host_active_via_custom(active);
+ return EC_SUCCESS;
+#elif defined(CONFIG_MKBP_USE_HOST_EVENT)
+ return mkbp_set_host_active_via_event(active, timestamp);
+#elif defined(CONFIG_MKBP_USE_GPIO)
+ return mkbp_set_host_active_via_gpio(active, timestamp);
+#endif
}
#ifdef CONFIG_MKBP_WAKEUP_MASK
@@ -92,24 +150,130 @@ static inline int host_is_sleeping(void)
}
#endif /* CONFIG_MKBP_WAKEUP_MASK */
-int mkbp_send_event(uint8_t event_type)
+/*
+ * This is the deferred function that ensures that we attempt to set the MKBP
+ * interrupt again if there was a failure in the system (EC or AP) and the AP
+ * never called get_next_event.
+ */
+static void force_mkbp_if_events(void);
+DECLARE_DEFERRED(force_mkbp_if_events);
+
+static void activate_mkbp_with_events(uint32_t events_to_add)
{
- set_event(event_type);
+ int interrupt_id = -1;
+ int skip_interrupt = 0;
+ int rv, schedule_deferred = 0;
#ifdef CONFIG_MKBP_WAKEUP_MASK
/* Only assert interrupt for wake events if host is sleeping */
- if (host_is_sleeping()) {
- /* Skip host wake if this isn't a wake event */
- if (!(host_get_events() & CONFIG_MKBP_WAKEUP_MASK) &&
- event_type != EC_MKBP_EVENT_KEY_MATRIX)
- return 0;
- }
+ skip_interrupt = host_is_sleeping() &&
+ !(host_get_events() & CONFIG_MKBP_WAKEUP_MASK);
#endif
- set_host_interrupt(1);
+ mutex_lock(&state.lock);
+ state.events |= events_to_add;
+
+ /* To skip the interrupt, we cannot have the EC_MKBP_EVENT_KEY_MATRIX */
+ skip_interrupt = skip_interrupt &&
+ !(state.events & (1 << EC_MKBP_EVENT_KEY_MATRIX));
+
+ if (state.events && state.interrupt == INTERRUPT_INACTIVE &&
+ !skip_interrupt) {
+ state.interrupt = INTERRUPT_INACTIVE_TO_ACTIVE;
+ interrupt_id = ++state.interrupt_id;
+ }
+ mutex_unlock(&state.lock);
+
+ /* If we don't need to send an interrupt we are done */
+ if (interrupt_id < 0)
+ return;
+
+ /* Send a rising edge MKBP interrupt */
+ rv = mkbp_set_host_active(1, &mkbp_last_event_time);
+
+ /*
+ * If this was the last interrupt to the AP, update state;
+ * otherwise the latest interrupt should update state.
+ */
+ mutex_lock(&state.lock);
+ if (state.interrupt == INTERRUPT_INACTIVE_TO_ACTIVE &&
+ interrupt_id == state.interrupt_id) {
+ schedule_deferred = 1;
+ state.interrupt = rv == EC_SUCCESS ? INTERRUPT_ACTIVE
+ : INTERRUPT_INACTIVE;
+ }
+ mutex_unlock(&state.lock);
+
+ if (schedule_deferred) {
+ hook_call_deferred(&force_mkbp_if_events_data, SECOND);
+ if (rv != EC_SUCCESS)
+ CPRINTS("Could not activate MKBP (%d). Deferring", rv);
+ }
+}
+
+/*
+ * This is the deferred function that ensures that we attempt to set the MKBP
+ * interrupt again if there was a failure in the system (EC or AP) and the AP
+ * never called get_next_event.
+ */
+static void force_mkbp_if_events(void)
+{
+ int toggled = 0;
+
+ mutex_lock(&state.lock);
+ if (state.interrupt == INTERRUPT_ACTIVE) {
+ if (++state.failed_attempts < 3) {
+ state.interrupt = INTERRUPT_INACTIVE;
+ toggled = 1;
+ }
+ }
+ mutex_unlock(&state.lock);
+
+ if (toggled)
+ CPRINTS("MKBP not cleared within threshold, toggling.");
+
+ activate_mkbp_with_events(0);
+}
+
+int mkbp_send_event(uint8_t event_type)
+{
+ activate_mkbp_with_events(1 << event_type);
+
return 1;
}
+static int set_inactive_if_no_events(void)
+{
+ int interrupt_cleared;
+
+ mutex_lock(&state.lock);
+ interrupt_cleared = !state.events;
+ if (interrupt_cleared) {
+ state.interrupt = INTERRUPT_INACTIVE;
+ state.failed_attempts = 0;
+ /* Only simple tasks (i.e. gpio set or no-op) allowed here */
+ mkbp_set_host_active(0, NULL);
+ }
+ mutex_unlock(&state.lock);
+
+ /* Cancel our safety net since the events were cleared. */
+ if (interrupt_cleared)
+ hook_call_deferred(&force_mkbp_if_events_data, -1);
+
+ return interrupt_cleared;
+}
+
+/* This can only be called when the state.lock mutex is held */
+static int take_event_if_set(uint8_t event_type)
+{
+ int taken;
+
+ taken = state.events & (1 << event_type);
+ state.events &= ~(1 << event_type);
+
+ return taken;
+}
+
static int mkbp_get_next_event(struct host_cmd_handler_args *args)
{
static int last;
@@ -122,24 +286,22 @@ static int mkbp_get_next_event(struct host_cmd_handler_args *args)
* Find the next event to service. We do this in a round-robin
* way to make sure no event gets starved.
*/
+ mutex_lock(&state.lock);
for (i = 0; i < EC_MKBP_EVENT_COUNT; ++i)
- if (event_is_set((last + i) % EC_MKBP_EVENT_COUNT))
+ if (take_event_if_set((last + i) % EC_MKBP_EVENT_COUNT))
break;
+ mutex_unlock(&state.lock);
if (i == EC_MKBP_EVENT_COUNT) {
- set_host_interrupt(0);
- return EC_RES_UNAVAILABLE;
+ if (set_inactive_if_no_events())
+ return EC_RES_UNAVAILABLE;
+ /* An event was set just now, restart loop. */
+ continue;
}
evt = (i + last) % EC_MKBP_EVENT_COUNT;
last = evt + 1;
- /*
- * Clear the event before retrieving the event data in case the
- * event source wants to send the same event.
- */
- clear_event(evt);
-
for (src = __mkbp_evt_srcs; src < __mkbp_evt_srcs_end; ++src)
if (src->event_type == evt)
break;
@@ -158,12 +320,16 @@ static int mkbp_get_next_event(struct host_cmd_handler_args *args)
* event first.
*/
data_size = src->get_data(resp + 1);
- if (data_size == -EC_ERROR_BUSY)
- set_event(evt);
+ if (data_size == -EC_ERROR_BUSY) {
+ mutex_lock(&state.lock);
+ state.events |= 1 << evt;
+ mutex_unlock(&state.lock);
+ }
} while (data_size == -EC_ERROR_BUSY);
- if (!events)
- set_host_interrupt(0);
+ /* If there are no more events and we support the "more" flag, set it */
+ if (!set_inactive_if_no_events() && args->version >= 2)
+ resp[0] |= EC_MKBP_HAS_MORE_EVENTS;
if (data_size < 0)
return EC_RES_ERROR;
@@ -173,7 +339,7 @@ static int mkbp_get_next_event(struct host_cmd_handler_args *args)
}
DECLARE_HOST_COMMAND(EC_CMD_GET_NEXT_EVENT,
mkbp_get_next_event,
- EC_VER_MASK(0) | EC_VER_MASK(1));
+ EC_VER_MASK(0) | EC_VER_MASK(1) | EC_VER_MASK(2));
#ifdef CONFIG_MKBP_WAKEUP_MASK
static int mkbp_get_wake_mask(struct host_cmd_handler_args *args)