diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:29:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:29:51 +0000 |
commit | 6e7a315eb67cb6c113cf37e1d66c4f11a51a2b3e (patch) | |
tree | 32451fa3cdd9321fb2591fada9891b2cb70a9cd1 /grub-core/term/arm/cros_ec.c | |
parent | Initial commit. (diff) | |
download | grub2-3e86199209a10ed6555e30b8b71884cb7cc45ed2.tar.xz grub2-3e86199209a10ed6555e30b8b71884cb7cc45ed2.zip |
Adding upstream version 2.06.upstream/2.06upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'grub-core/term/arm/cros_ec.c')
-rw-r--r-- | grub-core/term/arm/cros_ec.c | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/grub-core/term/arm/cros_ec.c b/grub-core/term/arm/cros_ec.c new file mode 100644 index 0000000..f414481 --- /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 <http://www.gnu.org/licenses/>. + */ + +#include <grub/mm.h> +#include <grub/time.h> +#include <grub/misc.h> +#include <grub/arm/cros_ec.h> +#include <grub/fdtbus.h> + +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; +} + |