changeset 2944:32bcd454bda4

Major update to allow hardware ECC: change driver interface (to "v2" - with CDL marker), expand ECC interface
author wry
date Fri, 09 Oct 2009 14:25:00 +0000
parents cc87500cad3c
children 1c9fa987701f
files packages/io/nand/current/ChangeLog packages/io/nand/current/cdl/nand.cdl packages/io/nand/current/doc/nand.sgml packages/io/nand/current/include/nand.h packages/io/nand/current/include/nand_device.h packages/io/nand/current/include/nand_ecc.h packages/io/nand/current/include/nand_oob.h packages/io/nand/current/src/nand.c packages/io/nand/current/src/nand_bbt.c packages/io/nand/current/src/nand_bbt.h packages/io/nand/current/src/nand_ecc_mtd.c packages/io/nand/current/src/nand_oob.c packages/io/nand/current/tests/rwbenchmark.c
diffstat 13 files changed, 680 insertions(+), 162 deletions(-) [+]
line wrap: on
line diff
--- a/packages/io/nand/current/ChangeLog
+++ b/packages/io/nand/current/ChangeLog
@@ -1,3 +1,16 @@
+2009-10-08  Ross Younger  <wry@eCosCentric.com>
+
+	* nand_ecc.h, nand.c, nand_ecc_mtd.c: Expand ECC interface
+	to better allow hardware assistance.
+	* nand_device.h, nand.cdl: Change function set ("v2"), add cdl marker
+	* nand.c: Implement v2 read/write, updated devinit
+	* nand.c, nand_bbt.h: Internally expose "raw" page read/write fns
+	* nand_oob.c, nand_oob.h: Create packed_{read,write} fns
+	* nand_bbt.c: Use raw page read/write fns, packed read/write fns
+	- and sanity test them on startup
+	* nand.sgml: Document driver interface change.
+	* rwbenchmark.c: Check that we read the requisite amount of data.
+
 2009-09-28  Ross Younger  <wry@eCosCentric.com>
 
 	* tests: Don't put large arrays on stack.
--- a/packages/io/nand/current/cdl/nand.cdl
+++ b/packages/io/nand/current/cdl/nand.cdl
@@ -66,6 +66,17 @@ cdl_package CYGPKG_IO_NAND {
     compile         nand.c nand_bbt.c nand_ecc_mtd.c nand_oob.c util.c
     compile         -library=libextras.a nandinit.cxx
 
+    cdl_option    CYGSEM_IO_NAND_INTERFACE_VERSION {
+        display         "NAND device interface version"
+        flavor          data
+        calculated      2
+        description     "The current compatibility level provided by
+            the NAND layer. As the NAND device interface evolves, this
+            version number will be incremented; devices may require
+            that the infrastructure provide a certain level in order
+            to function correctly."
+    }
+
 	cdl_component CYGPKG_IO_NAND_OPTIONS {
 		display "NAND library build options"
 		flavor  none
--- a/packages/io/nand/current/doc/nand.sgml
+++ b/packages/io/nand/current/doc/nand.sgml
@@ -741,11 +741,11 @@ command;
 <listitem><para>interrogating the device to confirm its presence and
 properties;
 </para></listitem>
-<listitem><para>setting up the partition table list (see <xref
-linkend="nand-devs-partitions" />);
+<listitem><para>setting up the partition table list (see
+"Planning a port" above);
 </para></listitem>
-<listitem><para>setting up mutexes as necessary (see <xref
-linkend="nand-devs-locking" />);
+<listitem><para>setting up mutexes as necessary (see
+"Locking against concurrent access" above);
 </para></listitem>
 <listitem><para>populating the other members of the
 <type>cyg_nand_device</type> struct (see below).
@@ -810,23 +810,35 @@ not currently provided, though one may b
 
 <sect2 id="nand-devs-readwrite">
 <title>Reading, writing and erasing data</title>
-<programlisting>
-static int my_read_page(cyg_nand_device *dev, cyg_nand_page_addr page,
-void * dest, size_t size, void * spare, size_t spare_size);
+The read and write operations are divided into three phases, with the following flow:
+<itemizedlist>
+<listitem><para>Begin. This is called once; the driver should lock any
+platform-level mutex and send the command and address.</para>
+<listitem><para>Stride. This is called one or more times to read the page
+data from the device. <note><para>The reason for this is if the platform
+provides a NAND controller with hardware ECC: it is often necessary to
+read out the ECC registers every so often.</para></note></para>
+<listitem><para>Finish. This is called once; it should read or write
+the spare area, (on programming) send a "program confirm" command and
+check its status, and unlock any platform-level mutex.</para>
+</itemizedlist>
 
-static int my_write_page(cyg_nand_device *dev, cyg_nand_page_addr page,
-const void * src, size_t size,
-const void * spare, size_t spare_size);
+<para>Erasing is a single-shot call which should lock any
+platform-specific mutex, send the command, check its status and unlock
+the mutex.</para>
+
+<programlisting>
+static int my_read_begin(cyg_nand_device *dev, cyg_nand_page_addr page);
+static int my_read_stride(cyg_nand_device *dev, void * dest, size_t size);
+static int my_read_finish(cyg_nand_device *dev, void * spare, size_t spare_size);
+
+static int my_write_begin(cyg_nand_device *dev, cyg_nand_page_addr page);
+static int my_write_stride(cyg_nand_device *dev, const void * src, size_t size);
+static int my_write_finish(cyg_nand_device *dev, const void * spare, size_t spare_size);
 
 static int my_erase_block(cyg_nand_device *dev, cyg_nand_block_addr blk);
 </programlisting>
 
-<para> These functions are very much like the corresponding calls in the
-NAND application interface. They typically lock the mutex, send command
-and address bytes, (on read and write calls) perform the appropriate
-bulk data transfer, then check the device response and unlock.</para>
-</sect2>
-
 <sect2 id="nand-devs-isfactorybad">
 <title>Searching for factory-bad blocks</title>
 <programlisting>
@@ -868,12 +880,19 @@ enabling read-write mode!  </para></tip>
 <sect2 id="nand-devs-declaring-fun-set">
 <title>Declaring the function set</title>
 <programlisting>
-CYG_NAND_FUNS(mydev_funs, my_devinit, my_read_page, my_write_page, my_erase_block, my_is_factory_bad);
+CYG_NAND_FUNS_V2(mydev_funs, my_devinit,
+        my_read_begin, my_read_stride, my_read_finish,
+        my_write_begin, my_write_stride, my_write_finish,
+        my_erase_block, my_is_factory_bad);
 </programlisting>
+
 <para> This macro ties the above functions together into a struct whose
 name is given as its first argument. The name of the resulting struct
 must be quoted when the driver is formally instantiated, which is normally
 done by the low-level functions.</para>
+<note><para>Earlier versions of this library used a slightly different
+device interface, keyed off the macro CYG_NAND_FUNS. This interface has
+been retired.</para></note>
 </sect2>
 </sect1>
 
@@ -928,8 +947,8 @@ handful of MMIO accesses, but should hop
 once you've figured out how the components interrelate.</para>
 
 <para> The worst case is where you have no support from any sort of
-controller hardware and have to bit-bang to talk to the chip. This is
-a much more involved process; you have to take great care to get the
+controller hardware and have to bit-bang GPIO lines to talk to the chip.
+This is a much more involved process; you have to take great care to get the
 timings right with carefully tuned delays. The result is usually
 quite CPU intensive, and could be clock speed sensitive too; you should
 check for and take account of any CDL settings in the architecture and
--- a/packages/io/nand/current/include/nand.h
+++ b/packages/io/nand/current/include/nand.h
@@ -110,10 +110,15 @@ cyg_nand_partition* cyg_nand_get_partiti
  * If @dest@ is not NULL, it will be used to store the page contents (the
  * lesser of @size@ bytes and the actual page size).
  * If @spare@ is not NULL, it will be used to store the contents of the 
- * page's spare area (the lesser of @spare_size@ bytes and the actual
- * spare area size).
- * The data read from the chip will be automatically ECC-checked and
- * repaired if necessary.
+ * page's spare area (the lesser of @spare_size@ bytes and
+ * NAND_APPSPARE_PER_PAGE(dev)).
+ *
+ * The data read from the chip will, as far as possible, be ECC-checked
+ * and repaired if necessary.
+ *
+ * It is possible to request less than a whole page, but not all NAND
+ * chips can cope with this; nor do some ECC implementations. In such
+ * a case, this call returns -ENOSYS.
  *
  * Returns 0 for success, otherwise a negative error code.
  * -EIO : The block could not be successfully read due to an I/O error
@@ -125,23 +130,29 @@ cyg_nand_partition* cyg_nand_get_partiti
  *
  * -ENOENT : The page address was not valid.
  * -EINVAL : The page address is within a block that is marked bad.
+ * -ENOSYS : You have asked for a part-page read, but this is not
+ *  supported by an underlying layer (the NAND chip, or the ECC
+ *  implementation).
  */
 __externC
 int cyg_nand_read_page(cyg_nand_partition *ctx, cyg_nand_page_addr page, 
         void * dest, size_t size, void * spare, size_t spare_size);
 
-/* Writes (at most) a single page and/or its spare area to the device.
- * If less than a whole page worth of data is provided, the rest is
- * assumed to be all-0xFF.
+/* Writes a single page and/or its spare area to the device.
  * ECC will be automatically computed and written.
  *
  * @page@ specifies the page to be written.
  * If @src@ is not NULL, the page contents (the lesser of @size@ bytes
  * and the actual page size) will be read from it.
  * If @spare@ is not NULL, the spare area contents 
- * (the lesser of @spare_size@ bytes and the actual spare area size)
+ * (the lesser of @spare_size@ bytes and NAND_APPSPARE_PER_PAGE(dev))
  * will be read from it.
  *
+ * It is possible to write less than a whole page, but not all NAND
+ * chips can cope with this; nor do some ECC implementations. In such
+ * a case, this call returns -ENOSYS. (If successful, the write is
+ * made up to a whole page by padding with 0xFF.)
+ *
  * Returns 0 for success, otherwise a negative error code.
  * -EIO : The page could not be successfully written due to an I/O error.
  *   In this case, the application should copy out data from the preceding 
@@ -150,6 +161,8 @@ int cyg_nand_read_page(cyg_nand_partitio
  *
  * -ENOENT : The page address was not valid.
  * -EINVAL : The page address is within a block that is marked bad.
+ * -ENOSYS : You have asked for a part-page write, but this is not
+ *  supported by an underlying layer.
  */
 __externC
 int cyg_nand_write_page(cyg_nand_partition *ctx, cyg_nand_page_addr page, 
--- a/packages/io/nand/current/include/nand_device.h
+++ b/packages/io/nand/current/include/nand_device.h
@@ -108,38 +108,81 @@ typedef struct _cyg_nand_partition_t cyg
  * drivers are not given partition information.
  * 2. These functions all return 0 on success (positive if a value
  * is required), or a negative error code.
+ * 3. The read and write sequence from the library is strictly:
+ *      (a) _begin
+ *      (b) Call _stride a number of times, for (at most) a whole page
+ *      (c) _finish
+ *      (d) If another page is to be read, repeat from (a).
  */
-struct cyg_nand_dev_fns_v1 {
+struct cyg_nand_dev_fns_v2 {
     /* Initialises the device, confirms its presence,
      * interrogates it as necessary, 
      * sets up the in-memory partition array (if configured),
      * and populates other members of the _cyg_nand_device_t.
      *
-     * In a multi-threaded eCos configuration, devices MUST set up a
-     * mutex at some appropriate level before returning from this call.
+     * If the device requires any locking on top of the per-device mutex
+     * that this library provides, it should be set up by this call.
      *
      * Note that:
      * 1. The driver MUST NOT malloc; the NAND layer may run without
      * dynamic memory allocation, so anything the device requires should
      * be set up as a static global. Implementers should use the
      * driver API (cyg/hal/drv_api.h) as far as possible.
-     * 2. We do not define at which level a mutex should be set up, as it
+     * 2. We do not define at which level mutexes should apply, as it
      * may differ from device to device - they may need to lock out just
      * a single device, or an entire bus, or something more esoteric.
      */
     int (*devinit) (cyg_nand_device *dev);
 
-    /* Reads a single page and its spare area. See cyg_nand_read_page().
-     * (N.B. This is a 'raw' read. No attempt should be made to check
-     * and repair ECC, or to map spare data around the ECC location;
-     * that's done by the caller.) */
-    int (*read_page) (cyg_nand_device *dev, cyg_nand_page_addr page,
-            void * dest, size_t size, void * spare, size_t spare_size);
-    /* Writes a single page and its spare area. See cyg_nand_write_page().
-     * (Again, this is a 'raw' write.) */
-    int (*write_page)(cyg_nand_device *dev, cyg_nand_page_addr page, 
-            const void * src, size_t size,
-            const void * spare, size_t spare_size);
+    /* NOTE: read_page and write_page were replaced in interface v2
+     * with the following stride-based functions.
+     * The "striding" interface allows easy interfacing with hardware
+     * ECC logic present in some NAND controllers.
+     */
+
+    /* Initialises a page read operation, but does not actually read any
+     * data.  */
+    int (*read_begin)(cyg_nand_device *dev, cyg_nand_page_addr page);
+
+    /* Reads (up to) a stride of data from the chip.
+     * This call may wait for data to become available.
+     *
+     * The number of bytes to read - up to a stride - is given as `size'.
+     * Only that many bytes will be written to dest.
+     * If dest is NULL then the driver should read the data anyway
+     * but throw it away: this may be required for hardware ECC logic.
+     *
+     * The size of a stride is implicit from the ECC algorithm used.
+     */
+    int (*read_stride)(cyg_nand_device *dev, void * dest, size_t size);
+
+    /* Reads out the OOB and (if necessary) finalises the read.
+     * NOTE that this may be called without having read a whole page:
+     * if so, use the Change Read Column command as appropriate to get
+     * at the OOB area. If that proves impossible, return -ENOSYS. */
+    int (*read_finish)(cyg_nand_device *dev,
+                       void * spare, size_t spare_size);
+
+    /* Initialises a write operation, but does not actually write any data. */
+    int (*write_begin)(cyg_nand_device *dev, cyg_nand_page_addr page);
+
+    /* Writes (up to) a stride of data from the chip.
+     *
+     * The number of bytes to written - up to a stride - is given as `size'.
+     * Only that many bytes will be written to the chip.
+     * If called with src==NULL, the driver should write `size' 0xFF bytes;
+     * this may be necessary for hardware ECC calculation.
+     *
+     * The size of a stride is implicit from the ECC algorithm used. */
+    int (*write_stride)(cyg_nand_device *dev, const void * src, size_t size);
+
+    /* Sends the OOB data to the chip and finalises the write.
+     * NOTE that this may be called without having written a whole page:
+     * if so, use the Change Write Column command as appropriate to get
+     * at the OOB area. If that proves impossible, return -ENOSYS. */
+    int (*write_finish)(cyg_nand_device *dev,
+                        const void * spare, size_t spare_size);
+
 
     /* Erases an eraseblock. See cyg_nand_erase_block(). */
     int (*erase_block)(cyg_nand_device *dev, cyg_nand_block_addr blk);
@@ -150,12 +193,12 @@ struct cyg_nand_dev_fns_v1 {
     int (*is_factory_bad)(cyg_nand_device *dev, cyg_nand_block_addr blk);
 };
 
+
 /* Context for a physical NAND device.
  * Each device on the system should have its own.
  */
 struct _cyg_nand_device_t {
-    int version; /* Reserved for future expansion. Currently set to 1 
-                    by the NAND library.*/
+    int version; /* Indicates the compatibility level this device supports. */
 
     cyg_drv_mutex_t devlock; // Device-level locking applied by this library.
 
@@ -168,7 +211,7 @@ struct _cyg_nand_device_t {
     const char *devname; /* Device name for use by applications,
                             e.g. "onboard" or "bus1". */
 
-    struct cyg_nand_dev_fns_v1 *fns; /* Access functions */
+    struct cyg_nand_dev_fns_v2 *fns; /* Access functions */
     void * priv; /* If required, points to private device-specific data.  */
 
 
@@ -195,22 +238,34 @@ struct _cyg_nand_device_t {
 
     cyg_nand_ecc_t *ecc;
     const cyg_nand_oob_layout *oob;
+
+    /* Expansion fields go here. */
+
 } CYG_HAL_TABLE_TYPE;
 
-#define CYG_NAND_FUNS(_funs_, _devinit_, _rdpg_, _wrpg_, _erasebl_, _factorybad_) \
-struct cyg_nand_dev_fns_v1 _funs_ = \
-{                                   \
-    .devinit = _devinit_,           \
-    .read_page = _rdpg_,            \
-    .write_page = _wrpg_,           \
-    .erase_block = _erasebl_,       \
-    .is_factory_bad = _factorybad_, \
+#define CYG_NAND_FUNS ERROR_driver_function_update_to_v2_needed
+
+#define CYG_NAND_FUNS_V2(_funsv2_, _devinit_,   \
+        _rdbegin_, _rdstride_, _rdfin_,         \
+        _wrbegin_, _wrstride_, _wrfin_,         \
+        _erasebl_, _factorybad_ )               \
+struct cyg_nand_dev_fns_v2 _funsv2_ = { \
+    .devinit = _devinit_,               \
+    .read_begin = _rdbegin_,            \
+    .read_stride = _rdstride_,          \
+    .read_finish = _rdfin_,             \
+    .write_begin = _wrbegin_,           \
+    .write_stride = _wrstride_,         \
+    .write_finish = _wrfin_,            \
+    .erase_block = _erasebl_,           \
+    .is_factory_bad = _factorybad_,     \
 }
 
+
 #define CYG_NAND_DEVICE(_structname_, _devname_, _funs_, _priv_, _ecc_, _oob_)\
 struct _cyg_nand_device_t _structname_ CYG_HAL_TABLE_ENTRY(cyg_nand_dev) =    \
 {                               \
-    .version = 1,               \
+    .version = 2,               \
     .is_inited = 0,             \
     .devname = _devname_,       \
     .fns = _funs_,              \
@@ -220,8 +275,8 @@ struct _cyg_nand_device_t _structname_ C
 }
 
 /* To declare a new NAND device,
- * first provide its functions and tie them together with CYG_NAND_FUNCTIONS,
- * then instantiate it with CYG_NAND_DEVICE.
+ * first provide its functions and tie them together with CYG_NAND_FUNCTIONS_V2,
+ * then instantiate it with CYG_NAND_DEVICE_V2.
  * - You can optionally provide private data in 'priv' which will be passed
  *   to the driver functions (e.g. for register addresses or GPIO config).
  * - You can use a static ECC param block, or pass it as NULL in the macro
--- a/packages/io/nand/current/include/nand_ecc.h
+++ b/packages/io/nand/current/include/nand_ecc.h
@@ -51,34 +51,73 @@
 
 struct _cyg_nand_device_t;
 
-/* ECC algorithm parameterisation block. */
+/* A "software" ECC algorithm:
+ *  - must read all its data from RAM
+ *  - probably doesn't need an initialisation step
+ *
+ * A "hardware" algorithm: 
+ *  - updates its internal state as data goes by
+ *  - probably does need an initialisation step (and locking, but the
+ *  caller should take care of that)
+ */
+
 typedef struct {
+    const unsigned flags;
+#define NAND_ECC_FLAG_IS_HARDWARE (1<<0) /* Engages `hardware' semantics */
+
     /* How many data bytes does this algorithm read? */
     const unsigned  data_size;
     /* How many ECC bytes does this algorithm use per data block? */
     const unsigned  ecc_size;
-    /* Calculates the ECC for the given data block */
-    void (*calc)(const CYG_BYTE *dat, CYG_BYTE *ecc);
+    /* Initialises an ECC computation. May be NULL if not required. */
+    void (*init)(struct _cyg_nand_device_t *dev);
+
+    /* Returns the ECC for the given data block.
+     * If IS_HARDWARE:
+     *  - dat and nbytes are ignored
+     * If ! IS_HARDWARE:
+     *  - dat and nbytes are required
+     *  - if nbytes is less than the chunk size, the remainder are
+     *    assumed to be 0xff.
+     */
+    void (*calc)(struct _cyg_nand_device_t *dev, 
+                 const CYG_BYTE *dat, size_t nbytes, CYG_BYTE *ecc);
+
     /* Repairs the ECC for the given data block, if needed.
      * Call this if your read-from-chip ECC doesn't match what you computed
      * over the data block. Both *dat and *ecc_read may be corrected.
+     *
+     * `nbytes' is the number of bytes we're interested in; if a correction
+     * is indicated outside of that range, it will be ignored.
+     *
      * Returns: 
      *       0 for no errors
      *       1 for a corrected single bit error in the data
      *       2 for a corrected single bit error in the ECC
      *      -1 for an uncorrectable error (more than one bit)
      */
-    int (*repair)(CYG_BYTE *dat, CYG_BYTE *ecc_read, const CYG_BYTE *ecc_calc);
+    int (*repair)(struct _cyg_nand_device_t *dev,
+                  CYG_BYTE *dat, size_t nbytes, 
+                  CYG_BYTE *ecc_read, const CYG_BYTE *ecc_calc);
 } cyg_nand_ecc_t;
 
-#define CYG_NAND_ECC_ALG(_name, _dataz, _eccz, _calc, _repair)  \
-    cyg_nand_ecc_t _name = {                            \
-        .data_size = _dataz,                                    \
-        .ecc_size = _eccz,                                      \
-        .calc = _calc,                                          \
-        .repair = _repair,                                      \
+#define CYG_NAND_ECC_ALG(_name, _dataz, _eccz, _init, _calc, _repair, _flags)  \
+    cyg_nand_ecc_t _name = {                                           \
+        .data_size = _dataz,                                           \
+        .ecc_size = _eccz,                                             \
+        .init = _init,                                                 \
+        .calc = _calc,                                                 \
+        .repair = _repair,                                             \
+        .flags = _flags,                                               \
     }
 
+#define CYG_NAND_ECC_ALG_SW(_name, _dataz, _eccz, _init, _calc, _repair) \
+    CYG_NAND_ECC_ALG(_name, _dataz, _eccz, _init, _calc, _repair, 0)
+
+#define CYG_NAND_ECC_ALG_HW(_name, _dataz, _eccz, _init, _calc, _repair) \
+    CYG_NAND_ECC_ALG(_name, _dataz, _eccz, _init, _calc, _repair, \
+            NAND_ECC_FLAG_IS_HARDWARE)
+
 /* Useful code ==================================================== */
 
 /* Quick & dirty calc: no of ECC bytes per page.
@@ -92,8 +131,9 @@ typedef struct {
 void nand_ecci_calc_page(struct _cyg_nand_device_t *dev,
                          const CYG_BYTE *page, CYG_BYTE *ecc_o);
 
-/* Checks and (if necessary) repairs the ECC for a whole device page.
- * 'page' points to the data; a whole page will necessarily be read.
+/* Checks and (if necessary) repairs the ECC for (up to) a whole device page.
+ * 'page' points to the data; an error at position after @nbytes@ will not
+ * be corrected.
  * Broadly the same semantics as for cyg_nand_ecc_t.repair; 
  * both ECCs are of size CYG_NAND_ECCPERPAGE(dev), and ecc_read may
  * be corrected as well as the data.
@@ -105,7 +145,7 @@ void nand_ecci_calc_page(struct _cyg_nan
  *     -1 if there was an uncorrectable error (>1 bit in a single ECC block)
  */ 
 int nand_ecci_repair_page(struct _cyg_nand_device_t *dev,
-                          CYG_BYTE *page,
+                          CYG_BYTE *page, size_t nbytes,
                           CYG_BYTE *ecc_read, const CYG_BYTE *ecc_calc);
 
 /* Implementations ================================================ */
--- a/packages/io/nand/current/include/nand_oob.h
+++ b/packages/io/nand/current/include/nand_oob.h
@@ -91,6 +91,7 @@ typedef struct {
 typedef struct {
     const oobpos_t ecc_size; // total ECC _bytes_
     const oob_vector ecc[CYG_NAND_OOB_MAX_ECC_SLOTS];
+    // CAUTION! Both ecc and app vectors MUST BE IN ORDER!
     const oobpos_t app_size; // total bytes available for app data
     const oob_vector app[CYG_NAND_OOB_MAX_APP_SLOTS];
 } cyg_nand_oob_layout;
@@ -115,6 +116,21 @@ typedef struct {
                                 CYG_BYTE *app, const unsigned app_max,
                                 CYG_BYTE *ecc, const CYG_BYTE *packed);
 
+/* Writes @len@ bytes of @data@ into an @oobbuf@ such that the data 
+ * will end up at the given @rawpos@ in the packed layout.
+ * Returns 0 for success or -1 if it's not possible. */
+__externC int nand_oob_packed_write(struct _cyg_nand_device_t *dev,
+        size_t rawpos, size_t len,
+        CYG_BYTE *oobbuf, const CYG_BYTE *data);
+
+/* Opposite of nand_oob_packed_write.
+ * Reads @len@ bytes from an application @oobbuf@ such that they
+ * came from the given @rawpos@ in the packed layout; copies them
+ * to @data@.
+ * Returns 0 for success or -1 if it's not possible. */
+__externC int nand_oob_packed_read(struct _cyg_nand_device_t *dev,
+        size_t rawpos, size_t len,
+        const CYG_BYTE *oobbuf, CYG_BYTE *data);
 
 /* And now some layouts from the Linux MTD layer. */
 
--- a/packages/io/nand/current/src/nand.c
+++ b/packages/io/nand/current/src/nand.c
@@ -146,23 +146,37 @@ int cyg_nand_lookup(const char *devname,
     if (!rv) {
         if (!dev->is_inited) {
             int i;
+
+            if (dev->version != 2) {
+                NAND_ERROR(dev, "Device %s declares incompatible version %d (expected 2)", devname, dev->version);
+                goto done;
+            }
             CYG_CHECK_DATA_PTRC(dev->fns);
             CYG_CHECK_FUNC_PTRC(dev->fns->devinit);
-            CYG_CHECK_FUNC_PTRC(dev->fns->read_page);
-            CYG_CHECK_FUNC_PTRC(dev->fns->write_page);
-            CYG_CHECK_FUNC_PTRC(dev->fns->erase_block);
-            CYG_CHECK_FUNC_PTRC(dev->fns->is_factory_bad);
 
-            dev->version = 1;
             dev->pf = nand_default_pf;
             for (i=0; i<CYGNUM_NAND_MAX_PARTITIONS; i++)
                 dev->partition[i].dev = 0;
             dev->bbt.data = 0; // Paranoia, ensure devinit sets up
+
             rv = dev->fns->devinit(dev);
+
             if (rv) {
                 NAND_ERROR(dev,"Could not initialise NAND device \"%s\": code %d\n", devname, rv);
                 goto done;
             }
+
+            // Now check that we have everything we need
+            CYG_CHECK_FUNC_PTRC(dev->fns->read_begin);
+            CYG_CHECK_FUNC_PTRC(dev->fns->read_stride);
+            CYG_CHECK_FUNC_PTRC(dev->fns->read_finish);
+            CYG_CHECK_FUNC_PTRC(dev->fns->write_begin);
+            CYG_CHECK_FUNC_PTRC(dev->fns->write_stride);
+            CYG_CHECK_FUNC_PTRC(dev->fns->write_finish);
+
+            CYG_CHECK_FUNC_PTRC(dev->fns->erase_block);
+            CYG_CHECK_FUNC_PTRC(dev->fns->is_factory_bad);
+
             CYG_CHECK_DATA_PTRC(dev->bbt.data);
             CYG_CHECK_DATA_PTRC(dev->ecc);
             CYG_CHECK_DATA_PTRC(dev->oob);
@@ -191,6 +205,8 @@ int cyg_nand_lookup(const char *devname,
                 goto done;
             }
 
+            // NOW we are ready to read from the device !
+
             rv = cyg_nand_bbti_find_tables(dev);
             if (rv == -ENOENT) {
                 NAND_CHATTER(1,dev, "Creating initial bad block table on device %s\n", devname);
@@ -245,44 +261,142 @@ static int valid_page_addr(cyg_nand_part
 int cyg_nand_read_page(cyg_nand_partition *prt, cyg_nand_page_addr page,
                 void * dest, size_t size, void * spare, size_t spare_size)
 {
-    int tries=0;
     int rv;
     PARTITION_CHECK(prt);
     cyg_nand_device *dev = prt->dev;
     DEV_INIT_CHECK(dev);
 
-    if (dest)  CYG_CHECK_DATA_PTRC(dest);
-    if (spare) CYG_CHECK_DATA_PTRC(spare);
-
     if (size > (1<<dev->page_bits)) return -EFBIG;
     if (spare_size > dev->spare_per_page) return -EFBIG;
 
-    LOCK_DEV(dev);
+    EG(valid_page_addr(prt, page));
 
-    CYG_BYTE ecc_read[CYG_NAND_ECCPERPAGE(dev)],
-             ecc_calc[CYG_NAND_ECCPERPAGE(dev)];
-    CYG_BYTE oob_buf[dev->spare_per_page];
-
-    EG(valid_page_addr(prt, page));
     cyg_nand_block_addr blk = CYG_NAND_PAGE2BLOCKADDR(dev,page);
     if (cyg_nand_bbti_query(dev, blk) != CYG_NAND_BBT_OK) {
         NAND_CHATTER(1,dev,"Asked to read page %u in bad block %u\n", page, blk);
         EG(-EINVAL);
     }
 
+    EG(nandi_read_page_raw(dev, page, dest, size, spare, spare_size));
+
+err_exit:
+    return rv;
+}
+
+
+/* Internal, mostly-unchecked interface to read a page. */
+int nandi_read_page_raw(cyg_nand_device *dev, cyg_nand_page_addr page,
+            CYG_BYTE * dest, const size_t sizex, CYG_BYTE * spare, size_t spare_size)
+{
+    CYG_BYTE ecc_read[CYG_NAND_ECCPERPAGE(dev)],
+             ecc_calc[CYG_NAND_ECCPERPAGE(dev)];
+    CYG_BYTE *ecc_calc_p, *ecc_read_p;
+
+    CYG_BYTE oob_buf[dev->spare_per_page];
+    int rv=0,tries=0;
+    size_t remain;
+    const int ecc_is_hw = (dev->ecc->flags & NAND_ECC_FLAG_IS_HARDWARE);
+
+    // Stride for reading from device:
+    const unsigned read_data_stride = ecc_is_hw ? dev->ecc->data_size : NAND_BYTES_PER_PAGE(dev);
+    // Stride for calculating/checking/repairing ECC:
+    const unsigned ecc_data_stride = dev->ecc->data_size;
+    // Stride within the ECC data
+    const unsigned ecc_stride = dev->ecc->ecc_size;
+
+    if (dest)  CYG_CHECK_DATA_PTRC(dest);
+    if (spare) CYG_CHECK_DATA_PTRC(spare);
+
+    if (!ecc_is_hw) {
+        // if s/w ecc, part-stride reads are not yet supported
+        if (sizex % ecc_data_stride != 0)
+            EG(-ENOSYS);
+    }
+
+    LOCK_DEV(dev);
+
     do {
+        CYG_BYTE *data_dest = dest;
+        CYG_BYTE *ecc_dest = ecc_calc;
+
         ++tries;
+        remain = sizex;
 
-        EG(dev->fns->read_page(dev, page, dest, size, oob_buf, dev->spare_per_page));
+        EG(dev->fns->read_begin(dev, page));
+
+        if (dest) {
+            int step;
+            while (remain) {
+                step = read_data_stride;
+
+                if (ecc_is_hw && dev->ecc->init) dev->ecc->init(dev);
+
+                if (step > remain) {
+                    step = remain;
+                    EG(dev->fns->read_stride(dev, data_dest, step));
+                    EG(dev->fns->read_stride(dev, 0, read_data_stride - step));
+                } else {
+                    EG(dev->fns->read_stride(dev, data_dest, step));
+                }
+                if (ecc_is_hw) {
+                    dev->ecc->calc(dev, 0, 0, ecc_dest);
+                    ecc_dest += ecc_stride;
+                }
+                data_dest += step;
+                remain -= step;
+            }
+        }
+
+        EG(dev->fns->read_finish(dev, oob_buf, dev->spare_per_page));
 
         nand_oob_unpack(dev, spare, spare_size, ecc_read, oob_buf);
 
-        if (!dest) goto err_exit; // No data? Can't ECC it!
-        if (size != 1<<dev->page_bits) goto err_exit;
-        // FIXME: Make part-page ECC work.
+        if (dest && !ecc_is_hw) {
+            // Calculate software ECC in one go to try and take advantage
+            // of the instruction cache.
+            int step = ecc_data_stride;
+            data_dest = dest;
+            remain = sizex;
+            ecc_calc_p = ecc_calc;
+
+            while (remain) {
+                // We have already checked above that we're not doing any part-strides.
+                if (dev->ecc->init) dev->ecc->init(dev);
+                dev->ecc->calc(dev, data_dest, step, ecc_calc_p);
+
+                remain -= step;
+                data_dest += step;
+                ecc_calc_p += ecc_stride;
+            }
+        }
 
-        nand_ecci_calc_page(dev, dest, ecc_calc);
-        rv = nand_ecci_repair_page(dev, dest, ecc_read, ecc_calc);
+        rv = 0;
+        if (dest) {
+            int step = ecc_data_stride;
+            int step_rv;
+            remain = sizex;
+            data_dest = dest;
+
+            ecc_calc_p = ecc_calc;
+            ecc_read_p = ecc_read;
+
+            while (remain) {
+                if (step > remain) step = remain;
+
+                step_rv = dev->ecc->repair(dev,data_dest,step,ecc_read_p,ecc_calc_p);
+                if (step_rv == -1) {
+                    rv = -1;
+                    break;
+                }
+                rv |= step_rv;
+
+                data_dest += step;
+                ecc_read_p += ecc_stride;
+                ecc_calc_p += ecc_stride;
+                remain -= step;
+            }
+        }
+
         if (rv==-1 && (tries < CYGNUM_NAND_MAX_READ_RETRIES) ) {
             NAND_CHATTER(4, dev, "NAND: ECC uncorrectable error on read, retrying\n");
         }
@@ -292,60 +406,121 @@ int cyg_nand_read_page(cyg_nand_partitio
         case 0:
             NAND_CHATTER(8,dev,"Read page %u OK\n", page);
             break;
-        case -1:
-            NAND_ERROR(dev,"NAND: Page %u read gave ECC uncorrectable error\n", page);
-            rv=-EIO;
-            break;
         case 1:
         case 2:
         case 3:
             NAND_CHATTER(2,dev, "Page %u ECC correction, type %d\n", page,rv);
             rv=0;
             break;
+        case -1:
+        default:
+            NAND_ERROR(dev,"NAND: Page %u read gave ECC uncorrectable error\n", page);
+            rv=-EIO;
+            break;
     }
 err_exit:
     UNLOCK_DEV(dev);
     return rv;
 }
 
+/* Internal, mostly-unchecked interface to write a page. */
 __externC
 int cyg_nand_write_page(cyg_nand_partition *prt, cyg_nand_page_addr page,
         const void * src, size_t size, const void * spare, size_t spare_size)
 {
-#ifdef CYGSEM_IO_NAND_READONLY
-    return -EROFS;
-#else
     int rv;
     PARTITION_CHECK(prt);
     cyg_nand_device *dev = prt->dev;
     DEV_INIT_CHECK(dev);
 
-    if (src)   CYG_CHECK_DATA_PTRC(src);
-    if (spare) CYG_CHECK_DATA_PTRC(spare);
-
-    LOCK_DEV(dev);
+    EG(valid_page_addr(prt, page));
 
-    CYG_BYTE ecc[CYG_NAND_ECCPERPAGE(dev)];
-    CYG_BYTE oob_packed[dev->spare_per_page];
-
-    EG(valid_page_addr(prt, page));
     cyg_nand_block_addr blk = CYG_NAND_PAGE2BLOCKADDR(dev,page);
     if (cyg_nand_bbti_query(dev, blk) != CYG_NAND_BBT_OK) {
         NAND_CHATTER(1,dev,"Asked to write page %u in bad block %u\n", page, blk);
         EG(-EINVAL);
     }
+    EG(nandi_write_page_raw(dev, page, src, size, spare, spare_size));
 
-    if (src && (size == 1<<dev->page_bits)) {
-        nand_ecci_calc_page(dev, src, ecc);
-        // FIXME: Make ECC work on part-pages.
-    } else {
-        // No data, can't compute an ECC, hope they're not overwriting...
-        memset(ecc, 0xff, CYG_NAND_ECCPERPAGE(dev));
+err_exit:
+    return rv;
+}
+
+__externC
+int nandi_write_page_raw(cyg_nand_device *dev, cyg_nand_page_addr page,
+        const CYG_BYTE * src, const size_t sizex, const CYG_BYTE * spare, size_t spare_size)
+{
+#ifdef CYGSEM_IO_NAND_READONLY
+    return -EROFS;
+#else
+    int rv;
+    CYG_BYTE oob_packed[dev->spare_per_page];
+    CYG_BYTE ecc[CYG_NAND_ECCPERPAGE(dev)];
+
+    const int ecc_is_hw = (dev->ecc->flags & NAND_ECC_FLAG_IS_HARDWARE);
+    const unsigned write_data_stride = ecc_is_hw ? dev->ecc->data_size : NAND_BYTES_PER_PAGE(dev);
+    const unsigned ecc_data_stride = dev->ecc->data_size;
+    const unsigned ecc_stride = dev->ecc->ecc_size;
+    size_t remain;
+    CYG_BYTE *ecc_dest = ecc;
+
+    memset(ecc, 0xff, CYG_NAND_ECCPERPAGE(dev));
+
+    if (src)   CYG_CHECK_DATA_PTRC(src);
+    if (spare) CYG_CHECK_DATA_PTRC(spare);
+
+    // If we're doing software ECC, do it all in one go now.
+    if (src && !ecc_is_hw) {
+        int step = ecc_data_stride;
+        const CYG_BYTE *data_src = src;
+        remain = sizex;
+
+        // if s/w ecc, part-stride writes are not yet supported
+        if (remain % ecc_data_stride != 0)
+            EG(-ENOSYS);
+
+        while (remain) {
+            //if (step > remain) step = remain; // NYI
+            if (dev->ecc->init) dev->ecc->init(dev);
+            dev->ecc->calc(dev, data_src, step, ecc_dest);
+
+            remain -= step;
+            data_src += step;
+            ecc_dest += ecc_stride;
+        }
     }
-    nand_oob_pack(dev, spare, spare_size, ecc, oob_packed);
+
+    LOCK_DEV(dev);
+    EG(dev->fns->write_begin(dev, page));
+    if (src) {
+        int step = write_data_stride;
+        remain = sizex;
+        while (remain) {
+            if (ecc_is_hw && dev->ecc->init) dev->ecc->init(dev);
 
+            if (step > remain) {
+                step = remain;
+                // We can only support part-stride writes by assuming
+                // that the rest of the stride is 0xFF.
+                // (Woe betide the caller if it isn't.)
+                EG(dev->fns->write_stride(dev, src, step));
+                EG(dev->fns->write_stride(dev, 0, write_data_stride - step));
+            } else {
+                EG(dev->fns->write_stride(dev, src, step));
+            }
+            if (ecc_is_hw) {
+                dev->ecc->calc(dev, 0, 0, ecc_dest);
+                ecc_dest += ecc_stride;
+            }
+            src += step;
+            remain -= step;
+        }
+    }
+
+    nand_oob_pack(dev, spare, spare_size, ecc, oob_packed);
     NAND_CHATTER(8,dev,"Write page %u\n", page);
-    EG(dev->fns->write_page(dev, page, src, size, oob_packed, dev->spare_per_page));
+    EG(dev->fns->write_finish(dev, oob_packed, dev->spare_per_page));
+
     /* N.B. We don't read-back to verify; drivers may do so themselves if
      * they wish. Typically the spec sheet says that a read-back test
      * is unnecessary if the device reports a successful program, and 
@@ -446,14 +621,17 @@ void nand_ecci_calc_page(cyg_nand_device
     CYG_CHECK_DATA_PTRC(ecc_o);
 
     for (i=0; i<nblocks; i++) {
-        dev->ecc->calc(page,ecc_o);
+        if (dev->ecc->init)
+            dev->ecc->init(dev);
+        dev->ecc->calc(dev,page,dev->ecc->data_size,ecc_o);
         page += dev->ecc->data_size;
         ecc_o += dev->ecc->ecc_size;
     }
 }
 
-/* Checks and (if necessary) repairs the ECC for a whole device page.
- * 'page' points to the data; a whole page will necessarily be read.
+/* Checks and (if necessary) repairs the ECC for (up to) a whole device page.
+ * 'page' points to the data; an error at position after @nbytes@ will not
+ * be corrected.
  * Broadly the same semantics as for cyg_nand_ecc_t.repair; 
  * both ECCs are of size CYG_NAND_ECCPERPAGE(dev), and ecc_read may
  * be corrected as well as the data.
@@ -464,7 +642,8 @@ void nand_ecci_calc_page(cyg_nand_device
  *      3 if there was at least one corrected error in both data and ECC
  *     -1 if there was an uncorrectable error (>1 bit in a single ECC block)
  */
-int nand_ecci_repair_page(cyg_nand_device *dev, CYG_BYTE *page, CYG_BYTE *ecc_read, const CYG_BYTE *ecc_calc)
+int nand_ecci_repair_page(cyg_nand_device *dev, CYG_BYTE *page, size_t remain,
+        CYG_BYTE *ecc_read, const CYG_BYTE *ecc_calc)
 {
     int i, page_rv=0;
     const int nblocks = (1<<dev->page_bits) / dev->ecc->data_size;
@@ -473,10 +652,13 @@ int nand_ecci_repair_page(cyg_nand_devic
     CYG_CHECK_DATA_PTRC(ecc_calc);
 
     for (i=0; i<nblocks; i++) {
-        int chunk_rv = dev->ecc->repair(page,ecc_read,ecc_calc);
+        int stride = dev->ecc->data_size;
+        if (stride > remain) stride = remain;
+        int chunk_rv = dev->ecc->repair(dev,page,stride,ecc_read,ecc_calc);
         if (chunk_rv < 0) return chunk_rv;
         page_rv |= chunk_rv;
         page += dev->ecc->data_size;
+        remain -= dev->ecc->data_size;
         ecc_read += dev->ecc->ecc_size;
         ecc_calc += dev->ecc->ecc_size;
     }
--- a/packages/io/nand/current/src/nand_bbt.c
+++ b/packages/io/nand/current/src/nand_bbt.c
@@ -183,6 +183,7 @@ static CYG_BYTE nand_pattern_mirror [] =
 #define NAND_PATTERN_OFFSET 8
 #define NAND_PATTERN_SIZE 4
 #define NAND_VERSION_OFFSET 12
+#define NAND_VERSION_SIZE 1
 
 #define EG(x) do { rv = (x); if (rv != 0) goto err_exit; } while(0)
 
@@ -239,21 +240,14 @@ static int bbti_incorporate_one(cyg_nand
 
     int blks_to_read = 1 << dev->blockcount_bits;
     while (blks_to_read>0) {
-        CYG_BYTE oob[dev->spare_per_page];
-        CYG_BYTE ecc_read[CYG_NAND_ECCPERPAGE(dev)],
-                 ecc_calc[CYG_NAND_ECCPERPAGE(dev)];
-
-        EG(dev->fns->read_page(dev, pg, bbt_pagebuf, 1<<dev->page_bits, oob, dev->spare_per_page));
-
-        nand_oob_unpack(dev, 0, 0, ecc_read, oob);
-        nand_ecci_calc_page(dev, bbt_pagebuf, ecc_calc);
-        rv = nand_ecci_repair_page(dev, bbt_pagebuf, ecc_read, ecc_calc);
+        rv = nandi_read_page_raw(dev, pg, bbt_pagebuf, 1<<dev->page_bits, 0, 0);
+        // read_page_raw does the ecc for us, and we don't care about the OOB here as we already know it's one of ours.
         if (rv<0) {
             // This is bad.
             int is_primary = blk == dev->bbt.primary;
             NAND_CHATTER(1,dev, "Reading BBT %s (page %u): uncorrectable error\n", is_primary ? "primary" : "mirror", pg);
             (void) is_primary;
-            EG(-EIO);
+            EG(rv);
         }
         if (rv>0) {
             NAND_CHATTER(2,dev,"Reading BBT page %u: ECC repaired, code %d\n", pg, rv);
@@ -293,24 +287,91 @@ err_exit:
     return rv;
 }
 
+static int nandi_bbt_packing_test(cyg_nand_device *dev)
+{
+    // Startup sanity check: Can we pack the BBT marker and version into the app spare area, do they appear in the right place, and can we extract them?
+    CYG_BYTE appspare[NAND_APPSPARE_PER_PAGE(dev)],
+             packed[NAND_SPARE_PER_PAGE(dev)],
+             testdata[NAND_PATTERN_SIZE > NAND_VERSION_SIZE ? NAND_PATTERN_SIZE : NAND_VERSION_SIZE],
+             ecc[CYG_NAND_ECCPERPAGE(dev)];
+    int i, rv, fail = 0;
+
+#define MAGIC 42
+    memset(ecc, 0xff, sizeof ecc);
+    memset(testdata, 0, sizeof testdata);
+    for (i=0; i<NAND_PATTERN_SIZE; i++)
+        testdata[i] = i+MAGIC;
+
+    i = nand_oob_packed_write(dev, NAND_PATTERN_OFFSET, NAND_PATTERN_SIZE, appspare, testdata);
+    if (i == 0)
+        i = nand_oob_packed_write(dev, NAND_VERSION_OFFSET, NAND_VERSION_SIZE, appspare, testdata);
+
+    if (i != 0) {
+        NAND_ERROR(dev,"BUG: BBT ident/version offset will not fit into this device's spare area\n");
+        /* Read the warning in nand_oob.c by nand_mtd_oob_8.
+         * To make that work, you have to teach this layer how to find
+         * the BBT on such a device. */
+        EG(-ENOSYS);
+    }
+
+    nand_oob_pack(dev, appspare, sizeof appspare, ecc, packed);
+    for (i=0; i<NAND_PATTERN_SIZE; i++)
+        if (packed[NAND_PATTERN_OFFSET + i] != i + MAGIC)
+            ++fail;
+    for (i=0; i<NAND_VERSION_SIZE; i++)
+        if (packed[NAND_PATTERN_OFFSET + i] != i + MAGIC)
+            ++fail;
+
+    if (fail) {
+        NAND_ERROR(dev, "BUG: NAND BBT tag pack did not work");
+        EG(-ENOSYS);
+    }
+
+    memset(appspare, 0xff, sizeof appspare);
+    nand_oob_unpack(dev, appspare, sizeof appspare, ecc, packed);
+
+    memset(testdata, 0, sizeof testdata);
+    i = nand_oob_packed_read(dev, NAND_PATTERN_OFFSET, NAND_PATTERN_SIZE, appspare, testdata);
+    for (i=0; i<NAND_PATTERN_SIZE; i++)
+        if (testdata[i] != i + MAGIC)
+            ++fail;
+
+    memset(testdata, 0, sizeof testdata);
+    i = nand_oob_packed_read(dev, NAND_VERSION_OFFSET, NAND_VERSION_SIZE, appspare, testdata);
+    for (i=0; i<NAND_VERSION_SIZE; i++)
+        if (testdata[i] != i + MAGIC)
+            ++fail;
+
+    for (i=0; i < sizeof ecc; i++)
+        if (ecc[i] != 0xff)
+            ++fail;
+
+    if (fail) {
+        NAND_ERROR(dev, "BUG: NAND BBT tag unpack did not work");
+        EG(-ENOSYS);
+    }
+
+    rv = 0;
+err_exit:
+    return rv;
+}
+
+
 int cyg_nand_bbti_find_tables(cyg_nand_device *dev)
 {
     int rv = 0;
+    CYG_BYTE pri_ver = 0, mir_ver = 0;
+    CYG_BYTE patternbuf[NAND_PATTERN_SIZE];
+    CYG_BYTE versionbuf[NAND_VERSION_SIZE];
+
     NAND_CHATTER(3,dev, "Looking for bad-block table:\n");
     /* TODO: What happens if a BBT block goes bad? Can we put it beyond use?
      * - erasing it _should_ clear the descriptor pattern.
      * Obviously, we should look for further instances of the BBT patterns
      * with fresher version tag(s). */
 
-    CYG_BYTE pri_ver = 0, mir_ver = 0;
+    EG(nandi_bbt_packing_test(dev));
 
-    if (NAND_VERSION_OFFSET > dev->spare_per_page) {
-        NAND_ERROR(dev,"BUG: BBT ident/version offset overrun this device's spare area size\n");
-        /* Read the warning in nand_oob.c by nand_mtd_oob_8.
-         * To make that work, you have to teach this layer how to find
-         * the BBT on such a device. */
-        EG(-ENOSYS);
-    }
     cyg_nand_block_addr start = (1<<dev->blockcount_bits)-1;
     int i;
     for (i=0; i<4; i++) {
@@ -320,15 +381,24 @@ int cyg_nand_bbti_find_tables(cyg_nand_d
         cyg_nand_block_addr blk = start - i;
         cyg_nand_page_addr pg = CYG_NAND_BLOCK2PAGEADDR(dev,blk);
 
-        CYG_BYTE oobbuf[16]; // this covers the pattern and version areas
+        CYG_BYTE oobbuf[NAND_APPSPARE_PER_PAGE(dev)];
 
-        rv = dev->fns->read_page(dev, pg, 0, 0, oobbuf, sizeof oobbuf);
+        rv = nandi_read_page_raw(dev, pg, 0, 0, oobbuf, sizeof oobbuf);
         if (rv<0) {
             NAND_CHATTER(1,dev, "bbti_find_tables: Error %d reading OOB of page %u (block %u)\n", -rv, pg, blk);
             EG(-EIO);
         }
-        if (0==memcmp(&oobbuf[NAND_PATTERN_OFFSET],nand_pattern_primary,NAND_PATTERN_SIZE)) {
-            CYG_BYTE found_ver = oobbuf[NAND_VERSION_OFFSET];
+
+        rv = nand_oob_packed_read(dev, NAND_PATTERN_OFFSET, NAND_PATTERN_SIZE, oobbuf, patternbuf);
+        if (rv != 0) EG(-EIO); // should never fail given the sanity check above
+        rv = nand_oob_packed_read(dev, NAND_VERSION_OFFSET, NAND_VERSION_SIZE, oobbuf, versionbuf);
+        if (rv != 0) EG(-EIO); // should never fail given the sanity check above
+
+        if (0==memcmp(patternbuf, nand_pattern_primary, NAND_PATTERN_SIZE)) {
+            CYG_BYTE found_ver = *versionbuf;
+#if (NAND_VERSION_SIZE != 1)
+#error CT_ASSERT: version handling needs recoded
+#endif
             if (pri_ver) {
                 /* hmm, we've already found one */
                 if (found_ver < pri_ver) continue /* older */;
@@ -338,8 +408,8 @@ int cyg_nand_bbti_find_tables(cyg_nand_d
             dev->bbt.primary = blk;
             continue;
         }
-        if (0==memcmp(&oobbuf[NAND_PATTERN_OFFSET],nand_pattern_mirror,NAND_PATTERN_SIZE)) {
-            CYG_BYTE found_ver = oobbuf[NAND_VERSION_OFFSET];
+        if (0==memcmp(patternbuf, nand_pattern_mirror, NAND_PATTERN_SIZE)) {
+            CYG_BYTE found_ver = *versionbuf;
             if (mir_ver) {
                 /* hmm, we've already found one */
                 if (found_ver < mir_ver) continue /* older */;
@@ -519,16 +589,15 @@ static int bbti_write_one_table(cyg_nand
         }
 
         /* Prep ECC & OOB, then send */
-        CYG_BYTE oob[dev->spare_per_page];
-        memset(oob, 0xff, dev->spare_per_page);
-        memcpy(&oob[NAND_PATTERN_OFFSET], pattern, NAND_PATTERN_SIZE);
-        oob[NAND_VERSION_OFFSET] = dev->bbt.version;
+        CYG_BYTE appspare[NAND_APPSPARE_PER_PAGE(dev)];
+        memset(appspare, 0xff, sizeof appspare);
 
-        CYG_BYTE ecc[CYG_NAND_ECCPERPAGE(dev)];
-        nand_ecci_calc_page(dev, bbt_pagebuf, ecc);
-        nand_oob_pack(dev, 0, 0, ecc, oob);
+        rv = nand_oob_packed_write(dev, NAND_PATTERN_OFFSET, NAND_PATTERN_SIZE, appspare, pattern);
+        if (rv != 0) EG(-EIO); // Should never fail, we've passed the startup sanity check.
+        rv = nand_oob_packed_write(dev, NAND_VERSION_OFFSET, NAND_VERSION_SIZE, appspare, &dev->bbt.version);
+        if (rv != 0) EG(-EIO); // Should never fail, we've passed the startup sanity check.
 
-        rv = dev->fns->write_page(dev, pg, bbt_pagebuf, pagesize, oob, dev->spare_per_page);
+        rv = nandi_write_page_raw(dev, pg, bbt_pagebuf, pagesize, appspare, sizeof appspare);
         if (rv==-EIO) {
             /* Ouch. Our BBT block has failed. We cannot write another, 
              * as we might trample app data. We'll mark bad, and hope
@@ -556,11 +625,6 @@ static int cyg_nand_bbti_write_tables(cy
 {
     int rv, retries=-1;
 
-    if (NAND_VERSION_OFFSET > dev->spare_per_page) {
-        NAND_ERROR(dev,"BUG: BBT ident/version offset overrun this device's spare area size\n");
-        return -ENOSYS;
-    }
-
 top:
     ++retries;
 
--- a/packages/io/nand/current/src/nand_bbt.h
+++ b/packages/io/nand/current/src/nand_bbt.h
@@ -112,6 +112,16 @@ int cyg_nand_bbti_build_tables(cyg_nand_
  * first page of the eraseblock.
  */
 
+/* Raw unchecked NAND access, for use by the BBT ===================== */
+
+int nandi_read_page_raw(cyg_nand_device *dev, cyg_nand_page_addr page,
+                        CYG_BYTE * dest, size_t size,
+                        CYG_BYTE * spare, size_t spare_size);
+
+int nandi_write_page_raw(cyg_nand_device *dev, cyg_nand_page_addr page,
+                         const CYG_BYTE * src, size_t size,
+                         const CYG_BYTE * spare, size_t spare_size);
+
 /* =================================================================== */
 
 #endif
--- a/packages/io/nand/current/src/nand_ecc_mtd.c
+++ b/packages/io/nand/current/src/nand_ecc_mtd.c
@@ -134,7 +134,8 @@ void nand_trans_result(u_char reg2, u_ch
 /*
  * Calculate 3 byte ECC code for 256 byte block
  */
-static void mtd_calculate_ecc(const u_char *dat, u_char *ecc_code)
+static void mtd_calculate_ecc(struct _cyg_nand_device_t *dev,
+        const u_char *dat, size_t nbytes, u_char *ecc_code)
 {
     u_char idx, reg1, reg2, reg3;
     int j;
@@ -145,9 +146,11 @@ static void mtd_calculate_ecc(const u_ch
     
     /* Build up column parity */ 
     for(j = 0; j < 256; j++) {
+        u_char d = 0xff;
+        if (j < nbytes) d = dat[j];
         
         /* Get CP0 - CP5 from table */
-        idx = nand_ecc_precalc_table[dat[j]];
+        idx = nand_ecc_precalc_table[d];
         reg1 ^= (idx & 0x3f);
         
         /* All bit XOR = 1 ? */
@@ -169,7 +172,8 @@ static void mtd_calculate_ecc(const u_ch
 /*
  * Detect and correct a 1 bit error for 256 byte block
  */
-static int mtd_correct_data(u_char *dat, u_char *read_ecc, const u_char *calc_ecc)
+static int mtd_correct_data(struct _cyg_nand_device_t *dev,
+        u_char *dat, size_t nbytes, u_char *read_ecc, const u_char *calc_ecc)
 {
     u_char a, b, c, d1, d2, d3, add, bit, i;
     
@@ -215,9 +219,12 @@ static int mtd_correct_data(u_char *dat,
                 b >>= 1;
             }
             b = 0x01;
-            a = dat[add];
-            a ^= (b << bit);
-            dat[add] = a;
+
+            if (add < nbytes) {
+                a = dat[add];
+                a ^= (b << bit);
+                dat[add] = a;
+            }
             return 1;
         }
         else {
@@ -255,5 +262,5 @@ static int mtd_correct_data(u_char *dat,
     return -1;
 }
 
-CYG_NAND_ECC_ALG(linux_mtd_ecc, 256, 3, mtd_calculate_ecc, mtd_correct_data);
+CYG_NAND_ECC_ALG_SW(linux_mtd_ecc, 256, 3, NULL, mtd_calculate_ecc, mtd_correct_data);
 
--- a/packages/io/nand/current/src/nand_oob.c
+++ b/packages/io/nand/current/src/nand_oob.c
@@ -100,6 +100,71 @@ void nand_oob_unpack(struct _cyg_nand_de
     unpack(layout->app, CYG_NAND_OOB_MAX_APP_SLOTS, app_o, app_max, oob);
 }
 
+/* Writes @len@ bytes of @data@ into an @oobbuf@ such that the data 
+ * will end up at the given @rawpos@ in the packed layout.
+ * Returns 0 for success or -1 if it's not possible. */
+__externC
+int nand_oob_packed_write(struct _cyg_nand_device_t *dev,
+        size_t rawpos, size_t len,
+        CYG_BYTE *oobbuf, const CYG_BYTE *data)
+{
+    const cyg_nand_oob_layout *layout = dev->oob;
+    int i;
+
+    for (i=0; i<CYG_NAND_OOB_MAX_APP_SLOTS; i++) {
+        const oob_vector *v = &layout->app[i];
+        int offset = rawpos - v->pos; // offset within this vector element (and lower fencepost check)
+        if (offset >= 0) {
+            int avail = v->len - offset; // how many bytes can we put in this element, starting at offset? This gives us an upper fencepost check.
+            if (avail > 0) {
+                // OK, we can do something here.
+                if (avail > len) avail = len;
+                memcpy(&oobbuf[offset], data, len);
+                len -= avail;
+                data += avail;
+                if (!len) return 0;
+            }
+        }
+        oobbuf += v->len;
+    }
+    return -1; // it didn't work...
+}
+
+/* Opposite of nand_oob_packed_write.
+ * Reads @len@ bytes from an application @oobbuf@ such that they
+ * came from the given @rawpos@ in the packed layout; copies them
+ * to @data@.
+ * Returns 0 for success or -1 if it's not possible. */
+__externC
+int nand_oob_packed_read(struct _cyg_nand_device_t *dev,
+        size_t rawpos, size_t len,
+        const CYG_BYTE *oobbuf, CYG_BYTE *data)
+{
+    const cyg_nand_oob_layout *layout = dev->oob;
+    int i;
+
+    for (i=0; i<CYG_NAND_OOB_MAX_APP_SLOTS; i++) {
+        const oob_vector *v = &layout->app[i];
+        int offset = rawpos - v->pos; // offset within this vector element (and lower fencepost check)
+        if (offset >= 0) {
+            int avail = v->len - offset; // how many bytes can we put in this element, starting at offset? This gives us an upper fencepost check.
+            if (avail > 0) {
+                // OK, we can do something here.
+                if (avail > len) avail = len;
+                memcpy(data, &oobbuf[offset], len);
+                len -= avail;
+                data += avail;
+                if (!len) return 0;
+            }
+        }
+        oobbuf += v->len;
+    }
+    return -1; // it didn't work...
+
+}
+
+// FIXME TODO: create a test case / add to unit test: does it cross boundaries correctly?
+
 /* Layouts from the Linux MTD layer. */
 
 #if 0
--- a/packages/io/nand/current/tests/rwbenchmark.c
+++ b/packages/io/nand/current/tests/rwbenchmark.c
@@ -332,6 +332,14 @@ timing ft_read[NREADS];
 timing ft_write[NWRITES];
 timing ft_erase[NERASES];
 
+static void check_ff(const CYG_BYTE * buf, size_t size)
+{
+    while (size--) {
+        if (*buf != 0xFF) {
+            CYG_TEST_FAIL("readback check failed");
+        }
+    }
+}
 
 void test_reads(cyg_nand_partition *part, cyg_nand_block_addr b)
 {
@@ -343,31 +351,45 @@ void test_reads(cyg_nand_partition *part
     unsigned char oob[oobz];
 #define ft ft_read
 
+    // TODO: Rather than reading 0xff, read back a test pattern that we've just put in there.
+#define CLEARDATA() memset(pagebuffer, 0, sizeof pagebuffer)
+#define CHECKDATA() check_ff(pagebuffer, sizeof pagebuffer)
+#define CLEAROOB() memset(oob, 0, oobz)
+#define CHECKOOB() check_ff(oob, oobz)
+
     for (i=0; i < NREADS; i++) {
+        CLEARDATA();
         wait_for_tick();
         get_timestamp(&ft[i].start);
         cyg_nand_read_page(part, pg, pagebuffer, sizeof pagebuffer, 0, 0);
         get_timestamp(&ft[i].end);
+        CHECKDATA();
         ++pg;
         if (pg > pgend) pg = pgstart;
     }
     show_times(ft, NREADS, "NAND page reads (page data only)");
 
     for (i=0; i < NREADS; i++) {
+        CLEAROOB();
         wait_for_tick();
         get_timestamp(&ft[i].start);
         cyg_nand_read_page(part, pg, 0, 0, oob, oobz);
         get_timestamp(&ft[i].end);
+        CHECKOOB();
         ++pg;
         if (pg > pgend) pg = pgstart;
     }
     show_times(ft, NREADS, "NAND page reads (OOB only)");
 
     for (i=0; i < NREADS; i++) {
+        CLEARDATA();
+        CLEAROOB();
         wait_for_tick();
         get_timestamp(&ft[i].start);
         cyg_nand_read_page(part, pg, pagebuffer, sizeof pagebuffer, oob, oobz);
         get_timestamp(&ft[i].end);
+        CHECKDATA();
+        CHECKOOB();
         ++pg;
         if (pg > pgend) pg = pgstart;
     }
@@ -442,6 +464,7 @@ void rwbenchmark_main(void)
     show_test_parameters();
     show_times_hdr();
 
+    cyg_nand_erase_block(part, block);
     test_reads(part, block);
     test_writes(part,block);
     test_erases(part,block);