chrome-ec/util/ectool_keyscan.c

679 lines
16 KiB
C

/* Copyright 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_MAX]; /* 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 limits for now */
if (matrix->row >= KEYBOARD_ROWS ||
matrix->col >= KEYBOARD_COLS_MAX) {
fprintf(stderr, "Matrix pos out of range (%d,%d)\n",
matrix->row, matrix->col);
fclose(f);
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: : FAIL\n", testnum, test->name);
}
}
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;
}