mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-14 16:46:23 +00:00
This patch makes EC firmware and cbi-util share the common code. BUG=b:70294260 BRANCH=none TEST=Set fields using ectool. Verify the contents by cbi command. Verify cbi-util creates the same binary as before. Verify emerge ec-utils ec-devutils pass. Change-Id: If5e65e48dd03960e0adf23ef775f67aecf785d85 Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/932001
358 lines
8.5 KiB
C
358 lines
8.5 KiB
C
/* Copyright 2018 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.
|
|
*
|
|
* Cros Board Info
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "crc8.h"
|
|
#include "cros_board_info.h"
|
|
#include "gpio.h"
|
|
#include "host_command.h"
|
|
#include "i2c.h"
|
|
#include "timer.h"
|
|
|
|
#ifdef HOST_TOOLS_BUILD
|
|
#include <string.h>
|
|
#else
|
|
#include "util.h"
|
|
#endif
|
|
|
|
/*
|
|
* Functions and variables defined here shared with host tools (e.g. cbi-util).
|
|
* TODO: Move these to common/cbi/cbi.c and common/cbi/utils.c if they grow.
|
|
*/
|
|
uint8_t cbi_crc8(const struct cbi_header *h)
|
|
{
|
|
return crc8((uint8_t *)&h->crc + 1,
|
|
h->total_size - sizeof(h->magic) - sizeof(h->crc));
|
|
}
|
|
|
|
uint8_t *cbi_set_data(uint8_t *p, enum cbi_data_tag tag,
|
|
const void *buf, int size)
|
|
{
|
|
struct cbi_data *d = (struct cbi_data *)p;
|
|
d->tag = tag;
|
|
d->size = size;
|
|
memcpy(d->value, buf, size);
|
|
p += sizeof(*d) + size;
|
|
return p;
|
|
}
|
|
|
|
struct cbi_data *cbi_find_tag(const void *cbi, enum cbi_data_tag tag)
|
|
{
|
|
struct cbi_data *d;
|
|
const struct cbi_header *h = cbi;
|
|
const uint8_t *p;
|
|
for (p = h->data; p + sizeof(*d) < (uint8_t *)cbi + h->total_size;) {
|
|
d = (struct cbi_data *)p;
|
|
if (d->tag == tag)
|
|
return d;
|
|
p += sizeof(*d) + d->size;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Functions and variables specific to EC firmware
|
|
*/
|
|
#ifndef HOST_TOOLS_BUILD
|
|
|
|
#define CPRINTS(format, args...) cprints(CC_SYSTEM, "CBI " format, ## args)
|
|
|
|
#define EEPROM_PAGE_WRITE_SIZE 16
|
|
#define EEPROM_PAGE_WRITE_MS 5
|
|
#define EC_ERROR_CBI_CACHE_INVALID EC_ERROR_INTERNAL_FIRST
|
|
|
|
static int cached_read_result = EC_ERROR_CBI_CACHE_INVALID;
|
|
static uint8_t cbi[CBI_EEPROM_SIZE];
|
|
static struct cbi_header * const head = (struct cbi_header *)cbi;
|
|
|
|
static int read_eeprom(uint8_t offset, uint8_t *in, int in_size)
|
|
{
|
|
return i2c_xfer(I2C_PORT_EEPROM, I2C_ADDR_EEPROM,
|
|
&offset, 1, in, in_size, I2C_XFER_SINGLE);
|
|
}
|
|
|
|
/*
|
|
* Get board information from EEPROM
|
|
*/
|
|
static int do_read_board_info(void)
|
|
{
|
|
CPRINTS("Reading board info");
|
|
|
|
/* Read header */
|
|
if (read_eeprom(0, cbi, sizeof(*head))) {
|
|
CPRINTS("Failed to read header");
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
|
|
/* Check magic */
|
|
if (memcmp(head->magic, cbi_magic, sizeof(head->magic))) {
|
|
CPRINTS("Bad magic");
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
|
|
/* check version */
|
|
if (head->major_version > CBI_VERSION_MAJOR) {
|
|
CPRINTS("Version mismatch");
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
|
|
/* Check the data size. It's expected to support up to 64k but our
|
|
* buffer has practical limitation. */
|
|
if (head->total_size < sizeof(*head) ||
|
|
sizeof(cbi) < head->total_size) {
|
|
CPRINTS("Bad size: %d", head->total_size);
|
|
return EC_ERROR_OVERFLOW;
|
|
}
|
|
|
|
/* Read the data */
|
|
if (read_eeprom(sizeof(*head), head->data,
|
|
head->total_size - sizeof(*head))) {
|
|
CPRINTS("Failed to read body");
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
|
|
/* Check CRC. This supports new fields unknown to this parser. */
|
|
if (cbi_crc8(head) != head->crc) {
|
|
CPRINTS("Bad CRC");
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int read_board_info(void)
|
|
{
|
|
if (cached_read_result == EC_ERROR_CBI_CACHE_INVALID) {
|
|
cached_read_result = do_read_board_info();
|
|
if (cached_read_result)
|
|
/* On error (I2C or bad contents), retry a read */
|
|
cached_read_result = do_read_board_info();
|
|
}
|
|
/* Else, we already tried and know the result. Return the cached
|
|
* error code immediately to avoid wasteful reads. */
|
|
return cached_read_result;
|
|
}
|
|
|
|
int cbi_get_board_info(enum cbi_data_tag tag, uint8_t *buf, uint8_t *size)
|
|
{
|
|
const struct cbi_data *d;
|
|
|
|
if (read_board_info())
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
d = cbi_find_tag(cbi, tag);
|
|
if (!d)
|
|
/* Not found */
|
|
return EC_ERROR_UNKNOWN;
|
|
if (*size < d->size)
|
|
/* Insufficient buffer size */
|
|
return EC_ERROR_INVAL;
|
|
|
|
/* Clear the buffer in case len < *size */
|
|
memset(buf, 0, *size);
|
|
/* Copy the value */
|
|
memcpy(buf, d->value, d->size);
|
|
*size = d->size;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int cbi_set_board_info(enum cbi_data_tag tag, const uint8_t *buf, uint8_t size)
|
|
{
|
|
struct cbi_data *d;
|
|
|
|
d = cbi_find_tag(cbi, tag);
|
|
if (!d) {
|
|
/* Not found. Check if new item would fit */
|
|
if (sizeof(cbi) < head->total_size + sizeof(*d) + size)
|
|
return EC_ERROR_OVERFLOW;
|
|
/* Append new item */
|
|
cbi_set_data(&cbi[head->total_size], tag, buf, size);
|
|
head->total_size += (sizeof(*d) + size);
|
|
return EC_SUCCESS;
|
|
}
|
|
/* No expand or shrink. Items are tightly packed. */
|
|
if (d->size != size)
|
|
return EC_ERROR_INVAL;
|
|
/* Overwrite existing item */
|
|
memcpy(d->value, buf, d->size);
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int eeprom_is_write_protected(void)
|
|
{
|
|
return !gpio_get_level(GPIO_WP_L);
|
|
}
|
|
|
|
static int write_board_info(void)
|
|
{
|
|
/* The code is only tested for ST M24C02, whose page size for a single
|
|
* write is 16 byte. To support different EEPROMs, you may need to
|
|
* craft the i2c packets accordingly. */
|
|
uint8_t buf[EEPROM_PAGE_WRITE_SIZE + 1]; /* '1' for offset byte */
|
|
uint8_t *p = cbi;
|
|
int rest = head->total_size;
|
|
|
|
if (eeprom_is_write_protected()) {
|
|
CPRINTS("Failed to write for WP");
|
|
return EC_ERROR_ACCESS_DENIED;
|
|
}
|
|
|
|
buf[0] = 0; /* Offset 0 */
|
|
while (rest > 0) {
|
|
int size = MIN(EEPROM_PAGE_WRITE_SIZE, rest);
|
|
int rv;
|
|
rest -= size;
|
|
memcpy(&buf[1], p, size);
|
|
rv = i2c_xfer(I2C_PORT_EEPROM, I2C_ADDR_EEPROM, buf, size + 1,
|
|
NULL, 0, I2C_XFER_SINGLE);
|
|
if (rv) {
|
|
CPRINTS("Failed to write for %d", rv);
|
|
return rv;
|
|
}
|
|
/* Wait for internal write cycle completion */
|
|
msleep(EEPROM_PAGE_WRITE_MS);
|
|
buf[0] += size;
|
|
p += size;
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int cbi_get_board_version(uint32_t *ver)
|
|
{
|
|
uint8_t size = sizeof(*ver);
|
|
return cbi_get_board_info(CBI_TAG_BOARD_VERSION, (uint8_t *)ver, &size);
|
|
}
|
|
|
|
int cbi_get_sku_id(uint32_t *id)
|
|
{
|
|
uint8_t size = sizeof(*id);
|
|
return cbi_get_board_info(CBI_TAG_SKU_ID, (uint8_t *)id, &size);
|
|
}
|
|
|
|
int cbi_get_oem_id(uint32_t *id)
|
|
{
|
|
uint8_t size = sizeof(*id);
|
|
return cbi_get_board_info(CBI_TAG_OEM_ID, (uint8_t *)id, &size);
|
|
}
|
|
|
|
/*
|
|
* For backward compatibility. New code should use cbi_get_board_version.
|
|
*/
|
|
int board_get_version(void)
|
|
{
|
|
uint32_t ver;
|
|
uint8_t size = sizeof(ver);
|
|
if (cbi_get_board_info(CBI_TAG_BOARD_VERSION, (uint8_t *)&ver, &size))
|
|
return -1;
|
|
return ver;
|
|
}
|
|
|
|
static int hc_cbi_get(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct __ec_align4 ec_params_get_cbi *p = args->params;
|
|
uint8_t size = MIN(args->response_max, UINT8_MAX);
|
|
|
|
if (p->flag & CBI_GET_RELOAD)
|
|
cached_read_result = EC_ERROR_CBI_CACHE_INVALID;
|
|
|
|
if (cbi_get_board_info(p->tag, args->response, &size))
|
|
return EC_RES_INVALID_PARAM;
|
|
|
|
args->response_size = size;
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_GET_CROS_BOARD_INFO,
|
|
hc_cbi_get,
|
|
EC_VER_MASK(0));
|
|
|
|
static int hc_cbi_set(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct __ec_align4 ec_params_set_cbi *p = args->params;
|
|
int rv;
|
|
|
|
if (p->flag & CBI_SET_INIT) {
|
|
memset(cbi, 0, sizeof(cbi));
|
|
memcpy(head->magic, cbi_magic, sizeof(cbi_magic));
|
|
head->total_size = sizeof(*head);
|
|
cached_read_result = EC_SUCCESS;
|
|
} else {
|
|
if (read_board_info())
|
|
return EC_RES_ERROR;
|
|
}
|
|
|
|
if (cbi_set_board_info(p->tag, p->data, p->size))
|
|
return EC_RES_INVALID_PARAM;
|
|
|
|
/* Whether we're modifying existing data or creating new one,
|
|
* we take over the format. */
|
|
head->major_version = CBI_VERSION_MAJOR;
|
|
head->minor_version = CBI_VERSION_MINOR;
|
|
head->crc = cbi_crc8(head);
|
|
|
|
/* Skip write if client asks so. */
|
|
if (p->flag & CBI_SET_NO_SYNC)
|
|
return EC_RES_SUCCESS;
|
|
|
|
rv = write_board_info();
|
|
if (rv)
|
|
return (rv == EC_ERROR_ACCESS_DENIED) ?
|
|
EC_RES_ACCESS_DENIED : EC_RES_ERROR;
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_SET_CROS_BOARD_INFO,
|
|
hc_cbi_set,
|
|
EC_VER_MASK(0));
|
|
|
|
static void dump_cbi(void)
|
|
{
|
|
uint8_t buf[16];
|
|
int i;
|
|
for (i = 0; i < CBI_EEPROM_SIZE; i += sizeof(buf)) {
|
|
int j;
|
|
if (read_eeprom(i, buf, sizeof(buf))) {
|
|
ccprintf("\nFailed to read EEPROM\n");
|
|
return;
|
|
}
|
|
for (j = 0; j < sizeof(buf); j++)
|
|
ccprintf(" %02x", buf[j]);
|
|
ccprintf("\n");
|
|
}
|
|
}
|
|
|
|
static int cc_cbi(int argc, char **argv)
|
|
{
|
|
uint32_t val;
|
|
|
|
ccprintf("CBI_VERSION: 0x%04x\n", head->version);
|
|
ccprintf("TOTAL_SIZE: %u\n", head->total_size);
|
|
ccprintf("BOARD_VERSION: ");
|
|
if (cbi_get_board_version(&val) == EC_SUCCESS)
|
|
ccprintf("%u (0x%x)\n", val, val);
|
|
else
|
|
ccprintf("UNKNOWN\n");
|
|
|
|
ccprintf("OEM_ID: ");
|
|
if (cbi_get_oem_id(&val) == EC_SUCCESS)
|
|
ccprintf("%u (0x%x)\n", val, val);
|
|
else
|
|
ccprintf("UNKNOWN\n");
|
|
|
|
ccprintf("SKU_ID: ");
|
|
if (cbi_get_sku_id(&val) == EC_SUCCESS)
|
|
ccprintf("%u (0x%x)\n", val, val);
|
|
else
|
|
ccprintf("UNKNOWN\n");
|
|
|
|
dump_cbi();
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(cbi, cc_cbi, NULL, NULL);
|
|
#endif /* !HOST_TOOLS_BUILD */
|