| /* 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 <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <termios.h> |
| #include <unistd.h> |
| #include <sys/stat.h> |
| #include <endian.h> |
| |
| #include "comm-host.h" |
| #include "keyboard_config.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 */ |
| }; |
| |
| /* 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[KEYBOARD_COLS]; /* 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 >= KEYBOARD_ROWS || |
| matrix->col >= KEYBOARD_COLS) { |
| 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, '\0' |
| }; |
| |
| /** |
| * 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(crosbug.com/p/23826): 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; |
| } |