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