| /* |
| * 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, |
| }; |