From 68dd7cdd0c0e72d1ea1dcf484feab17c9a74f5c7 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Tue, 30 Apr 2013 19:10:54 -0600 Subject: SMART block driver plus changes to M25P and RAM drivers needed for SMART support --- nuttx/configs/mikroe-stm32f4/src/up_nsh.c | 6 +- nuttx/configs/mikroe-stm32f4/usbnsh/defconfig | 4 +- nuttx/configs/sim/mtdpart/defconfig | 1 - nuttx/configs/sim/nxffs/defconfig | 1 - nuttx/drivers/mtd/Kconfig | 57 +- nuttx/drivers/mtd/Make.defs | 14 +- nuttx/drivers/mtd/m25px.c | 253 ++- nuttx/drivers/mtd/rammtd.c | 56 +- nuttx/drivers/mtd/smart.c | 2177 +++++++++++++++++++++++++ nuttx/include/nuttx/smart.h | 116 ++ 10 files changed, 2657 insertions(+), 28 deletions(-) create mode 100644 nuttx/drivers/mtd/smart.c create mode 100644 nuttx/include/nuttx/smart.h diff --git a/nuttx/configs/mikroe-stm32f4/src/up_nsh.c b/nuttx/configs/mikroe-stm32f4/src/up_nsh.c index e3f973794..179d0eab0 100644 --- a/nuttx/configs/mikroe-stm32f4/src/up_nsh.c +++ b/nuttx/configs/mikroe-stm32f4/src/up_nsh.c @@ -153,7 +153,7 @@ ****************************************************************************/ /* Pre-allocated simulated flash */ -#ifdef CONFIG_MTD_RAM +#ifdef CONFIG_RAMMTD //static uint8_t g_simflash[CONFIG_EXAMPLES_SMART_BUFSIZE]; #endif @@ -217,7 +217,7 @@ int nsh_archinitialize(void) /* Create a RAM MTD device if configured */ -#ifdef CONFIG_MTD_RAM +#ifdef CONFIG_RAMMTD { uint8_t *start = (uint8_t *) kmalloc(CONFIG_EXAMPLES_SMART_BUFSIZE); mtd = rammtd_initialize(start, CONFIG_EXAMPLES_SMART_BUFSIZE); @@ -229,7 +229,7 @@ int nsh_archinitialize(void) #endif } -#endif /* CONFIG_MTD_RAM */ +#endif /* CONFIG_RAMMTD */ #endif /* CONFIG_MTD */ #endif /* CONFIG_STM32_SPI3 */ diff --git a/nuttx/configs/mikroe-stm32f4/usbnsh/defconfig b/nuttx/configs/mikroe-stm32f4/usbnsh/defconfig index fb474a0c9..3567353f5 100644 --- a/nuttx/configs/mikroe-stm32f4/usbnsh/defconfig +++ b/nuttx/configs/mikroe-stm32f4/usbnsh/defconfig @@ -413,8 +413,8 @@ CONFIG_MP25P_BYTEWRITE=y CONFIG_MTD_SMART=y CONFIG_MTD_SMART_SECTOR_SIZE=512 # CONFIG_MTD_RAMTRON is not set -CONFIG_MTD_RAM=y -CONFIG_MTD_RAM_SMART=y +CONFIG_RAMMTD=y +CONFIG_RAMMTD_SMART=y # CONFIG_MTD_SST25 is not set # CONFIG_MTD_SST39FV is not set # CONFIG_MTD_W25 is not set diff --git a/nuttx/configs/sim/mtdpart/defconfig b/nuttx/configs/sim/mtdpart/defconfig index f3e195919..e35f72438 100644 --- a/nuttx/configs/sim/mtdpart/defconfig +++ b/nuttx/configs/sim/mtdpart/defconfig @@ -216,7 +216,6 @@ CONFIG_RAMMTD_FLASHSIM=y # CONFIG_MTD_AT45DB is not set # CONFIG_MTD_MP25P is not set # CONFIG_MTD_RAMTRON is not set -# CONFIG_MTD_RAM is not set # CONFIG_MTD_SST25 is not set # CONFIG_MTD_SST39FV is not set # CONFIG_MTD_W25 is not set diff --git a/nuttx/configs/sim/nxffs/defconfig b/nuttx/configs/sim/nxffs/defconfig index a2e663e8a..d3b0b0f9b 100644 --- a/nuttx/configs/sim/nxffs/defconfig +++ b/nuttx/configs/sim/nxffs/defconfig @@ -216,7 +216,6 @@ CONFIG_RAMMTD_FLASHSIM=y # CONFIG_MTD_AT45DB is not set # CONFIG_MTD_MP25P is not set # CONFIG_MTD_RAMTRON is not set -# CONFIG_MTD_RAM is not set # CONFIG_MTD_SST25 is not set # CONFIG_MTD_SST39FV is not set # CONFIG_MTD_W25 is not set diff --git a/nuttx/drivers/mtd/Kconfig b/nuttx/drivers/mtd/Kconfig index f7ded475a..6755ffe7a 100644 --- a/nuttx/drivers/mtd/Kconfig +++ b/nuttx/drivers/mtd/Kconfig @@ -51,6 +51,13 @@ config RAMMTD_FLASHSIM RAMMTD_FLASHSIM will add some extra logic to improve the level of FLASH simulation. +config RAMMTD_SMART + bool "SMART block driver support in the RAM MTD driver" + default n + ---help--- + Builds in additional ioctl and interface code to support the + SMART block driver / filesystem. + endif config MTD_AT24XX @@ -113,8 +120,54 @@ config MP25P_MANUFACTURER for the STMicro MP25x serial FLASH. If, for example, you are using the a Macronix International MX25 serial FLASH, the correct manufacturer ID would be 0xc2. +config MP25P_MEMORY_TYPE + hex "MP25P memory type ID" + default 0x20 + ---help--- + The memory type for M25 "P" series is 0x20, but the driver also supports "F" series + devices, such as the EON EN25F80 part which adds a 4K sector erase capability. The + ID for "F" series parts from EON is 0x31. + +config MP25P_SUBSECTOR_ERASE + bool "Sub-Sector Erase" + default n + ---help--- + Some devices (the EON EN25F80) support a smaller erase block size (4K vs 64K). + This option enables support for sub-sector erase. The SMART file system can + take advantage of this option if it is enabled. + +config MP25P_BYTEWRITE + bool "Enable ByteWrite ioctl support" + default n + ---help--- + The MP25P series of devices allow writing to a page with less than a full-page + size of data. In this case, only the written bytes are updated without affecting + the other bytes in the page. The SMART FS requires this option for proper operation. + endif +config MTD_SMART + bool "Sector Mapped Allocation for Really Tiny (SMART) Flash support" + default y + select MP25P_BYTEWRITE + ---help--- + The MP25x series of Flash devices are typically very small and have a very large + erase block size. This causes issues with the standard Flash Translation Layer + block driver since it tries to allocate a RAM block the size of a flash erase + block, which is typically 64K. This block driver uses a different approach + to sacrifice performance for RAM memory footprint by saving data in sectors + (typically 2K - 4K based on memory size) and relocating sectors as needed when + an erase block needs to be erased. + +config MTD_SMART_SECTOR_SIZE + int "SMART Device sector size" + depends on MTD_SMART + default 1024 + ---help--- + Sets the size of a single alloction on the SMART device. Larger sector sizes + reduce overhead per sector, but cause more wasted space with a lot of smaller + files. + config MTD_RAMTRON bool "SPI-based RAMTRON NVRAM Devices FM25V10" default n @@ -122,10 +175,6 @@ config MTD_RAMTRON ---help--- SPI-based RAMTRON NVRAM Devices FM25V10 -config MTD_RAM - bool "Memory bus ram" - default n - config MTD_SST25 bool "SPI-based SST25 FLASH" default n diff --git a/nuttx/drivers/mtd/Make.defs b/nuttx/drivers/mtd/Make.defs index fd16fd405..3f6c8b6fb 100644 --- a/nuttx/drivers/mtd/Make.defs +++ b/nuttx/drivers/mtd/Make.defs @@ -3,7 +3,7 @@ # These driver supports various Memory Technology Devices (MTD) using the # NuttX MTD interface. # -# Copyright (C) 2009-2012 Gregory Nutt. All rights reserved. +# Copyright (C) 2009-2013 Gregory Nutt. All rights reserved. # Author: Gregory Nutt # # Redistribution and use in source and binary forms, with or without @@ -39,12 +39,16 @@ ifeq ($(CONFIG_MTD),y) -CSRCS += at45db.c flash_eraseall.c ftl.c m25px.c rammtd.c ramtron.c +CSRCS += at45db.c flash_eraseall.c ftl.c m25px.c ramtron.c ifeq ($(CONFIG_MTD_PARTITION),y) CSRCS += mtd_partition.c endif +ifeq ($(CONFIG_RAMMTD),y) +CSRCS += rammtd.c +endif + ifeq ($(CONFIG_MTD_AT24XX),y) CSRCS += at24xx.c endif @@ -65,6 +69,12 @@ ifeq ($(CONFIG_MTD_AT25),y) CSRCS += at25.c endif +ifeq ($(CONFIG_MTD_SMART),y) +ifeq ($(CONFIG_FS_SMARTFS),y) +CSRCS += smart.c +endif +endif + # Include MTD driver support DEPPATH += --dep-path mtd diff --git a/nuttx/drivers/mtd/m25px.c b/nuttx/drivers/mtd/m25px.c index 4f96c5a3b..ad0f536c2 100644 --- a/nuttx/drivers/mtd/m25px.c +++ b/nuttx/drivers/mtd/m25px.c @@ -78,12 +78,18 @@ # define CONFIG_MP25P_MANUFACTURER 0x20 #endif +#ifndef CONFIG_MP25P_MEMORY_TYPE +# define CONFIG_MP25P_MEMORY_TYPE 0x20 +#endif + /* M25P Registers *******************************************************************/ /* Indentification register values */ #define M25P_MANUFACTURER CONFIG_MP25P_MANUFACTURER -#define M25P_MEMORY_TYPE 0x20 +#define M25P_MEMORY_TYPE CONFIG_MP25P_MEMORY_TYPE +#define M25P_RES_ID 0x13 #define M25P_M25P1_CAPACITY 0x11 /* 1 M-bit */ +#define M25P_EN25F80_CAPACITY 0x14 /* 8 M-bit */ #define M25P_M25P32_CAPACITY 0x16 /* 32 M-bit */ #define M25P_M25P64_CAPACITY 0x17 /* 64 M-bit */ #define M25P_M25P128_CAPACITY 0x18 /* 128 M-bit */ @@ -98,6 +104,17 @@ #define M25P_M25P1_PAGE_SHIFT 8 /* Page size 1 << 8 = 256 */ #define M25P_M25P1_NPAGES 512 +/* EN25F80 capacity is 1,048,576 bytes: + * (16 sectors) * (65,536 bytes per sector) + * (512 pages) * (256 bytes per page) + */ + +#define M25P_EN25F80_SECTOR_SHIFT 16 /* Sector size 1 << 15 = 65,536 */ +#define M25P_EN25F80_NSECTORS 16 +#define M25P_EN25F80_PAGE_SHIFT 8 /* Page size 1 << 8 = 256 */ +#define M25P_EN25F80_NPAGES 4096 +#define M25P_EN25F80_SUBSECT_SHIFT 12 /* Sub-Sector size 1 << 12 = 4,096 */ + /* M25P32 capacity is 4,194,304 bytes: * (64 sectors) * (65,536 bytes per sector) * (16384 pages) * (256 bytes per page) @@ -142,6 +159,7 @@ #define M25P_BE 0xc7 /* 1 Bulk Erase 0 0 0 */ #define M25P_DP 0xb9 /* 2 Deep power down 0 0 0 */ #define M25P_RES 0xab /* 2 Read Electronic Signature 0 3 >=1 */ +#define M25P_SSE 0x20 /* 1 Sub-Sector Erase 0 0 0 */ /* NOTE 1: All parts, NOTE 2: M25P632/M25P64 */ @@ -181,6 +199,9 @@ struct m25p_dev_s uint8_t pageshift; /* 8 */ uint16_t nsectors; /* 128 or 64 */ uint32_t npages; /* 32,768 or 65,536 */ +#ifdef CONFIG_MP25P_SUBSECTOR_ERASE + uint8_t subsectorshift; /* 0, 12 or 13 (4K or 8K) */ +#endif }; /************************************************************************************ @@ -198,6 +219,9 @@ static inline void m25p_sectorerase(struct m25p_dev_s *priv, off_t offset); static inline int m25p_bulkerase(struct m25p_dev_s *priv); static inline void m25p_pagewrite(struct m25p_dev_s *priv, FAR const uint8_t *buffer, off_t offset); +#ifdef CONFIG_MP25P_SUBSECTOR_ERASE +static inline void m25p_subsectorerase(struct m25p_dev_s *priv, off_t offset); +#endif /* MTD driver methods */ @@ -292,6 +316,10 @@ static inline int m25p_readid(struct m25p_dev_s *priv) { /* Okay.. is it a FLASH capacity that we understand? */ +#ifdef CONFIG_MP25P_SUBSECTOR_ERASE + priv->subsectorshift = 0; +#endif + if (capacity == M25P_M25P1_CAPACITY) { /* Save the FLASH geometry */ @@ -302,6 +330,19 @@ static inline int m25p_readid(struct m25p_dev_s *priv) priv->npages = M25P_M25P1_NPAGES; return OK; } + else if (capacity == M25P_EN25F80_CAPACITY) + { + /* Save the FLASH geometry */ + + priv->sectorshift = M25P_EN25F80_SECTOR_SHIFT; + priv->nsectors = M25P_EN25F80_NSECTORS; + priv->pageshift = M25P_EN25F80_PAGE_SHIFT; + priv->npages = M25P_EN25F80_NPAGES; +#ifdef CONFIG_MP25P_SUBSECTOR_ERASE + priv->subsectorshift = M25P_EN25F80_SUBSECT_SHIFT; +#endif + return OK; + } else if (capacity == M25P_M25P32_CAPACITY) { /* Save the FLASH geometry */ @@ -356,7 +397,7 @@ static void m25p_waitwritecomplete(struct m25p_dev_s *priv) /* Send "Read Status Register (RDSR)" command */ (void)SPI_SEND(priv->dev, M25P_RDSR); - + /* Loop as long as the memory is busy with a write cycle */ do @@ -424,7 +465,7 @@ static void m25p_writeenable(struct m25p_dev_s *priv) /* Send "Write Enable (WREN)" command */ (void)SPI_SEND(priv->dev, M25P_WREN); - + /* Deselect the FLASH */ SPI_SELECT(priv->dev, SPIDEV_FLASH, false); @@ -476,6 +517,53 @@ static inline void m25p_sectorerase(struct m25p_dev_s *priv, off_t sector) fvdbg("Erased\n"); } +/************************************************************************************ + * Name: m25p_subsectorerase + ************************************************************************************/ + +#ifdef CONFIG_MP25P_SUBSECTOR_ERASE +static inline void m25p_subsectorerase(struct m25p_dev_s *priv, off_t subsector) +{ + off_t offset = subsector << priv->subsectorshift; + + fvdbg("subsector: %9lx\n", (long)subsector); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + m25p_waitwritecomplete(priv); + + /* Send write enable instruction */ + + m25p_writeenable(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Sub-Sector Erase (SSE)" instruction */ + + (void)SPI_SEND(priv->dev, M25P_SSE); + + /* Send the sector offset high byte first. For all of the supported + * parts, the sector number is completely contained in the first byte + * and the values used in the following two bytes don't really matter. + */ + + (void)SPI_SEND(priv->dev, (offset >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (offset >> 8) & 0xff); + (void)SPI_SEND(priv->dev, offset & 0xff); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Erased\n"); +} +#endif + /************************************************************************************ * Name: m25p_bulkerase ************************************************************************************/ @@ -533,7 +621,7 @@ static inline void m25p_pagewrite(struct m25p_dev_s *priv, FAR const uint8_t *bu /* Enable the write access to the FLASH */ m25p_writeenable(priv); - + /* Select this FLASH part */ SPI_SELECT(priv->dev, SPIDEV_FLASH, true); @@ -551,13 +639,60 @@ static inline void m25p_pagewrite(struct m25p_dev_s *priv, FAR const uint8_t *bu /* Then write the specified number of bytes */ SPI_SNDBLOCK(priv->dev, buffer, 1 << priv->pageshift); - + /* Deselect the FLASH: Chip Select high */ SPI_SELECT(priv->dev, SPIDEV_FLASH, false); fvdbg("Written\n"); } +/************************************************************************************ + * Name: m25p_bytewrite + ************************************************************************************/ + +#ifdef CONFIG_MP25P_BYTEWRITE +static inline void m25p_bytewrite(struct m25p_dev_s *priv, FAR const uint8_t *buffer, + off_t offset, uint16_t count) +{ + fvdbg("offset: %08lx count:%d\n", (long)offset, count); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + m25p_waitwritecomplete(priv); + + /* Enable the write access to the FLASH */ + + m25p_writeenable(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Page Program (PP)" command */ + + (void)SPI_SEND(priv->dev, M25P_PP); + + /* Send the page offset high byte first. */ + + (void)SPI_SEND(priv->dev, (offset >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (offset >> 8) & 0xff); + (void)SPI_SEND(priv->dev, offset & 0xff); + + /* Then write the specified number of bytes */ + + SPI_SNDBLOCK(priv->dev, buffer, count); + + /* Deselect the FLASH: Chip Select high */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Written\n"); +} +#endif + /************************************************************************************ * Name: m25p_erase ************************************************************************************/ @@ -614,6 +749,7 @@ static ssize_t m25p_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t n { FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; size_t blocksleft = nblocks; + size_t pagesize = 1 << priv->pageshift; fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); @@ -623,6 +759,7 @@ static ssize_t m25p_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t n while (blocksleft-- > 0) { m25p_pagewrite(priv, buffer, startblock); + buffer += pagesize; startblock++; } m25p_unlock(priv->dev); @@ -703,10 +840,15 @@ static int m25p_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) * appear so. */ - geo->blocksize = (1 << priv->pageshift); - geo->erasesize = (1 << priv->sectorshift); - geo->neraseblocks = priv->nsectors; - ret = OK; + geo->blocksize = (1 << priv->pageshift); + geo->erasesize = (1 << priv->sectorshift); + geo->neraseblocks = priv->nsectors; +#ifdef CONFIG_MP25P_SUBSECTOR_ERASE + geo->subsectorsize = (1 << priv->subsectorshift); + geo->nsubsectors = priv->nsectors * (1 << (priv->sectorshift - + priv->subsectorshift)); +#endif + ret = OK; fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", geo->blocksize, geo->erasesize, geo->neraseblocks); @@ -723,7 +865,98 @@ static int m25p_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) m25p_unlock(priv->dev); } break; - + +#ifdef CONFIG_FS_SMARTFS + case MTDIOC_GETCAPS: + { + ret = 0; +#ifdef CONFIG_MP25P_BYTEWRITE + ret |= MTDIOC_CAPS_BYTEWRITE; +#endif + +#ifdef CONFIG_MP25P_SUBSECTOR_ERASE + ret |= MTDIOC_CAPS_SECTERASE; +#endif + break; + } +#endif /* CONFIG_FS_SMARTFS */ + +#ifdef CONFIG_MP25P_SUBSECTOR_ERASE + case MTDIOC_SECTERASE: + { + m25p_subsectorerase(priv, (off_t) arg); + ret = OK; + break; + } +#endif + +#ifdef CONFIG_MP25P_BYTEWRITE + case MTDIOC_BYTEWRITE: + { + struct mtd_byte_write_s *bytewrite = (struct mtd_byte_write_s *) arg; + int startpage; + int endpage; + int count; + int index; + int pagesize; + int bytestowrite; + size_t offset; + + /* We must test if the offset + count crosses one or more pages + * and perform individual writes. The devices can only write in + * page increments. + */ + + startpage = bytewrite->offset / (1 << priv->pageshift); + endpage = (bytewrite->offset + bytewrite->count) / (1 << priv->pageshift); + + if (startpage == endpage) + { + m25p_bytewrite(priv, bytewrite->buffer, bytewrite->offset, + bytewrite->count); + } + else + { + /* Write the 1st partial-page */ + + count = bytewrite->count; + pagesize = (1 << priv->pageshift); + offset = bytewrite->offset; + bytestowrite = pagesize - (bytewrite->offset & (pagesize-1)); + m25p_bytewrite(priv, bytewrite->buffer, offset, bytestowrite); + + /* Update offset and count */ + + offset += bytestowrite; + count -= bytestowrite; + index = bytestowrite; + + /* Write full pages */ + + while (count >= pagesize) + { + m25p_bytewrite(priv, &bytewrite->buffer[index], offset, pagesize); + + /* Update offset and count */ + + offset += pagesize; + count -= pagesize; + index += pagesize; + } + + /* Now write any partial page at the end */ + + if (count > 0) + { + m25p_bytewrite(priv, &bytewrite->buffer[index], offset, count); + } + } + + ret = OK; + break; + } +#endif + case MTDIOC_XIPBASE: default: ret = -ENOTTY; /* Bad command */ diff --git a/nuttx/drivers/mtd/rammtd.c b/nuttx/drivers/mtd/rammtd.c index 1d7a570e1..cbfe83e45 100644 --- a/nuttx/drivers/mtd/rammtd.c +++ b/nuttx/drivers/mtd/rammtd.c @@ -1,7 +1,7 @@ /**************************************************************************** * drivers/mtd/rammtd.c * - * Copyright (C) 2011-2012 Gregory Nutt. All rights reserved. + * Copyright (C) 2011-2013 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -224,6 +224,30 @@ static int ram_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks return OK; } +/**************************************************************************** + * Name: ram_readbytes + ****************************************************************************/ + +#ifdef CONFIG_RAMMTD_SMART +static ssize_t ram_read_bytes(FAR struct mtd_dev_s *dev, off_t offset, + size_t nbytes, FAR uint8_t *buf) +{ + FAR struct ram_dev_s *priv = (FAR struct ram_dev_s *)dev; + + DEBUGASSERT(dev && buf); + + /* Don't let read read past end of buffer */ + + if (offset + nbytes > priv->nblocks * CONFIG_RAMMTD_ERASESIZE) + { + return 0; + } + + ram_read(buf, &priv->start[offset], nbytes); + return nbytes; +} +#endif + /**************************************************************************** * Name: ram_bread ****************************************************************************/ @@ -349,13 +373,31 @@ static int ram_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) { size_t size = priv->nblocks * CONFIG_RAMMTD_ERASESIZE; - /* Erase the entire device */ + /* Erase the entire device */ memset(priv->start, CONFIG_RAMMTD_ERASESTATE, size); - ret = OK; + ret = OK; } break; - + +#ifdef CONFIG_RAMMTD_SMART + case MTDIOC_GETCAPS: + { + ret = MTDIOC_CAPS_BYTEWRITE; + break; + } + + case MTDIOC_BYTEWRITE: + { + struct mtd_byte_write_s *bytewrite = (struct mtd_byte_write_s *) arg; + + ram_write(&priv->start[bytewrite->offset], bytewrite->buffer, + bytewrite->count); + ret = OK; + break; + } +#endif + default: ret = -ENOTTY; /* Bad command */ break; @@ -402,7 +444,7 @@ FAR struct mtd_dev_s *rammtd_initialize(FAR uint8_t *start, size_t size) fdbg("Need to provide at least one full erase block\n"); return NULL; } - + /* Perform initialization as necessary */ priv->mtd.erase = ram_erase; @@ -411,6 +453,10 @@ FAR struct mtd_dev_s *rammtd_initialize(FAR uint8_t *start, size_t size) priv->mtd.ioctl = ram_ioctl; priv->mtd.erase = ram_erase; +#ifdef CONFIG_RAMMTD_SMART + priv->mtd.read = ram_read_bytes; +#endif + priv->start = start; priv->nblocks = nblocks; return &priv->mtd; diff --git a/nuttx/drivers/mtd/smart.c b/nuttx/drivers/mtd/smart.c new file mode 100644 index 000000000..c20656565 --- /dev/null +++ b/nuttx/drivers/mtd/smart.c @@ -0,0 +1,2177 @@ +/**************************************************************************** + * drivers/mtd/smart.c + * + * Sector Mapped Allocation for Really Tiny (SMART) Flash block driver. + * + * Copyright (C) 2013 Ken Pettit. All rights reserved. + * Author: Ken Pettit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Private Definitions + ****************************************************************************/ + +#define SMART_STATUS_COMMITTED 0x80 +#define SMART_STATUS_RELEASED 0x40 +#define SMART_STATUS_SIZEBITS 0x1C +#define SMART_STATUS_VERBITS 0x03 +#define SMART_STATUS_VERSION 0x01 + +#define SMART_SECTSIZE_256 0x00 +#define SMART_SECTSIZE_512 0x04 +#define SMART_SECTSIZE_1024 0x08 +#define SMART_SECTSIZE_2048 0x0C +#define SMART_SECTSIZE_4096 0x10 +#define SMART_SECTSIZE_8192 0x14 +#define SMART_SECTSIZE_16384 0x18 + +#define SMART_FMT_STAT_UNKNOWN 0 +#define SMART_FMT_STAT_FORMATTED 1 +#define SMART_FMT_STAT_NOFMT 2 + +#define SMART_FMT_POS1 sizeof(struct smart_sect_header_s) +#define SMART_FMT_POS2 (SMART_FMT_POS1 + 1) +#define SMART_FMT_POS3 (SMART_FMT_POS1 + 2) +#define SMART_FMT_POS4 (SMART_FMT_POS1 + 3) + +#define SMART_FMT_SIG1 'S' +#define SMART_FMT_SIG2 'M' +#define SMART_FMT_SIG3 'R' +#define SMART_FMT_SIG4 'T' + +#define SMART_FMT_VERSION_POS (SMART_FMT_POS1 + 4) +#define SMART_FMT_NAMESIZE_POS (SMART_FMT_POS1 + 5) +#define SMART_FMT_ROOTDIRS_POS (SMART_FMT_POS1 + 6) +#define SMARTFS_FMT_AGING_POS 32 + +#define SMART_FMT_VERSION 1 + +#define SMART_FIRST_ALLOC_SECTOR 16 /* First logical sector number we will + * use for assignment of requested Alloc + * sectors. All enries below this are + * reserved (some for root dir entries, + * other for our use, such as format + * sector, etc. */ + +#if defined(CONFIG_FS_READAHEAD) || (defined(CONFIG_FS_WRITABLE) && defined(CONFIG_FS_WRITEBUFFER)) +# define CONFIG_SMART_RWBUFFER 1 +#endif + +#ifndef CONFIG_MTD_SMART_SECTOR_SIZE +# define CONFIG_MTD_SMART_SECTOR_SIZE 1024 +#endif + +#ifndef offsetof +#define offsetof(type, member) ( (size_t) &( ( (type *) 0)->member)) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct smart_struct_s +{ + FAR struct mtd_dev_s *mtd; /* Contained MTD interface */ + struct mtd_geometry_s geo; /* Device geometry */ + uint16_t neraseblocks; /* Number of erase blocks or sub-sectors */ + uint16_t freesectors; /* Total number of free sectors */ + uint16_t mtdBlksPerSector; /* Number of MTD blocks per SMART Sector */ + uint16_t sectorsPerBlk; /* Number of sectors per erase block */ + uint16_t sectorsize; /* Sector size on device */ + uint16_t totalsectors; /* Total number of sectors on device */ + FAR uint16_t *sMap; /* Virtual to physical sector map */ + FAR uint8_t *releasecount; /* Count of released sectors per erase block */ + FAR uint8_t *freecount; /* Count of free sectors per erase block */ + FAR char *rwbuffer; /* Our sector read/write buffer */ + uint8_t formatversion; /* Format version on the device */ + uint8_t formatstatus; /* Indicates the status of the device format */ + uint8_t namesize; /* Length of filenames on this device */ +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + uint8_t rootdirentries; /* Number of root directory entries */ + uint8_t minor; /* Minor number of the block entry */ +#endif +}; + +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS +struct smart_multiroot_device_s +{ + FAR struct smart_struct_s* dev; + uint8_t rootdirnum; +}; +#endif + +struct smart_sect_header_s +{ + uint8_t logicalsector[2]; /* The logical sector number */ + uint8_t seq[2]; /* Incrementing sequence number */ + uint8_t status; /* Status of this sector: + * Bit 7: 1 = Not commited + * 0 = commited + * Bit 6: 1 = Not released + * 0 = released + * Bit 5: Reserved - 1 + * Bit 4: Reserved - 1 + * Bit 3: Reserved - 1 + * Bit 2: Reserved - 1 + * Bit 1-0: Format version */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int smart_open(FAR struct inode *inode); +static int smart_close(FAR struct inode *inode); +static ssize_t smart_reload(struct smart_struct_s *dev, FAR uint8_t *buffer, + off_t startblock, size_t nblocks); +static ssize_t smart_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#ifdef CONFIG_FS_WRITABLE +static ssize_t smart_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#endif +static int smart_geometry(FAR struct inode *inode, struct geometry *geometry); +static int smart_ioctl(FAR struct inode *inode, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct block_operations g_bops = +{ + smart_open, /* open */ + smart_close, /* close */ + smart_read, /* read */ +#ifdef CONFIG_FS_WRITABLE + smart_write, /* write */ +#else + NULL, /* write */ +#endif + smart_geometry, /* geometry */ + smart_ioctl /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: smart_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int smart_open(FAR struct inode *inode) +{ + fvdbg("Entry\n"); + return OK; +} + +/**************************************************************************** + * Name: smart_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int smart_close(FAR struct inode *inode) +{ + fvdbg("Entry\n"); + return OK; +} + +/**************************************************************************** + * Name: smart_reload + * + * Description: Read the specified numer of sectors + * + ****************************************************************************/ + +static ssize_t smart_reload(struct smart_struct_s *dev, FAR uint8_t *buffer, + off_t startblock, size_t nblocks) +{ + ssize_t nread; + ssize_t mtdBlocks, mtdStartBlock; + + /* Calculate the number of MTD blocks to read */ + + mtdBlocks = nblocks * dev->mtdBlksPerSector; + + /* Calculate the first MTD block number */ + + mtdStartBlock = startblock * dev->mtdBlksPerSector; + + /* Read the full erase block into the buffer */ + + fdbg("Read %d blocks starting at block %d\n", mtdBlocks, mtdStartBlock); + nread = MTD_BREAD(dev->mtd, mtdStartBlock, mtdBlocks, buffer); + if (nread != mtdBlocks) + { + fdbg("Read %d blocks starting at block %d failed: %d\n", + nblocks, startblock, nread); + } + + return nread; +} + +/**************************************************************************** + * Name: smart_read + * + * Description: Read the specified numer of sectors + * + ****************************************************************************/ + +static ssize_t smart_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + struct smart_struct_s *dev; + + fvdbg("SMART: sector: %d nsectors: %d\n", start_sector, nsectors); + + DEBUGASSERT(inode && inode->i_private); +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev; +#else + dev = (struct smart_struct_s *)inode->i_private; +#endif + return smart_reload(dev, buffer, start_sector, nsectors); +} + +/**************************************************************************** + * Name: smart_write + * + * Description: Write (or buffer) the specified number of sectors + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t smart_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + struct smart_struct_s *dev; + off_t alignedblock; + off_t mask; + off_t blkstowrite; + off_t offset; + off_t nextblock; + off_t mtdBlksPerErase; + off_t eraseblock; + size_t remaining; + size_t nxfrd; + int ret; + off_t mtdstartblock, mtdblockcount; + + fvdbg("sector: %d nsectors: %d\n", start_sector, nsectors); + + DEBUGASSERT(inode && inode->i_private); +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev; +#else + dev = (struct smart_struct_s *)inode->i_private; +#endif + + /* I think maybe we need to lock on a mutex here */ + + /* Get the aligned block. Here is is assumed: (1) The number of R/W blocks + * per erase block is a power of 2, and (2) the erase begins with that same + * alignment. + */ + + mask = dev->sectorsPerBlk - 1; + alignedblock = ((start_sector + mask) & ~mask) * dev->mtdBlksPerSector; + + /* Convert SMART blocks into MTD blocks */ + + mtdstartblock = start_sector * dev->mtdBlksPerSector; + mtdblockcount = nsectors * dev->mtdBlksPerSector; + mtdBlksPerErase = dev->mtdBlksPerSector * dev->sectorsPerBlk; + + fvdbg("mtdsector: %d mtdnsectors: %d\n", mtdstartblock, mtdblockcount); + + /* Start at first block to be written */ + + remaining = mtdblockcount; + nextblock = mtdstartblock; + offset = 0; + + /* Loop for all blocks to be written */ + + while (remaining > 0) + { + /* If this is an aligned block, then erase the block */ + + if (alignedblock == nextblock) + { + /* Erase the erase block */ + + eraseblock = alignedblock / mtdBlksPerErase; + ret = MTD_ERASE(dev->mtd, eraseblock, 1); + if (ret < 0) + { + fdbg("Erase block=%d failed: %d\n", eraseblock, ret); + + /* Unlock the mutex if we add one */ + + return ret; + } + } + + /* Calculate the number of blocks to write. */ + + blkstowrite = mtdBlksPerErase; + if (nextblock != alignedblock) + { + blkstowrite = alignedblock - nextblock; + } + + if (blkstowrite > remaining) + { + blkstowrite = remaining; + } + + /* Try to write to the sector. */ + + fdbg("Write MTD block %d from offset %d\n", nextblock, offset); + nxfrd = MTD_BWRITE(dev->mtd, nextblock, blkstowrite, &buffer[offset]); + if (nxfrd != blkstowrite) + { + /* The block is not empty!! What to do? */ + + fdbg("Write block %d failed: %d.\n", nextblock, nxfrd); + + /* Unlock the mutex if we add one */ + + return -EIO; + } + + /* Then update for amount written */ + + nextblock += blkstowrite; + remaining -= blkstowrite; + offset += blkstowrite * dev->geo.blocksize; + alignedblock += mtdBlksPerErase; + } + + return nsectors; +} +#endif /* CONFIG_FS_WRITABLE */ + +/**************************************************************************** + * Name: smart_geometry + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int smart_geometry(FAR struct inode *inode, struct geometry *geometry) +{ + struct smart_struct_s *dev; + uint32_t erasesize; + + fvdbg("Entry\n"); + + DEBUGASSERT(inode); + if (geometry) + { +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev; +#else + dev = (struct smart_struct_s *)inode->i_private; +#endif + geometry->geo_available = true; + geometry->geo_mediachanged = false; +#ifdef CONFIG_FS_WRITABLE + geometry->geo_writeenabled = true; +#else + geometry->geo_writeenabled = false; +#endif + + erasesize = dev->geo.erasesize; + if (erasesize == 0) + { + erasesize = 65536; + } + + geometry->geo_nsectors = dev->geo.neraseblocks * erasesize / + dev->sectorsize; + geometry->geo_sectorsize = dev->sectorsize; + + fvdbg("available: true mediachanged: false writeenabled: %s\n", + geometry->geo_writeenabled ? "true" : "false"); + fvdbg("nsectors: %d sectorsize: %d\n", + geometry->geo_nsectors, geometry->geo_sectorsize); + + return OK; + } + + return -EINVAL; +} + +/**************************************************************************** + * Name: smart_setsectorsize + * + * Description: Sets the device's sector size and recalculates sector size + * dependant variables. + * + ****************************************************************************/ + +static int smart_setsectorsize(struct smart_struct_s *dev, uint16_t size) +{ + uint32_t erasesize; + uint32_t totalsectors; + int ret; + + /* Check if the device supports sub-sector erase regions. If it does, then + * use the sub-sector erase size instead of the larger erase size to + * provide finer granularity. + */ + +#ifdef CONFIG_MTD_SUBSECTOR_ERASE + ret = MTD_IOCTL(dev->mtd, MTDIOC_GETCAPS, 0); + if (ret >= 0) + { + /* The block device supports the GETCAPS ioctl. Now check if sub-sector + * erase is supported. + */ + + if (!(ret & MTDIOC_CAPS_SECTERASE)) + { + /* Ensure the subsector size is zero */ + dev->geo.subsectorsize = 0; + dev->geo.nsubsectors = 0; + } + } + + /* Now calculate the variables */ + + if (dev->geo.subsectorsize != 0) + { + /* Use the sub-sector erase size */ + + erasesize = dev->geo.subsectorsize; + dev->neraseblocks = dev->geo.nsubsectors; + } + else +#endif /* CONFIG_MTD_SUBSECTOR_ERASE */ + { + erasesize = dev->geo.erasesize; + dev->neraseblocks = dev->geo.neraseblocks; + } + + /* Most FLASH devices have erase size of 64K, but geo.erasesize is only + * 16 bits, so it will be zero + */ + + if (erasesize == 0) + { + erasesize = 65536; + } + + dev->sectorsize = size; + dev->mtdBlksPerSector = dev->sectorsize / dev->geo.blocksize; + dev->sectorsPerBlk = erasesize / dev->sectorsize; + + /* Release any existing rwbuffer and sMap */ + + if (dev->sMap != NULL) + { + kfree(dev->sMap); + } + + if (dev->rwbuffer != NULL) + { + kfree(dev->rwbuffer); + } + + /* Allocate a virtual to physical sector map buffer. Also allocate + * the storage space for releasecount and freecounts. + */ + + totalsectors = dev->neraseblocks * dev->sectorsPerBlk; + dev->totalsectors = (uint16_t) totalsectors; + + dev->sMap = (uint16_t *) kmalloc(totalsectors * sizeof(uint16_t) + + (dev->neraseblocks << 1)); + if (!dev->sMap) + { + fdbg("Error allocating SMART virtual map buffer\n"); + kfree(dev); + return -EINVAL; + } + + dev->releasecount = (uint8_t *) dev->sMap + (totalsectors * sizeof(uint16_t)); + dev->freecount = dev->releasecount + dev->neraseblocks; + + /* Allocate a read/write buffer */ + + dev->rwbuffer = (char *) kmalloc(size); + if (!dev->rwbuffer) + { + fdbg("Error allocating SMART read/write buffer\n"); + kfree(dev->sMap); + kfree(dev); + return -EINVAL; + } + + return OK; +} + +/**************************************************************************** + * Name: smart_scan + * + * Description: Performs a scan of the MTD device searching for format + * information and fills in logical sector mapping, freesector + * count, etc. + * + ****************************************************************************/ + +static int smart_scan(struct smart_struct_s *dev) +{ + int sector; + int ret; + int x; + uint16_t totalsectors; + uint16_t sectorsize; + uint16_t logicalsector; + uint16_t seq1; + uint16_t seq2; + char devname[18]; + size_t readaddress; + struct smart_sect_header_s header; + struct mtd_byte_write_s bytewrite; +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + struct smart_multiroot_device_s *rootdirdev; +#endif + + fvdbg("Entry\n"); + + /* Read the 1st header from the device. We always keep the + * 1st sector's header's sectorsize field accurate, even + * after we erase an MTD block/sector */ + + ret = MTD_READ(dev->mtd, 0, sizeof(struct smart_sect_header_s), + (uint8_t *) &header); + if (ret != sizeof(struct smart_sect_header_s)) + { + goto err_out; + } + + /* Now set the sectorsize and other sectorsize derived variables */ + + if (header.status == CONFIG_SMARTFS_ERASEDSTATE) + { + sectorsize = CONFIG_MTD_SMART_SECTOR_SIZE; + } + else + { + sectorsize = (header.status & SMART_STATUS_SIZEBITS) << 7; + } + + ret = smart_setsectorsize(dev, sectorsize); + if (ret != OK) + { + goto err_out; + } + + /* Initialize the device variables */ + + totalsectors = dev->neraseblocks * dev->sectorsPerBlk; + dev->formatstatus = SMART_FMT_STAT_NOFMT; + dev->freesectors = totalsectors; + + /* Initialize the freecount and releasecount arrays */ + + for (sector = 0; sector < dev->neraseblocks; sector++) + { + dev->freecount[sector] = dev->sectorsPerBlk; + dev->releasecount[sector] = 0; + } + + /* Initialize the sector map */ + + for (sector = 0; sector < totalsectors; sector++) + { + dev->sMap[sector] = -1; + } + + /* Now scan the MTD device */ + + for (sector = 0; sector < totalsectors; sector++) + { + fvdbg("Scan sector %d\n", sector); + + /* Calculate the read address for this sector */ + + readaddress = sector * dev->mtdBlksPerSector * dev->geo.blocksize; + + /* Read the header for this sector */ + + ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s), + (uint8_t *) &header); + if (ret != sizeof(struct smart_sect_header_s)) + { + goto err_out; + } + + /* Get the logical sector number for this physical sector */ + + logicalsector = *((uint16_t *) header.logicalsector); +#if CONFIG_SMARTFS_ERASEDSTATE == 0x00 + if (logicalsector == 0) + { + logicalsector = -1; + } +#endif + + /* Test if this sector has been commited */ + + if ((header.status & SMART_STATUS_COMMITTED) == + (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED)) + { + continue; + } + + /* This block is commited, therefore not free. Update the + * erase block's freecount. + */ + + dev->freecount[sector / dev->sectorsPerBlk]--; + dev->freesectors--; + + /* Test if this sector has been release and if it has, + * update the erase block's releasecount. + */ + + if ((header.status & SMART_STATUS_RELEASED) != + (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_RELEASED)) + { + dev->releasecount[sector / dev->sectorsPerBlk]++; + continue; + } + + if ((header.status & SMART_STATUS_VERBITS) != SMART_STATUS_VERSION) + { + continue; + } + + /* Validate the logical sector number is in bounds */ + + if (logicalsector >= totalsectors) + { + /* Error in logical sector read from the MTD device */ + + fdbg("Invalid logical sector %d at physical %d.\n", + logicalsector, sector); + continue; + } + + /* If this is logical sector zero, then read in the signature + * information to validate the format signature. + */ + + if (logicalsector == 0) + { + /* Read the sector data */ + + ret = MTD_READ(dev->mtd, readaddress, 32, + (uint8_t*) dev->rwbuffer); + if (ret != 32) + { + fdbg("Error reading physical sector %d.\n", sector); + goto err_out; + } + + /* Validate the format signature */ + + if (dev->rwbuffer[SMART_FMT_POS1] != SMART_FMT_SIG1 || + dev->rwbuffer[SMART_FMT_POS2] != SMART_FMT_SIG2 || + dev->rwbuffer[SMART_FMT_POS3] != SMART_FMT_SIG3 || + dev->rwbuffer[SMART_FMT_POS4] != SMART_FMT_SIG4) + { + /* Invalid signature on a sector claiming to be sector 0! + * What should we do? Release it?*/ + + continue; + } + + /* TODO: May want to validate / save the erase block aging info */ + + /* Mark the volume as formatted and set the sector size */ + + dev->formatstatus = SMART_FMT_STAT_FORMATTED; + dev->namesize = dev->rwbuffer[SMART_FMT_NAMESIZE_POS]; + dev->formatversion = dev->rwbuffer[SMART_FMT_VERSION_POS]; + +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + dev->rootdirentries = dev->rwbuffer[SMART_FMT_ROOTDIRS_POS]; + + /* If rootdirentries is greater than 1, then we need to register + * additional block devices. + */ + + for (x = 1; x < dev->rootdirentries; x++) + { + snprintf(devname, 18, "/dev/smart%dd%d", dev->minor, x + 1); + + /* Inode private data is a reference to a struct containing + * the SMART device structure and the root directory number. + */ + + rootdirdev = (struct smart_multiroot_device_s*) kmalloc(sizeof(*rootdirdev)); + if (rootdirdev == NULL) + { + fdbg("Memory alloc failed\n"); + ret = -ENOMEM; + goto err_out; + } + + /* Populate the rootdirdev */ + + rootdirdev->dev = dev; + rootdirdev->rootdirnum = x; + ret = register_blockdriver(dev->rwbuffer, &g_bops, 0, rootdirdev); + + /* Inode private data is a reference to the SMART device structure */ + + ret = register_blockdriver(devname, &g_bops, 0, rootdirdev); + } +#endif + } + + /* Test for duplicate logical sectors on the device */ + + if (dev->sMap[logicalsector] != -1) + { + /* TODO: Uh-oh, we found more than 1 physical sector claiming to be + * the * same logical sector. Use the sequence number information + * to resolve who wins. + */ + + uint16_t loser; + + seq2 = *((uint16_t *) header.seq); + + /* We must re-read the 1st physical sector to get it's seq number */ + + readaddress = dev->sMap[logicalsector] * dev->mtdBlksPerSector * dev->geo.blocksize; + ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s), + (uint8_t *) &header); + if (ret != sizeof(struct smart_sect_header_s)) + { + goto err_out; + } + + seq1 = *((uint16_t *) header.seq); + + /* Now determine who wins */ + + if (seq1 > 0xFFF0 && seq2 < 10) + { + /* Seq 2 is the winner ... we assume it wrapped */ + + loser = dev->sMap[logicalsector]; + dev->sMap[logicalsector] = sector; + } + else if (seq2 > seq1) + { + /* Seq 2 is bigger, so it's the winner */ + + loser = dev->sMap[logicalsector]; + dev->sMap[logicalsector] = sector; + } + else + { + /* We keep the original mapping and seq2 is the loser */ + + loser = sector; + } + + /* Now release the loser sector */ + + readaddress = loser * dev->mtdBlksPerSector * dev->geo.blocksize; + ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s), + (uint8_t *) &header); + if (ret != sizeof(struct smart_sect_header_s)) + { + goto err_out; + } + +#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF + header.status &= ~SMART_STATUS_RELEASED; +#else + header.status |= SMART_STATUS_RELEASED; +#endif + bytewrite.offset = readaddress + offsetof(struct smart_sect_header_s, status); + bytewrite.count = 1; + bytewrite.buffer = &header.status; + ret = MTD_IOCTL(dev->mtd, MTDIOC_BYTEWRITE, (unsigned long) &bytewrite); + } + + /* Update the logical to physical sector map */ + + dev->sMap[logicalsector] = sector; + } + + fdbg("SMART Scan\n"); + fdbg(" Erase size: %10d\n", dev->sectorsPerBlk * dev->sectorsize); + fdbg(" Erase count: %10d\n", dev->neraseblocks); + fdbg(" Sect/block: %10d\n", dev->sectorsPerBlk); + fdbg(" MTD Blk/Sect: %10d\n", dev->mtdBlksPerSector); + + ret = OK; + +err_out: + return ret; +} + +/**************************************************************************** + * Name: smart_getformat + * + * Description: Populates the SMART format structure based on the format + * information for the inode. + * + ****************************************************************************/ + +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS +static inline int smart_getformat(struct smart_struct_s *dev, + struct smart_format_s *fmt, uint8_t rootdirnum) +#else +static inline int smart_getformat(struct smart_struct_s *dev, struct smart_format_s *fmt) +#endif +{ + int ret; + int x; + + fvdbg("Entry\n"); + DEBUGASSERT(fmt); + + /* Test if we know the format status or not. If we don't know the + * status, then we must perform a scan of the device to search + * for the format marker + */ + + if (dev->formatstatus != SMART_FMT_STAT_FORMATTED) + { + /* Perform the scan */ + + ret = smart_scan(dev); + + if (ret != OK) + { + goto err_out; + } + } + + /* TODO: Determine if the underlying MTD device supports bytewrite */ + + /* Now fill in the structure */ + + if (dev->formatstatus == SMART_FMT_STAT_FORMATTED) + { + fmt->flags = SMART_FMT_ISFORMATTED; + } + else + { + fmt->flags = 0; + } + + fmt->sectorsize = dev->sectorsize; + fmt->availbytes = dev->sectorsize - sizeof(struct smart_sect_header_s); + fmt->nsectors = dev->neraseblocks * dev->sectorsPerBlk; + fmt->nfreesectors = dev->freesectors; + fmt->namesize = dev->namesize; +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + fmt->nrootdirentries = dev->rootdirentries; + fmt->rootdirnum = rootdirnum; +#endif + + /* Add the released sectors to the reported free sector count */ + + for (x = 0; x < dev->neraseblocks; x++) + { + fmt->nfreesectors += dev->releasecount[x]; + } + + /* Subtract the reserved sector count */ + + fmt->nfreesectors -= dev->sectorsPerBlk + 4; + + ret = OK; + +err_out: + return ret; +} + +/**************************************************************************** + * Name: smart_llformat + * + * Description: Performs a low-level format of the flash device. This + * involves erasing the device and writing a valid sector + * zero (logical) with proper format signature. + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static inline int smart_llformat(struct smart_struct_s *dev, unsigned long arg) +{ + struct smart_sect_header_s *sectorheader; + size_t wrcount; + size_t totalsectors; + int x; + int ret; + uint8_t sectsize; + + fvdbg("Entry\n"); + + /* Erase the MTD device */ + + ret = MTD_IOCTL(dev->mtd, MTDIOC_BULKERASE, 0); + if (ret != OK) + { + return ret; + } + + /* Now construct a logical sector zero header to write to the device. + * We fill it with zero so when we add sector aging, all the sector + * ages will already be initialized to zero without needing special + * logic to deal with a 0xFF erased-state value. + */ + + sectorheader = (struct smart_sect_header_s *) dev->rwbuffer; + memset(dev->rwbuffer, 0, dev->sectorsize); + memset(dev->rwbuffer, CONFIG_SMARTFS_ERASEDSTATE, SMARTFS_FMT_AGING_POS); + *((uint16_t *) sectorheader->seq) = 0; + + sectsize = (CONFIG_MTD_SMART_SECTOR_SIZE >> 9) << 2; +#if ( CONFIG_SMARTFS_ERASEDSTATE == 0xFF ) + *((uint16_t *) sectorheader->logicalsector) = 0; + sectorheader->status = (uint8_t) ~(SMART_STATUS_COMMITTED | SMART_STATUS_VERBITS | + SMART_STATUS_SIZEBITS) | SMART_STATUS_VERSION | + sectsize; +#else + *((uint16_t *) sectorheader->logicalsector) = 0xFFFF; + sectorheader->status = (uint8_t) (SMART_STATUS_COMMITTED | SMART_STATUS_VERSION | + sectsize); +#endif + + /* Now add the format signature to the sector */ + + dev->rwbuffer[SMART_FMT_POS1] = SMART_FMT_SIG1; + dev->rwbuffer[SMART_FMT_POS2] = SMART_FMT_SIG2; + dev->rwbuffer[SMART_FMT_POS3] = SMART_FMT_SIG3; + dev->rwbuffer[SMART_FMT_POS4] = SMART_FMT_SIG4; + + dev->rwbuffer[SMART_FMT_VERSION_POS] = SMART_FMT_VERSION; + dev->rwbuffer[SMART_FMT_NAMESIZE_POS] = CONFIG_SMARTFS_MAXNAMLEN; + + /* Record the number of root directory entries we have */ + + dev->rwbuffer[SMART_FMT_ROOTDIRS_POS] = (uint8_t) arg; + + /* Write the sector to the flash */ + + wrcount = MTD_BWRITE(dev->mtd, 0, dev->mtdBlksPerSector, + (uint8_t *) dev->rwbuffer); + if (wrcount != dev->mtdBlksPerSector) + { + /* The block is not empty!! What to do? */ + + fdbg("Write block 0 failed: %d.\n", wrcount); + + /* Unlock the mutex if we add one */ + + return -EIO; + } + + /* Now initialize our internal control variables */ + + ret = smart_setsectorsize(dev, CONFIG_MTD_SMART_SECTOR_SIZE); + if (ret != OK) + { + return ret; + } + + dev->formatstatus = SMART_FMT_STAT_UNKNOWN; + dev->freesectors = dev->neraseblocks * dev->sectorsPerBlk - 1; + for (x = 0; x < dev->neraseblocks; x++) + { + /* Initialize the released and free counts */ + + dev->releasecount[x] = 0; + dev->freecount[x] = dev->sectorsPerBlk; + } + + /* Account for the format sector */ + + dev->freecount[0]--; + + /* Now initialize the logical to physical sector map */ + + dev->sMap[0] = 0; /* Logical sector zero = physical sector 0 */ + + totalsectors = dev->neraseblocks * dev->sectorsPerBlk; + for (x = 1; x < totalsectors; x++) + { + /* Mark all other logical sectors as non-existant */ + + dev->sMap[x] = -1; + } + +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + + /* Un-register any extra directory device entries */ + + for (x = 2; x < 8; x++) + { + snprintf(dev->rwbuffer, 18, "/dev/smart%dd%d", dev->minor, x); + unregister_blockdriver(dev->rwbuffer); + } +#endif + + return OK; +} +#endif /* CONFIG_FS_WRITABLE */ + +/**************************************************************************** + * Name: smart_findfreephyssector + * + * Description: Finds a free physical sector based on free and released + * count logic, taking into account reserved sectors. + * + ****************************************************************************/ + +static int smart_findfreephyssector(struct smart_struct_s *dev) +{ + uint16_t allocfreecount; + uint16_t allocblock; + uint16_t physicalsector; + uint16_t x; + uint32_t readaddr; + struct smart_sect_header_s header; + int ret; + + /* Determine which erase block we should allocate the new + * sector from. This is based on the number of free sectors + * available in each erase block. */ + + allocfreecount = 0; + allocblock = 0xFFFF; + physicalsector = 0xFFFF; + for (x = 0; x < dev->neraseblocks; x++) + { + /* Test if this block has more free blocks than the + * currently selected block */ + + if (dev->freecount[x] > allocfreecount) + { + /* Assign this block to alloc from */ + + allocblock = x; + allocfreecount = dev->freecount[x]; + } + } + + /* Check if we found an allocblock. */ + + if (allocblock == 0xFFFF) + { + /* No free sectors found! Bug? */ + return -EIO; + } + + /* Now find a free physical sector within this selected + * erase block to allocate. */ + + for (x = allocblock * dev->sectorsPerBlk; + x < (allocblock+1) * dev->sectorsPerBlk; x++) + { + /* Check if this physical sector is available */ + + readaddr = x * dev->mtdBlksPerSector * dev->geo.blocksize; + ret = MTD_READ(dev->mtd, readaddr, sizeof(struct smart_sect_header_s), + (uint8_t *) &header); + if (ret != sizeof(struct smart_sect_header_s)) + { + fvdbg("Error reading phys sector %d\n", physicalsector); + return -EIO; + } + + if ((*((uint16_t *) header.logicalsector) == 0xFFFF) && + (*((uint16_t *) header.seq) == 0xFFFF) && + ((header.status & SMART_STATUS_COMMITTED) == + (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED))) + { + physicalsector = x; + break; + } + } + + return physicalsector; +} + +/**************************************************************************** + * Name: smart_garbagecollect + * + * Description: Performs garbage collection if needed. This is determined + * by the count of released sectors relative to free and + * total sectors. + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static int smart_garbagecollect(struct smart_struct_s *dev) +{ + uint16_t releasedsectors; + uint16_t collectblock; + uint16_t releasemax; + uint16_t newsector; + bool collect = TRUE; + int x; + int ret; + struct smart_sect_header_s *header; + struct mtd_byte_write_s bytewrite; + + while (collect) + { + collect = FALSE; + + /* Calculate the number of released sectors on the device */ + + releasedsectors = 0; + collectblock = 0xFFFF; + releasemax = 0; + for (x = 0; x < dev->neraseblocks; x++) + { + releasedsectors += dev->releasecount[x]; + if (dev->releasecount[x] > releasemax) +// if (dev->releasecount[x] > releasemax && +// dev->freecount[x] < (dev->sectorsPerBlk >> 1)) + { + releasemax = dev->releasecount[x]; + collectblock = x; + } + } + + /* Test if the released sectors count is greater than the + * free sectors. If it is, then we will do garbage collection */ + + if (releasedsectors > dev->freesectors) + collect = TRUE; + + /* Test if we have more reached our reserved free sector limit */ + + if (dev->freesectors <= (dev->sectorsPerBlk << 0) + 4) + collect = TRUE; + + /* Test if we need to garbage collect */ + + if (collect) + { + if (collectblock == 0xFFFF) + { + /* Need to collect, but no sectors with released blocks! */ + + ret = -ENOSPC; + goto errout; + } + + fdbg("Collecting block %d, free=%d released=%d\n", + collectblock, dev->freecount[collectblock], + dev->releasecount[collectblock]); + + if (dev->freecount[collectblock] == 6) + { + fdbg("here!\n"); + } + + /* Perform collection on block with the most released sectors. + * First mark the block as having no free sectors so we don't + * try to move sectors into the block we are trying to erase. */ + + dev->freecount[collectblock] = 0; + + /* Next move all live data in the block to a new home. */ + + for (x = collectblock * dev->sectorsPerBlk; x < + (collectblock + 1) * dev->sectorsPerBlk; x++) + { + /* Read the next sector from this erase block */ + + ret = MTD_BREAD(dev->mtd, x * dev->mtdBlksPerSector, + dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer); + if (ret != dev->mtdBlksPerSector) + { + fdbg("Error reading sector %d\n", x); + ret = -EIO; + goto errout; + } + + /* Test if if the block is in use */ + + header = (struct smart_sect_header_s *) dev->rwbuffer; + if (((header->status & SMART_STATUS_COMMITTED) == + (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED)) || + ((header->status & SMART_STATUS_RELEASED) != + (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_RELEASED))) + { + /* This sector doesn't have live data (free or released). + * just continue to the next sector and don't move it. */ + + continue; + } + + /* Find a new sector where it can live, NOT in this erase block */ + + newsector = smart_findfreephyssector(dev); + if (newsector == 0xFFFF) + { + /* Unable to find a free sector!!! */ + + fdbg("Can't find a free sector for relocation\n"); + ret = -EIO; + goto errout; + } + + /* Increment the sequence number and clear the "commit" flag */ + + (*((uint16_t *) header->seq))++; + if (*((uint16_t *) header->seq) == 0xFFFF) + { + *((uint16_t *) header->seq) = 1; + } +#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF + header->status |= SMART_STATUS_COMMITTED; +#else + header->status &= ~SMART_STATUS_COMMITTED; +#endif + + /* Write the data to the new physical sector location */ + + ret = MTD_BWRITE(dev->mtd, newsector * dev->mtdBlksPerSector, + dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer); + + /* Commit the sector */ + + bytewrite.offset = newsector * dev->mtdBlksPerSector * dev->geo.blocksize + + offsetof(struct smart_sect_header_s, status); +#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF + header->status &= ~SMART_STATUS_COMMITTED; +#else + header->status |= SMART_STATUS_COMMITTED; +#endif + bytewrite.count = 1; + bytewrite.buffer = &header->status; + ret = MTD_IOCTL(dev->mtd, MTDIOC_BYTEWRITE, (unsigned long) &bytewrite); + + /* Release the old physical sector */ + +#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF + header->status &= ~SMART_STATUS_RELEASED; +#else + header->status |= SMART_STATUS_RELEASED; +#endif + bytewrite.offset = x * dev->mtdBlksPerSector * dev->geo.blocksize + + offsetof(struct smart_sect_header_s, status); + ret = MTD_IOCTL(dev->mtd, MTDIOC_BYTEWRITE, (unsigned long) &bytewrite); + + /* Update the variables */ + + dev->sMap[*((uint16_t *) header->logicalsector)] = newsector; + dev->freecount[newsector / dev->sectorsPerBlk]--; + } + + /* Now erase the erase block */ + +#ifdef CONFIG_MTD_SUBSECTOR_ERASE + if (dev->geo.subsectorsize != 0) + { + /* Perform a sub-sector erase */ + MTD_IOCTL(dev->mtd, MTDIOC_SECTERASE, collectblock); + } + else +#endif + { + MTD_ERASE(dev->mtd, collectblock, 1); + } + + dev->freesectors += dev->releasecount[collectblock]; + dev->freecount[collectblock] = dev->sectorsPerBlk; + dev->releasecount[collectblock] = 0; + + /* If this is block zero, then be sure to write the sector size */ + + if (collectblock == 0) + { + /* Set the sector size in the 1st header */ + + uint8_t sectsize = dev->sectorsize >> 7; + header = (struct smart_sect_header_s *) dev->rwbuffer; +#if ( CONFIG_SMARTFS_ERASEDSTATE == 0xFF ) + header->status = (uint8_t) ~SMART_STATUS_SIZEBITS | sectsize; +#else + header->status = (uint8_t) sectsize; +#endif + /* Write the sector size to the device */ + + bytewrite.offset = offsetof(struct smart_sect_header_s, status); + bytewrite.count = 1; + bytewrite.buffer = &header->status; + MTD_IOCTL(dev->mtd, MTDIOC_BYTEWRITE, (unsigned long) &bytewrite); + } + + /* Update the block aging information in the format signature sector */ + } + else + { + /* Test for aging sectors and push them to a new location + * so we wear evenly. + */ + } + } + + return OK; + +errout: + return ret; +} +#endif /* CONFIG_FS_WRITABLE */ + +/**************************************************************************** + * Name: smart_writesector + * + * Description: Writes data to the specified logical sector. The sector + * should have already been allocated prior to the write. If + * the logical sector already has data on the device, it will + * be released and a new physical sector will be created and + * mapped to the logical sector. + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static inline int smart_writesector(struct smart_struct_s *dev, unsigned long arg) +{ + int ret; + uint16_t x; + bool needsrelocate = FALSE; + uint16_t mtdblock; + uint16_t physsector; + struct smart_read_write_s *req; + struct smart_sect_header_s *header; + struct mtd_byte_write_s bytewrite; + uint8_t byte; + + fvdbg("Entry\n"); + req = (struct smart_read_write_s *) arg; + DEBUGASSERT(req->offset <= dev->sectorsize); + DEBUGASSERT(req->offset+req->count <= dev->sectorsize); + + /* Ensure the logical sector has been allocated */ + + if (req->logsector >= dev->totalsectors) + { + fdbg("Logical sector %d too large\n", req->logsector); + + ret = -EINVAL; + goto errout; + } + + physsector = dev->sMap[req->logsector]; + if (physsector == 0xFFFF) + { + fdbg("Logical sector %d not allocated\n", req->logsector); + ret = -EINVAL; + goto errout; + } + + /* Read the sector data into our buffer */ + + mtdblock = physsector * dev->mtdBlksPerSector; + ret = MTD_BREAD(dev->mtd, mtdblock, dev->mtdBlksPerSector, (uint8_t *) + dev->rwbuffer); + if (ret != dev->mtdBlksPerSector) + { + fdbg("Error reading phys sector %d\n", physsector); + ret = -EIO; + goto errout; + } + + /* Test if we need to relocate the sector to perform the write */ + + for (x = 0; x < req->count; x++) + { + /* Test if the next byte can be written to the flash */ + + byte = dev->rwbuffer[sizeof(struct smart_sect_header_s) + req->offset + x]; +#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF + if (((byte ^ req->buffer[x]) | byte) != byte) + { + needsrelocate = TRUE; + break; + } +#else + if (((byte ^ req->buffer[x]) | req->buffer[x]) != req->buffer[x]) + { + needsrelocate = TRUE; + break; + } +#endif + } + + if (needsrelocate) + { + /* Find a new physical sector to save data to */ + + physsector = smart_findfreephyssector(dev); + if (physsector == 0xFFFF) + { + fdbg("Error relocating sector %d\n", req->logsector); + ret = -EIO; + goto errout; + } + + /* Update the sequence number to indicate the sector was moved */ + + header = (struct smart_sect_header_s *) dev->rwbuffer; + (*((uint16_t *) header->seq))++; +#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF + header->status |= SMART_STATUS_COMMITTED; +#else + header->status &= SMART_STATUS_COMMITTED; +#endif + } + + /* Now copy the data to the sector buffer. */ + + memcpy(&dev->rwbuffer[sizeof(struct smart_sect_header_s) + req->offset], + req->buffer, req->count); + + /* Now write the sector buffer to the device. */ + + if (needsrelocate) + { + /* Write the entire sector to the new physical location, uncommitted. */ + + ret = MTD_BWRITE(dev->mtd, physsector * dev->mtdBlksPerSector, + dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer); + if (ret != dev->mtdBlksPerSector) + { + fdbg("Error writing to physical sector %d\n", physsector); + ret = -EIO; + goto errout; + } + + /* Commit the new physical sector */ + +#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF + header->status &= ~SMART_STATUS_COMMITTED; +#else + header->status |= SMART_STATUS_COMMITTED; +#endif + bytewrite.offset = physsector * dev->mtdBlksPerSector * + dev->geo.blocksize + offsetof(struct smart_sect_header_s, status); + bytewrite.count = 1; + bytewrite.buffer = (uint8_t *) &header->status; + ret = MTD_IOCTL(dev->mtd, MTDIOC_BYTEWRITE, (unsigned long) &bytewrite); + if (ret != OK) + { + fvdbg("Error committing physical sector %d\n", physsector); + ret = -EIO; + goto errout; + } + + /* Release the old physical sector */ + +#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF + header->status &= ~SMART_STATUS_RELEASED; +#else + header->status |= SMART_STATUS_RELEASED; +#endif + bytewrite.offset = mtdblock * dev->geo.blocksize + + offsetof(struct smart_sect_header_s, status); + ret = MTD_IOCTL(dev->mtd, MTDIOC_BYTEWRITE, (unsigned long) &bytewrite); + + /* Update releasecount for released sector and freecount for the + * newly allocated physical sector. */ + + dev->releasecount[dev->sMap[req->logsector] / dev->sectorsPerBlk]++; + dev->freecount[physsector / dev->sectorsPerBlk]--; + dev->freesectors--; + + /* Update the sector map */ + + dev->sMap[req->logsector] = physsector; + + /* Since we performed a relocation, do garbage collection to + * ensure we don't fill up our flash with released blocks. + */ + + smart_garbagecollect(dev); + } + else + { + /* Not relocated. Just write the portion of the sector that needs + * to be written. */ + + bytewrite.offset = mtdblock * dev->geo.blocksize + + sizeof(struct smart_sect_header_s) + req->offset; + bytewrite.count = req->count; + bytewrite.buffer = req->buffer; + ret = MTD_IOCTL(dev->mtd, MTDIOC_BYTEWRITE, (unsigned long) &bytewrite); + } + + ret = OK; + +errout: + return ret; +} +#endif /* CONFIG_FS_WRITABLE */ + +/**************************************************************************** + * Name: smart_readsector + * + * Description: Reads data from the specified logical sector. The sector + * should have already been allocated prior to the read. + * + ****************************************************************************/ + +static inline int smart_readsector(struct smart_struct_s *dev, unsigned long arg) +{ + int ret; + uint32_t readaddr; + uint16_t physsector; + struct smart_read_write_s *req; + struct smart_sect_header_s header; + + fvdbg("Entry\n"); + req = (struct smart_read_write_s *) arg; + DEBUGASSERT(req->offset < dev->sectorsize); + DEBUGASSERT(req->offset+req->count < dev->sectorsize); + + /* Ensure the logical sector has been allocated */ + + if (req->logsector >= dev->totalsectors) + { + fdbg("Logical sector %d too large\n", req->logsector); + + ret = -EINVAL; + goto errout; + } + + physsector = dev->sMap[req->logsector]; + if (physsector == 0xFFFF) + { + fdbg("Logical sector %d not allocated\n", req->logsector); + ret = -EINVAL; + goto errout; + } + + /* Read the sector header data to validate as a sanity check */ + + ret = MTD_READ(dev->mtd, physsector * dev->mtdBlksPerSector * dev->geo.blocksize, + sizeof(struct smart_sect_header_s), (uint8_t *) &header); + if (ret != sizeof(struct smart_sect_header_s)) + { + fvdbg("Error reading sector %d header\n", physsector); + ret = -EIO; + goto errout; + } + + /* Do a sanity check on the header data */ + + if (((*(uint16_t *) header.logicalsector) != req->logsector) || + ((header.status & SMART_STATUS_COMMITTED) == + (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED))) + { + /* Error in sector header! How do we handle this? */ + + fdbg("Error in logical sector %d header, phys=%d\n", + req->logsector, physsector); + ret = -EIO; + goto errout; + } + + /* Read the sector data into the buffer */ + + readaddr = (uint32_t) physsector * dev->mtdBlksPerSector * dev->geo.blocksize + + req->offset + sizeof(struct smart_sect_header_s);; + + ret = MTD_READ(dev->mtd, readaddr, req->count, (uint8_t *) + req->buffer); + if (ret != req->count) + { + fdbg("Error reading phys sector %d\n", physsector); + ret = -EIO; + goto errout; + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: smart_allocsector + * + * Description: Allocates a new logical sector. If an argument is given, + * then it tries to allocate the specified sector number. + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static inline int smart_allocsector(struct smart_struct_s *dev, unsigned long requested) +{ + int x; + int ret; + uint16_t logsector = 0xFFFF; /* Logical sector number selected */ + uint16_t physicalsector; /* The selected physical sector */ + uint16_t releasecount; + struct smart_sect_header_s *header; + uint8_t sectsize; + + /* Validate that we have enough sectors available to perform an + * allocation. We have to ensure we keep enough reserved sectors + * on hand to do released sector garbage collection. */ + + releasecount = 0; + for (x = 0; x < dev->neraseblocks; x++) + { + releasecount += dev->releasecount[x]; + } + + if (dev->freesectors <= (dev->sectorsPerBlk << 0) + 4) + { + /* We are at our free sector limit. Test if we have + * sectors we can release */ + + if (releasecount == 0) + { + /* No space left!! */ + + return -ENOSPC; + } + } + + /* Check if a specific sector is being requested and allocate that + * sector if it isn't already in use */ + + if ((requested > 2) && (requested < dev->totalsectors)) + { + /* Validate the sector is not already allocated */ + + if (dev->sMap[requested] == (uint16_t) -1) + { + logsector = requested; + } + } + + /* Check if we need to scan for an available logical sector */ + + if (logsector == 0xFFFF) + { + /* Loop through all sectors and find one to allocate */ + + for (x = SMART_FIRST_ALLOC_SECTOR; x < dev->totalsectors; x++) + { + if (dev->sMap[x] == (uint16_t) -1) + { + /* Unused logical sector found. Use this one */ + + logsector = x; + break; + } + } + } + + /* Test for an error allocating a sector */ + + if (logsector == 0xFFFF) + { + /* Hmmm. We think we had enough logical sectors, but + * something happened and we didn't find any free + * logical sectors. What do do? Report an error? + * rescan and try again to "self heal" in case of a + * bug in our code? */ + + fdbg("Couldn't find free sector when I expected too\n"); + + /* Unlock the mutex if we add one */ + + return -EIO; + } + + /* Check if we need to do garbage collection. We have to + * ensure we keep enough reserved free sectors to per garbage + * collection as it involves moving sectors from blocks with + * released sectors into blocks with free sectors, then + * erasing the vacated block. */ + + smart_garbagecollect(dev); + + /* Find a free physical sector */ + + physicalsector = smart_findfreephyssector(dev); + fvdbg("Alloc: log=%d, phys=%d, erase block=%d, free=%d, released=%d\n", + logsector, physicalsector, physicalsector / + dev->sectorsPerBlk, dev->freesectors, releasecount); + + /* Create a header to assign the logical sector */ + + memset(dev->rwbuffer, CONFIG_SMARTFS_ERASEDSTATE, dev->sectorsize); + header = (struct smart_sect_header_s *) dev->rwbuffer; + *((uint16_t *) header->logicalsector) = logsector; + *((uint16_t *) header->seq) = 0; + sectsize = dev->sectorsize >> 7; + +#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF + header->status = ~(SMART_STATUS_COMMITTED | SMART_STATUS_SIZEBITS | + SMART_STATUS_VERBITS) | SMART_STATUS_VERSION | sectsize; +#else + header->status = SMART_STATUS_COMMITTED | SMART_STATUS_VERSION | sectsize; +#endif + + /* Write the header to the physical sector location */ + + x = physicalsector * dev->mtdBlksPerSector; + + fvdbg("Write MTD block %d\n", x); + ret = MTD_BWRITE(dev->mtd, x, 1, (uint8_t *) dev->rwbuffer); + if (ret != 1) + { + /* The block is not empty!! What to do? */ + + fdbg("Write block %d failed: %d.\n", x, ret); + + /* Unlock the mutex if we add one */ + + return -EIO; + } + + /* Map the sector and update the free sector counts */ + + dev->sMap[logsector] = physicalsector; + dev->freecount[physicalsector / dev->sectorsPerBlk]--; + dev->freesectors--; + + /* Return the logical sector number */ + + return logsector; +} +#endif /* CONFIG_FS_WRITABLE */ + +/**************************************************************************** + * Name: smart_freesector + * + * Description: Frees a logical sector from the device. Freeing (also + * called releasing) is performed by programming the released + * bit in the sector header's status byte. + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static inline int smart_freesector(struct smart_struct_s *dev, unsigned long + logicalsector) +{ + int ret; + int readaddr; + uint16_t physsector; + uint16_t block; + struct smart_sect_header_s header; + struct mtd_byte_write_s bytewrite; + + /* Check if the logical sector is within bounds */ + + if ((logicalsector > 2) && (logicalsector < dev->totalsectors)) + { + /* Validate the sector is actually allocated */ + + if (dev->sMap[logicalsector] == (uint16_t) -1) + { + fdbg("Invalid release - sector %d not allocated\n", logicalsector); + ret = -EINVAL; + goto errout; + } + } + + /* Okay to release the sector. Read the sector header info */ + + physsector = dev->sMap[logicalsector]; + readaddr = physsector * dev->mtdBlksPerSector * dev->geo.blocksize; + ret = MTD_READ(dev->mtd, readaddr, sizeof(struct smart_sect_header_s), + (uint8_t *) &header); + if (ret != sizeof(struct smart_sect_header_s)) + { + goto errout; + } + + /* Do a sanity check on the logical sector number */ + + if (*((uint16_t *) header.logicalsector) != (uint16_t) logicalsector) + { + /* Hmmm... something is wrong. This should always match! Bug in our code? */ + + fdbg("Sector %d logical sector in header doesn't match\n", logicalsector); + ret = -EINVAL; + goto errout; + } + + /* Mark the sector as released */ + +#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF + header.status &= ~SMART_STATUS_RELEASED; +#else + header.status |= SMART_STATUS_RELEASED; +#endif + + /* Write the status back to the device */ + + bytewrite.offset = readaddr + offsetof(struct smart_sect_header_s, status); + bytewrite.count = 1; + bytewrite.buffer = &header.status; + ret = MTD_IOCTL(dev->mtd, MTDIOC_BYTEWRITE, (unsigned long) &bytewrite); + if (ret != OK) + { + fdbg("Error updating physicl sector %d status\n", physsector); + goto errout; + } + + /* Update the erase block's release count */ + + block = physsector / dev->sectorsPerBlk; + dev->releasecount[block]++; + + /* Unmap this logical sector */ + + dev->sMap[logicalsector] = (uint16_t) -1; + + /* If this block has only released blocks, then erase it */ + + if (dev->releasecount[block] + dev->freecount[block] == dev->sectorsPerBlk) + { + /* Erase the block */ + +#ifdef CONFIG_MTD_SUBSECTOR_ERASE + if (dev->geo.subsectorsize != 0) + { + /* Perform a sub-sector erase */ + MTD_IOCTL(dev->mtd, MTDIOC_SECTERASE, block); + } + else +#endif + MTD_ERASE(dev->mtd, block, 1); + + dev->freesectors += dev->releasecount[block]; + dev->releasecount[block] = 0; + dev->freecount[block] = dev->sectorsPerBlk; + } + + ret = OK; + +errout: + return ret; +} +#endif /* CONFIG_FS_WRITABLE */ + +/**************************************************************************** + * Name: smart_ioctl + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int smart_ioctl(FAR struct inode *inode, int cmd, unsigned long arg) +{ + struct smart_struct_s *dev ; + int ret; + + fvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev; +#else + dev = (struct smart_struct_s *)inode->i_private; +#endif + + /* Process the ioctl's we care about first, pass any we don't respond + * to directly to the underlying MTD device. + */ + + switch (cmd) + { + case BIOC_XIPBASE: + /* The argument accompanying the BIOC_XIPBASE should be non-NULL. If + * DEBUG is enabled, we will catch it here instead of in the MTD + * driver. + */ + +#ifdef CONFIG_DEBUG + if (arg == 0) + { + fdbg("ERROR: BIOC_XIPBASE argument is NULL\n"); + return -EINVAL; + } +#endif + + /* Just change the BIOC_XIPBASE command to the MTDIOC_XIPBASE command. */ + + cmd = MTDIOC_XIPBASE; + break; + + case BIOC_GETFORMAT: + + /* Return the format information for the device */ + +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + ret = smart_getformat(dev, (struct smart_format_s *) arg, + ((struct smart_multiroot_device_s*) inode->i_private)->rootdirnum); +#else + ret = smart_getformat(dev, (struct smart_format_s *) arg); +#endif + goto ok_out; + + case BIOC_READSECT: + + /* Do a logical sector read and return the data */ + ret = smart_readsector(dev, arg); + goto ok_out; + +#ifdef CONFIG_FS_WRITABLE + case BIOC_LLFORMAT: + + /* Perform a low-level format on the flash */ + + ret = smart_llformat(dev, arg); + goto ok_out; + + case BIOC_ALLOCSECT: + + /* Allocate a logical sector for the upper layer file system */ + + ret = smart_allocsector(dev, arg); + goto ok_out; + + case BIOC_FREESECT: + + /* Free the specified logical sector */ + + ret = smart_freesector(dev, arg); + goto ok_out; + + case BIOC_WRITESECT: + + /* Write to the sector */ + + ret = smart_writesector(dev, arg); + goto ok_out; +#endif /* CONFIG_FS_WRITABLE */ + + } + + /* No other block driver ioctl commmands are not recognized by this + * driver. Other possible MTD driver ioctl commands are passed through + * to the MTD driver (unchanged). + */ + + ret = MTD_IOCTL(dev->mtd, cmd, arg); + if (ret < 0) + { + fdbg("ERROR: MTD ioctl(%04x) failed: %d\n", cmd, ret); + } + +ok_out: + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: smart_initialize + * + * Description: + * Initialize to provide a block driver wrapper around an MTD interface + * + * Input Parameters: + * minor - The minor device number. The MTD block device will be + * registered as as /dev/smartN where N is the minor number. + * mtd - The MTD device that supports the FLASH interface. + * + ****************************************************************************/ + +int smart_initialize(int minor, FAR struct mtd_dev_s *mtd) +{ + struct smart_struct_s *dev; + int ret = -ENOMEM; + uint32_t totalsectors; +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + struct smart_multiroot_device_s *rootdirdev; +#endif + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (minor < 0 || minor > 255 || !mtd) + { + return -EINVAL; + } +#endif + + /* Allocate a SMART device structure */ + + dev = (struct smart_struct_s *)kmalloc(sizeof(struct smart_struct_s)); + if (dev) + { + /* Initialize the SMART device structure */ + + dev->mtd = mtd; + + /* Get the device geometry. (casting to uintptr_t first eliminates + * complaints on some architectures where the sizeof long is different + * from the size of a pointer). + */ + + /* Set these to zero in case the device doesn't support them */ + + dev->geo.subsectorsize= 0; + dev->geo.nsubsectors = 0; + ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&dev->geo)); + if (ret < 0) + { + fdbg("MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret); + kfree(dev); + goto errout; + } + + /* Set the sector size to the default for now */ + + dev->sMap = NULL; + dev->rwbuffer = NULL; + ret = smart_setsectorsize(dev, CONFIG_MTD_SMART_SECTOR_SIZE); + if (ret != OK) + { + kfree(dev); + goto errout; + } + + /* Calculate the totalsectors on this device and validate */ + + totalsectors = dev->neraseblocks * dev->sectorsPerBlk; + if (totalsectors > 65534) + { + fdbg("SMART Sector size too small for device\n"); + kfree(dev); + ret = -EINVAL; + goto errout; + } + dev->freesectors = (uint16_t) totalsectors; + + /* Mark the device format status an unknown */ + + dev->formatstatus = SMART_FMT_STAT_UNKNOWN; + dev->namesize = CONFIG_SMARTFS_MAXNAMLEN; +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + dev->minor = minor; +#endif + + /* Create a MTD block device name */ + +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + snprintf(dev->rwbuffer, 18, "/dev/smart%dd1", minor); + + /* Inode private data is a reference to a struct containing + * the SMART device structure and the root directory number. + */ + + rootdirdev = (struct smart_multiroot_device_s*) kmalloc(sizeof(*rootdirdev)); + if (rootdirdev == NULL) + { + fdbg("register_blockdriver failed: %d\n", -ret); + kfree(dev->sMap); + kfree(dev->rwbuffer); + kfree(dev); + ret = -ENOMEM; + goto errout; + } + + /* Populate the rootdirdev */ + + rootdirdev->dev = dev; + rootdirdev->rootdirnum = 0; + ret = register_blockdriver(dev->rwbuffer, &g_bops, 0, rootdirdev); + +#else + snprintf(dev->rwbuffer, 18, "/dev/smart%d", minor); + + /* Inode private data is a reference to the SMART device structure */ + + ret = register_blockdriver(dev->rwbuffer, &g_bops, 0, dev); +#endif + + if (ret < 0) + { + fdbg("register_blockdriver failed: %d\n", -ret); + kfree(dev->sMap); + kfree(dev->rwbuffer); + kfree(dev); + } + + /* Do a scan of the device */ + + smart_scan(dev); + } + +errout: + return ret; +} diff --git a/nuttx/include/nuttx/smart.h b/nuttx/include/nuttx/smart.h new file mode 100644 index 000000000..7e9fafd77 --- /dev/null +++ b/nuttx/include/nuttx/smart.h @@ -0,0 +1,116 @@ +/**************************************************************************** + * include/nuttx/smart.h + * Sector Mapped Allocation for Really Tiny (SMART) FLASH interface + * + * Copyright (C) 2013 Ken Pettit. All rights reserved. + * Author: Ken Pettit + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_SMART_H +#define __INCLUDE_NUTTX_SMART_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ + +/* Macros to hide implementation */ + +#define SMART_FMT_ISFORMATTED 0x01 +#define SMART_FMT_HASBYTEWRITE 0x02 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* The following defines the format information for the device. This + * information is retrieved via the BIOC_GETFORMAT ioctl. + */ + +struct smart_format_s +{ + uint16_t sectorsize; /* Size of one read/write sector */ + uint16_t availbytes; /* Number of bytes available in each sector */ + uint16_t nsectors; /* Total number of sectors on device */ + uint16_t nfreesectors; /* Number of free sectors on device */ + uint8_t flags; /* Format flags (see above) */ + uint8_t namesize; /* Size of filenames on this volume */ +#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS + uint8_t nrootdirentries; /* Number of root directories on this device */ + uint8_t rootdirnum; /* Root directory number for this dev entry */ +#endif +}; + +/* The following defines the information for writing a logical sector + * to the device. + */ + +struct smart_read_write_s +{ + uint16_t logsector; /* The logical sector number */ + uint16_t offset; /* Offset within the sector to write to */ + uint16_t count; /* Number of bytes to write */ + const uint8_t *buffer; /* Pointer to the data to write */ +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __INCLUDE_NUTTX_SMART_H */ -- cgit v1.2.3