/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Read/write/erase Embedded Controller integrated flash */
#define LOG_TAG "fwtool"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "ec_commands.h"
#include "flash_device.h"
#include "update_log.h"
#define CROS_EC_DEV_NAME "/dev/cros_ec"
struct cros_ec_command {
uint32_t version;
uint32_t command;
uint8_t *outdata;
uint32_t outsize;
uint8_t *indata;
uint32_t insize;
uint32_t result;
};
#define CROS_EC_DEV_IOCXCMD _IOWR(':', 0, struct cros_ec_command)
#define CROS_EC_DEV_IOCRDMEM _IOWR(':', 1, struct cros_ec_readmem)
struct ec_data {
int fd;
struct ec_response_get_protocol_info proto;
struct ec_response_flash_info_1 info;
struct ec_response_flash_region_info ro_region;
};
static int ec_command(void *hnd, int command, int version,
const void *outdata, int outsize, void *indata, int insize)
{
struct ec_data *ec = hnd;
struct cros_ec_command s_cmd;
int r;
if (ec->fd < 0)
return -ENODEV;
s_cmd.command = command;
s_cmd.version = version;
s_cmd.result = 0xff;
s_cmd.outsize = outsize;
s_cmd.outdata = (uint8_t *)outdata;
s_cmd.insize = insize;
s_cmd.indata = indata;
r = ioctl(ec->fd, CROS_EC_DEV_IOCXCMD, &s_cmd);
if (r < 0) {
ALOGD("Cmd 0x%x failed %d\n", command, errno);
return -errno;
} else if (s_cmd.result != EC_RES_SUCCESS) {
ALOGD("Cmd 0x%x error %d\n", command, s_cmd.result);
return s_cmd.result;
}
return 0;
}
static void *ec_open(const void *params)
{
int res;
struct ec_params_flash_region_info region;
const char *path = params ? params : CROS_EC_DEV_NAME;
struct ec_data *dev = calloc(1, sizeof(struct ec_data));
if (!dev)
return NULL;
dev->fd = open(path, O_RDWR);
if (dev->fd == -1) {
ALOGE("Cannot open EC device %s : %d\n", path, errno);
goto out_free;
}
res = ec_command(dev, EC_CMD_GET_PROTOCOL_INFO, 0, NULL, 0,
&dev->proto, sizeof(dev->proto));
if (res) {
ALOGE("Cannot get EC protocol info for %s : %d\n", path, res);
goto out_close;
}
res = ec_command(dev, EC_CMD_FLASH_INFO, 1, NULL, 0,
&dev->info, sizeof(dev->info));
if (res) {
ALOGE("Cannot get EC flash info for %s : %d\n", path, res);
goto out_close;
}
region.region = EC_FLASH_REGION_RO;
res = ec_command(dev, EC_CMD_FLASH_REGION_INFO, 1,
®ion, sizeof(region),
&dev->ro_region, sizeof(dev->ro_region));
if (res) {
ALOGE("Cannot get EC RO info for %s : %d\n", path, res);
goto out_close;
}
ALOGD("EC %s: size %d erase_block_size %d write_ideal_size %d\n",
path, dev->info.flash_size, dev->info.erase_block_size,
dev->info.write_ideal_size);
return dev;
out_close:
close(dev->fd);
dev->fd = -1;
out_free:
free(dev);
return NULL;
}
static void ec_close(void *hnd)
{
struct ec_data *dev = hnd;
close(dev->fd);
free(dev);
}
static int ec_read(void *hnd, off_t offset, void *buffer, size_t count)
{
struct ec_data *dev = hnd;
ssize_t res;
struct ec_params_flash_read p;
uint8_t *ptr = buffer;
uint32_t read_size = dev->proto.max_response_packet_size
- sizeof(struct ec_host_response);
while (count) {
p.offset = offset;
p.size = MIN(read_size, count);
res = ec_command(dev, EC_CMD_FLASH_READ, 0, &p, sizeof(p),
ptr, read_size);
if (res) {
ALOGW("Cannot read at %ld : %zd\n", offset, res);
return res;
}
count -= p.size;
ptr += p.size;
offset += p.size;
}
return 0;
}
static int ec_write(void *hnd, off_t offset, void *buffer, size_t count)
{
struct ec_data *dev = hnd;
ssize_t res;
struct ec_params_flash_write *p;
uint8_t *packet_data;
uint8_t *ptr = buffer;
uint32_t write_size = dev->info.write_ideal_size;
uint32_t total_size = sizeof(*p) + write_size;
p = malloc(total_size);
if (!p)
return -ENOMEM;
packet_data = (uint8_t *)p + sizeof(*p);
while (count) {
p->offset = offset;
p->size = write_size;
memcpy(packet_data, ptr, write_size);
res = ec_command(dev, EC_CMD_FLASH_WRITE, 1, p, total_size,
NULL, 0);
if (res) {
ALOGW("Cannot write at %ld : %zd\n", offset, res);
return res;
}
count -= write_size;
ptr += write_size;
offset += write_size;
}
return 0;
}
static int ec_erase(void *hnd, off_t offset, size_t count)
{
struct ec_data *dev = hnd;
int res;
struct ec_params_flash_erase erase;
erase.offset = offset;
erase.size = count;
res = ec_command(dev, EC_CMD_FLASH_ERASE, 0, &erase, sizeof(erase),
NULL, 0);
if (res) {
ALOGW("Cannot erase at %ld : %d\n", offset, res);
return res;
}
return 0;
}
static size_t ec_get_size(void *hnd)
{
struct ec_data *dev = hnd;
return dev && dev->fd > 0 ? dev->info.flash_size : 0;
}
static size_t ec_get_write_size(void *hnd)
{
struct ec_data *dev = hnd;
return dev && dev->fd > 0 ? dev->info.write_ideal_size : 0;
}
static size_t ec_get_erase_size(void *hnd)
{
struct ec_data *dev = hnd;
return dev && dev->fd > 0 ? dev->info.erase_block_size : 0;
}
static off_t ec_get_fmap_offset(void *hnd)
{
struct ec_data *dev = hnd;
if (!hnd)
return 0;
/*
* Try to find the FMAP signature at 64-byte boundaries
* from the end of the RO region.
*/
return dev->ro_region.offset + dev->ro_region.size;
}
const struct flash_device_ops flash_ec_ops = {
.name = "ec",
.open = ec_open,
.close = ec_close,
.read = ec_read,
.write = ec_write,
.erase = ec_erase,
.get_size = ec_get_size,
.get_write_size = ec_get_write_size,
.get_erase_size = ec_get_erase_size,
.get_fmap_offset = ec_get_fmap_offset,
.cmd = ec_command,
};