nuttx/drivers/mtd/dhara.c

802 lines
20 KiB
C
Raw Permalink Normal View History

/****************************************************************************
* 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 <nuttx/config.h>
#include <errno.h>
#include <debug.h>
#include <stdio.h>
#include <nuttx/nuttx.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mtd/mtd.h>
#include <dhara/map.h>
#include <dhara/nand.h>
/****************************************************************************
* 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);
}