diff --git a/grub-core/term/arm/cros.c b/grub-core/term/arm/cros.c new file mode 100644 index 000000000..1ff9f8ccf --- /dev/null +++ b/grub-core/term/arm/cros.c @@ -0,0 +1,125 @@ +/* + * GRUB -- GRand Unified Bootloader + * + * Copyright (C) 2012 Google Inc. + * Copyright (C) 2016 Free Software Foundation, Inc. + * + * This is based on depthcharge code. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct grub_ps2_state ps2_state; + +struct grub_cros_ec_keyscan old_scan; + +static const struct grub_fdtbus_dev *cros_ec; + +static grub_uint8_t map_code[GRUB_CROS_EC_KEYSCAN_COLS][GRUB_CROS_EC_KEYSCAN_ROWS]; + +static grub_uint8_t e0_translate[16] = + { + 0x1c, 0x1d, 0x35, 0x00, + 0x38, 0x00, 0x47, 0x48, + 0x49, 0x4b, 0x4d, 0x4f, + 0x50, 0x51, 0x52, 0x53, + }; + +/* If there is a character pending, return it; + otherwise return GRUB_TERM_NO_KEY. */ +static int +grub_cros_keyboard_getkey (struct grub_term_input *term __attribute__ ((unused))) +{ + struct grub_cros_ec_keyscan scan; + int i, j; + if (grub_cros_ec_scan_keyboard (cros_ec, &scan) < 0) + return GRUB_TERM_NO_KEY; + for (i = 0; i < GRUB_CROS_EC_KEYSCAN_COLS; i++) + if (scan.data[i] ^ old_scan.data[i]) + for (j = 0; j < GRUB_CROS_EC_KEYSCAN_ROWS; j++) + if ((scan.data[i] ^ old_scan.data[i]) & (1 << j)) + { + grub_uint8_t code = map_code[i][j]; + int ret; + grub_uint8_t brk = 0; + if (!(scan.data[i] & (1 << j))) + brk = 0x80; + grub_dprintf ("cros_keyboard", "key <%d, %d> code %x\n", i, j, code); + if (code < 0x60) + ret = grub_ps2_process_incoming_byte (&ps2_state, code | brk); + else if (code >= 0x60 && code < 0x70 && e0_translate[code - 0x60]) + { + grub_ps2_process_incoming_byte (&ps2_state, 0xe0); + ret = grub_ps2_process_incoming_byte (&ps2_state, e0_translate[code - 0x60] | brk); + } + else + ret = GRUB_TERM_NO_KEY; + old_scan.data[i] ^= (1 << j); + if (ret != GRUB_TERM_NO_KEY) + return ret; + } + return GRUB_TERM_NO_KEY; +} + +static struct grub_term_input grub_cros_keyboard_term = + { + .name = "cros_keyboard", + .getkey = grub_cros_keyboard_getkey + }; + +static grub_err_t +cros_attach (const struct grub_fdtbus_dev *dev) +{ + grub_size_t keymap_size, i; + const grub_uint8_t *keymap = grub_fdtbus_get_prop (dev, "linux,keymap", &keymap_size); + + if (!dev->parent || !grub_cros_ec_validate (dev->parent)) + return GRUB_ERR_IO; + + if (keymap) + { + for (i = 0; i + 3 < keymap_size; i += 4) + if (keymap[i+1] < GRUB_CROS_EC_KEYSCAN_COLS && keymap[i] < GRUB_CROS_EC_KEYSCAN_ROWS + && keymap[i+2] == 0 && keymap[i+3] < 0x80) + map_code[keymap[i+1]][keymap[i]] = keymap[i+3]; + } + + cros_ec = dev->parent; + ps2_state.current_set = 1; + ps2_state.at_keyboard_status = 0; + grub_term_register_input ("cros_keyboard", &grub_cros_keyboard_term); + return GRUB_ERR_NONE; +} + +static struct grub_fdtbus_driver cros = +{ + .compatible = "google,cros-ec-keyb", + .attach = cros_attach +}; + +void +grub_cros_init (void) +{ + grub_fdtbus_register (&cros); +} diff --git a/grub-core/term/arm/cros_ec.c b/grub-core/term/arm/cros_ec.c new file mode 100644 index 000000000..f4144818b --- /dev/null +++ b/grub-core/term/arm/cros_ec.c @@ -0,0 +1,238 @@ +/* + * GRUB -- GRand Unified Bootloader + * + * Copyright (C) 2012 Google Inc. + * Copyright (C) 2016 Free Software Foundation, Inc. + * + * This is based on depthcharge code. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include + +static const grub_uint64_t FRAMING_TIMEOUT_MS = 300; + +static const grub_uint8_t EC_FRAMING_BYTE = 0xec; + +#define EC_CMD_MKBP_STATE 0x60 +#define EC_CMD_VERSION0 0xdc + +static grub_uint64_t last_transfer; + +static void +stop_bus (const struct grub_fdtbus_dev *spi) +{ + spi->driver->stop (spi); + last_transfer = grub_get_time_ms (); +} + +static int +wait_for_frame (const struct grub_fdtbus_dev *spi) +{ + grub_uint64_t start = grub_get_time_ms (); + grub_uint8_t byte; + do + { + if (spi->driver->receive (spi, &byte, 1)) + return -1; + if (byte != EC_FRAMING_BYTE && + grub_get_time_ms () - start > FRAMING_TIMEOUT_MS) + { + grub_dprintf ("cros", "Timeout waiting for framing byte.\n"); + return -1; + } + } + while (byte != EC_FRAMING_BYTE); + return 0; +} + +/* + * Calculate a simple 8-bit checksum of a data block + * + * @param data Data block to checksum + * @param size Size of data block in bytes + * @return checksum value (0 to 255) + */ +static grub_uint8_t +cros_ec_calc_checksum (const void *data, int size) +{ + grub_uint8_t csum; + const grub_uint8_t *bytes = data; + int i; + + for (i = csum = 0; i < size; i++) + csum += bytes[i]; + return csum & 0xff; +} + +enum +{ + /* response, arglen */ + CROS_EC_SPI_IN_HDR_SIZE = 2, + /* version, cmd, arglen */ + CROS_EC_SPI_OUT_HDR_SIZE = 3 +}; + +static grub_uint8_t busbuf[256]; +#define MSG_BYTES ((int)sizeof (busbuf)) + +static int +ec_command (const struct grub_fdtbus_dev *dev, int cmd, int cmd_version, + const void *dout, int dout_len, void *din, int din_len) +{ + const struct grub_fdtbus_dev *spi = dev->parent; + grub_uint8_t *bytes; + + /* Header + data + checksum. */ + grub_uint32_t out_bytes = CROS_EC_SPI_OUT_HDR_SIZE + dout_len + 1; + grub_uint32_t in_bytes = CROS_EC_SPI_IN_HDR_SIZE + din_len + 1; + + /* + * Sanity-check I/O sizes given transaction overhead in internal + * buffers. + */ + if (out_bytes > MSG_BYTES) + { + grub_dprintf ("cros", "Cannot send %d bytes\n", dout_len); + return -1; + } + if (in_bytes > MSG_BYTES) + { + grub_dprintf ("cros", "Cannot receive %d bytes\n", din_len); + return -1; + } + + /* Prepare the output. */ + bytes = busbuf; + *bytes++ = EC_CMD_VERSION0 + cmd_version; + *bytes++ = cmd; + *bytes++ = dout_len; + grub_memcpy (bytes, dout, dout_len); + bytes += dout_len; + + *bytes++ = cros_ec_calc_checksum (busbuf, + CROS_EC_SPI_OUT_HDR_SIZE + dout_len); + + /* Depthcharge uses 200 us here but GRUB timer resolution is only 1ms, + decrease this when we increase timer resolution. */ + while (grub_get_time_ms () - last_transfer < 1) + ; + + if (spi->driver->start (spi)) + return -1; + + /* Allow EC to ramp up clock after being awoken. */ + /* Depthcharge only waits 100 us here but GRUB timer resolution is only 1ms, + decrease this when we increase timer resolution. */ + grub_millisleep (1); + + if (spi->driver->send (spi, busbuf, out_bytes)) + { + stop_bus (spi); + return -1; + } + + /* Wait until the EC is ready. */ + if (wait_for_frame (spi)) + { + stop_bus (spi); + return -1; + } + + /* Read the response code and the data length. */ + bytes = busbuf; + if (spi->driver->receive (spi, bytes, 2)) + { + stop_bus (spi); + return -1; + } + grub_uint8_t result = *bytes++; + grub_uint8_t length = *bytes++; + + /* Make sure there's enough room for the data. */ + if (CROS_EC_SPI_IN_HDR_SIZE + length + 1 > MSG_BYTES) + { + grub_dprintf ("cros", "Received length %#02x too large\n", length); + stop_bus (spi); + return -1; + } + + /* Read the data and the checksum, and finish up. */ + if (spi->driver->receive (spi, bytes, length + 1)) + { + stop_bus (spi); + return -1; + } + bytes += length; + int expected = *bytes++; + stop_bus (spi); + + /* Check the integrity of the response. */ + if (result != 0) + { + grub_dprintf ("cros", "Received bad result code %d\n", result); + return -result; + } + + int csum = cros_ec_calc_checksum (busbuf, + CROS_EC_SPI_IN_HDR_SIZE + length); + + if (csum != expected) + { + grub_dprintf ("cros", "Invalid checksum rx %#02x, calced %#02x\n", + expected, csum); + return -1; + } + + /* If the caller wants the response, copy it out for them. */ + if (length < din_len) + din_len = length; + if (din) + { + grub_memcpy (din, (grub_uint8_t *) busbuf + CROS_EC_SPI_IN_HDR_SIZE, din_len); + } + + return din_len; +} + +int +grub_cros_ec_scan_keyboard (const struct grub_fdtbus_dev *dev, struct grub_cros_ec_keyscan *scan) +{ + if (ec_command (dev, EC_CMD_MKBP_STATE, 0, NULL, 0, scan, + sizeof (*scan)) < (int) sizeof (*scan)) + return -1; + + return 0; +} + +int +grub_cros_ec_validate (const struct grub_fdtbus_dev *dev) +{ + if (!grub_fdtbus_is_compatible("google,cros-ec-spi", dev)) + return 0; + if (!dev->parent) + return 0; + if (!dev->parent->driver) + return 0; + if (!dev->parent->driver->send + || !dev->parent->driver->receive) + return 0; + return 1; +} + diff --git a/include/grub/arm/cros_ec.h b/include/grub/arm/cros_ec.h new file mode 100644 index 000000000..45a372572 --- /dev/null +++ b/include/grub/arm/cros_ec.h @@ -0,0 +1,21 @@ +#ifndef GRUB_ARM_CROS_EC_H +#define GRUB_ARM_CROS_EC_H 1 + +#include +#include + +#define GRUB_CROS_EC_KEYSCAN_COLS 13 +#define GRUB_CROS_EC_KEYSCAN_ROWS 8 + +struct grub_cros_ec_keyscan { + grub_uint8_t data[GRUB_CROS_EC_KEYSCAN_COLS]; +}; + +int +grub_cros_ec_scan_keyboard (const struct grub_fdtbus_dev *dev, + struct grub_cros_ec_keyscan *scan); + +int +grub_cros_ec_validate (const struct grub_fdtbus_dev *dev); + +#endif