diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index f8afb3851a..136272bdff 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -1215,4 +1215,19 @@ config GD5F_SPIFREQUENCY endif # MTD_GD5F +config MTD_DHARA + bool "MTD Nandflash use dhara map" + default n + +if MTD_DHARA + +config DHARA_GC_RATIO + int "dhara garbage collection ratio" + default 4 + +config DHARA_READ_NCACHES + int "dhara read cache numbers" + default 4 +endif + endif # MTD diff --git a/drivers/mtd/Make.defs b/drivers/mtd/Make.defs index bafb1bbf89..0db6d50965 100644 --- a/drivers/mtd/Make.defs +++ b/drivers/mtd/Make.defs @@ -164,6 +164,31 @@ CSRCS += smart.c endif endif +ifeq ($(CONFIG_MTD_DHARA),y) + +master.zip: + $(call DOWNLOAD,https://github.com/dlbeer/dhara/archive/refs/heads,master.zip) + +.dharaunpack: master.zip + $(Q) unzip master.zip -d mtd/ + $(Q) mv mtd/dhara-master mtd/dhara + $(Q) touch mtd/dhara/.dharaunpack + +ifeq ($(wildcard mtd/dhara/.git),) +context:: .dharaunpack + +distclean:: + $(call DELFILE, master.zip) + $(call DELDIR, mtd/dhara) +endif + +CSRCS += dhara.c +CSRCS += mtd/dhara/dhara/map.c +CSRCS += mtd/dhara/dhara/error.c +CSRCS += mtd/dhara/dhara/journal.c +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)drivers$(DELIM)mtd$(DELIM)dhara +endif + # Include MTD driver support DEPPATH += --dep-path mtd diff --git a/drivers/mtd/dhara.c b/drivers/mtd/dhara.c new file mode 100644 index 0000000000..f7494d1393 --- /dev/null +++ b/drivers/mtd/dhara.c @@ -0,0 +1,801 @@ +/**************************************************************************** + * drivers/mtd/dhara.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct dhara_pagecache_s +{ + dq_entry_t node; + dhara_page_t page; + FAR uint8_t *buffer; +}; + +typedef struct dhara_pagecache_s dhara_pagecache_t; + +struct dhara_dev_s +{ + struct dhara_nand nand; + struct dhara_map map; + mutex_t lock; + + FAR struct mtd_dev_s *mtd; /* Contained MTD interface */ + struct mtd_geometry_s geo; /* Device geometry */ + uint16_t blkper; /* R/W blocks per erase block */ + uint16_t refs; /* Number of references */ + bool unlinked; /* The driver has been unlinked */ + + /* Two pagesize buffer first is for working temp buffer + * second is for journel use + */ + + FAR uint8_t *pagebuf; + + /* Read cache for accelerate read dhara meta data */ + + struct dq_queue_s readcache; + dhara_pagecache_t readpage[CONFIG_DHARA_READ_NCACHES]; +}; + +typedef struct dhara_dev_s dhara_dev_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int dhara_open(FAR struct inode *inode); +static int dhara_close(FAR struct inode *inode); +static ssize_t dhara_read(FAR struct inode *inode, + FAR unsigned char *buffer, + blkcnt_t start_sector, + unsigned int nsectors); +static ssize_t dhara_write(FAR struct inode *inode, + FAR const unsigned char *buffer, + blkcnt_t start_sector, + unsigned int nsectors); +static int dhara_geometry(FAR struct inode *inode, + FAR struct geometry *geometry); +static int dhara_ioctl(FAR struct inode *inode, + int cmd, + unsigned long arg); +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS +static int dhara_unlink(FAR struct inode *inode); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct block_operations g_dhara_bops = +{ + dhara_open, /* open */ + dhara_close, /* close */ + dhara_read, /* read */ + dhara_write, /* write */ + dhara_geometry, /* geometry */ + dhara_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + , dhara_unlink /* unlink */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int dhara_convert_result(dhara_error_t err) +{ + switch (err) + { + case DHARA_E_NONE: + return 0; + + case DHARA_E_BAD_BLOCK: + case DHARA_E_ECC: + case DHARA_E_TOO_BAD: + case DHARA_E_CORRUPT_MAP: + return -EBADMSG; + + case DHARA_E_JOURNAL_FULL: + case DHARA_E_MAP_FULL: + return -ENOSPC; + + case DHARA_E_NOT_FOUND: + return -ENOENT; + + default: + return -EFAULT; + } +} + +static bool dhara_check_ff(FAR const uint8_t *buf, size_t size) +{ + FAR const uint8_t *p = buf; + size_t len; + + for (len = 0; len < 16; len++) + { + if (!size) + return true; + + if (*p != 0xff) + return false; + + p++; + size--; + } + + return memcmp(buf, p, size) == 0; +} + +static int dhara_init_readcache(FAR dhara_dev_t *dev) +{ + FAR dq_queue_t *q = &dev->readcache; + int i = 0; + + dq_init(q); + + do + { + dev->readpage[i].page = DHARA_PAGE_NONE; + dev->readpage[i].buffer = kmm_malloc(dev->geo.blocksize); + dq_addfirst(&dev->readpage[i].node, q); + } + while (dev->readpage[i++].buffer && i < CONFIG_DHARA_READ_NCACHES); + + return i == CONFIG_DHARA_READ_NCACHES ? 0 : -ENOMEM; +} + +static void dhara_deinit_readcache(FAR dhara_dev_t *dev) +{ + int i; + + for (i = 0; i < CONFIG_DHARA_READ_NCACHES; i++) + { + if (dev->readpage[i].buffer) + { + kmm_free(dev->readpage[i].buffer); + } + } +} + +static uint8_t *dhara_find_readcache(FAR dhara_dev_t *dev, + dhara_page_t page) +{ + FAR dq_queue_t *q = &dev->readcache; + FAR dhara_pagecache_t *cache; + FAR dq_entry_t *c; + + for (c = dq_peek(q); c; c = dq_next(c)) + { + cache = (FAR dhara_pagecache_t *)c; + if (cache->page == page) + { + dq_rem(c, q); + dq_addfirst(c, q); + return cache->buffer; + } + } + + return NULL; +} + +static dhara_pagecache_t *dhara_grab_readcache(FAR dhara_dev_t *dev) +{ + FAR dq_queue_t *q = &dev->readcache; + FAR dhara_pagecache_t *cache; + FAR dq_entry_t *c; + + c = dq_tail(q); + dq_rem(c, q); + cache = (FAR dhara_pagecache_t *)c; + cache->page = DHARA_PAGE_NONE; + return cache; +} + +static void dhara_insert_readcache(FAR dhara_dev_t *dev, + dhara_pagecache_t *cache) +{ + FAR dq_queue_t *q = &dev->readcache; + + if (cache->page != DHARA_PAGE_NONE) + dq_addfirst((dq_entry_t *)cache, q); + else + dq_addlast((dq_entry_t *)cache, q); +} + +static void dhara_discard_readcache(FAR dhara_dev_t *dev, + dhara_page_t page) +{ + FAR dhara_pagecache_t *cache; + FAR dq_queue_t *q = &dev->readcache; + FAR dq_entry_t *c; + + for (c = dq_peek(q); c; c = dq_next(c)) + { + cache = (FAR dhara_pagecache_t *)c; + if (cache->page == page) + { + cache->page = DHARA_PAGE_NONE; + dq_rem(c, q); + dq_addlast(c, q); + break; + } + } +} + +static void dhara_update_readcache(FAR dhara_dev_t *dev, + dhara_page_t page, + FAR const uint8_t *data) +{ + FAR dhara_pagecache_t *cache; + FAR dq_queue_t *q = &dev->readcache; + FAR dq_entry_t *c; + + for (c = dq_peek(q); c; c = dq_next(c)) + { + cache = (FAR dhara_pagecache_t *)c; + if (cache->page == page) + { + cache->page = page; + memcpy(cache->buffer, data, dev->geo.blocksize); + dq_rem(c, q); + dq_addfirst(c, q); + break; + } + } +} + +/**************************************************************************** + * Name: dhara_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int dhara_open(FAR struct inode *inode) +{ + FAR dhara_dev_t *dev; + + DEBUGASSERT(inode && inode->i_private); + dev = (FAR dhara_dev_t *) inode->i_private; + nxmutex_lock(&dev->lock); + dev->refs++; + nxmutex_unlock(&dev->lock); + + return 0; +} + +/**************************************************************************** + * Name: dhara_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int dhara_close(FAR struct inode *inode) +{ + FAR dhara_dev_t *dev; + + DEBUGASSERT(inode && inode->i_private); + dev = (FAR dhara_dev_t *) inode->i_private; + nxmutex_lock(&dev->lock); + dev->refs--; + nxmutex_unlock(&dev->lock); + + if (dev->refs == 0 && dev->unlinked) + { + nxmutex_destroy(&dev->lock); + dhara_deinit_readcache(dev); + kmm_free(dev->pagebuf); + kmm_free(dev); + } + + return 0; +} + +/**************************************************************************** + * Name: dhara_read + * + * Description: Read the specified number of sectors + * + ****************************************************************************/ + +static ssize_t dhara_read(FAR struct inode *inode, + FAR unsigned char *buffer, + blkcnt_t start_sector, + unsigned int nsectors) +{ + FAR dhara_dev_t *dev; + size_t nread = 0; + int ret = 0; + + DEBUGASSERT(inode && inode->i_private); + dev = (FAR dhara_dev_t *)inode->i_private; + + nxmutex_lock(&dev->lock); + while (nsectors-- > 0) + { + dhara_error_t err; + ret = dhara_map_read(&dev->map, + start_sector, + buffer, + &err); + if (ret < 0) + { + ret = dhara_convert_result(err); + ferr("Read startblock %lld failed nread %zd err: %s\n", + (long long)start_sector, nread, dhara_strerror(err)); + break; + } + + nread++; + start_sector++; + buffer += dev->geo.blocksize; + } + + nxmutex_unlock(&dev->lock); + return nread ? nread : ret; +} + +/**************************************************************************** + * Name: dhara_write + * + * Description: Write (or buffer) the specified number of sectors + * + ****************************************************************************/ + +static ssize_t dhara_write(FAR struct inode *inode, + FAR const unsigned char *buffer, + blkcnt_t start_sector, + unsigned int nsectors) +{ + FAR dhara_dev_t *dev; + size_t nwrite = 0; + int ret = 0; + + DEBUGASSERT(inode && inode->i_private); + dev = (FAR dhara_dev_t *)inode->i_private; + + nxmutex_lock(&dev->lock); + while (nsectors-- > 0) + { + dhara_error_t err; + ret = dhara_map_write(&dev->map, + start_sector, + buffer, + &err); + if (ret < 0) + { + ret = dhara_convert_result(err); + ferr("Write starting at block %lld failed nwrite %zu err %s\n", + (long long)start_sector, nwrite, dhara_strerror(err)); + break; + } + + nwrite++; + start_sector++; + buffer += dev->geo.blocksize; + } + + nxmutex_unlock(&dev->lock); + return nwrite ? nwrite : ret; +} + +/**************************************************************************** + * Name: dhara_geometry + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int dhara_geometry(FAR struct inode *inode, + FAR struct geometry *geometry) +{ + FAR dhara_dev_t *dev; + + DEBUGASSERT(inode && inode->i_private); + dev = (FAR dhara_dev_t *)inode->i_private; + + if (geometry) + { + geometry->geo_available = true; + geometry->geo_mediachanged = false; + geometry->geo_writeenabled = true; + geometry->geo_nsectors = dev->geo.neraseblocks * dev->blkper; + geometry->geo_sectorsize = dev->geo.blocksize; + + strcpy(geometry->geo_model, dev->geo.model); + nxmutex_unlock(&dev->lock); + return 0; + } + + return -EINVAL; +} + +/**************************************************************************** + * Name: dhara_ioctl + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int dhara_ioctl(FAR struct inode *inode, + int cmd, + unsigned long arg) +{ + FAR dhara_dev_t *dev; + int ret; + + DEBUGASSERT(inode && inode->i_private); + dev = (dhara_dev_t *)inode->i_private; + + /* No other block driver ioctl commands 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 && ret != -ENOTTY) + { + ferr("MTD ioctl(%04x) failed: %d\n", cmd, ret); + } + + return ret; +} + +/**************************************************************************** + * Name: dhara_unlink + * + * Description: Unlink the device + * + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS +static int dhara_unlink(FAR struct inode *inode) +{ + FAR dhara_dev_t *dev; + + DEBUGASSERT(inode && inode->i_private); + dev = (FAR dhara_dev_t *)inode->i_private; + nxmutex_lock(&dev->lock); + dev->unlinked = true; + nxmutex_unlock(&dev->lock); + + if (dev->refs == 0) + { + nxmutex_destroy(&dev->lock); + dhara_deinit_readcache(dev); + kmm_free(dev->pagebuf); + kmm_free(dev); + } + + return 0; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/* dhara nand interface implement */ + +int dhara_nand_is_bad(FAR const struct dhara_nand *n, + dhara_block_t bno) +{ + FAR dhara_dev_t *dev = (FAR dhara_dev_t *)n; + return MTD_ISBAD(dev->mtd, bno); +} + +void dhara_nand_mark_bad(FAR const struct dhara_nand *n, + dhara_block_t bno) +{ + FAR dhara_dev_t *dev = (FAR dhara_dev_t *)n; + MTD_MARKBAD(dev->mtd, bno); +} + +int dhara_nand_erase(FAR const struct dhara_nand *n, + dhara_block_t bno, + FAR dhara_error_t *err) +{ + FAR dhara_dev_t *dev = (FAR dhara_dev_t *)n; + dhara_page_t pno = bno << n->log2_ppb; + int ret; + int i; + + ret = MTD_ERASE(dev->mtd, bno, 1); + if (ret < 0) + { + dhara_set_error(err, DHARA_E_BAD_BLOCK); + return ret; + } + + for (i = 0; i < dev->blkper; i++) + { + dhara_discard_readcache(dev, pno + i); + } + + return 0; +} + +int dhara_nand_prog(FAR const struct dhara_nand *n, + dhara_page_t p, + FAR const uint8_t *data, + FAR dhara_error_t *err) +{ + FAR dhara_dev_t *dev = (FAR dhara_dev_t *)n; + int ret; + + ret = MTD_BWRITE(dev->mtd, p, 1, data); + if (ret < 0) + { + dhara_set_error(err, DHARA_E_BAD_BLOCK); + return ret; + } + + dhara_update_readcache(dev, p, data); + return 0; +} + +int dhara_nand_is_free(FAR const struct dhara_nand *n, + dhara_page_t p) +{ + FAR dhara_dev_t *dev = (FAR dhara_dev_t *)n; + size_t page_size = 1 << n->log2_page_size; + FAR uint8_t *buf = dev->pagebuf; + dhara_error_t err; + + if (dhara_nand_read(n, p, 0, page_size, buf, &err) < 0) + { + ferr("Fail to read page for free check err %s\n", + dhara_strerror(err)); + return 0; + } + + return dhara_check_ff(buf, page_size); +} + +int dhara_nand_read(FAR const struct dhara_nand *n, + dhara_page_t p, + size_t offset, + size_t length, + FAR uint8_t *data, + dhara_error_t *err) +{ + FAR dhara_dev_t *dev = (FAR dhara_dev_t *)n; + FAR dhara_pagecache_t *cache; + FAR uint8_t *buf; + int ret; + + buf = dhara_find_readcache(dev, p); + if (buf) + { + memcpy(data, buf + offset, length); + return 0; + } + + cache = dhara_grab_readcache(dev); + ret = MTD_BREAD(dev->mtd, p, 1, cache->buffer); + if (ret == -EUCLEAN) + { + ret = 0; /* Ignore the correctable ECC error */ + } + + if (ret < 0) + { + dhara_insert_readcache(dev, cache); + dhara_set_error(err, DHARA_E_ECC); + return ret; + } + + memcpy(data, cache->buffer + offset, length); + cache->page = p; + dhara_insert_readcache(dev, cache); + return ret; +} + +int dhara_nand_copy(FAR const struct dhara_nand *n, + dhara_page_t src, + dhara_page_t dst, + FAR dhara_error_t *err) +{ + FAR dhara_dev_t *dev = (FAR dhara_dev_t *)n; + size_t page_size = 1 << n->log2_page_size; + FAR uint8_t *buf = dev->pagebuf; + int ret; + + ret = dhara_nand_read(n, src, 0, page_size, buf, err); + if (ret < 0) + { + ferr("src_page %d, ret %d failed: %s\n", + src, ret, dhara_strerror(*err)); + return ret; + } + + ret = dhara_nand_prog(n, dst, buf, err); + if (ret < 0) + { + ferr("dst_page %d, ret %d failed: %s\n", + dst, ret, dhara_strerror(*err)); + return ret; + } + + return 0; +} + +/**************************************************************************** + * Name: dhara_initialize_by_path + * + * Description: + * Initialize to provide a block driver wrapper around an MTD interface + * + * Input Parameters: + * path - The block device path. + * mtd - The MTD device that supports the FLASH interface. + * + ****************************************************************************/ + +int dhara_initialize_by_path(FAR const char *path, + FAR struct mtd_dev_s *mtd) +{ + FAR dhara_dev_t *dev; + int ret; + + /* Sanity check */ + + if (path == NULL || mtd == NULL) + { + return -EINVAL; + } + + /* Allocate a DHARA_MTDBLOCK device structure */ + + dev = (FAR dhara_dev_t *) + kmm_zalloc(sizeof(dhara_dev_t)); + if (dev == NULL) + { + return -ENOMEM; + } + + nxmutex_init(&dev->lock); + + /* Initialize the DHARA_MTDBLOCK 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). + */ + + ret = MTD_IOCTL(mtd, + MTDIOC_GEOMETRY, + (unsigned long)((uintptr_t)&dev->geo)); + if (ret < 0) + { + ferr("MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret); + goto err; + } + + /* Get the number of R/W blocks per erase block */ + + dev->blkper = dev->geo.erasesize / dev->geo.blocksize; + DEBUGASSERT(dev->blkper * dev->geo.blocksize == dev->geo.erasesize); + + /* Init dhara */ + + dev->nand.log2_page_size = fls(dev->geo.blocksize) - 1; + dev->nand.log2_ppb = fls(dev->blkper) - 1; + dev->nand.num_blocks = dev->geo.neraseblocks; + + dev->pagebuf = kmm_zalloc(dev->geo.blocksize * 2); + if (!dev->pagebuf) + { + ret = -ENOMEM; + goto err; + } + + ret = dhara_init_readcache(dev); + if (ret != 0) + { + goto err; + } + + dhara_map_init(&dev->map, &dev->nand, + dev->pagebuf + dev->geo.blocksize, + CONFIG_DHARA_GC_RATIO); + + dhara_map_resume(&dev->map, NULL); + + /* Inode private data is a reference to the + * DHARA_MTDBLOCK device structure + */ + + ret = register_blockdriver(path, &g_dhara_bops, 0666, dev); + if (ret < 0) + { + ferr("register_blockdriver failed: %d\n", ret); + goto err; + } + + return ret; + +err: + nxmutex_destroy(&dev->lock); + dhara_deinit_readcache(dev); + kmm_free(dev->pagebuf); + kmm_free(dev); + return ret; +} + +/**************************************************************************** + * Name: dhara_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/mtdblockN where N is the minor number. + * mtd - The MTD device that supports the FLASH interface. + * + ****************************************************************************/ + +int dhara_initialize(int minor, FAR struct mtd_dev_s *mtd) +{ + char path[PATH_MAX]; + +#ifdef CONFIG_DEBUG_FEATURES + /* Sanity check */ + + if (minor < 0 || minor > 255) + { + return -EINVAL; + } +#endif + + /* Do the real work by dhara_mtdblock_initialize_by_path */ + + snprintf(path, sizeof(path), "/dev/mtdblock%d", minor); + return dhara_initialize_by_path(path, mtd); +} diff --git a/include/nuttx/mtd/mtd.h b/include/nuttx/mtd/mtd.h index 5567a336c3..1a1c4923d9 100644 --- a/include/nuttx/mtd/mtd.h +++ b/include/nuttx/mtd/mtd.h @@ -723,6 +723,40 @@ int rpmsgmtd_register(FAR const char *remotecpu, FAR const char *remotepath, int rpmsgmtd_server_init(void); #endif +/**************************************************************************** + * Name: dhara_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/mtdblockN where N is the minor number. + * mtd - The MTD device that supports the FLASH interface. + * + ****************************************************************************/ + +#ifdef CONFIG_MTD_DHARA +int dhara_initialize(int minor, FAR struct mtd_dev_s *mtd); +#endif + +/**************************************************************************** + * Name: dhara_initialize_by_path + * + * Description: + * Initialize to provide a block driver wrapper around an MTD interface + * + * Input Parameters: + * path - The block device path. + * mtd - The MTD device that supports the FLASH interface. + * + ****************************************************************************/ + +#ifdef CONFIG_MTD_DHARA +int dhara_initialize_by_path(FAR const char *path, + FAR struct mtd_dev_s *mtd); +#endif + #undef EXTERN #ifdef __cplusplus }