| /* |
| * 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 SPI flash through Linux kernel MTD interface */ |
| |
| #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/types.h> |
| |
| #include <mtd/mtd-user.h> |
| |
| #include "flash_device.h" |
| #include "update_log.h" |
| #include "vboot_interface.h" |
| |
| static const char * const DEFAULT_MTD_FILE = "/dev/mtd/mtd0"; |
| |
| struct mtd_data { |
| int fd; |
| struct mtd_info_user info; |
| }; |
| |
| static void *mtd_open(const void *params) |
| { |
| const char *path = params ? params : DEFAULT_MTD_FILE; |
| struct mtd_data *dev = calloc(1, sizeof(struct mtd_data)); |
| if (!dev) |
| return NULL; |
| |
| dev->fd = open(path, O_RDWR); |
| if (dev->fd == -1) { |
| ALOGE("No MTD device %s : %d\n", path, errno); |
| goto out_free; |
| } |
| |
| if (ioctl(dev->fd, MEMGETINFO, &dev->info)) { |
| ALOGE("Cannot get MTD info for %s : %d\n", path, errno); |
| goto out_close; |
| } |
| |
| if (dev->info.type != MTD_NORFLASH) { |
| ALOGE("Unsupported MTD device type: %d\n", dev->info.type); |
| goto out_close; |
| } |
| |
| ALOGD("MTD %s: size %d erasesize %d min_io_size %d\n", |
| path, dev->info.size, dev->info.erasesize, dev->info.writesize); |
| |
| return dev; |
| |
| out_close: |
| close(dev->fd); |
| dev->fd = -1; |
| out_free: |
| free(dev); |
| |
| return NULL; |
| } |
| |
| static void mtd_close(void *hnd) |
| { |
| struct mtd_data *dev = hnd; |
| |
| close(dev->fd); |
| free(dev); |
| } |
| |
| static int mtd_read(void *hnd, off_t offset, void *buffer, size_t count) |
| { |
| struct mtd_data *dev = hnd; |
| ssize_t res; |
| uint8_t *ptr = buffer; |
| |
| if (lseek(dev->fd, offset, SEEK_SET) != offset) { |
| ALOGW("Cannot seek to %ld\n", offset); |
| return errno; |
| |
| } |
| |
| while (count) { |
| res = read(dev->fd, ptr, count); |
| if (res < 0) { |
| ALOGW("Cannot read at %ld : %zd\n", offset, res); |
| return errno; |
| } |
| count -= res; |
| ptr += res; |
| } |
| return 0; |
| } |
| |
| static int mtd_write(void *hnd, off_t offset, void *buffer, size_t count) |
| { |
| struct mtd_data *dev = hnd; |
| ssize_t res; |
| uint8_t *ptr = buffer; |
| |
| if (lseek(dev->fd, offset, SEEK_SET) != offset) { |
| ALOGW("Cannot seek to %ld\n", offset); |
| return errno; |
| } |
| |
| while (count) { |
| res = write(dev->fd, ptr, count); |
| if (res < 0) { |
| ALOGW("Cannot write at %ld : %zd\n", offset, res); |
| return errno; |
| } |
| count -= res; |
| ptr += res; |
| } |
| return 0; |
| } |
| |
| static int mtd_erase(void *hnd, off_t offset, size_t count) |
| { |
| struct mtd_data *dev = hnd; |
| int res; |
| struct erase_info_user ei; |
| |
| ei.start = offset; |
| ei.length = count; |
| res = ioctl(dev->fd, MEMERASE, &ei); |
| if (res < 0) { |
| ALOGW("Cannot erase at %ld : %d\n", offset, res); |
| return errno; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Write Protect handling : |
| * struct erase_info_user ei; |
| * |
| * ei.start = eb * info.erasesize; |
| * ei.length = info.erasesize; |
| * ret = ioctl(fd, MEMISLOCKED, &ei); |
| * ret = ioctl(fd, MEMLOCK, &ei); |
| * ret = ioctl(fd, MEMUNLOCK, &ei); |
| */ |
| |
| static size_t mtd_get_size(void *hnd) |
| { |
| struct mtd_data *dev = hnd; |
| |
| return dev && dev->fd > 0 ? dev->info.size : 0; |
| } |
| |
| static size_t mtd_get_write_size(void *hnd) |
| { |
| struct mtd_data *dev = hnd; |
| |
| return dev && dev->fd > 0 ? dev->info.writesize : 0; |
| } |
| |
| static size_t mtd_get_erase_size(void *hnd) |
| { |
| struct mtd_data *dev = hnd; |
| |
| return dev && dev->fd > 0 ? dev->info.erasesize : 0; |
| } |
| |
| static off_t mtd_get_fmap_offset(void *hnd __attribute__((unused))) |
| { |
| /* Get the SPI FMAP offset passed by the firmware in the device-tree */ |
| return fdt_read_u32("fmap-offset") + 64; |
| } |
| |
| const struct flash_device_ops flash_mtd_ops = { |
| .name = "spi", |
| .open = mtd_open, |
| .close = mtd_close, |
| .read = mtd_read, |
| .write = mtd_write, |
| .erase = mtd_erase, |
| .get_size = mtd_get_size, |
| .get_write_size = mtd_get_write_size, |
| .get_erase_size = mtd_get_erase_size, |
| .get_fmap_offset = mtd_get_fmap_offset, |
| }; |