From 4769627290a2cdfa851b76f2d1a550654b3b04fb Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 10 Oct 2012 09:54:34 -0700 Subject: ectool: Add keyscan test features Add a way of easily setting up keyscan tests using a simple text file format. The steps to run a test are as follows: - read the test file - read the key matrix information - translate the ascii characters from tests into keyscan codes - send the keyscan codes to the EC - tell the EC to start the test - wait for the required time, then collect what input we have received - check that the input matches the expected input BUG=chrome-os-partner:12179 BRANCH=none TEST=manual for now: On snow: ./ectool keyscan 10000 key_sequence.txt See that the test passes. Change-Id: I7de646205803a99443503a1b4bbf32f5fe89c534 Signed-off-by: Simon Glass Reviewed-on: https://gerrit.chromium.org/gerrit/35119 Reviewed-by: Randall Spangler --- util/build.mk | 5 +- util/ectool.c | 4 + util/ectool.h | 30 +++ util/ectool_keyscan.c | 682 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 719 insertions(+), 2 deletions(-) create mode 100644 util/ectool.h create mode 100644 util/ectool_keyscan.c diff --git a/util/build.mk b/util/build.mk index fc021471f2..62b25b4e6b 100644 --- a/util/build.mk +++ b/util/build.mk @@ -7,9 +7,10 @@ # host-util-bin=ectool lbplay burn_my_ec +host-util-common=ectool_keyscan ifeq ($(CONFIG_LPC),y) -host-util-common=comm-lpc +host-util-common+=comm-lpc else -host-util-common=comm-i2c +host-util-common+=comm-i2c endif build-util-bin=ec_uartd stm32mon diff --git a/util/ectool.c b/util/ectool.c index b72f28ee13..eaf1147950 100644 --- a/util/ectool.c +++ b/util/ectool.c @@ -14,6 +14,7 @@ #include "battery.h" #include "comm-host.h" +#include "ectool.h" #include "lightbar.h" #include "lock/gec_lock.h" @@ -91,6 +92,8 @@ const char help_str[] = " Read I2C bus\n" " i2cwrite\n" " Write I2C bus\n" + " keyscan \n" + " Test low-level key scanning\n" " lightbar [CMDS]\n" " Various lightbar control commands\n" " port80flood\n" @@ -2696,6 +2699,7 @@ const struct command commands[] = { {"i2cwrite", cmd_i2c_write}, {"lightbar", cmd_lightbar}, {"keyconfig", cmd_keyconfig}, + {"keyscan", cmd_keyscan}, {"pstoreinfo", cmd_pstore_info}, {"pstoreread", cmd_pstore_read}, {"pstorewrite", cmd_pstore_write}, diff --git a/util/ectool.h b/util/ectool.h new file mode 100644 index 0000000000..65eebcf6f2 --- /dev/null +++ b/util/ectool.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2012 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. + */ + +/** + * Test low-level key scanning + * + * ectool keyscan + * + * is the length of a beat in microseconds. This indicates the + * typing speed. Typically we scan at 10ms in the EC, so the beat period + * will typically be 1-5ms, with the scan changing only every 20-30ms at + * most. + * specifies a file containing keys that are depressed on each + * beat in the following format: + * + * + * + * is a beat number (0, 1, 2). The timestamp of this event will + * be + * . + * is a (possibly empty) list of ASCII keys + * + * The key matrix is read from the fdt. + * + * @param argc Number of arguments (excluding 'ectool') + * @param argv List of arguments + * @return 0 if ok, -1 on error + */ +int cmd_keyscan(int argc, char *argv[]); diff --git a/util/ectool_keyscan.c b/util/ectool_keyscan.c new file mode 100644 index 0000000000..7ca0af660e --- /dev/null +++ b/util/ectool_keyscan.c @@ -0,0 +1,682 @@ +/* Copyright (c) 2012 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "comm-host.h" +#include "ectool.h" + +enum { + /* Alloc this many more scans when needed */ + KEYSCAN_ALLOC_STEP = 64, + KEYSCAN_MAX_TESTS = 10, /* Maximum number of tests supported */ + KEYSCAN_MAX_INPUT_LEN = 20, /* Maximum characters we can receive */ +}; + +#ifdef KB_OUTPUTS +#define KEYSCAN_OUTPUTS KB_OUTPUTS +#else +/* Use a suitable default */ +#define KEYSCAN_OUTPUTS 13 +#endif + +/* A single entry of the key matrix */ +struct matrix_entry { + int row; /* key matrix row */ + int col; /* key matrix column */ + int keycode; /* corresponding linux key code */ +}; + +struct keyscan_test_item { + uint32_t beat; /* Beat number */ + uint8_t scan[KEYSCAN_OUTPUTS]; /* Scan data */ +}; + +/* A single test, consisting of a list of key scans and expected ascii input */ +struct keyscan_test { + char *name; /* name of test */ + char *expect; /* resulting input we expect to see */ + int item_count; /* number of items in data */ + int item_alloced; /* number of items alloced in data */ + struct keyscan_test_item *items; /* key data for EC */ +}; + +/* A list of tests that we can run */ +struct keyscan_info { + unsigned int beat_us; /* length of each beat in microseconds */ + struct keyscan_test tests[KEYSCAN_MAX_TESTS]; /* the tests */ + int test_count; /* number of tests */ + struct matrix_entry *matrix; /* the key matrix info */ + int matrix_count; /* number of keys in matrix */ +}; + +/** + * Read the key matrix from the device tree + * + * Keymap entries in the fdt take the form of 0xRRCCKKKK where + * RR=Row CC=Column KKKK=Key Code + * + * @param keyscan keyscan information + * @param path path to device tree file containing data + * @return 0 if ok, -1 on error + */ +static int keyscan_read_fdt_matrix(struct keyscan_info *keyscan, + const char *path) +{ + struct stat buf; + uint32_t word; + int upto; + FILE *f; + int err; + + /* Allocate memory for key matrix */ + if (stat(path, &buf)) { + fprintf(stderr, "Cannot stat key matrix file '%s'\n", path); + return -1; + } + keyscan->matrix_count = buf.st_size / 4; + keyscan->matrix = calloc(keyscan->matrix_count, + sizeof(*keyscan->matrix)); + if (!keyscan->matrix) { + fprintf(stderr, "Out of memory for key matrix\n"); + return -1; + } + + f = fopen(path, "rb"); + if (!f) { + fprintf(stderr, "Cannot open key matrix file '%s'\n", path); + return -1; + } + + /* Now read the data */ + upto = err = 0; + while (fread(&word, 1, sizeof(word), f) == sizeof(word)) { + struct matrix_entry *matrix = &keyscan->matrix[upto++]; + + word = be32toh(word); + matrix->row = word >> 24; + matrix->col = (word >> 16) & 0xff; + matrix->keycode = word & 0xffff; + + /* Hard-code some sanity limits for now */ + if (matrix->row >= 8 || matrix->col >= 13) { + fprintf(stderr, "Matrix pos out of range (%d,%d)\n", + matrix->row, matrix->col); + return -1; + } + } + fclose(f); + if (!err && upto != keyscan->matrix_count) { + fprintf(stderr, "Read mismatch from matrix file '%s'\n", path); + err = -1; + } + + return err; +} + +/* + * translate Linux's KEY_... values into ascii. We change space into 0xfe + * since we use the numeric value (&32) for space. That avoids ambiguity + * when we see a space in a key sequence file. + */ +static const unsigned char kbd_plain_xlate[] = { + 0xff, 0x1b, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '-', '=', '\b', '\t', /* 0x00 - 0x0f */ + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', '\r', 0xff, 'a', 's', /* 0x10 - 0x1f */ + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', + '\'', '`', 0xff, '\\', 'z', 'x', 'c', 'v', /* 0x20 - 0x2f */ + 'b', 'n', 'm', ',' , '.', '/', 0xff, 0xff, 0xff, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x30 - 0x3f */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, '7', + '8', '9', '-', '4', '5', '6', '+', '1', /* 0x40 - 0x4f */ + '2', '3', '0', '.', 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x50 - 0x5F */ + '\r', 0xff, 0xff +}; + +/** + * Add a new key to a scan + * + * Given a new key, this looks it up in the matrix and adds it to the scan, + * so that if this scan were reported by the EC, the AP would the given key. + * + * The format of keys is a list of ascii characters, or & followed by a numeric + * ascii value, or * followed by a numeric keycode value. Spaces are ignored + * (use '*32' for space). + * + * Examples: + * a - a + * &20 - space + * *58 - KEY_CAPSLOCK + * + * @param keyscan keyscan information + * @param keysp point to the current key string on entry; on exit it + * is updated to point to just after the string, plus any + * following space + * @param path path to device tree file containing data + * @return 0 if ok, -1 on error + */ +static int keyscan_add_to_scan(struct keyscan_info *keyscan, char **keysp, + uint8_t scan[]) +{ + const uint8_t *pos; + struct matrix_entry *matrix; + int keycode = -1, i; + char *keys = *keysp; + int key = ' '; + + if (*keys == '&') { + /* Numeric ascii code */ + keys++; + key = strtol(keys, &keys, 10); + if (!key || keys == *keysp) { + fprintf(stderr, "Invalid numeric ascii\n"); + return -1; + } + if (*keys == ' ') + keys++; + else if (*keys) { + fprintf(stderr, "Expect space after numeric ascii\n"); + return -1; + } + } else if (*keys == '*') { + /* Numeric ascii code */ + keys++; + keycode = strtol(keys, &keys, 10); + if (!keycode || keys == *keysp) { + fprintf(stderr, "Invalid numeric keycode\n"); + return -1; + } + if (*keys == ' ') + keys++; + else if (*keys) { + fprintf(stderr, "Expect space after num. keycode\n"); + return -1; + } + } else { + key = *keys++; + } + + /* Convert keycode to key if needed */ + if (keycode == -1) { + pos = strchr(kbd_plain_xlate, key); + if (!pos) { + fprintf(stderr, "Key '%c' not found in xlate table\n", + key); + return -1; + } + keycode = pos - kbd_plain_xlate; + } + + /* Look up keycode in matrix */ + for (i = 0, matrix = keyscan->matrix; i < keyscan->matrix_count; + i++, matrix++) { + if (matrix->keycode == keycode) { +#ifdef DEBUG + printf("%d: %d,%d\n", matrix->keycode, matrix->row, + matrix->col); +#endif + scan[matrix->col] |= 1 << matrix->row; + *keysp = keys; + return 0; + } + } + fprintf(stderr, "Key '%c' (keycode %d) not found in matrix\n", key, + keycode); + + return -1; +} + +/** + * Add a new keyscan to the given test + * + * This processes a new keyscan, consisting of a beat number and a sequence + * of keys. + * + * The format of keys is a beat number followed by a list of keys, each + * either ascii characters, or & followed by a numeric ascii value, or * + * followed by a numeric keycode value. Spaces are ignored (use '*32' for + * space). + * + * Examples: + * 0 abc - beat 0, press a, b and c + * 4 a &20 - beat 4, press a and space + * 8 *58 &13 - beat 8, press KEY_CAPSLOCK + * + * @param keyscan keyscan information + * @param linenum line number we are reading from (for error reporting) + * @param test test to add this scan to + * @param keys key string to process + * @return 0 if ok, -1 on error + */ +static int keyscan_process_keys(struct keyscan_info *keyscan, int linenum, + struct keyscan_test *test, char *keys) +{ + struct keyscan_test_item *item; + unsigned long int beat; + + /* Allocate more items if needed */ + if (!test->item_alloced || test->item_count == test->item_alloced) { + int size, new_size; + + size = test->item_alloced * sizeof(struct keyscan_test_item); + new_size = size + KEYSCAN_ALLOC_STEP * + sizeof(struct keyscan_test_item); + test->items = realloc(test->items, new_size); + if (!test->items) { + fprintf(stderr, "Out of memory realloc()\n"); + return -1; + } + memset((char *)test->items + size, '\0', new_size - size); + test->item_alloced += KEYSCAN_ALLOC_STEP; + new_size = size; + } + + /* read the beat number */ + item = &test->items[test->item_count]; + beat = strtol(keys, &keys, 10); + item->beat = beat; + + /* Read keys and add them to our scan */ + if (*keys == ' ') { + keys++; + while (*keys) { + if (keyscan_add_to_scan(keyscan, &keys, item->scan)) { + fprintf(stderr, "Line %d: Cannot parse" + " key input '%s'\n", linenum, + keys); + return -1; + } + } + } else if (*keys) { + fprintf(stderr, "Line %d: Need space after beat\n", + linenum); + return -1; + } + test->item_count++; + + return 0; +} + +/* These are the commands we understand in a key sequence file */ +enum keyscan_cmd { + KEYSCAN_CMD_TEST, /* start a new test */ + KEYSCAN_CMD_ENDTEST, /* end a test */ + KEYSCAN_CMD_SEQ, /* add a keyscan to a test sequence */ + KEYSCAN_CMD_EXPECT, /* indicate what input is expected */ + + KEYSCAN_CMD_COUNT +}; + +/* Names for each of the keyscan commands */ +static const char *keyscan_cmd_name[KEYSCAN_CMD_COUNT] = { + "test", + "endtest", + "seq", + "expect", +}; + +/** + * Read a command from a string and return it + * + * @param str String containing command + * @param len Length of command string + * @return detected command, or -1 if none + */ +static enum keyscan_cmd keyscan_read_cmd(const char *str, int len) +{ + int i; + + for (i = 0; i < KEYSCAN_CMD_COUNT; i++) { + if (!strncmp(keyscan_cmd_name[i], str, len)) + return i; + } + + return -1; +} + +/** + * Process a key sequence file a build up a list of tets from it + * + * @param f File containing keyscan info + * @param keyscan keyscan information + * @return 0 if ok, -1 on error + */ +static int keyscan_process_file(FILE *f, struct keyscan_info *keyscan) +{ + struct keyscan_test *cur_test; + char line[256]; + char *str; + int linenum; + + keyscan->test_count = 0; + + linenum = 0; + cur_test = NULL; + while (str = fgets(line, sizeof(line), f), str) { + char *args, *end; + int cmd, len; + + linenum++; + len = strlen(str); + + /* chomp the trailing newline */ + if (len > 0 && str[len - 1] == '\n') { + len--; + str[len] = '\0'; + } + + /* deal with blank lines and comments */ + if (!len || *str == '#') + continue; + + /* get the command */ + for (end = str; *end && *end != ' '; end++) + ; + + cmd = keyscan_read_cmd(str, end - str); + args = end + (*end != '\0'); + switch (cmd) { + case KEYSCAN_CMD_TEST: + /* Start a new test */ + if (keyscan->test_count == KEYSCAN_MAX_TESTS) { + fprintf(stderr, "KEYSCAN_MAX_TESTS " + "exceeded\n"); + return -1; + } + cur_test = &keyscan->tests[keyscan->test_count]; + cur_test->name = strdup(args); + if (!cur_test->name) { + fprintf(stderr, "Line %d: out of memory\n", + linenum); + } + break; + case KEYSCAN_CMD_EXPECT: + /* Get expect string */ + if (!cur_test) { + fprintf(stderr, "Line %d: expect should be " + "inside test\n", linenum); + return -1; + } + cur_test->expect = strdup(args); + if (!cur_test->expect) { + fprintf(stderr, "Line %d: out of memory\n", + linenum); + } + break; + case KEYSCAN_CMD_ENDTEST: + /* End of a test */ + keyscan->test_count++; + cur_test = NULL; + break; + case KEYSCAN_CMD_SEQ: + if (keyscan_process_keys(keyscan, linenum, cur_test, + args)) { + fprintf(stderr, "Line %d: Cannot parse key " + "input '%s'\n", linenum, args); + return -1; + } + break; + default: + fprintf(stderr, "Line %d: Uknown command '%1.*s'\n", + linenum, (int)(end - str), str); + return -1; + } + } + + return 0; +} + +/** + * Print out a list of all tests + * + * @param keyscan keyscan information + */ +static void keyscan_print(struct keyscan_info *keyscan) +{ + int testnum; + int i; + + for (testnum = 0; testnum < keyscan->test_count; testnum++) { + struct keyscan_test *test = &keyscan->tests[testnum]; + + printf("Test: %s\n", test->name); + for (i = 0; i < test->item_count; i++) { + struct keyscan_test_item *item; + int j; + + item = &test->items[i]; + printf("%2d %7d: ", i, item->beat); + for (j = 0; j < sizeof(item->scan); j++) + printf("%02x ", item->scan[j]); + printf("\n"); + } + printf("\n"); + } +} + +/** + * Set the terminal to raw mode, or cooked + * + * @param tty_fd Terminal file descriptor to change + * @Param raw 0 for cooked, non-zero for raw + */ +static void set_to_raw(int tty_fd, int raw) +{ + struct termios tty_attr; + int value; + + value = fcntl(tty_fd, F_GETFL); + + tcgetattr(tty_fd, &tty_attr); + if (raw) { + tty_attr.c_lflag &= ~ICANON; + value |= O_NONBLOCK; + } else { + tty_attr.c_lflag |= ICANON; + value &= ~O_NONBLOCK; + } + tcsetattr(tty_fd, TCSANOW, &tty_attr); + fcntl(tty_fd, F_SETFL, value); +} + +/** + * Read input for a whlie until wee see no more + * + * @param fd File descriptor for input + * @param input Place to put input string + * @param max_len Maximum length of input string + * @param wait Number of microseconds to wait for input + */ +static void keyscan_get_input(int fd, char *input, int max_len, int wait) +{ + int len; + + usleep(wait); + input[0] = '\0'; + len = read(fd, input, max_len - 1); + if (len > 0) + input[len] = '\0'; +} + +static int keyscan_send_sequence(struct keyscan_info *keyscan, + struct keyscan_test *test) +{ + struct ec_params_keyscan_seq_ctrl *req; + struct keyscan_test_item *item; + int upto, size, rv; + + size = sizeof(*req) + sizeof(item->scan); + req = (struct ec_params_keyscan_seq_ctrl *)malloc(size); + if (!req) { + fprintf(stderr, "Out of memory for message\n"); + return -1; + } + for (upto = rv = 0, item = test->items; rv >= 0 && + upto < test->item_count; upto++, item++) { + req->cmd = EC_KEYSCAN_SEQ_ADD; + req->add.time_us = item->beat * keyscan->beat_us; + memcpy(req->add.scan, item->scan, sizeof(item->scan)); + rv = ec_command(EC_CMD_KEYSCAN_SEQ_CTRL, 0, req, size, NULL, 0); + } + free(req); + if (rv < 0) + return rv; + + return 0; +} + +/** + * Run a single test + * + * @param keyscan keyscan information + * @param test test to run + * @return 0 if test passes, -ve if some error occurred + */ +static int run_test(struct keyscan_info *keyscan, struct keyscan_test *test) +{ + struct ec_params_keyscan_seq_ctrl ctrl; + char input[KEYSCAN_MAX_INPUT_LEN]; + struct ec_result_keyscan_seq_ctrl *resp; + int wait_us; + int size; + int rv; + int fd = 0; + int i; + + /* First clear the sequence */ + ctrl.cmd = EC_KEYSCAN_SEQ_CLEAR; + rv = ec_command(EC_CMD_KEYSCAN_SEQ_CTRL, 0, &ctrl, sizeof(ctrl), + NULL, 0); + if (rv < 0) + return rv; + + rv = keyscan_send_sequence(keyscan, test); + if (rv < 0) + return rv; + + /* Start it */ + set_to_raw(fd, 1); + ctrl.cmd = EC_KEYSCAN_SEQ_START; + rv = ec_command(EC_CMD_KEYSCAN_SEQ_CTRL, 0, &ctrl, sizeof(ctrl), + NULL, 0); + if (rv < 0) + return rv; + + /* Work out how long we need to wait */ + wait_us = 100 * 1000; /* Wait 100ms to at least */ + if (test->item_count) { + struct keyscan_test_item *ksi; + + ksi = &test->items[test->item_count - 1]; + wait_us += ksi->beat * keyscan->beat_us; + } + + /* Wait for input */ + keyscan_get_input(fd, input, sizeof(input), wait_us); + set_to_raw(fd, 0); + + /* Ask EC for results */ + size = sizeof(*resp) + test->item_count; + resp = malloc(size); + if (!resp) { + fprintf(stderr, "Out of memory for results\n"); + return -1; + } + ctrl.cmd = EC_KEYSCAN_SEQ_COLLECT; + ctrl.collect.start_item = 0; + ctrl.collect.num_items = test->item_count; + rv = ec_command(EC_CMD_KEYSCAN_SEQ_CTRL, 0, &ctrl, sizeof(ctrl), + resp, size); + if (rv < 0) + return rv; + + /* Check what scans were skipped */ + for (i = 0; i < resp->collect.num_items; i++) { + struct ec_collect_item *item; + struct keyscan_test_item *ksi; + + item = &resp->collect.item[i]; + ksi = &test->items[i]; + if (!(item->flags & EC_KEYSCAN_SEQ_FLAG_DONE)) + printf(" [skip %d at beat %u] ", i, ksi->beat); + } + + if (strcmp(input, test->expect)) { + printf("Expected '%s', got '%s' ", test->expect, input); + return -1; + } + + return 0; +} + +/** + * Run all tests + * + * @param keyscan keyscan information + * @return 0 if ok, -1 on error + */ +static int keyscan_run_tests(struct keyscan_info *keyscan) +{ + int testnum; + int any_err = 0; + + for (testnum = 0; testnum < keyscan->test_count; testnum++) { + struct keyscan_test *test = &keyscan->tests[testnum]; + int err; + + fflush(stdout); + err = run_test(keyscan, test); + any_err |= err; + if (err) { + printf("%d: %s: ", testnum, test->name); + printf(" : %s\n", err ? "FAIL" : "pass"); + } + } + + return any_err ? -1 : 0; +} + +int cmd_keyscan(int argc, char *argv[]) +{ + struct keyscan_info keyscan; + FILE *f; + int err; + + argc--; + argv++; + if (argc < 2) { + fprintf(stderr, "Must specify beat period and filename\n"); + return -1; + } + memset(&keyscan, '\0', sizeof(keyscan)); + keyscan.beat_us = atoi(argv[0]); + if (keyscan.beat_us < 100) + fprintf(stderr, "Warning: beat period is normally > 100us\n"); + f = fopen(argv[1], "r"); + if (!f) { + perror("Cannot open file\n"); + return -1; + } + + /* TODO(sjg@chromium.org): Read key matrix from fdt */ + err = keyscan_read_fdt_matrix(&keyscan, "test/test-matrix.bin"); + if (!err) + err = keyscan_process_file(f, &keyscan); + if (!err) + keyscan_print(&keyscan); + if (!err) + err = keyscan_run_tests(&keyscan); + fclose(f); + + return err; +} -- cgit v1.2.1