239 lines
5.8 KiB
C
239 lines
5.8 KiB
C
|
/*
|
||
|
* 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;
|
||
|
}
|
||
|
|