/**************************************************************************** * 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->i_private); dev = 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->i_private); dev = 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->i_private); dev = 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->i_private); dev = 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->i_private); dev = 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->i_private); dev = 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->i_private); dev = 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); }