aboutsummaryrefslogtreecommitdiff
path: root/libflash
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-07-02 15:36:20 +1000
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-07-02 15:36:20 +1000
commit1d880992fd8c8457a2d990ac6622cfd58fb1b261 (patch)
treec4c843b12e96b5612c315db5a23c5da1a900618c /libflash
downloadskiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.zip
skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.gz
skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.bz2
Initial commit of Open Source release
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'libflash')
-rw-r--r--libflash/Makefile.inc7
-rw-r--r--libflash/ffs.h159
-rw-r--r--libflash/libffs.c280
-rw-r--r--libflash/libffs.h56
-rw-r--r--libflash/libflash-priv.h213
-rw-r--r--libflash/libflash.c636
-rw-r--r--libflash/libflash.h83
-rw-r--r--libflash/test/Makefile.check20
-rw-r--r--libflash/test/stubs.c16
-rw-r--r--libflash/test/test-flash.c418
10 files changed, 1888 insertions, 0 deletions
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 <linux/types.h>
+#else
+#include <stdint.h>
+#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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <ccan/endian/endian.h>
+
+#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 <libflash/libflash.h>
+#include <libflash/ffs.h>
+
+/* 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 <ccan/endian/endian.h>
+#include <ccan/array_size/array_size.h>
+#include <ccan/container_of/container_of.h>
+
+/* 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <stdint.h>
+#include <stdbool.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <libflash/libflash.h>
+#include <libflash/libflash-priv.h>
+
+#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;
+}
+