summaryrefslogtreecommitdiff
path: root/common/keyboard_mkbp.c
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2013-03-26 13:18:39 -0700
committerChromeBot <chrome-bot@google.com>2013-03-27 11:35:20 -0700
commitc2b94fd184e6f5df5e5d8fe4018055a29a96a064 (patch)
tree7ccb95f13ca0cf5ff21b67687b4bb8fe30ad06d3 /common/keyboard_mkbp.c
parent0a2603e8ea234a9263a0a2e6be5daed362c12d72 (diff)
downloadchrome-ec-c2b94fd184e6f5df5e5d8fe4018055a29a96a064.tar.gz
Move files in preparation for merging keyboard_scan modules
This is part one of a series to merge the keyboard scan interface to be common across all platforms. This change just moves and renames files and APIs and removes some read code, and sets up protocol-specific CONFIG options. It makes the next CL which actually merges keyboard scanning easier to parse. BUG=chrome-os-partner:18360 BRANCH=none TEST=compile all boards; test keyboard on spring and link Change-Id: I815a40aae4e5d5f333b8501aff9656080533d913 Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/46549 Reviewed-by: Bill Richardson <wfrichar@chromium.org>
Diffstat (limited to 'common/keyboard_mkbp.c')
-rw-r--r--common/keyboard_mkbp.c656
1 files changed, 656 insertions, 0 deletions
diff --git a/common/keyboard_mkbp.c b/common/keyboard_mkbp.c
new file mode 100644
index 0000000000..9d2613b187
--- /dev/null
+++ b/common/keyboard_mkbp.c
@@ -0,0 +1,656 @@
+/* Copyright (c) 2013 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.
+ *
+ * Keyboard scanner module for Chrome EC STM32
+ */
+
+#include "atomic.h"
+#include "chipset.h"
+#include "console.h"
+#include "gpio.h"
+#include "host_command.h"
+#include "keyboard_config.h"
+#include "keyboard_protocol.h"
+#include "keyboard_raw.h"
+#include "keyboard_scan.h"
+#include "keyboard_test.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_KEYSCAN, outstr)
+#define CPRINTF(format, args...) cprintf(CC_KEYSCAN, format, ## args)
+
+#define SCAN_TIME_COUNT 32
+
+static struct mutex scanning_enabled;
+
+static uint8_t debounced_state[KEYBOARD_COLS]; /* Debounced key matrix */
+static uint8_t prev_state[KEYBOARD_COLS]; /* Matrix from previous scan */
+static uint8_t debouncing[KEYBOARD_COLS]; /* Mask of keys being debounced */
+static uint32_t scan_time[SCAN_TIME_COUNT]; /* Times of last scans */
+static int scan_time_index; /* Current scan_time[] index */
+/* Index into scan_time[] when each key started debouncing */
+static uint8_t scan_edge_index[KEYBOARD_COLS][KEYBOARD_ROWS];
+
+/*****************************************************************************/
+
+#define KB_FIFO_DEPTH 16 /* FIXME: this is pretty huge */
+static uint32_t kb_fifo_start; /* first entry */
+static uint32_t kb_fifo_end; /* last entry */
+static uint32_t kb_fifo_entries; /* number of existing entries */
+static uint8_t kb_fifo[KB_FIFO_DEPTH][KEYBOARD_COLS];
+
+/*
+ * Our configuration. The debounce parameters are not yet supported.
+ */
+static struct ec_mkbp_config config = {
+ .valid_mask = EC_MKBP_VALID_SCAN_PERIOD | EC_MKBP_VALID_POLL_TIMEOUT |
+ EC_MKBP_VALID_MIN_POST_SCAN_DELAY |
+ EC_MKBP_VALID_OUTPUT_SETTLE | EC_MKBP_VALID_DEBOUNCE_DOWN |
+ EC_MKBP_VALID_DEBOUNCE_UP | EC_MKBP_VALID_FIFO_MAX_DEPTH,
+ .valid_flags = EC_MKBP_FLAGS_ENABLE,
+ .flags = EC_MKBP_FLAGS_ENABLE,
+ .scan_period_us = 3000,
+ .poll_timeout_us = 100 * 1000,
+ .min_post_scan_delay_us = 1000,
+ .output_settle_us = 50,
+ .debounce_down_us = 9000,
+ .debounce_up_us = 30000,
+ .fifo_max_depth = KB_FIFO_DEPTH,
+};
+
+void keyboard_clear_state(void)
+{
+ int i;
+
+ CPRINTF("clearing keyboard fifo\n");
+ kb_fifo_start = 0;
+ kb_fifo_end = 0;
+ kb_fifo_entries = 0;
+ for (i = 0; i < KB_FIFO_DEPTH; i++)
+ memset(kb_fifo[i], 0, KEYBOARD_COLS);
+}
+
+int keyboard_fifo_add(const uint8_t *buffp)
+{
+ int ret = EC_SUCCESS;
+
+ if (kb_fifo_entries >= config.fifo_max_depth) {
+ CPRINTF("%s: FIFO depth %d reached\n", __func__,
+ config.fifo_max_depth);
+ ret = EC_ERROR_OVERFLOW;
+ goto kb_fifo_push_done;
+ }
+
+ memcpy(kb_fifo[kb_fifo_end], buffp, KEYBOARD_COLS);
+
+ kb_fifo_end = (kb_fifo_end + 1) % KB_FIFO_DEPTH;
+
+ atomic_add(&kb_fifo_entries, 1);
+
+kb_fifo_push_done:
+ return ret;
+}
+
+/**
+ * Pop keyboard state from FIFO
+ *
+ * @return EC_SUCCESS if entry popped, EC_ERROR_UNKNOWN if FIFO is empty
+ */
+static int kb_fifo_remove(uint8_t *buffp)
+{
+ if (!kb_fifo_entries) {
+ /* no entry remaining in FIFO : return last known state */
+ int last = (kb_fifo_start + KB_FIFO_DEPTH - 1) % KB_FIFO_DEPTH;
+ memcpy(buffp, kb_fifo[last], KEYBOARD_COLS);
+
+ /*
+ * Bail out without changing any FIFO indices and let the
+ * caller know something strange happened. The buffer will
+ * will contain the last known state of the keyboard.
+ */
+ return EC_ERROR_UNKNOWN;
+ }
+ memcpy(buffp, kb_fifo[kb_fifo_start], KEYBOARD_COLS);
+
+ kb_fifo_start = (kb_fifo_start + 1) % KB_FIFO_DEPTH;
+
+ atomic_sub(&kb_fifo_entries, 1);
+
+ return EC_SUCCESS;
+}
+
+/**
+ * Assert host keyboard interrupt line.
+ */
+static void set_host_interrupt(int active)
+{
+ /* interrupt host by using active low EC_INT signal */
+ gpio_set_level(GPIO_EC_INT, !active);
+}
+
+/**
+ * Check special runtime key combinations.
+ *
+ * @param state Keyboard state to use when checking keys.
+ * @return 1 if a special key was pressed, 0 if not
+ */
+static int check_runtime_keys(const uint8_t *state)
+{
+ int num_press;
+ int c;
+
+ /* Count number of key pressed */
+ for (c = num_press = 0; c < KEYBOARD_COLS; c++) {
+ if (state[c])
+ ++num_press;
+ }
+
+ if (num_press != 3)
+ return 0;
+
+ if (state[KEYBOARD_COL_KEY_R] == KEYBOARD_MASK_KEY_R &&
+ state[KEYBOARD_COL_VOL_UP] == KEYBOARD_MASK_VOL_UP &&
+ (state[KEYBOARD_COL_RIGHT_ALT] == KEYBOARD_MASK_RIGHT_ALT ||
+ state[KEYBOARD_COL_LEFT_ALT] == KEYBOARD_MASK_LEFT_ALT)) {
+ keyboard_clear_state();
+ chipset_reset(0);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Print the keyboard state. */
+static void print_state(const uint8_t *state, const char *msg)
+{
+ int c;
+
+ CPRINTF("[%T KB %s:", msg);
+ for (c = 0; c < KEYBOARD_COLS; c++) {
+ if (state[c])
+ CPRINTF(" %02x", state[c]);
+ else
+ CPUTS(" --");
+ }
+ CPUTS("]\n");
+}
+
+/**
+ * Read the raw keyboard matrix state.
+ *
+ * Used in pre-init, so must not make task-switching-dependent calls; udelay()
+ * is ok because it's a spin-loop.
+ *
+ * @param state Destination for new state (must be KEYBOARD_COLS long).
+ *
+ * @return 1 if at least one key is pressed, else zero.
+ */
+static int read_matrix(uint8_t *state)
+{
+ int c;
+ uint8_t r;
+ int pressed = 0;
+
+ for (c = 0; c < KEYBOARD_COLS; c++) {
+ /* Assert output, then wait a bit for it to settle */
+ keyboard_raw_drive_column(c);
+ udelay(config.output_settle_us);
+
+ r = keyboard_raw_read_rows();
+
+#ifdef CONFIG_KEYBOARD_TEST
+ /* Use simulated keyscan sequence instead if testing active */
+ r = keyscan_seq_get_scan(c, r);
+#endif
+
+#ifdef OR_WITH_CURRENT_STATE_FOR_TESTING
+ /* KLUDGE - or current state in, so we can make sure
+ * all the lines are hooked up */
+ r |= state[c];
+#endif
+
+ state[c] = r;
+ pressed |= r;
+ }
+ keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
+
+ return pressed ? 1 : 0;
+}
+
+/**
+ * Update keyboard state using low-level interface to read keyboard.
+ *
+ * @param state Keyboard state to update.
+ *
+ * @return 1 if any key is still pressed, 0 if no key is pressed.
+ */
+static int check_keys_changed(uint8_t *state)
+{
+ int any_pressed = 0;
+ int c, i;
+ int any_change = 0;
+ uint8_t new_state[KEYBOARD_COLS];
+ uint32_t tnow = get_time().le.lo;
+
+ /* Save the current scan time */
+ if (++scan_time_index >= SCAN_TIME_COUNT)
+ scan_time_index = 0;
+ scan_time[scan_time_index] = tnow;
+
+ /* Read the raw key state */
+ any_pressed = read_matrix(new_state);
+
+ /* Check for changes between previous scan and this one */
+ for (c = 0; c < KEYBOARD_COLS; c++) {
+ int diff = new_state[c] ^ prev_state[c];
+
+ if (!diff)
+ continue;
+
+ for (i = 0; i < KEYBOARD_ROWS; i++) {
+ if (diff & (1 << i))
+ scan_edge_index[c][i] = scan_time_index;
+ }
+
+ debouncing[c] |= diff;
+ prev_state[c] = new_state[c];
+ }
+
+ /* Check for keys which are done debouncing */
+ for (c = 0; c < KEYBOARD_COLS; c++) {
+ int debc = debouncing[c];
+
+ if (!debc)
+ continue;
+
+ for (i = 0; i < KEYBOARD_ROWS; i++) {
+ int mask = 1 << i;
+ int new_mask = new_state[c] & mask;
+
+ /* Are we done debouncing this key? */
+ if (!(debc & mask))
+ continue; /* Not debouncing this key */
+ if (tnow - scan_time[scan_edge_index[c][i]] <
+ (new_mask ? config.debounce_down_us :
+ config.debounce_up_us))
+ continue; /* Not done debouncing */
+
+ debouncing[c] &= ~mask;
+
+ /* Did the key change from its previous state? */
+ if ((state[c] & mask) == new_mask)
+ continue; /* No */
+
+ state[c] ^= mask;
+ any_change = 1;
+ }
+ }
+
+ if (any_change) {
+#ifdef CONFIG_KEYBOARD_SUPPRESS_NOISE
+ keyboard_suppress_noise();
+#endif
+ print_state(state, "state");
+
+#ifdef PRINT_SCAN_TIMES
+ /* Print delta times from now back to each previous scan */
+ for (i = 0; i < SCAN_TIME_COUNT; i++) {
+ int tnew = scan_time[
+ (SCAN_TIME_COUNT + scan_time_index - i) %
+ SCAN_TIME_COUNT];
+ CPRINTF(" %d", tnow - tnew);
+ }
+ CPRINTF("\n");
+#endif
+
+ /* Swallow special keys */
+ if (check_runtime_keys(state))
+ return 0;
+ else if (keyboard_fifo_add(state) == EC_SUCCESS)
+ set_host_interrupt(1);
+ else
+ CPRINTF("dropped keystroke\n");
+ }
+
+ return any_pressed;
+}
+
+/*
+ * Check if the user has triggered a recovery reset
+ *
+ * Pressing Power + Refresh + ESC. triggers a recovery reset. Here we check
+ * for this.
+ *
+ * @param state Keyboard state to check
+ * @return 1 if there is a recovery reset, else 0
+ */
+static int check_recovery_key(const uint8_t *state)
+{
+ int c;
+
+ /* check the recovery key only if we're booting due to a
+ * reset-pin-caused reset. */
+ if (!(system_get_reset_flags() & RESET_FLAG_RESET_PIN))
+ return 0;
+
+ /* cold boot : Power + Refresh were pressed,
+ * check if ESC is also pressed for recovery. */
+ if (!(state[KEYBOARD_COL_ESC] & KEYBOARD_MASK_ESC))
+ return 0;
+
+ /* Make sure only other allowed keys are pressed. This protects
+ * against accidentally triggering the special key when a cat sits on
+ * your keyboard. Currently, only the requested key and ESC are
+ * allowed. */
+ for (c = 0; c < KEYBOARD_COLS; c++) {
+ if (state[c] &&
+ (c != KEYBOARD_COL_ESC || state[c] != KEYBOARD_MASK_ESC) &&
+ (c != KEYBOARD_COL_REFRESH ||
+ state[c] != KEYBOARD_MASK_REFRESH))
+ return 0; /* Additional disallowed key pressed */
+ }
+
+ CPRINTF("Keyboard RECOVERY detected !\n");
+
+ host_set_single_event(EC_HOST_EVENT_KEYBOARD_RECOVERY);
+
+ return 1;
+}
+
+void keyboard_scan_init(void)
+{
+ keyboard_raw_init();
+
+ /* Tri-state (put into Hi-Z) the outputs */
+ keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
+
+ /* Initialize raw state */
+ read_matrix(debounced_state);
+ memcpy(prev_state, debounced_state, sizeof(prev_state));
+
+ /* is recovery key pressed on cold startup ? */
+ check_recovery_key(debounced_state);
+}
+
+/* Scan the keyboard until all keys are released */
+static void scan_keyboard(void)
+{
+ timestamp_t poll_deadline, start;
+ int keys_changed = 1;
+
+ mutex_lock(&scanning_enabled);
+ keyboard_raw_drive_column(KEYBOARD_COLUMN_ALL);
+ keyboard_raw_enable_interrupt(1);
+ mutex_unlock(&scanning_enabled);
+
+ /*
+ * if a key was pressed after the last polling,
+ * re-start immediatly polling instead of waiting
+ * for the next interrupt.
+ */
+ if (!keyboard_raw_read_rows()) {
+#ifdef CONFIG_KEYBOARD_TEST
+ task_wait_event(keyscan_seq_next_event_delay());
+#else
+ task_wait_event(-1);
+#endif
+ }
+
+ keyboard_raw_enable_interrupt(0);
+ keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
+
+ /* Busy polling keyboard state. */
+ while (1) {
+ int wait_time;
+
+ if (!(config.flags & EC_MKBP_FLAGS_ENABLE))
+ break;
+
+ /* If we saw any keys pressed, reset deadline */
+ start = get_time();
+ if (keys_changed)
+ poll_deadline.val = start.val + config.poll_timeout_us;
+ else if (timestamp_expired(poll_deadline, &start))
+ break;
+
+ /* Scan immediately, with no delay */
+ mutex_lock(&scanning_enabled);
+ keys_changed = check_keys_changed(debounced_state);
+ mutex_unlock(&scanning_enabled);
+
+ /* Wait a bit before scanning again */
+ wait_time = config.scan_period_us -
+ (get_time().val - start.val);
+ if (wait_time < config.min_post_scan_delay_us)
+ wait_time = config.min_post_scan_delay_us;
+ task_wait_event(wait_time);
+ }
+}
+
+void keyboard_scan_task(void)
+{
+ print_state(debounced_state, "init state");
+
+ keyboard_raw_task_start();
+
+ while (1) {
+ if (config.flags & EC_MKBP_FLAGS_ENABLE) {
+ scan_keyboard();
+ } else {
+ keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
+ task_wait_event(-1);
+ }
+ }
+}
+
+int keyboard_has_char(void)
+{
+ /* TODO: needs to be implemented */
+ return 0;
+}
+
+void keyboard_put_char(uint8_t chr, int send_irq)
+{
+ /* TODO: needs to be implemented */
+}
+
+static int keyboard_get_scan(struct host_cmd_handler_args *args)
+{
+ kb_fifo_remove(args->response);
+ if (!kb_fifo_entries)
+ set_host_interrupt(0);
+
+ args->response_size = KEYBOARD_COLS;
+
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_MKBP_STATE,
+ keyboard_get_scan,
+ EC_VER_MASK(0));
+
+static int keyboard_get_info(struct host_cmd_handler_args *args)
+{
+ struct ec_response_mkbp_info *r = args->response;
+
+ r->rows = KEYBOARD_ROWS;
+ r->cols = KEYBOARD_COLS;
+ r->switches = 0;
+
+ args->response_size = sizeof(*r);
+
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_MKBP_INFO,
+ keyboard_get_info,
+ EC_VER_MASK(0));
+
+void keyboard_scan_enable(int enable)
+{
+ if (enable) {
+ mutex_unlock(&scanning_enabled);
+ task_wake(TASK_ID_KEYSCAN);
+ } else {
+ /*
+ * TODO: using a mutex to control scanning isn't very
+ * responsive. If we just started scanning the matrix, the
+ * mutex will already be locked, and we'll finish the entire
+ * matrix scan before we stop driving columns. We should
+ * instead do something like link, where disabling scanning
+ * immediately stops driving the columns.
+ */
+ mutex_lock(&scanning_enabled);
+ keyboard_raw_drive_column(KEYBOARD_COLUMN_NONE);
+ }
+}
+
+/* Changes to col,row here need to also be reflected in kernel.
+ * drivers/input/mkbp.c ... see KEY_BATTERY.
+ */
+#define BATTERY_KEY_COL 0
+#define BATTERY_KEY_ROW 7
+#define BATTERY_KEY_ROW_MASK (1 << BATTERY_KEY_ROW)
+
+void keyboard_send_battery_key(void)
+{
+ mutex_lock(&scanning_enabled);
+ debounced_state[BATTERY_KEY_COL] ^= BATTERY_KEY_ROW_MASK;
+ if (keyboard_fifo_add(debounced_state) == EC_SUCCESS)
+ set_host_interrupt(1);
+ else
+ CPRINTF("dropped battery keystroke\n");
+ mutex_unlock(&scanning_enabled);
+}
+
+static int command_keyboard_press(int argc, char **argv)
+{
+ int r, c, p;
+ char *e;
+
+ if (argc != 4)
+ return EC_ERROR_PARAM_COUNT;
+
+ c = strtoi(argv[1], &e, 0);
+ if (*e || c < 0 || c >= KEYBOARD_COLS)
+ return EC_ERROR_PARAM1;
+
+ r = strtoi(argv[2], &e, 0);
+ if (*e || r < 0 || r >= KEYBOARD_ROWS)
+ return EC_ERROR_PARAM2;
+
+ p = strtoi(argv[3], &e, 0);
+ if (*e || p < 0 || p > 1)
+ return EC_ERROR_PARAM3;
+
+ /*
+ * TODO(sjg@chromium.org): This ignores debouncing, so is a bit
+ * dodgy and might have strange side-effects on real key scans.
+ */
+ if (p)
+ debounced_state[c] |= (1 << r);
+ else
+ debounced_state[c] &= ~(1 << r);
+
+ if (keyboard_fifo_add(debounced_state) == EC_SUCCESS)
+ set_host_interrupt(1);
+ else
+ ccprintf("dropped keystroke\n");
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(kbpress, command_keyboard_press,
+ "[col] [row] [0 | 1]",
+ "Simulate keypress",
+ NULL);
+
+/**
+ * Copy keyscan configuration from one place to another according to flags
+ *
+ * This is like a structure copy, except that only selected fields are
+ * copied.
+ *
+ * TODO(sjg@chromium.org): Consider making this table drive as ectool.
+ *
+ * @param src Source config
+ * @param dst Destination config
+ * @param valid_mask Bits representing which fields to copy - each bit is
+ * from enum mkbp_config_valid
+ * @param valid_flags Bit mask controlling flags to copy. Any 1 bit means
+ * that the corresponding bit in src->flags is copied
+ * over to dst->flags
+ */
+static void keyscan_copy_config(const struct ec_mkbp_config *src,
+ struct ec_mkbp_config *dst,
+ uint32_t valid_mask, uint8_t valid_flags)
+{
+ uint8_t new_flags;
+
+ if (valid_mask & EC_MKBP_VALID_SCAN_PERIOD)
+ dst->scan_period_us = src->scan_period_us;
+ if (valid_mask & EC_MKBP_VALID_POLL_TIMEOUT)
+ dst->poll_timeout_us = src->poll_timeout_us;
+ if (valid_mask & EC_MKBP_VALID_MIN_POST_SCAN_DELAY) {
+ /*
+ * Key scanning is high priority, so we should require at
+ * least 100us min delay here. Setting this to 0 will cause
+ * watchdog events. Use 200 to be safe.
+ */
+ dst->min_post_scan_delay_us =
+ MAX(src->min_post_scan_delay_us, 200);
+ }
+ if (valid_mask & EC_MKBP_VALID_OUTPUT_SETTLE)
+ dst->output_settle_us = src->output_settle_us;
+ if (valid_mask & EC_MKBP_VALID_DEBOUNCE_DOWN)
+ dst->debounce_down_us = src->debounce_down_us;
+ if (valid_mask & EC_MKBP_VALID_DEBOUNCE_UP)
+ dst->debounce_up_us = src->debounce_up_us;
+ if (valid_mask & EC_MKBP_VALID_FIFO_MAX_DEPTH) {
+ /* Sanity check for fifo depth */
+ dst->fifo_max_depth = MIN(src->fifo_max_depth,
+ KB_FIFO_DEPTH);
+ }
+ new_flags = dst->flags & ~valid_flags;
+ new_flags |= src->flags & valid_flags;
+ dst->flags = new_flags;
+
+ /*
+ * If we just enabled key scanning, kick the task so that it will
+ * fall out of the task_wait_event() in keyboard_scan_task().
+ */
+ if ((new_flags & EC_MKBP_FLAGS_ENABLE) &&
+ !(dst->flags & EC_MKBP_FLAGS_ENABLE))
+ task_wake(TASK_ID_KEYSCAN);
+}
+
+static int host_command_mkbp_set_config(struct host_cmd_handler_args *args)
+{
+ const struct ec_params_mkbp_set_config *req = args->params;
+
+ keyscan_copy_config(&req->config, &config,
+ config.valid_mask & req->config.valid_mask,
+ config.valid_flags & req->config.valid_flags);
+
+ return EC_RES_SUCCESS;
+}
+
+static int host_command_mkbp_get_config(struct host_cmd_handler_args *args)
+{
+ struct ec_response_mkbp_get_config *resp = args->response;
+
+ memcpy(&resp->config, &config, sizeof(config));
+ args->response_size = sizeof(*resp);
+
+ return EC_RES_SUCCESS;
+}
+
+DECLARE_HOST_COMMAND(EC_CMD_MKBP_SET_CONFIG,
+ host_command_mkbp_set_config,
+ EC_VER_MASK(0));
+
+DECLARE_HOST_COMMAND(EC_CMD_MKBP_GET_CONFIG,
+ host_command_mkbp_get_config,
+ EC_VER_MASK(0));