507 lines
15 KiB
C
507 lines
15 KiB
C
/* Copyright 2014 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.
|
|
*
|
|
* Event handling in MKBP keyboard protocol
|
|
*/
|
|
|
|
#include "atomic.h"
|
|
#include "chipset.h"
|
|
#include "gpio.h"
|
|
#include "host_command.h"
|
|
#include "host_command_heci.h"
|
|
#include "hwtimer.h"
|
|
#include "timer.h"
|
|
#include "link_defs.h"
|
|
#include "mkbp_event.h"
|
|
#include "power.h"
|
|
#include "util.h"
|
|
|
|
#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;
|
|
|
|
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
|
|
static uint32_t mkbp_event_wake_mask = CONFIG_MKBP_EVENT_WAKEUP_MASK;
|
|
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
|
|
|
|
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
|
|
static uint32_t mkbp_host_event_wake_mask = CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK;
|
|
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
|
|
|
|
#if defined(CONFIG_MKBP_USE_GPIO) || \
|
|
defined(CONFIG_MKBP_USE_GPIO_AND_HOST_EVENT)
|
|
static int mkbp_set_host_active_via_gpio(int active, uint32_t *timestamp)
|
|
{
|
|
/*
|
|
* 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();
|
|
}
|
|
|
|
gpio_set_level(GPIO_EC_INT_L, !active);
|
|
|
|
if (timestamp)
|
|
interrupt_enable();
|
|
|
|
#ifdef CONFIG_MKBP_USE_GPIO_AND_HOST_EVENT
|
|
/*
|
|
* In case EC_INT_L is not a wake pin, make sure that we also attempt to
|
|
* wake the AP via a host event. Only use this second notification
|
|
* interface in suspend since MKBP events are a part of the
|
|
* HOST_EVENT_ALWAYS_REPORT_DEFAULT_MASK. This can cause an MKBP host
|
|
* event to be set in S0, but not triggering an SCI since the event is
|
|
* not in the SCI mask. This would also cause the board to prematurely
|
|
* wake up when suspending due to the lingering event.
|
|
*/
|
|
if (active && chipset_in_state(CHIPSET_STATE_ANY_SUSPEND))
|
|
host_set_single_event(EC_HOST_EVENT_MKBP);
|
|
#endif /* CONFIG_MKBP_USE_GPIO_AND_HOST_EVENT */
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
#endif /* CONFIG_MKBP_USE_GPIO(_AND_HOST_EVENT)? */
|
|
|
|
#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;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_MKBP_USE_HECI
|
|
static int mkbp_set_host_active_via_heci(int active, uint32_t *timestamp)
|
|
{
|
|
if (active)
|
|
return heci_send_mkbp_event(timestamp);
|
|
return EC_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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 int mkbp_set_host_active(int active, uint32_t *timestamp)
|
|
{
|
|
#if defined(CONFIG_MKBP_USE_CUSTOM)
|
|
return mkbp_set_host_active_via_custom(active, timestamp);
|
|
#elif defined(CONFIG_MKBP_USE_HOST_EVENT)
|
|
return mkbp_set_host_active_via_event(active, timestamp);
|
|
#elif defined(CONFIG_MKBP_USE_GPIO) ||\
|
|
defined(CONFIG_MKBP_USE_GPIO_AND_HOST_EVENT)
|
|
return mkbp_set_host_active_via_gpio(active, timestamp);
|
|
#elif defined(CONFIG_MKBP_USE_HECI)
|
|
return mkbp_set_host_active_via_heci(active, timestamp);
|
|
#endif
|
|
}
|
|
|
|
#if defined(CONFIG_MKBP_EVENT_WAKEUP_MASK) || \
|
|
defined(CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK)
|
|
/**
|
|
* Check if the host is sleeping. Check our power state in addition to the
|
|
* self-reported sleep state of host (CONFIG_POWER_TRACK_HOST_SLEEP_STATE).
|
|
*/
|
|
static inline int host_is_sleeping(void)
|
|
{
|
|
int is_sleeping = !chipset_in_state(CHIPSET_STATE_ON);
|
|
|
|
#ifdef CONFIG_POWER_TRACK_HOST_SLEEP_STATE
|
|
enum host_sleep_event sleep_state = power_get_host_sleep_state();
|
|
is_sleeping |=
|
|
(sleep_state == HOST_SLEEP_EVENT_S0IX_SUSPEND ||
|
|
sleep_state == HOST_SLEEP_EVENT_S3_SUSPEND ||
|
|
sleep_state == HOST_SLEEP_EVENT_S3_WAKEABLE_SUSPEND);
|
|
#endif
|
|
return is_sleeping;
|
|
}
|
|
#endif /* CONFIG_MKBP_(HOST_EVENT_)?WAKEUP_MASK */
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
int interrupt_id = -1;
|
|
int skip_interrupt = 0;
|
|
int rv, schedule_deferred = 0;
|
|
|
|
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
|
|
/* Check to see if this host event should wake the system. */
|
|
skip_interrupt = host_is_sleeping() &&
|
|
!(host_get_events() &
|
|
mkbp_host_event_wake_mask);
|
|
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
|
|
|
|
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
|
|
/* Check to see if this MKBP event should wake the system. */
|
|
if (!skip_interrupt)
|
|
skip_interrupt = host_is_sleeping() &&
|
|
!(events_to_add & mkbp_event_wake_mask);
|
|
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
|
|
|
|
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 & BIT(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(BIT(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 & BIT(event_type);
|
|
state.events &= ~BIT(event_type);
|
|
|
|
return taken;
|
|
}
|
|
|
|
static enum ec_status mkbp_get_next_event(struct host_cmd_handler_args *args)
|
|
{
|
|
static int last;
|
|
int i, evt;
|
|
uint8_t *resp = args->response;
|
|
const struct mkbp_event_source *src;
|
|
|
|
int data_size = -EC_ERROR_BUSY;
|
|
|
|
do {
|
|
/*
|
|
* 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 (take_event_if_set((last + i) % EC_MKBP_EVENT_COUNT))
|
|
break;
|
|
mutex_unlock(&state.lock);
|
|
|
|
if (i == EC_MKBP_EVENT_COUNT) {
|
|
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;
|
|
|
|
for (src = __mkbp_evt_srcs; src < __mkbp_evt_srcs_end; ++src)
|
|
if (src->event_type == evt)
|
|
break;
|
|
|
|
if (src == __mkbp_evt_srcs_end)
|
|
return EC_RES_ERROR;
|
|
|
|
resp[0] = evt; /* Event type */
|
|
|
|
/*
|
|
* get_data() can return -EC_ERROR_BUSY which indicates that the
|
|
* next element in the keyboard FIFO does not match what we were
|
|
* called with. For example, get_data is expecting a keyboard
|
|
* matrix, however the next element in the FIFO is a button
|
|
* event instead. Therefore, we have to service that button
|
|
* event first.
|
|
*/
|
|
data_size = src->get_data(resp + 1);
|
|
if (data_size == -EC_ERROR_BUSY) {
|
|
mutex_lock(&state.lock);
|
|
state.events |= BIT(evt);
|
|
mutex_unlock(&state.lock);
|
|
}
|
|
} while (data_size == -EC_ERROR_BUSY);
|
|
|
|
/* 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;
|
|
args->response_size = 1 + data_size;
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_GET_NEXT_EVENT,
|
|
mkbp_get_next_event,
|
|
EC_VER_MASK(0) | EC_VER_MASK(1) | EC_VER_MASK(2));
|
|
|
|
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
|
|
#ifdef CONFIG_MKBP_USE_HOST_EVENT
|
|
static enum ec_status
|
|
mkbp_get_host_event_wake_mask(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_response_host_event_mask *r = args->response;
|
|
|
|
r->mask = CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK;
|
|
args->response_size = sizeof(*r);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_HOST_EVENT_GET_WAKE_MASK,
|
|
mkbp_get_host_event_wake_mask,
|
|
EC_VER_MASK(0));
|
|
#endif /* CONFIG_MKBP_USE_HOST_EVENT */
|
|
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
|
|
|
|
#if defined(CONFIG_MKBP_EVENT_WAKEUP_MASK) || \
|
|
defined(CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK)
|
|
static enum ec_status hc_mkbp_wake_mask(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_response_mkbp_event_wake_mask *r = args->response;
|
|
const struct ec_params_mkbp_event_wake_mask *p = args->params;
|
|
enum ec_mkbp_event_mask_action action = p->action;
|
|
|
|
switch (action) {
|
|
case GET_WAKE_MASK:
|
|
switch (p->mask_type) {
|
|
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
|
|
case EC_MKBP_HOST_EVENT_WAKE_MASK:
|
|
r->wake_mask = mkbp_host_event_wake_mask;
|
|
break;
|
|
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
|
|
|
|
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
|
|
case EC_MKBP_EVENT_WAKE_MASK:
|
|
r->wake_mask = mkbp_event_wake_mask;
|
|
break;
|
|
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
|
|
|
|
default:
|
|
/* Unknown mask, or mask is not in use. */
|
|
return EC_RES_INVALID_PARAM;
|
|
}
|
|
|
|
args->response_size = sizeof(*r);
|
|
break;
|
|
|
|
case SET_WAKE_MASK:
|
|
args->response_size = 0;
|
|
|
|
switch (p->mask_type) {
|
|
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
|
|
case EC_MKBP_HOST_EVENT_WAKE_MASK:
|
|
CPRINTF("MKBP hostevent mask updated to: 0x%08x "
|
|
"(was 0x%08x)\n",
|
|
p->new_wake_mask,
|
|
mkbp_host_event_wake_mask);
|
|
mkbp_host_event_wake_mask = p->new_wake_mask;
|
|
break;
|
|
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
|
|
|
|
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
|
|
case EC_MKBP_EVENT_WAKE_MASK:
|
|
mkbp_event_wake_mask = p->new_wake_mask;
|
|
CPRINTF("MKBP event mask updated to: 0x%08x\n",
|
|
mkbp_event_wake_mask);
|
|
break;
|
|
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
|
|
|
|
default:
|
|
/* Unknown mask, or mask is not in use. */
|
|
return EC_RES_INVALID_PARAM;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return EC_RES_INVALID_PARAM;
|
|
}
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_MKBP_WAKE_MASK,
|
|
hc_mkbp_wake_mask,
|
|
EC_VER_MASK(0));
|
|
|
|
static int command_mkbp_wake_mask(int argc, char **argv)
|
|
{
|
|
if (argc == 3) {
|
|
char *e;
|
|
uint32_t new_mask = strtoul(argv[2], &e, 0);
|
|
|
|
if (*e)
|
|
return EC_ERROR_PARAM2;
|
|
|
|
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
|
|
if (strncmp(argv[1], "event", 5) == 0)
|
|
mkbp_event_wake_mask = new_mask;
|
|
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
|
|
|
|
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
|
|
if (strncmp(argv[1], "hostevent", 9) == 0)
|
|
mkbp_host_event_wake_mask = new_mask;
|
|
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
|
|
} else if (argc != 1) {
|
|
return EC_ERROR_PARAM_COUNT;
|
|
}
|
|
|
|
#ifdef CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK
|
|
ccprintf("MKBP host event wake mask: 0x%08x\n",
|
|
mkbp_host_event_wake_mask);
|
|
#endif /* CONFIG_MKBP_HOST_EVENT_WAKEUP_MASK */
|
|
#ifdef CONFIG_MKBP_EVENT_WAKEUP_MASK
|
|
ccprintf("MKBP event wake mask: 0x%08x\n", mkbp_event_wake_mask);
|
|
#endif /* CONFIG_MKBP_EVENT_WAKEUP_MASK */
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(mkbpwakemask, command_mkbp_wake_mask,
|
|
"[event | hostevent] [new_mask]",
|
|
"Show or set MKBP event/hostevent wake mask");
|
|
#endif /* CONFIG_MKBP_(HOST)?EVENT_WAKEUP_MASK */
|