Files
OpenCellular/common/cbi.c
Daisuke Nojiri 5946911129 CBI: Make data offset and size variable
Currently CBI data offset and size are fixed. This patch makes them
variable. Each data item consists of <tag><size><value> where <tag>
is a numeric value assigned to each data item, <size> is the number
of bytes used for <value>.

BUG=b:70294260
BRANCH=none
TEST=Use 'ectool cbi set' to set board version, oem, sku.
Verify the contents by cbi console command and ectool cbi get.
1. ectool cbi set 0 0x202 2 2 (Init CBI and write board ver. of size 2)
2. ectool cbi set 1 1 1 (write oem id of size 1)
3. ectool cbi set 2 2 1 (write sku id of size 1)
4. ectool cbi get 0
 514 (0x202)
5. ectool cbi get 1
 1 (0x1)
6. ectool cbi get 2
 2 (0x2)
7. Run cbi console command:
 CBI_VERSION: 0x0000
 TOTAL_SIZE: 18
 BOARD_VERSION: 514 (0x202)
 OEM_ID: 1 (0x1)
 SKU_ID: 2 (0x2)
  43 42 49 8c 00 00 12 00 00 02 02 02 01 01 01 02
  01 02

Change-Id: I5a30a4076e3eb448f4808d2af8ec4ef4c016ae5e
Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/920905
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
2018-02-16 18:47:52 -08:00

329 lines
7.8 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"
#include "util.h"
#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 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));
}
/*
* Get board information from EEPROM
*/
static int do_read_board_info(void)
{
uint8_t offset;
CPRINTS("Reading board info");
/* Read header */
offset = 0;
if (i2c_xfer(I2C_PORT_EEPROM, I2C_ADDR_EEPROM,
&offset, 1, cbi, sizeof(*head), I2C_XFER_SINGLE)) {
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 */
offset = sizeof(*head);
if (i2c_xfer(I2C_PORT_EEPROM, I2C_ADDR_EEPROM, &offset, 1,
head->data, head->total_size - sizeof(*head),
I2C_XFER_SINGLE)) {
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;
}
static struct cbi_data *find_tag(enum cbi_data_tag tag)
{
struct cbi_data *d;
uint8_t *p;
for (p = head->data; p + sizeof(*d) < cbi + head->total_size;) {
d = (struct cbi_data *)p;
if (d->tag == tag)
return d;
p += sizeof(*d) + d->size;
}
return NULL;
}
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 = find_tag(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 = find_tag(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 */
d = (struct cbi_data *)&cbi[head->total_size];
d->tag = tag;
d->size = size;
memcpy(d->value, buf, d->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)
{
int i;
for (i = 0; i < head->total_size; i++) {
ccprintf(" %02x", cbi[i]);
if (i % 16 == 15)
ccprintf("\n");
}
ccprintf("\n");
}
static int command_dump_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, command_dump_cbi, NULL, NULL);