From 1d880992fd8c8457a2d990ac6622cfd58fb1b261 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Wed, 2 Jul 2014 15:36:20 +1000 Subject: Initial commit of Open Source release Signed-off-by: Benjamin Herrenschmidt --- libflash/Makefile.inc | 7 + libflash/ffs.h | 159 +++++++++++ libflash/libffs.c | 280 +++++++++++++++++++ libflash/libffs.h | 56 ++++ libflash/libflash-priv.h | 213 +++++++++++++++ libflash/libflash.c | 636 +++++++++++++++++++++++++++++++++++++++++++ libflash/libflash.h | 83 ++++++ libflash/test/Makefile.check | 20 ++ libflash/test/stubs.c | 16 ++ libflash/test/test-flash.c | 418 ++++++++++++++++++++++++++++ 10 files changed, 1888 insertions(+) create mode 100644 libflash/Makefile.inc create mode 100644 libflash/ffs.h create mode 100644 libflash/libffs.c create mode 100644 libflash/libffs.h create mode 100644 libflash/libflash-priv.h create mode 100644 libflash/libflash.c create mode 100644 libflash/libflash.h create mode 100644 libflash/test/Makefile.check create mode 100644 libflash/test/stubs.c create mode 100644 libflash/test/test-flash.c (limited to 'libflash') diff --git a/libflash/Makefile.inc b/libflash/Makefile.inc new file mode 100644 index 0000000..35f96f7 --- /dev/null +++ b/libflash/Makefile.inc @@ -0,0 +1,7 @@ +LIBFLASH_SRCS = libflash.c libffs.c +LIBFLASH_OBJS = $(LIBFLASH_SRCS:%.c=%.o) + +SUBDIRS += libflash +LIBFLASH = libflash/built-in.o + +$(LIBFLASH): $(LIBFLASH_OBJS:%=libflash/%) diff --git a/libflash/ffs.h b/libflash/ffs.h new file mode 100644 index 0000000..2969c4b --- /dev/null +++ b/libflash/ffs.h @@ -0,0 +1,159 @@ +/* Copyright 2013-2014 IBM Corp. + * + * 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. + */ +/* IBM_PROLOG_BEGIN_TAG */ +/* This is an automatically generated prolog. */ +/* */ +/* $Source: src/usr/pnor/ffs.h $ */ +/* */ +/* IBM CONFIDENTIAL */ +/* */ +/* COPYRIGHT International Business Machines Corp. 2012,2013 */ +/* */ +/* p1 */ +/* */ +/* Object Code Only (OCO) source materials */ +/* Licensed Internal Code Source Materials */ +/* IBM HostBoot Licensed Internal Code */ +/* */ +/* The source code for this program is not published or otherwise */ +/* divested of its trade secrets, irrespective of what has been */ +/* deposited with the U.S. Copyright Office. */ +/* */ +/* Origin: 30 */ +/* */ +/* IBM_PROLOG_END_TAG */ +/* + * Copyright (c) International Business Machines Corp., 2012 + * + * FSP Flash Structure + * + * This header defines the layout for the FSP Flash Structure. + */ + +#ifndef __FFS_H__ +#define __FFS_H__ + +/* Pull in the correct header depending on what is being built */ +#if defined(__KERNEL__) +#include +#else +#include +#endif + +/* The version of this partition implementation */ +#define FFS_VERSION_1 1 + +/* Magic number for the partition header (ASCII 'PART') */ +#define FFS_MAGIC 0x50415254 + +/* The maximum length of the partition name */ +#define PART_NAME_MAX 15 + +/* + * Sizes of the data structures + */ +#define FFS_HDR_SIZE sizeof(struct ffs_hdr) +#define FFS_ENTRY_SIZE sizeof(struct ffs_entry) + +/* + * Sizes of the data structures w/o checksum + */ +#define FFS_HDR_SIZE_CSUM (FFS_HDR_SIZE - sizeof(uint32_t)) +#define FFS_ENTRY_SIZE_CSUM (FFS_ENTRY_SIZE - sizeof(uint32_t)) + +/* pid of logical partitions/containers */ +#define FFS_PID_TOPLEVEL 0xFFFFFFFF + +/* + * Type of image contained w/in partition + */ +enum type { + FFS_TYPE_DATA = 1, + FFS_TYPE_LOGICAL = 2, + FFS_TYPE_PARTITION = 3, +}; + +/* + * Flag bit definitions + */ +#define FFS_FLAGS_PROTECTED 0x0001 +#define FFS_FLAGS_U_BOOT_ENV 0x0002 + +/* + * Number of user data words + */ +#define FFS_USER_WORDS 16 + +/** + * struct ffs_entry - Partition entry + * + * @name: Opaque null terminated string + * @base: Starting offset of partition in flash (in hdr.block_size) + * @size: Partition size (in hdr.block_size) + * @pid: Parent partition entry (FFS_PID_TOPLEVEL for toplevel) + * @id: Partition entry ID [1..65536] + * @type: Describe type of partition + * @flags: Partition attributes (optional) + * @actual: Actual partition size (in bytes) + * @resvd: Reserved words for future use + * @user: User data (optional) + * @checksum: Partition entry checksum (includes all above) + */ +struct ffs_entry { + char name[PART_NAME_MAX + 1]; + uint32_t base; + uint32_t size; + uint32_t pid; + uint32_t id; + uint32_t type; + uint32_t flags; + uint32_t actual; + uint32_t resvd[4]; + struct { + uint32_t data[FFS_USER_WORDS]; + } user; + uint32_t checksum; +} __attribute__ ((packed)); + +/** + * struct ffs_hdr - FSP Flash Structure header + * + * @magic: Eye catcher/corruption detector + * @version: Version of the structure + * @size: Size of partition table (in block_size) + * @entry_size: Size of struct ffs_entry element (in bytes) + * @entry_count: Number of struct ffs_entry elements in @entries array + * @block_size: Size of block on device (in bytes) + * @block_count: Number of blocks on device + * @resvd: Reserved words for future use + * @checksum: Header checksum + * @entries: Pointer to array of partition entries + */ +struct ffs_hdr { + uint32_t magic; + uint32_t version; + uint32_t size; + uint32_t entry_size; + uint32_t entry_count; + uint32_t block_size; + uint32_t block_count; + uint32_t resvd[4]; + uint32_t checksum; + struct ffs_entry entries[]; +} __attribute__ ((packed)); + + +#endif /* __FFS_H__ */ diff --git a/libflash/libffs.c b/libflash/libffs.c new file mode 100644 index 0000000..ef2aa4d --- /dev/null +++ b/libflash/libffs.c @@ -0,0 +1,280 @@ +/* Copyright 2013-2014 IBM Corp. + * + * 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. + */ +/* + */ +#include +#include +#include + +#include + +#include "libffs.h" + +enum ffs_type { + ffs_type_flash, + ffs_type_image, +}; + +struct ffs_handle { + struct ffs_hdr hdr; /* Converted header */ + enum ffs_type type; + struct flash_chip *chip; + uint32_t flash_offset; + uint32_t max_size; + void *cache; + uint32_t cached_size; +}; + +static uint32_t ffs_checksum(void* data, size_t size) +{ + uint32_t i, csum = 0; + + for (i = csum = 0; i < (size/4); i++) + csum ^= ((uint32_t *)data)[i]; + return csum; +} + +static int ffs_check_convert_header(struct ffs_hdr *dst, struct ffs_hdr *src) +{ + dst->magic = be32_to_cpu(src->magic); + if (dst->magic != FFS_MAGIC) + return FFS_ERR_BAD_MAGIC; + dst->version = be32_to_cpu(src->version); + if (dst->version != FFS_VERSION_1) + return FFS_ERR_BAD_VERSION; + if (ffs_checksum(src, FFS_HDR_SIZE) != 0) + return FFS_ERR_BAD_CKSUM; + dst->size = be32_to_cpu(src->size); + dst->entry_size = be32_to_cpu(src->entry_size); + dst->entry_count = be32_to_cpu(src->entry_count); + dst->block_size = be32_to_cpu(src->block_size); + dst->block_count = be32_to_cpu(src->block_count); + + return 0; +} + +int ffs_open_flash(struct flash_chip *chip, uint32_t offset, + uint32_t max_size, struct ffs_handle **ffs) +{ + struct ffs_hdr hdr; + struct ffs_handle *f; + uint32_t fl_size, erase_size; + int rc; + + if (!ffs) + return FLASH_ERR_PARM_ERROR; + *ffs = NULL; + + /* Grab some info about our flash chip */ + rc = flash_get_info(chip, NULL, &fl_size, &erase_size); + if (rc) { + FL_ERR("FFS: Error %d retrieving flash info\n", rc); + return rc; + } + if ((offset + max_size) < offset) + return FLASH_ERR_PARM_ERROR; + if ((offset + max_size) > fl_size) + return FLASH_ERR_PARM_ERROR; + + /* Read flash header */ + rc = flash_read(chip, offset, &hdr, sizeof(hdr)); + if (rc) { + FL_ERR("FFS: Error %d reading flash header\n", rc); + return rc; + } + + /* Allocate ffs_handle structure and start populating */ + f = malloc(sizeof(*f)); + if (!f) + return FLASH_ERR_MALLOC_FAILED; + memset(f, 0, sizeof(*f)); + f->type = ffs_type_flash; + f->flash_offset = offset; + f->max_size = max_size ? max_size : (fl_size - offset); + f->chip = chip; + + /* Convert and check flash header */ + rc = ffs_check_convert_header(&f->hdr, &hdr); + if (rc) { + FL_ERR("FFS: Error %d checking flash header\n", rc); + free(f); + return rc; + } + + /* + * Decide how much of the image to grab to get the whole + * partition map. + */ + f->cached_size = f->hdr.block_size * f->hdr.size; + FL_DBG("FFS: Partition map size: 0x%x\n", f->cached_size); + + /* Align to erase size */ + f->cached_size |= (erase_size - 1); + f->cached_size &= ~(erase_size - 1); + FL_DBG("FFS: Aligned to: 0x%x\n", f->cached_size); + + /* Allocate cache */ + f->cache = malloc(f->cached_size); + if (!f->cache) { + free(f); + return FLASH_ERR_MALLOC_FAILED; + } + + /* Read the cached map */ + rc = flash_read(chip, offset, f->cache, f->cached_size); + if (rc) { + FL_ERR("FFS: Error %d reading flash partition map\n", rc); + free(f); + } + if (rc == 0) + *ffs = f; + return rc; +} + +#if 0 /* XXX TODO: For FW updates so we can copy nvram around */ +int ffs_open_image(void *image, uint32_t size, uint32_t offset, + struct ffs_handle **ffs) +{ +} +#endif + +void ffs_close(struct ffs_handle *ffs) +{ + if (ffs->cache) + free(ffs->cache); + free(ffs); +} + +static struct ffs_entry *ffs_get_part(struct ffs_handle *ffs, uint32_t index, + uint32_t *out_offset) +{ + uint32_t esize = ffs->hdr.entry_size; + uint32_t offset = FFS_HDR_SIZE + index * esize; + + if (index > ffs->hdr.entry_count) + return NULL; + if (out_offset) + *out_offset = offset; + return (struct ffs_entry *)(ffs->cache + offset); +} + +static int ffs_check_convert_entry(struct ffs_entry *dst, struct ffs_entry *src) +{ + if (ffs_checksum(src, FFS_ENTRY_SIZE) != 0) + return FFS_ERR_BAD_CKSUM; + memcpy(dst->name, src->name, sizeof(dst->name)); + dst->base = be32_to_cpu(src->base); + dst->size = be32_to_cpu(src->size); + dst->pid = be32_to_cpu(src->pid); + dst->id = be32_to_cpu(src->id); + dst->type = be32_to_cpu(src->type); + dst->flags = be32_to_cpu(src->flags); + dst->actual = be32_to_cpu(src->actual); + + return 0; +} + +int ffs_lookup_part(struct ffs_handle *ffs, const char *name, + uint32_t *part_idx) +{ + struct ffs_entry ent; + uint32_t i; + int rc; + + /* Lookup the requested partition */ + for (i = 0; i < ffs->hdr.entry_count; i++) { + struct ffs_entry *src_ent = ffs_get_part(ffs, i, NULL); + rc = ffs_check_convert_entry(&ent, src_ent); + if (rc) { + FL_ERR("FFS: Bad entry %d in partition map\n", i); + continue; + } + if (!strncmp(name, ent.name, sizeof(ent.name))) + break; + } + if (i >= ffs->hdr.entry_count) + return FFS_ERR_PART_NOT_FOUND; + if (part_idx) + *part_idx = i; + return 0; +} + +int ffs_part_info(struct ffs_handle *ffs, uint32_t part_idx, + char **name, uint32_t *start, + uint32_t *total_size, uint32_t *act_size) +{ + struct ffs_entry *raw_ent; + struct ffs_entry ent; + char *n; + int rc; + + if (part_idx >= ffs->hdr.entry_count) + return FFS_ERR_PART_NOT_FOUND; + + raw_ent = ffs_get_part(ffs, part_idx, NULL); + if (!raw_ent) + return FFS_ERR_PART_NOT_FOUND; + + rc = ffs_check_convert_entry(&ent, raw_ent); + if (rc) { + FL_ERR("FFS: Bad entry %d in partition map\n", part_idx); + return rc; + } + if (start) + *start = ent.base * ffs->hdr.block_size; + if (total_size) + *total_size = ent.size * ffs->hdr.block_size; + if (act_size) + *act_size = ent.actual; + if (name) { + n = malloc(PART_NAME_MAX + 1); + memset(n, 0, PART_NAME_MAX + 1); + strncpy(n, ent.name, PART_NAME_MAX); + *name = n; + } + return 0; +} + +int ffs_update_act_size(struct ffs_handle *ffs, uint32_t part_idx, + uint32_t act_size) +{ + struct ffs_entry *ent; + uint32_t offset; + + if (part_idx >= ffs->hdr.entry_count) { + FL_DBG("FFS: Entry out of bound\n"); + return FFS_ERR_PART_NOT_FOUND; + } + + ent = ffs_get_part(ffs, part_idx, &offset); + if (!ent) { + FL_DBG("FFS: Entry not found\n"); + return FFS_ERR_PART_NOT_FOUND; + } + FL_DBG("FFS: part index %d at offset 0x%08x\n", + part_idx, offset); + + if (ent->actual == cpu_to_be32(act_size)) { + FL_DBG("FFS: ent->actual alrady matches: 0x%08x==0x%08x\n", + cpu_to_be32(act_size), ent->actual); + return 0; + } + ent->actual = cpu_to_be32(act_size); + ent->checksum = ffs_checksum(ent, FFS_ENTRY_SIZE_CSUM); + if (!ffs->chip) + return 0; + return flash_smart_write(ffs->chip, offset, ent, FFS_ENTRY_SIZE); +} diff --git a/libflash/libffs.h b/libflash/libffs.h new file mode 100644 index 0000000..5a3ff40 --- /dev/null +++ b/libflash/libffs.h @@ -0,0 +1,56 @@ +/* Copyright 2013-2014 IBM Corp. + * + * 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. + */ +#ifndef __LIBFFS_H +#define __LIBFFS_H + +#include +#include + +/* FFS handle, opaque */ +struct ffs_handle; + +/* Error codes: + * + * < 0 = flash controller errors + * 0 = success + * > 0 = libffs / libflash errors + */ +#define FFS_ERR_BAD_MAGIC 100 +#define FFS_ERR_BAD_VERSION 101 +#define FFS_ERR_BAD_CKSUM 102 +#define FFS_ERR_PART_NOT_FOUND 103 + +int ffs_open_flash(struct flash_chip *chip, uint32_t offset, + uint32_t max_size, struct ffs_handle **ffs); + +/* TODO +int ffs_open_image(void *image, uint32_t size, struct ffs_handle **ffs); +*/ + +void ffs_close(struct ffs_handle *ffs); + +int ffs_lookup_part(struct ffs_handle *ffs, const char *name, + uint32_t *part_idx); + +int ffs_part_info(struct ffs_handle *ffs, uint32_t part_idx, + char **name, uint32_t *start, + uint32_t *total_size, uint32_t *act_size); + +int ffs_update_act_size(struct ffs_handle *ffs, uint32_t part_idx, + uint32_t act_size); + + +#endif /* __LIBFFS_H */ diff --git a/libflash/libflash-priv.h b/libflash/libflash-priv.h new file mode 100644 index 0000000..44fa513 --- /dev/null +++ b/libflash/libflash-priv.h @@ -0,0 +1,213 @@ +/* Copyright 2013-2014 IBM Corp. + * + * 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. + */ +#ifndef __LIBFLASH_PRIV_H +#define __LIBFLASH_PRIV_H + +#include +#include +#include + +/* Flash commands */ +#define CMD_PP 0x02 +#define CMD_READ 0x03 +#define CMD_WRDI 0x04 +#define CMD_RDSR 0x05 +#define CMD_WREN 0x06 +#define CMD_SE 0x20 +#define CMD_RDSCUR 0x2b +#define CMD_BE32K 0x52 +#define CMD_CE 0x60 +#define CMD_RDID 0x9f +#define CMD_EN4B 0xb7 +#define CMD_BE 0xd8 +#define CMD_RDDPB 0xe0 +#define CMD_RDSPB 0xe2 +#define CMD_EX4B 0xe9 + +/* Flash status bits */ +#define STAT_WIP 0x01 +#define STAT_WEN 0x02 + +/* This isn't exposed to clients but is to controllers */ +struct flash_info { + uint32_t id; + uint32_t size; + uint32_t flags; +#define FL_ERASE_4K 0x00000001 /* Supports 4k erase */ +#define FL_ERASE_32K 0x00000002 /* Supports 32k erase */ +#define FL_ERASE_64K 0x00000004 /* Supports 64k erase */ +#define FL_ERASE_CHIP 0x00000008 /* Supports 64k erase */ +#define FL_ERASE_ALL (FL_ERASE_4K | FL_ERASE_32K | FL_ERASE_64K | \ + FL_ERASE_CHIP) +#define FL_CAN_4B 0x00000010 /* Supports 4b mode */ + const char *name; +}; + +/* Flash controller, return negative values for errors */ +struct spi_flash_ctrl { + /* + * The controller can provide basically two interfaces, + * either a fairly high level one and a lower level one. + * + * If all functions of the high level interface are + * implemented then the low level one is optional. A + * controller can implement some of the high level one + * in which case the missing ones will be handled by + * libflash using the low level interface. + * + * There are also some common functions. + */ + + /* ************************************************** + * Misc / common functions + * **************************************************/ + + /* + * - setup(ctrl, info, tsize) + * + * Provides the controller with an option to configure itself + * based on the specific flash type. It can also override some + * settings in the info block such as available erase sizes etc... + * which can be needed for high level controllers. It can also + * override the total flash size. + */ + int (*setup)(struct spi_flash_ctrl *ctrl, struct flash_info *info, + uint32_t *tsize); + + /* + * - set_4b(ctrl, enable) + * + * enable : Switch to 4bytes (true) or 3bytes (false) address mode + * + * Set the controller's address size. If the controller doesn't + * implement the low level command interface, then this must also + * configure the flash chip itself. Otherwise, libflash will do it. + * + * Note that if this isn't implemented, then libflash might still + * try to switch large flash chips to 4b mode if the low level cmd + * interface is implemented. It will then also stop using the high + * level command interface since it's assumed that it cannot handle + * 4b addresses. + */ + int (*set_4b)(struct spi_flash_ctrl *ctrl, bool enable); + + + + /* ************************************************** + * High level interface + * **************************************************/ + + /* + * Read chip ID. This can return up to 16 bytes though the + * current libflash will only use 3 (room for things like + * extended micron stuff). + * + * id_size is set on entry to the buffer size and need to + * be adjusted to the actual ID size read. + * + * If NULL, libflash will use cmd_rd to send normal RDID (0x9f) + * command. + */ + int (*chip_id)(struct spi_flash_ctrl *ctrl, uint8_t *id_buf, + uint32_t *id_size); + + /* + * Read from flash. There is no specific constraint on + * alignment or size other than not reading outside of + * the chip. + * + * If NULL, libflash will use cmd_rd to send normal + * READ (0x03) commands. + */ + int (*read)(struct spi_flash_ctrl *ctrl, uint32_t addr, void *buf, + uint32_t size); + + /* + * Write to flash. There is no specific constraint on + * alignment or size other than not reading outside of + * the chip. The driver is responsible for handling + * 256-bytes page alignment and to send the write enable + * commands when needed. + * + * If absent, libflash will use cmd_wr to send WREN (0x06) + * and PP (0x02) commands. + * + * Note: This does not need to handle erasing. libflash + * will ensure that this is never used for changing a bit + * value from 0 to 1. + */ + int (*write)(struct spi_flash_ctrl *ctrl, uint32_t addr, + const void *buf, uint32_t size); + + /* + * Erase. This will be called for erasing a portion of + * the flash using a granularity (alignment of start and + * size) that is no less than the smallest supported + * erase size in the info block (*). The driver is + * responsible to send write enable commands when needed. + * + * If absent, libflash will use cmd_wr to send WREN (0x06) + * and either of SE (0x20), BE32K (0x52) or BE (0xd8) + * based on what the flash chip supports. + * + * (*) Note: This is called with addr=0 and size=0xffffffff + * in which case this is used as a "chip erase". Return + * FLASH_ERR_CHIP_ER_NOT_SUPPORTED if not supported. Some + * future version of libflash might then emulate it using + * normal erase commands. + */ + int (*erase)(struct spi_flash_ctrl *ctrl, uint32_t addr, + uint32_t size); + + /* ************************************************** + * Low level interface + * **************************************************/ + + /* Note: For commands with no data, libflash will might use + * either cmd_rd or cmd_wr. + */ + + /* + * - cmd_rd(ctrl, cmd, has_addr, address, buffer, size); + * + * cmd : command opcode + * has_addr : send an address after the command + * address : address to send + * buffer : buffer for additional data to read (or NULL) + * size : size of additional data read (or NULL) + * + * Sends a command and optionally read additional data + */ + int (*cmd_rd)(struct spi_flash_ctrl *ctrl, uint8_t cmd, + bool has_addr, uint32_t addr, void *buffer, + uint32_t size); + /* + * - cmd_wr(ctrl, cmd, has_addr, address, buffer, size); + * + * cmd : command opcode + * has_addr : send an address after the command + * address : address to send + * buffer : buffer for additional data to write (or NULL) + * size : size of additional data write (or NULL) + * + * Sends a command and optionally write additional data + */ + int (*cmd_wr)(struct spi_flash_ctrl *ctrl, uint8_t cmd, + bool has_addr, uint32_t addr, const void *buffer, + uint32_t size); +}; + +#endif /* LIBFLASH_PRIV_H */ diff --git a/libflash/libflash.c b/libflash/libflash.c new file mode 100644 index 0000000..a3e6ff2 --- /dev/null +++ b/libflash/libflash.c @@ -0,0 +1,636 @@ +/* Copyright 2013-2014 IBM Corp. + * + * 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. + */ +#include +#include +#include + +#include "libflash.h" +#include "libflash-priv.h" + +static const struct flash_info flash_info[] = { + { 0xc22019, 0x02000000, FL_ERASE_ALL | FL_CAN_4B, "MXxxL25635F"}, + { 0xc2201a, 0x04000000, FL_ERASE_ALL | FL_CAN_4B, "MXxxL51235F"}, + { 0xef4018, 0x01000000, FL_ERASE_ALL, "W25Q128BV" }, + { 0x55aa55, 0x00100000, FL_ERASE_ALL | FL_CAN_4B, "TEST_FLASH"}, +}; + +struct flash_chip { + struct spi_flash_ctrl *ctrl; /* Controller */ + struct flash_info info; /* Flash info */ + uint32_t tsize; /* Corrected flash size */ + uint32_t min_erase_mask; /* Minimum erase size */ + bool mode_4b; /* Flash currently in 4b mode */ + struct flash_req *cur_req; /* Current request */ + void *smart_buf; /* Buffer for smart writes */ +}; + +static int fl_read_stat(struct flash_chip *c, uint8_t *stat) +{ + struct spi_flash_ctrl *ct = c->ctrl; + + return ct->cmd_rd(ct, CMD_RDSR, false, 0, stat, 1); +} + +static int fl_wren(struct flash_chip *c) +{ + struct spi_flash_ctrl *ct = c->ctrl; + uint8_t stat; + int i, rc; + + /* Some flashes need it to be hammered */ + for (i = 0; i < 10; i++) { + rc = ct->cmd_wr(ct, CMD_WREN, false, 0, NULL, 0); + if (rc) return rc; + rc = fl_read_stat(c, &stat); + if (rc) return rc; + if (stat & STAT_WEN) + return 0; + } + return FLASH_ERR_WREN_TIMEOUT; +} + +/* Synchronous write completion, probably need a yield hook */ +static int fl_sync_wait_idle(struct flash_chip *c) +{ + int rc; + uint8_t stat; + + /* XXX Add timeout */ + for (;;) { + rc = fl_read_stat(c, &stat); + if (rc) return rc; + if (!(stat & STAT_WIP)) + return 0; + } + /* return FLASH_ERR_WIP_TIMEOUT; */ +} + +int flash_read(struct flash_chip *c, uint32_t pos, void *buf, uint32_t len) +{ + struct spi_flash_ctrl *ct = c->ctrl; + + /* XXX Add sanity/bound checking */ + + /* + * If the controller supports read and either we are in 3b mode + * or we are in 4b *and* the controller supports it, then do a + * high level read. + */ + if ((!c->mode_4b || ct->set_4b) && ct->read) + return ct->read(ct, pos, buf, len); + + /* Otherwise, go manual if supported */ + if (!ct->cmd_rd) + return FLASH_ERR_CTRL_CMD_UNSUPPORTED; + return ct->cmd_rd(ct, CMD_READ, true, pos, buf, len); +} + +static void fl_get_best_erase(struct flash_chip *c, uint32_t dst, uint32_t size, + uint32_t *chunk, uint8_t *cmd) +{ + /* Smaller than 32k, use 4k */ + if ((dst & 0x7fff) || (size < 0x8000)) { + *chunk = 0x1000; + *cmd = CMD_SE; + return; + } + /* Smaller than 64k and 32k is supported, use it */ + if ((c->info.flags & FL_ERASE_32K) && + ((dst & 0xffff) || (size < 0x10000))) { + *chunk = 0x8000; + *cmd = CMD_BE32K; + return; + } + /* If 64K is not supported, use whatever smaller size is */ + if (!(c->info.flags & FL_ERASE_64K)) { + if (c->info.flags & FL_ERASE_32K) { + *chunk = 0x8000; + *cmd = CMD_BE32K; + } else { + *chunk = 0x1000; + *cmd = CMD_SE; + } + return; + } + /* Allright, let's go for 64K */ + *chunk = 0x10000; + *cmd = CMD_BE; +} + +int flash_erase(struct flash_chip *c, uint32_t dst, uint32_t size) +{ + struct spi_flash_ctrl *ct = c->ctrl; + uint32_t chunk; + uint8_t cmd; + int rc; + + /* Some sanity checking */ + if (((dst + size) <= dst) || !size || (dst + size) > c->tsize) + return FLASH_ERR_PARM_ERROR; + + /* Check boundaries fit erase blocks */ + if ((dst | size) & c->min_erase_mask) + return FLASH_ERR_ERASE_BOUNDARY; + + FL_DBG("LIBFLASH: Erasing 0x%08x..0%08x...\n", dst, dst + size); + + /* Use controller erase if supported */ + if (ct->erase) + return ct->erase(ct, dst, size); + + /* Allright, loop as long as there's something to erase */ + while(size) { + /* How big can we make it based on alignent & size */ + fl_get_best_erase(c, dst, size, &chunk, &cmd); + + /* Poke write enable */ + rc = fl_wren(c); + if (rc) + return rc; + + /* Send erase command */ + rc = ct->cmd_wr(ct, cmd, true, dst, NULL, 0); + if (rc) + return rc; + + /* Wait for write complete */ + rc = fl_sync_wait_idle(c); + if (rc) + return rc; + + size -= chunk; + dst += chunk; + } + return 0; +} + +int flash_erase_chip(struct flash_chip *c) +{ + struct spi_flash_ctrl *ct = c->ctrl; + int rc; + + /* XXX TODO: Fallback to using normal erases */ + if (!(c->info.flags & FL_ERASE_CHIP)) + return FLASH_ERR_CHIP_ER_NOT_SUPPORTED; + + FL_DBG("LIBFLASH: Erasing chip...\n"); + + /* Use controller erase if supported */ + if (ct->erase) + return ct->erase(ct, 0, 0xffffffff); + + rc = fl_wren(c); + if (rc) return rc; + + rc = ct->cmd_wr(ct, CMD_CE, false, 0, NULL, 0); + if (rc) + return rc; + + /* Wait for write complete */ + return fl_sync_wait_idle(c); +} + +static int fl_wpage(struct flash_chip *c, uint32_t dst, const void *src, + uint32_t size) +{ + struct spi_flash_ctrl *ct = c->ctrl; + int rc; + + if (size < 1 || size > 0x100) + return FLASH_ERR_BAD_PAGE_SIZE; + + rc = fl_wren(c); + if (rc) return rc; + + rc = ct->cmd_wr(ct, CMD_PP, true, dst, src, size); + if (rc) + return rc; + + /* Wait for write complete */ + return fl_sync_wait_idle(c); +} + +int flash_write(struct flash_chip *c, uint32_t dst, const void *src, + uint32_t size, bool verify) +{ + struct spi_flash_ctrl *ct = c->ctrl; + uint32_t todo = size; + uint32_t d = dst; + const void *s = src; + uint8_t vbuf[0x100]; + int rc; + + /* Some sanity checking */ + if (((dst + size) <= dst) || !size || (dst + size) > c->tsize) + return FLASH_ERR_PARM_ERROR; + + FL_DBG("LIBFLASH: Writing to 0x%08x..0%08x...\n", dst, dst + size); + + /* + * If the controller supports write and either we are in 3b mode + * or we are in 4b *and* the controller supports it, then do a + * high level write. + */ + if ((!c->mode_4b || ct->set_4b) && ct->write) { + rc = ct->write(ct, dst, src, size); + if (rc) + return rc; + goto writing_done; + } + + /* Otherwise, go manual if supported */ + if (!ct->cmd_wr) + return FLASH_ERR_CTRL_CMD_UNSUPPORTED; + + /* Iterate for each page to write */ + while(todo) { + uint32_t chunk; + + /* Handle misaligned start */ + chunk = 0x100 - (d & 0xff); + if (chunk > 0x100) + chunk = 0x100; + if (chunk > todo) + chunk = todo; + + rc = fl_wpage(c, d, s, chunk); + if (rc) return rc; + d += chunk; + s += chunk; + todo -= chunk; + } + + writing_done: + if (!verify) + return 0; + + /* Verify */ + FL_DBG("LIBFLASH: Verifying...\n"); + + while(size) { + uint32_t chunk; + + chunk = sizeof(vbuf); + if (chunk > size) + chunk = size; + rc = flash_read(c, dst, vbuf, chunk); + if (rc) return rc; + if (memcmp(vbuf, src, chunk)) { + FL_ERR("LIBFLASH: Miscompare at 0x%08x\n", dst); + return FLASH_ERR_VERIFY_FAILURE; + } + dst += chunk; + src += chunk; + size -= chunk; + } + return 0; +} + +enum sm_comp_res { + sm_no_change, + sm_need_write, + sm_need_erase, +}; + +static enum sm_comp_res flash_smart_comp(struct flash_chip *c, + const void *src, + uint32_t offset, uint32_t size) +{ + uint8_t *b = c->smart_buf + offset; + const uint8_t *s = src; + bool is_same = true; + uint32_t i; + + /* SRC DEST NEED_ERASE + * 0 1 0 + * 1 1 0 + * 0 0 0 + * 1 0 1 + */ + for (i = 0; i < size; i++) { + /* Any bit need to be set, need erase */ + if (s[i] & ~b[i]) + return sm_need_erase; + if (is_same && (b[i] != s[i])) + is_same = false; + } + return is_same ? sm_no_change : sm_need_write; +} + +int flash_smart_write(struct flash_chip *c, uint32_t dst, const void *src, + uint32_t size) +{ + uint32_t er_size = c->min_erase_mask + 1; + uint32_t end = dst + size; + int rc; + + /* Some sanity checking */ + if (end <= dst || !size || end > c->tsize) { + FL_DBG("LIBFLASH: Smart write param error\n"); + return FLASH_ERR_PARM_ERROR; + } + + FL_DBG("LIBFLASH: Smart writing to 0x%08x..0%08x...\n", + dst, dst + size); + + /* As long as we have something to write ... */ + while(dst < end) { + uint32_t page, off, chunk; + enum sm_comp_res sr; + + /* Figure out which erase page we are in and read it */ + page = dst & ~c->min_erase_mask; + off = dst & c->min_erase_mask; + FL_DBG("LIBFLASH: reading page 0x%08x..0x%08x...", + page, page + er_size); + rc = flash_read(c, page, c->smart_buf, er_size); + if (rc) { + FL_DBG(" error %d!\n", rc); + return rc; + } + + /* Locate the chunk of data we are working on */ + chunk = er_size - off; + if (size < chunk) + chunk = size; + + /* Compare against what we are writing and ff */ + sr = flash_smart_comp(c, src, off, chunk); + switch(sr) { + case sm_no_change: + /* Identical, skip it */ + FL_DBG(" same !\n"); + break; + case sm_need_write: + /* Just needs writing over */ + FL_DBG(" need write !\n"); + rc = flash_write(c, dst, src, chunk, true); + if (rc) { + FL_DBG("LIBFLASH: Write error %d !\n", rc); + return rc; + } + break; + case sm_need_erase: + FL_DBG(" need erase !\n"); + rc = flash_erase(c, page, er_size); + if (rc) { + FL_DBG("LIBFLASH: erase error %d !\n", rc); + return rc; + } + /* Then update the portion of the buffer and write the block */ + memcpy(c->smart_buf + off, src, chunk); + rc = flash_write(c, page, c->smart_buf, er_size, true); + if (rc) { + FL_DBG("LIBFLASH: write error %d !\n", rc); + return rc; + } + break; + } + dst += chunk; + src += chunk; + size -= chunk; + } + return 0; +} + + +static int flash_identify(struct flash_chip *c) +{ + struct spi_flash_ctrl *ct = c->ctrl; + const struct flash_info *info = NULL; + uint32_t iid, id_size; +#define MAX_ID_SIZE 16 + uint8_t id[MAX_ID_SIZE]; + int rc, i; + + if (ct->chip_id) { + /* High level controller interface */ + id_size = MAX_ID_SIZE; + rc = ct->chip_id(ct, id, &id_size); + if (rc) + return rc; + } else { + /* Fallback to get ID manually */ + rc = ct->cmd_rd(ct, CMD_RDID, false, 0, id, 3); + if (rc) + return rc; + id_size = 3; + } + if (id_size < 3) + return FLASH_ERR_CHIP_UNKNOWN; + + /* Convert to a dword for lookup */ + iid = id[0]; + iid = (iid << 8) | id[1]; + iid = (iid << 8) | id[2]; + + FL_DBG("LIBFLASH: Flash ID: %02x.%02x.%02x (%06x)\n", + id[0], id[1], id[2], iid); + + /* Lookup in flash_info */ + for (i = 0; i < ARRAY_SIZE(flash_info); i++) { + info = &flash_info[i]; + if (info->id == iid) + break; + } + if (info->id != iid) + return FLASH_ERR_CHIP_UNKNOWN; + + c->info = *info; + c->tsize = info->size; + + /* + * Let controller know about our settings and possibly + * override them + */ + if (ct->setup) { + rc = ct->setup(ct, &c->info, &c->tsize); + if (rc) + return rc; + } + + /* Calculate min erase granularity */ + if (c->info.flags & FL_ERASE_4K) + c->min_erase_mask = 0xfff; + else if (c->info.flags & FL_ERASE_32K) + c->min_erase_mask = 0x7fff; + else if (c->info.flags & FL_ERASE_64K) + c->min_erase_mask = 0xffff; + else { + /* No erase size ? oops ... */ + FL_ERR("LIBFLASH: No erase sizes !\n"); + return FLASH_ERR_CTRL_CONFIG_MISMATCH; + } + + FL_DBG("LIBFLASH: Found chip %s size %dM erase granule: %dK\n", + c->info.name, c->tsize >> 20, (c->min_erase_mask + 1) >> 10); + + return 0; +} + +static int flash_set_4b(struct flash_chip *c, bool enable) +{ + struct spi_flash_ctrl *ct = c->ctrl; + + return ct->cmd_wr(ct, enable ? CMD_EN4B : CMD_EX4B, false, 0, NULL, 0); +} + +int flash_force_4b_mode(struct flash_chip *c, bool enable_4b) +{ + struct spi_flash_ctrl *ct = c->ctrl; + int rc; + + /* + * We only allow force 4b if both controller and flash do 4b + * as this is mainly used if a 3rd party tries to directly + * access a direct mapped read region + */ + if (enable_4b && !((c->info.flags & FL_CAN_4B) && ct->set_4b)) + return FLASH_ERR_4B_NOT_SUPPORTED; + + /* Only send to flash directly on controllers that implement + * the low level callbacks + */ + if (ct->cmd_wr) { + rc = flash_set_4b(c, enable_4b); + if (rc) + return rc; + } + + /* Then inform the controller */ + if (ct->set_4b) + rc = ct->set_4b(ct, enable_4b); + return rc; +} + +static int flash_configure(struct flash_chip *c) +{ + struct spi_flash_ctrl *ct = c->ctrl; + int rc; + + /* Crop flash size if necessary */ + if (c->tsize > 0x01000000 && !(c->info.flags & FL_CAN_4B)) { + FL_ERR("LIBFLASH: Flash chip cropped to 16M, no 4b mode\n"); + c->tsize = 0x01000000; + } + + /* If flash chip > 16M, enable 4b mode */ + if (c->tsize > 0x01000000) { + FL_DBG("LIBFLASH: Flash >16MB, enabling 4B mode...\n"); + + /* Set flash to 4b mode if we can */ + if (ct->cmd_wr) { + rc = flash_set_4b(c, true); + if (rc) { + FL_ERR("LIBFLASH: Failed to set flash 4b mode\n"); + return rc; + } + } + + + /* Set controller to 4b mode if supported */ + if (ct->set_4b) { + FL_DBG("LIBFLASH: Enabling controller 4B mode...\n"); + rc = ct->set_4b(ct, true); + if (rc) { + FL_ERR("LIBFLASH: Failed" + " to set controller 4b mode\n"); + return rc; + } + } + } else { + FL_DBG("LIBFLASH: Flash <=16MB, disabling 4B mode...\n"); + + /* + * If flash chip supports 4b mode, make sure we disable + * it in case it was left over by the previous user + */ + if (c->info.flags & FL_CAN_4B) { + rc = flash_set_4b(c, false); + if (rc) { + FL_ERR("LIBFLASH: Failed to" + " clear flash 4b mode\n"); + return rc; + } + } + + /* Set controller to 3b mode if mode switch is supported */ + if (ct->set_4b) { + FL_DBG("LIBFLASH: Disabling controller 4B mode...\n"); + rc = ct->set_4b(ct, false); + if (rc) { + FL_ERR("LIBFLASH: Failed to" + " clear controller 4b mode\n"); + return rc; + } + } + } + return 0; +} + +int flash_get_info(struct flash_chip *chip, const char **name, + uint32_t *total_size, uint32_t *erase_granule) +{ + if (name) + *name = chip->info.name; + if (total_size) + *total_size = chip->tsize; + if (erase_granule) + *erase_granule = chip->min_erase_mask + 1; + return 0; +} + +int flash_init(struct spi_flash_ctrl *ctrl, struct flash_chip **flash) +{ + struct flash_chip *c; + int rc; + + *flash = NULL; + c = malloc(sizeof(struct flash_chip)); + if (!c) + return FLASH_ERR_MALLOC_FAILED; + memset(c, 0, sizeof(*c)); + c->ctrl = ctrl; + + rc = flash_identify(c); + if (rc) { + FL_ERR("LIBFLASH: Flash identification failed\n"); + goto bail; + } + c->smart_buf = malloc(c->min_erase_mask + 1); + if (!c->smart_buf) { + FL_ERR("LIBFLASH: Failed to allocate smart buffer !\n"); + rc = FLASH_ERR_MALLOC_FAILED; + goto bail; + } + rc = flash_configure(c); + if (rc) + FL_ERR("LIBFLASH: Flash configuration failed\n"); + bail: + if (rc) { + free(c); + return rc; + } + *flash = c; + return 0; +} + +void flash_exit(struct flash_chip *chip) +{ + /* XXX Make sure we are idle etc... */ + free(chip); +} + diff --git a/libflash/libflash.h b/libflash/libflash.h new file mode 100644 index 0000000..e8d357b --- /dev/null +++ b/libflash/libflash.h @@ -0,0 +1,83 @@ +/* Copyright 2013-2014 IBM Corp. + * + * 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. + */ +#ifndef __LIBFLASH_H +#define __LIBFLASH_H + +#include +#include + +#ifndef FL_INF +#define FL_INF(fmt...) do { printf(fmt); } while(0) +#endif + +#ifndef FL_DBG +//#define FL_DBG(fmt...) do { printf(fmt); } while(0) +#define FL_DBG(fmt...) do { } while(0) +#endif + +#ifndef FL_ERR +#define FL_ERR(fmt...) do { printf(fmt); } while(0) +#endif + +/* API status/return: + * + * <0 = flash controller errors passed through, + * 0 = success + * >0 = libflash error + */ + +#define FLASH_ERR_MALLOC_FAILED 1 +#define FLASH_ERR_CHIP_UNKNOWN 2 +#define FLASH_ERR_PARM_ERROR 3 +#define FLASH_ERR_ERASE_BOUNDARY 4 +#define FLASH_ERR_WREN_TIMEOUT 5 +#define FLASH_ERR_WIP_TIMEOUT 6 +#define FLASH_ERR_BAD_PAGE_SIZE 7 +#define FLASH_ERR_VERIFY_FAILURE 8 +#define FLASH_ERR_4B_NOT_SUPPORTED 9 +#define FLASH_ERR_CTRL_CONFIG_MISMATCH 10 +#define FLASH_ERR_CHIP_ER_NOT_SUPPORTED 11 +#define FLASH_ERR_CTRL_CMD_UNSUPPORTED 12 +#define FLASH_ERR_CTRL_TIMEOUT 13 + +/* Flash chip, opaque */ +struct flash_chip; +struct spi_flash_ctrl; + +int flash_init(struct spi_flash_ctrl *ctrl, struct flash_chip **flash); +void flash_exit(struct flash_chip *chip); + +int flash_get_info(struct flash_chip *chip, const char **name, + uint32_t *total_size, uint32_t *erase_granule); + +/* libflash sets the 4b mode automatically based on the flash + * size and controller capabilities but it can be overriden + */ +int flash_force_4b_mode(struct flash_chip *chip, bool enable_4b); + +int flash_read(struct flash_chip *c, uint32_t pos, void *buf, uint32_t len); +int flash_erase(struct flash_chip *c, uint32_t dst, uint32_t size); +int flash_write(struct flash_chip *c, uint32_t dst, const void *src, + uint32_t size, bool verify); +int flash_smart_write(struct flash_chip *c, uint32_t dst, const void *src, + uint32_t size); + +/* chip erase may not be supported by all chips/controllers, get ready + * for FLASH_ERR_CHIP_ER_NOT_SUPPORTED + */ +int flash_erase_chip(struct flash_chip *c); + +#endif /* __LIBFLASH_H */ diff --git a/libflash/test/Makefile.check b/libflash/test/Makefile.check new file mode 100644 index 0000000..f9f1ca8 --- /dev/null +++ b/libflash/test/Makefile.check @@ -0,0 +1,20 @@ +# -*-Makefile-*- +LIBFLASH_TEST := libflash/test/test-flash + +check: $(LIBFLASH_TEST:%=%-check) + +$(LIBFLASH_TEST:%=%-check) : %-check: % + $(VALGRIND) $< + +libflash/test/stubs.o: libflash/test/stubs.c + $(HOSTCC) $(HOSTCFLAGS) -g -c -o $@ $< + +$(LIBFLASH_TEST) : libflash/test/stubs.o libflash/libflash.c + +$(LIBFLASH_TEST) : % : %.c + $(HOSTCC) $(HOSTCFLAGS) -O0 -g -I include -I . -o $@ $< libflash/test/stubs.o + +clean: libflash-test-clean + +libflash-test-clean: + $(RM) libflash/test/*.o $(LIBFLASH_TEST) diff --git a/libflash/test/stubs.c b/libflash/test/stubs.c new file mode 100644 index 0000000..aabf018 --- /dev/null +++ b/libflash/test/stubs.c @@ -0,0 +1,16 @@ +/* Copyright 2013-2014 IBM Corp. + * + * 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. + */ +/* Stubs for libflash test */ diff --git a/libflash/test/test-flash.c b/libflash/test/test-flash.c new file mode 100644 index 0000000..5f48797 --- /dev/null +++ b/libflash/test/test-flash.c @@ -0,0 +1,418 @@ +/* Copyright 2013-2014 IBM Corp. + * + * 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. + */ +#include +#include +#include +#include + +#include +#include + +#include "../libflash.c" + +#define __unused __attribute__((unused)) + +#define ERR(fmt...) fprintf(stderr, fmt) + +/* Flash commands */ +#define CMD_PP 0x02 +#define CMD_READ 0x03 +#define CMD_WRDI 0x04 +#define CMD_RDSR 0x05 +#define CMD_WREN 0x06 +#define CMD_SE 0x20 +#define CMD_RDSCUR 0x2b +#define CMD_BE32K 0x52 +#define CMD_CE 0x60 +#define CMD_RDID 0x9f +#define CMD_EN4B 0xb7 +#define CMD_BE 0xd8 +#define CMD_RDDPB 0xe0 +#define CMD_RDSPB 0xe2 +#define CMD_EX4B 0xe9 + +/* Flash status bits */ +#define STAT_WIP 0x01 +#define STAT_WEN 0x02 + +static uint8_t *sim_image; +static uint32_t sim_image_sz = 0x100000; +static uint32_t sim_index; +static uint32_t sim_addr; +static uint32_t sim_er_size; +static uint8_t sim_sr; +static bool sim_fl_4b; +static bool sim_ct_4b; + +static enum sim_state { + sim_state_idle, + sim_state_rdid, + sim_state_rdsr, + sim_state_read_addr, + sim_state_read_data, + sim_state_write_addr, + sim_state_write_data, + sim_state_erase_addr, + sim_state_erase_done, +} sim_state; + +/* + * Simulated flash & controller + */ +static int sim_start_cmd(uint8_t cmd) +{ + if (sim_state != sim_state_idle) { + ERR("SIM: Command %02x in wrong state %d\n", cmd, sim_state); + return -1; + } + + sim_index = 0; + sim_addr = 0; + + switch(cmd) { + case CMD_RDID: + sim_state = sim_state_rdid; + break; + case CMD_RDSR: + sim_state = sim_state_rdsr; + break; + case CMD_EX4B: + sim_fl_4b = false; + break; + case CMD_EN4B: + sim_fl_4b = true; + break; + case CMD_WREN: + sim_sr |= STAT_WEN; + break; + case CMD_READ: + sim_state = sim_state_read_addr; + if (sim_ct_4b != sim_fl_4b) + ERR("SIM: 4b mode mismatch in READ !\n"); + break; + case CMD_PP: + sim_state = sim_state_write_addr; + if (sim_ct_4b != sim_fl_4b) + ERR("SIM: 4b mode mismatch in PP !\n"); + if (!(sim_sr & STAT_WEN)) + ERR("SIM: PP without WEN, ignoring... \n"); + break; + case CMD_SE: + case CMD_BE32K: + case CMD_BE: + if (sim_ct_4b != sim_fl_4b) + ERR("SIM: 4b mode mismatch in SE/BE !\n"); + if (!(sim_sr & STAT_WEN)) + ERR("SIM: SE/BE without WEN, ignoring... \n"); + sim_state = sim_state_erase_addr; + switch(cmd) { + case CMD_SE: sim_er_size = 0x1000; break; + case CMD_BE32K: sim_er_size = 0x8000; break; + case CMD_BE: sim_er_size = 0x10000; break; + } + break; + case CMD_CE: + if (!(sim_sr & STAT_WEN)) { + ERR("SIM: CE without WEN, ignoring... \n"); + break; + } + memset(sim_image, 0xff, sim_image_sz); + sim_sr |= STAT_WIP; + sim_sr &= ~STAT_WEN; + break; + default: + ERR("SIM: Unsupported command %02x\n", cmd); + return -1; + } + return 0; +} + +static void sim_end_cmd(void) +{ + /* For write and sector/block erase, set WIP & clear WEN here */ + if (sim_state == sim_state_write_data) { + sim_sr |= STAT_WIP; + sim_sr &= ~STAT_WEN; + } + sim_state = sim_state_idle; +} + +static bool sim_do_address(const uint8_t **buf, uint32_t *len) +{ + uint8_t asize = sim_fl_4b ? 4 : 3; + const uint8_t *p = *buf; + + while(*len) { + sim_addr = (sim_addr << 8) | *(p++); + *buf = p; + *len = *len - 1; + sim_index++; + if (sim_index >= asize) + return true; + } + return false; +} + +static int sim_wbytes(const void *buf, uint32_t len) +{ + const uint8_t *b = buf; + bool addr_complete; + + again: + switch(sim_state) { + case sim_state_read_addr: + addr_complete = sim_do_address(&b, &len); + if (addr_complete) { + sim_state = sim_state_read_data; + sim_index = 0; + if (len) + goto again; + } + break; + case sim_state_write_addr: + addr_complete = sim_do_address(&b, &len); + if (addr_complete) { + sim_state = sim_state_write_data; + sim_index = 0; + if (len) + goto again; + } + break; + case sim_state_write_data: + if (!(sim_sr & STAT_WEN)) + break; + while(len--) { + uint8_t c = *(b++); + if (sim_addr >= sim_image_sz) { + ERR("SIM: Write past end of flash\n"); + return -1; + } + /* Flash write only clears bits */ + sim_image[sim_addr] &= c; + sim_addr = (sim_addr & 0xffffff00) | + ((sim_addr + 1) & 0xff); + } + break; + case sim_state_erase_addr: + if (!(sim_sr & STAT_WEN)) + break; + addr_complete = sim_do_address(&b, &len); + if (addr_complete) { + memset(sim_image + sim_addr, 0xff, sim_er_size); + sim_sr |= STAT_WIP; + sim_sr &= ~STAT_WEN; + sim_state = sim_state_erase_done; + } + break; + default: + ERR("SIM: Write in wrong state %d\n", sim_state); + return -1; + } + return 0; +} + +static int sim_rbytes(void *buf, uint32_t len) +{ + uint8_t *b = buf; + + switch(sim_state) { + case sim_state_rdid: + while(len--) { + switch(sim_index) { + case 0: + *(b++) = 0x55; + break; + case 1: + *(b++) = 0xaa; + break; + case 2: + *(b++) = 0x55; + break; + default: + ERR("SIM: RDID index %d\n", sim_index); + *(b++) = 0; + break; + } + sim_index++; + } + break; + case sim_state_rdsr: + while(len--) { + *(b++) = sim_sr; + if (sim_index > 0) + ERR("SIM: RDSR index %d\n", sim_index); + sim_index++; + + /* If WIP was 1, clear it, ie, simulate write/erase + * completion + */ + sim_sr &= ~STAT_WIP; + } + break; + case sim_state_read_data: + while(len--) { + if (sim_addr >= sim_image_sz) { + ERR("SIM: Read past end of flash\n"); + return -1; + } + *(b++) = sim_image[sim_addr++]; + } + break; + default: + ERR("SIM: Read in wrong state %d\n", sim_state); + return -1; + } + return 0; +} + +static int sim_send_addr(uint32_t addr) +{ + const void *ap; + + /* Layout address MSB first in memory */ + addr = cpu_to_be32(addr); + + /* Send the right amount of bytes */ + ap = (char *)&addr; + + if (sim_ct_4b) + return sim_wbytes(ap, 4); + else + return sim_wbytes(ap + 1, 3); +} + +static int sim_cmd_rd(struct spi_flash_ctrl *ctrl __unused, uint8_t cmd, + bool has_addr, uint32_t addr, void *buffer, + uint32_t size) +{ + int rc; + + rc = sim_start_cmd(cmd); + if (rc) + goto bail; + if (has_addr) { + rc = sim_send_addr(addr); + if (rc) + goto bail; + } + if (buffer && size) + rc = sim_rbytes(buffer, size); + bail: + sim_end_cmd(); + return rc; +} + +static int sim_cmd_wr(struct spi_flash_ctrl *ctrl __unused, uint8_t cmd, + bool has_addr, uint32_t addr, const void *buffer, + uint32_t size) +{ + int rc; + + rc = sim_start_cmd(cmd); + if (rc) + goto bail; + if (has_addr) { + rc = sim_send_addr(addr); + if (rc) + goto bail; + } + if (buffer && size) + rc = sim_wbytes(buffer, size); + bail: + sim_end_cmd(); + return rc; +} + +static int sim_set_4b(struct spi_flash_ctrl *ctrl __unused, bool enable) +{ + sim_ct_4b = enable; + + return 0; +} + +static int sim_read(struct spi_flash_ctrl *ctrl __unused, uint32_t pos, + void *buf, uint32_t len) +{ + if (sim_ct_4b != sim_fl_4b) + ERR("SIM: 4b mode mismatch in autoread !\n"); + if ((pos + len) < pos) + return -1; + if ((pos + len) > sim_image_sz) + return -1; + memcpy(buf, sim_image + pos, len); + return 0; +}; + +struct spi_flash_ctrl sim_ctrl = { + .cmd_wr = sim_cmd_wr, + .cmd_rd = sim_cmd_rd, + .set_4b = sim_set_4b, + .read = sim_read, +}; + +int main(void) +{ + struct flash_chip *fl; + uint32_t total_size, erase_granule; + const char *name; + uint16_t *test; + int i, rc; + + sim_image = malloc(sim_image_sz); + memset(sim_image, 0xff, sim_image_sz); + test = malloc(0x10000 * 2); + + rc = flash_init(&sim_ctrl, &fl); + if (rc) { + ERR("flash_init failed with err %d\n", rc); + exit(1); + } + rc = flash_get_info(fl, &name, &total_size, &erase_granule); + if (rc) { + ERR("flash_get_info failed with err %d\n", rc); + exit(1); + } + + /* Make up a test pattern */ + for (i=0; i<0x10000;i++) + test[i] = cpu_to_be16(i); + + /* Write 64k of stuff at 0 and at 128k */ + printf("Writing test patterns...\n"); + flash_smart_write(fl, 0, test, 0x10000); + flash_smart_write(fl, 0x20000, test, 0x10000); + + /* Write "Hello world" straddling the 64k boundary */ +#define HW "Hello World" + printf("Writing test string...\n"); + flash_smart_write(fl, 0xfffc, HW, sizeof(HW)); + + /* Check result */ + if (memcmp(sim_image + 0xfffc, HW, sizeof(HW))) { + ERR("Test string mismatch !\n"); + exit(1); + } + printf("Test string pass\n"); + if (memcmp(sim_image, test, 0xfffc)) { + ERR("Test pattern mismatch !\n"); + exit(1); + } + printf("Test pattern pass\n"); + flash_exit(fl); + + return 0; +} + -- cgit v1.1